//+------------------------------------------------------------------+ //| FXMacroData.mqh | //| FXMacroData API helper for MetaTrader 4 and 5 | //| https://fxmacrodata.com | //+------------------------------------------------------------------+ // // INSTALLATION // 1. Copy this file to your MetaTrader Include folder: // MT4: \MQL4\Include\ // MT5: \MQL5\Include\ // 2. Allow the API host in Tools > Options > Expert Advisors: // Check "Allow WebRequest for listed URL" and add: // https://fxmacrodata.com // 3. In your indicator or EA: #include // // QUICK REFERENCE // FMD_GetIndicator("usd","inflation") // free, no key // FMD_GetIndicator("eur","policy_rate","MY_KEY") // paid currencies // FMD_GetCalendar("aud","MY_KEY") // FMD_GetForex("usd","jpy") // FMD_GetCOT("EUR","MY_KEY") // FMD_LastValue(json) // most recent val // FMD_JsonDouble(json,"val") // field extractor // FMD_JsonString(json,"date") // field extractor // // DOCUMENTATION https://fxmacrodata.com/documentation/metatrader // API KEY https://fxmacrodata.com/subscribe //+------------------------------------------------------------------+ #ifndef __FXMACRODATA_MQH__ #define __FXMACRODATA_MQH__ #define FXMD_API_BASE "https://fxmacrodata.com/api/v1" #define FXMD_TIMEOUT 15000 //+------------------------------------------------------------------+ //| Internal: perform an HTTP GET and return the response body. | //| Returns "" and prints an error when the request fails. | //+------------------------------------------------------------------+ string FMD_WebGet(const string url) { char post[]; char result[]; string headers_out; int status; ResetLastError(); #ifdef __MQL5__ status = WebRequest("GET", url, "Accept: application/json\r\n", FXMD_TIMEOUT, post, result, headers_out); #else status = WebRequest("GET", url, "", "", FXMD_TIMEOUT, post, 0, result, headers_out); #endif if(status != 200) { Print("[FXMacroData] HTTP ", status, " | URL: ", url, " | Error: ", GetLastError()); return ""; } return CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); } //+------------------------------------------------------------------+ //| Internal: build a URL with an optional ?api_key= suffix. | //+------------------------------------------------------------------+ string FMD_BuildUrl(const string path, const string api_key = "") { string url = FXMD_API_BASE + "/" + path; if(StringLen(api_key) > 0) url += "?api_key=" + api_key; return url; } //+------------------------------------------------------------------+ //| FMD_GetIndicator | //| | //| Fetch time-series data for a macroeconomic indicator. | //| | //| currency : lowercase code, e.g. "usd", "eur", "gbp", "jpy" | //| indicator : snake_case name, e.g. "inflation", "policy_rate", | //| "unemployment", "gdp", "trade_balance", "ppi" | //| api_key : your API key; omit/blank for free USD endpoints | //| | //| Returns : JSON string {"data":[{"date":"...","val":X,...}]} | //+------------------------------------------------------------------+ string FMD_GetIndicator(const string currency, const string indicator, const string api_key = "") { return FMD_WebGet( FMD_BuildUrl("announcements/" + currency + "/" + indicator, api_key)); } //+------------------------------------------------------------------+ //| FMD_GetCalendar | //| | //| Fetch upcoming release dates for a currency. | //| | //| currency : lowercase code, e.g. "usd", "eur" | //| api_key : your API key | //| | //| Returns : JSON string {"releases":[{"indicator":"...","date":"...","announcement_datetime":"..."},...]} //+------------------------------------------------------------------+ string FMD_GetCalendar(const string currency, const string api_key = "") { return FMD_WebGet(FMD_BuildUrl("calendar/" + currency, api_key)); } //+------------------------------------------------------------------+ //| FMD_GetForex | //| | //| Fetch FX spot rate history for a currency pair. | //| Free — no API key required. | //| | //| base, quote : lowercase codes, e.g. "usd", "jpy" | //| Returns : JSON string {"data":[{"date":"...","val":X},...]} | //+------------------------------------------------------------------+ string FMD_GetForex(const string base, const string quote) { return FMD_WebGet(FMD_BuildUrl("forex/" + base + "/" + quote)); } //+------------------------------------------------------------------+ //| FMD_GetCOT | //| | //| Fetch CFTC Commitment of Traders (COT) positioning data. | //| | //| currency : uppercase code, e.g. "EUR", "GBP", "JPY", "AUD" | //| api_key : your API key | //| Returns : JSON string with net positioning series | //+------------------------------------------------------------------+ string FMD_GetCOT(const string currency, const string api_key = "") { return FMD_WebGet(FMD_BuildUrl("cot/" + currency, api_key)); } //+------------------------------------------------------------------+ //| FMD_JsonString | //| | //| Extract a string value from a JSON object by key name. | //| Works on flat key-value pairs. | //| Returns "" when the key is not found. | //+------------------------------------------------------------------+ string FMD_JsonString(const string json, const string key) { string search = "\"" + key + "\":\""; int start = StringFind(json, search); if(start < 0) return ""; start += StringLen(search); int end = StringFind(json, "\"", start); if(end < 0) return ""; return StringSubstr(json, start, end - start); } //+------------------------------------------------------------------+ //| FMD_JsonDouble | //| | //| Extract a numeric value from a JSON object by key name. | //| Returns EMPTY_VALUE when the key is absent or the value is null.| //+------------------------------------------------------------------+ double FMD_JsonDouble(const string json, const string key) { string search = "\"" + key + "\":"; int start = StringFind(json, search); if(start < 0) return EMPTY_VALUE; start += StringLen(search); // skip whitespace (bounded: JSON whitespace before a value is at most a few chars) int limit = MathMin(start + 10, StringLen(json)); while(start < limit && StringGetCharacter(json, start) == ' ') start++; int end = start; while(end < StringLen(json)) { ushort c = StringGetCharacter(json, end); if(c == ',' || c == '}' || c == ']' || c == '\r' || c == '\n') break; end++; } string val = StringSubstr(json, start, end - start); if(val == "null" || StringLen(val) == 0) return EMPTY_VALUE; return StringToDouble(val); } //+------------------------------------------------------------------+ //| FMD_LastValue | //| | //| Extract the most recent non-null "val" from an /announcements/ | //| response. The API returns records in chronological order. | //| Scans forward once to collect all "val": positions, then | //| iterates from the end to find the last non-null entry — O(n). | //| | //| Returns EMPTY_VALUE when no valid value is found. | //+------------------------------------------------------------------+ double FMD_LastValue(const string json) { string search = "\"val\":"; int slen = StringLen(search); int jlen = StringLen(json); // Forward pass: collect positions of every "val": occurrence. int positions[]; int count = 0; int pos = 0; while(pos < jlen) { int found = StringFind(json, search, pos); if(found < 0) break; ArrayResize(positions, count + 1); positions[count++] = found; pos = found + slen; } if(count == 0) return EMPTY_VALUE; // Iterate from the last occurrence backwards looking for non-null. for(int i = count - 1; i >= 0; i--) { int start = positions[i] + slen; // skip whitespace (bounded: at most a few characters) int limit = MathMin(start + 10, jlen); while(start < limit && StringGetCharacter(json, start) == ' ') start++; int end = start; while(end < jlen) { ushort c = StringGetCharacter(json, end); if(c == ',' || c == '}' || c == ']') break; end++; } string val = StringSubstr(json, start, end - start); if(val != "null" && StringLen(val) > 0) return StringToDouble(val); } return EMPTY_VALUE; } #endif // __FXMACRODATA_MQH__