Market Data Service is a high-performance financial data API that provides comprehensive Symbol prices of different markets through both RESTful endpoints and real-time WebSocket connections.

MarketDataSender.mq5 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. //+------------------------------------------------------------------+
  2. //| MarketDataSender.mq5 (Final Fixed Version) |
  3. //+------------------------------------------------------------------+
  4. #property strict
  5. #property description "Fetches all symbols' candles and live prices, sends to API."
  6. #include <Trade\SymbolInfo.mqh>
  7. input string ApiBaseUrl = "http://market-price.insightbull.io";
  8. input int HistoricalCandleCount = 1000;
  9. input ENUM_TIMEFRAMES HistoricalTimeframe = PERIOD_H1;
  10. input int LivePriceIntervalSeconds = 5;
  11. // Globals
  12. string symbols[];
  13. int symbolIds[];
  14. datetime lastSend = 0;
  15. //+------------------------------------------------------------------+
  16. int OnInit()
  17. {
  18. Print("Initializing MarketDataSender EA...");
  19. if(!InitializeSymbols())
  20. {
  21. Print("❌ Failed to initialize symbols.");
  22. return(INIT_FAILED);
  23. }
  24. Print("✅ Symbols initialized: ", ArraySize(symbols));
  25. SendAllHistoricalCandles();
  26. return(INIT_SUCCEEDED);
  27. }
  28. //+------------------------------------------------------------------+
  29. void OnTick()
  30. {
  31. if(TimeCurrent() - lastSend >= LivePriceIntervalSeconds)
  32. {
  33. SendLivePrices();
  34. lastSend = TimeCurrent();
  35. }
  36. }
  37. //+------------------------------------------------------------------+
  38. bool InitializeSymbols()
  39. {
  40. int total = SymbolsTotal(true);
  41. if(total <= 0)
  42. {
  43. Print("❌ No symbols found!");
  44. return false;
  45. }
  46. ArrayResize(symbols, total);
  47. ArrayResize(symbolIds, total);
  48. for(int i = 0; i < total; i++)
  49. {
  50. symbols[i] = SymbolName(i, true);
  51. symbolIds[i] = -1;
  52. }
  53. if(!SyncSymbolsWithDatabase())
  54. {
  55. Print("❌ Failed to sync symbols with database");
  56. return false;
  57. }
  58. return true;
  59. }
  60. //+------------------------------------------------------------------+
  61. bool SyncSymbolsWithDatabase()
  62. {
  63. Print("Syncing symbols with database...");
  64. string url = ApiBaseUrl + "/api/symbols";
  65. string headers = "Content-Type: application/json\r\n";
  66. string resultHeaders = "";
  67. char result[];
  68. char emptyData[]; // ✅ required placeholder for GET request
  69. ResetLastError();
  70. // ✅ Correct GET request signature: includes empty data[]
  71. int res = WebRequest("GET", url, headers, 5000, emptyData, result, resultHeaders);
  72. if(res == -1)
  73. {
  74. int err = GetLastError();
  75. Print("❌ WebRequest connection error: ", err, " URL=", url);
  76. return false;
  77. }
  78. if(res != 200)
  79. {
  80. Print("❌ Failed to fetch symbols from API: HTTP ", res, " Response: ", CharArrayToString(result));
  81. return false;
  82. }
  83. string symbolsResponse = CharArrayToString(result);
  84. if(StringFind(symbolsResponse, "\"data\"") < 0)
  85. {
  86. Print("⚠️ Unexpected response format from symbols API: ", symbolsResponse);
  87. }
  88. for(int i = 0; i < ArraySize(symbols); i++)
  89. {
  90. string symbolName = symbols[i];
  91. int symbolId = FindSymbolId(symbolsResponse, symbolName);
  92. if(symbolId > 0)
  93. {
  94. symbolIds[i] = symbolId;
  95. Print("✅ Found existing symbol: ", symbolName, " (ID: ", symbolId, ")");
  96. }
  97. else
  98. {
  99. Sleep(300); // prevent overload (0.3 second delay)
  100. symbolId = CreateSymbolInDatabase(symbolName);
  101. if(symbolId > 0)
  102. {
  103. symbolIds[i] = symbolId;
  104. Print("✅ Created new symbol: ", symbolName, " (ID: ", symbolId, ")");
  105. }
  106. else
  107. {
  108. Print("❌ Failed to create symbol: ", symbolName," (ID: ", symbolId, ")");
  109. symbolIds[i] = -1;
  110. }
  111. }
  112. }
  113. return true;
  114. }
  115. //+------------------------------------------------------------------+
  116. int FindSymbolId(string response, string symbolName)
  117. {
  118. string searchPattern = StringFormat("\"symbol\":\"%s\"", symbolName);
  119. int symbolPos = StringFind(response, searchPattern);
  120. if(symbolPos < 0) return -1;
  121. int idPos = StringFind(response, "\"id\":", symbolPos);
  122. if(idPos < 0) return -1;
  123. int startPos = idPos + 5;
  124. int endPos = StringFind(response, ",", startPos);
  125. if(endPos < 0) endPos = StringFind(response, "}", startPos);
  126. string idStr = StringSubstr(response, startPos, endPos - startPos);
  127. return (int)StringToInteger(idStr);
  128. }
  129. //+------------------------------------------------------------------+
  130. int CreateSymbolInDatabase(string symbolName)
  131. {
  132. string baseAsset = "";
  133. string quoteAsset = "";
  134. string exchange = "MT5";
  135. string instrumentType = "forex";
  136. if(StringLen(symbolName) >= 6)
  137. {
  138. baseAsset = StringSubstr(symbolName, 0, 3);
  139. quoteAsset = StringSubstr(symbolName, 3, 3);
  140. }
  141. string json = StringFormat(
  142. "{\"symbol\":\"%s\",\"baseAsset\":\"%s\",\"quoteAsset\":\"%s\",\"exchange\":\"%s\",\"instrumentType\":\"%s\",\"isActive\":true}",
  143. symbolName, baseAsset, quoteAsset, exchange, instrumentType
  144. );
  145. string url = ApiBaseUrl + "/api/symbols";
  146. string headers = "Content-Type: application/json\r\n";
  147. string resultHeaders = "";
  148. char postData[];
  149. StringToCharArray(json, postData, 0, CP_UTF8);
  150. // ✅ FIX: Remove trailing null terminator from JSON
  151. if(ArraySize(postData) > 0 && postData[ArraySize(postData) - 1] == 0)
  152. ArrayResize(postData, ArraySize(postData) - 1);
  153. char result[];
  154. int res = WebRequest("POST", url, headers, 5000, postData, result, resultHeaders);
  155. if(res != 201 && res != 200)
  156. {
  157. Print("❌ Failed to create symbol: ", res, " Response: ", CharArrayToString(result));
  158. return -1;
  159. }
  160. string createResponse = CharArrayToString(result);
  161. int idPos = StringFind(createResponse, "\"id\":");
  162. if(idPos < 0) return -1;
  163. int startPos = idPos + 5;
  164. int endPos = StringFind(createResponse, ",", startPos);
  165. if(endPos < 0) endPos = StringFind(createResponse, "}", startPos);
  166. string idStr = StringSubstr(createResponse, startPos, endPos - startPos);
  167. return (int)StringToInteger(idStr);
  168. }
  169. //+------------------------------------------------------------------+
  170. //+------------------------------------------------------------------+
  171. //| Send all historical candles to the API (Fixed Version) |
  172. //+------------------------------------------------------------------+
  173. void SendAllHistoricalCandles()
  174. {
  175. Print("Starting historical upload for ", ArraySize(symbols), " symbols...");
  176. for(int i = 0; i < ArraySize(symbols); i++)
  177. {
  178. string sym = symbols[i];
  179. // --- Ensure data is ready ---
  180. Sleep(300);
  181. int tries = 0;
  182. while(!SeriesInfoInteger(sym, HistoricalTimeframe, SERIES_SYNCHRONIZED) && tries < 10)
  183. {
  184. Print("⏳ Waiting for ", sym, " history to load...");
  185. Sleep(500);
  186. tries++;
  187. }
  188. // --- Now copy candles ---
  189. MqlRates rates[];
  190. ResetLastError();
  191. int copied = CopyRates(sym, HistoricalTimeframe, 0, HistoricalCandleCount, rates);
  192. int err = GetLastError();
  193. if(copied <= 0)
  194. {
  195. Print("⚠️ Failed to copy candles for ", sym, " (copied=", copied, ", err=", err, ")");
  196. continue;
  197. }
  198. Print("✅ Copied ", copied, " candles for ", sym);
  199. // --- Print a few sample candles ---
  200. int sampleCount = MathMin(5, copied); // show up to 5 examples
  201. for(int j = 0; j < sampleCount; j++)
  202. {
  203. MqlRates r = rates[j];
  204. string openTime = TimeToString(r.time, TIME_DATE|TIME_SECONDS);
  205. string closeTime = TimeToString(r.time + PeriodSeconds(HistoricalTimeframe), TIME_DATE|TIME_SECONDS);
  206. PrintFormat(
  207. "🕒 [%s] %s → %s | O=%.5f H=%.5f L=%.5f C=%.5f | Vol=%.2f",
  208. sym, openTime, closeTime, r.open, r.high, r.low, r.close, r.tick_volume
  209. );
  210. }
  211. // --- Send candles in batches to API ---
  212. int sentTotal = 0;
  213. int batchSize = 200;
  214. for(int start = 0; start < copied; start += batchSize)
  215. {
  216. int size = MathMin(batchSize, copied - start);
  217. int symbolId = symbolIds[i];
  218. if(symbolId <= 0) continue;
  219. string json = BuildCandleJSONFromRates(symbolId, rates, start, size);
  220. string url = ApiBaseUrl + "/api/candles/bulk";
  221. string response;
  222. bool ok = SendJSON(url, json, response);
  223. if(!ok)
  224. {
  225. Print("❌ Failed to send candle batch for ", sym, " start=", start);
  226. break;
  227. }
  228. sentTotal += size;
  229. Print("📤 Sent candles for ", sym, ": ", sentTotal, "/", copied);
  230. }
  231. }
  232. Print("✅ Historical upload finished.");
  233. }
  234. //+------------------------------------------------------------------+
  235. void SendLivePrices()
  236. {
  237. bool firstItem = true;
  238. string json = "{\"prices\":[";
  239. int sentCount = 0;
  240. for(int i = 0; i < ArraySize(symbols); i++)
  241. {
  242. string sym = symbols[i];
  243. double bid = SymbolInfoDouble(sym, SYMBOL_BID);
  244. double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
  245. double last = SymbolInfoDouble(sym, SYMBOL_LAST);
  246. if(bid <= 0 || ask <= 0 || last <= 0) continue;
  247. int symId = symbolIds[i];
  248. if(symId <= 0) continue;
  249. string item = StringFormat("{\"symbolId\":%d,\"price\":%.8f,\"bid\":%.8f,\"ask\":%.8f,\"bidSize\":%.8f,\"askSize\":%.8f}",
  250. symId, last, bid, ask, 0.0, 0.0);
  251. if(!firstItem) json += ",";
  252. json += item;
  253. firstItem = false;
  254. sentCount++;
  255. }
  256. json += "]}";
  257. if(sentCount == 0)
  258. {
  259. Print("No valid live prices to send right now.");
  260. return;
  261. }
  262. string url = ApiBaseUrl + "/api/live-prices/bulk";
  263. string response;
  264. bool ok = SendJSON(url, json, response);
  265. if(ok)
  266. Print("✅ Sent ", sentCount, " live prices.");
  267. else
  268. Print("❌ Failed to send live prices (sent ", sentCount, " items). Response: ", response);
  269. }
  270. //+------------------------------------------------------------------+
  271. string ToISO8601(datetime t)
  272. {
  273. MqlDateTime st;
  274. TimeToStruct(t, st);
  275. return StringFormat("%04d-%02d-%02dT%02d:%02d:%02d.000Z", st.year, st.mon, st.day, st.hour, st.min, st.sec);
  276. }
  277. string BuildCandleJSONFromRates(int symbolId, MqlRates &rates[], int startIndex, int count)
  278. {
  279. string json = "{\"candles\":[";
  280. bool first = true;
  281. int ratesSize = ArraySize(rates);
  282. for(int i = startIndex; i < startIndex + count && i < ratesSize; i++)
  283. {
  284. MqlRates r = rates[i];
  285. if(r.time <= 0) continue;
  286. datetime open_dt = (datetime)r.time;
  287. datetime close_dt = (datetime)(r.time + (datetime)PeriodSeconds(HistoricalTimeframe));
  288. string openTime = ToISO8601(open_dt);
  289. string closeTime = ToISO8601(close_dt);
  290. double volume = (r.tick_volume > 0 ? r.tick_volume : 1);
  291. double quoteVolume = (r.real_volume > 0 ? r.real_volume : volume);
  292. string one = StringFormat(
  293. "{\"symbolId\":%d,\"openTime\":\"%s\",\"closeTime\":\"%s\",\"open\":%.5f,\"high\":%.5f,\"low\":%.5f,\"close\":%.5f,\"volume\":%.5f,\"tradesCount\":%d,\"quoteVolume\":%.5f}",
  294. symbolId, openTime, closeTime,
  295. r.open, r.high, r.low, r.close,
  296. volume, (int)volume, quoteVolume
  297. );
  298. if(!first) json += ",";
  299. json += one;
  300. first = false;
  301. }
  302. json += "]}";
  303. return json;
  304. }
  305. //+------------------------------------------------------------------+
  306. bool SendJSON(string url, string json, string &response)
  307. {
  308. ResetLastError();
  309. char postData[];
  310. StringToCharArray(json, postData, 0, CP_UTF8);
  311. // ✅ Remove trailing null terminator
  312. if(ArraySize(postData) > 0 && postData[ArraySize(postData) - 1] == 0)
  313. ArrayResize(postData, ArraySize(postData) - 1);
  314. if(ArraySize(postData) <= 0)
  315. {
  316. Print("❌ Empty postData for URL: ", url);
  317. return false;
  318. }
  319. char result[];
  320. string headers = "Content-Type: application/json\r\n";
  321. string resultHeaders = "";
  322. int timeout = 15000;
  323. int res = WebRequest("POST", url, headers, timeout, postData, result, resultHeaders);
  324. if(res == -1)
  325. {
  326. int err = GetLastError();
  327. Print("WebRequest error: ", err, " url=", url);
  328. return false;
  329. }
  330. response = CharArrayToString(result);
  331. if(res == 200 || res == 201)
  332. return true;
  333. Print("HTTP status ", res, " response: ", response);
  334. return false;
  335. }
  336. //+------------------------------------------------------------------+