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 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. //+------------------------------------------------------------------+
  2. //| MarketDataSender.mq5 |
  3. //| Copyright 2025, MetaQuotes Software Corp. |
  4. //| https://www.mql5.com |
  5. //+------------------------------------------------------------------+
  6. #property copyright "Copyright 2025, MetaQuotes Software Corp."
  7. #property link "https://www.mql5.com"
  8. #property version "1.00"
  9. #property description "Sends historical candles and live prices to REST API"
  10. #property script_show_inputs
  11. #include <Trade\SymbolInfo.mqh>
  12. #include <JAson.mqh> // Include JSON library
  13. //--- REST API Configuration
  14. input string ApiBaseUrl = "http://localhost:3000"; // Base URL of your API
  15. input string ApiKey = ""; // Optional API key if required
  16. //--- Historical Data Configuration
  17. input int HistoricalCandleCount = 1000; // Number of historical candles to send
  18. input ENUM_TIMEFRAMES HistoricalTimeframe = PERIOD_H1; // Timeframe for historical data
  19. //--- Global variables
  20. CJAson json;
  21. CSymbolInfo symbolInfo;
  22. string symbols[];
  23. datetime lastSentTime = 0;
  24. //+------------------------------------------------------------------+
  25. //| Expert initialization function |
  26. //+------------------------------------------------------------------+
  27. int OnInit()
  28. {
  29. // Get all symbols
  30. int count = SymbolsTotal(true);
  31. ArrayResize(symbols, count);
  32. for(int i = 0; i < count; i++)
  33. {
  34. symbols[i] = SymbolName(i, true);
  35. }
  36. // Send initial historical data
  37. SendHistoricalData();
  38. return(INIT_SUCCEEDED);
  39. }
  40. //+------------------------------------------------------------------+
  41. //| Expert tick function |
  42. //+------------------------------------------------------------------+
  43. void OnTick()
  44. {
  45. // Send live prices every second
  46. if(TimeCurrent() - lastSentTime >= 1)
  47. {
  48. SendLivePrices();
  49. lastSentTime = TimeCurrent();
  50. }
  51. }
  52. //+------------------------------------------------------------------+
  53. //| Send historical candle data |
  54. //+------------------------------------------------------------------+
  55. void SendHistoricalData()
  56. {
  57. for(int s = 0; s < ArraySize(symbols); s++)
  58. {
  59. string symbol = symbols[s];
  60. // Get historical candles
  61. MqlRates rates[];
  62. int copied = CopyRates(symbol, HistoricalTimeframe, 0, HistoricalCandleCount, rates);
  63. if(copied <= 0) continue;
  64. // Prepare JSON payload
  65. CJAsonArray candlesArray;
  66. for(int i = 0; i < copied; i++)
  67. {
  68. CJAson candleObj;
  69. candleObj.Add("symbolId", GetSymbolId(symbol)); // You need to implement GetSymbolId()
  70. candleObj.Add("openTime", TimeToString(rates[i].time, TIME_DATE|TIME_MINUTES|TIME_SECONDS));
  71. candleObj.Add("closeTime", TimeToString(rates[i].time + PeriodSeconds(HistoricalTimeframe), TIME_DATE|TIME_MINUTES|TIME_SECONDS));
  72. candleObj.Add("open", rates[i].open);
  73. candleObj.Add("high", rates[i].high);
  74. candleObj.Add("low", rates[i].low);
  75. candleObj.Add("close", rates[i].close);
  76. candleObj.Add("volume", rates[i].tick_volume);
  77. candlesArray.Add(candleObj);
  78. }
  79. // Create final payload
  80. CJAson payload;
  81. payload.Add("candles", candlesArray);
  82. // Send to API
  83. string url = ApiBaseUrl + "/api/candles/bulk";
  84. string result;
  85. string headers = "Content-Type: application/json";
  86. if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
  87. // Implement retry logic with exponential backoff
  88. int retries = 3;
  89. int delayMs = 1000;
  90. int res = -1;
  91. for(int attempt = 0; attempt < retries; attempt++) {
  92. res = WebRequest("POST", url, headers, 5000, payload.GetJson(), result);
  93. if(res == 200) break;
  94. Print("Attempt ", attempt+1, " failed (", res, "). Retrying in ", delayMs, "ms");
  95. Sleep(delayMs);
  96. delayMs *= 2; // Exponential backoff
  97. }
  98. if(res == 200) {
  99. Print("Successfully sent historical data for ", symbol);
  100. } else {
  101. Print("Permanent failure sending historical data for ", symbol, ": ", res, " - ", result);
  102. // TODO: Implement dead letter queue storage
  103. }
  104. }
  105. }
  106. //+------------------------------------------------------------------+
  107. //| Send live prices |
  108. //+------------------------------------------------------------------+
  109. void SendLivePrices()
  110. {
  111. CJAsonArray pricesArray;
  112. for(int s = 0; s < ArraySize(symbols); s++)
  113. {
  114. string symbol = symbols[s];
  115. if(!symbolInfo.Name(symbol)) continue;
  116. symbolInfo.RefreshRates();
  117. CJAson priceObj;
  118. priceObj.Add("symbolId", GetSymbolId(symbol));
  119. priceObj.Add("price", symbolInfo.Last());
  120. priceObj.Add("bid", symbolInfo.Bid());
  121. priceObj.Add("ask", symbolInfo.Ask());
  122. priceObj.Add("bidSize", symbolInfo.VolumeBid());
  123. priceObj.Add("askSize", symbolInfo.VolumeAsk());
  124. pricesArray.Add(priceObj);
  125. }
  126. // Create final payload
  127. CJAson payload;
  128. payload.Add("prices", pricesArray);
  129. // Send to API
  130. string url = ApiBaseUrl + "/api/live-prices/bulk";
  131. string result;
  132. string headers = "Content-Type: application/json";
  133. if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
  134. // Implement retry logic with exponential backoff
  135. int retries = 3;
  136. int delayMs = 1000;
  137. int res = -1;
  138. for(int attempt = 0; attempt < retries; attempt++) {
  139. res = WebRequest("POST", url, headers, 5000, payload.GetJson(), result);
  140. if(res == 200) break;
  141. Print("Attempt ", attempt+1, " failed (", res, "). Retrying in ", delayMs, "ms");
  142. Sleep(delayMs);
  143. delayMs *= 2; // Exponential backoff
  144. }
  145. if(res == 200) {
  146. Print("Successfully sent live prices");
  147. } else {
  148. Print("Permanent failure sending live prices: ", res, " - ", result);
  149. // TODO: Implement dead letter queue storage
  150. }
  151. }
  152. //+------------------------------------------------------------------+
  153. //| Initialize symbol map from database |
  154. //+------------------------------------------------------------------+
  155. bool InitializeSymbolMap()
  156. {
  157. // Fetch existing symbols from API
  158. string url = ApiBaseUrl + "/api/symbols";
  159. string result;
  160. string headers = "Content-Type: application/json";
  161. if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
  162. int res = WebRequest("GET", url, headers, 5000, "", result);
  163. if(res != 200)
  164. {
  165. Print("Failed to fetch symbols: ", res, " - ", result);
  166. return false;
  167. }
  168. // Parse response
  169. CJAson parser;
  170. if(!parser.Parse(result))
  171. {
  172. Print("Failed to parse symbols response");
  173. return false;
  174. }
  175. // Create lookup table
  176. CJAsonArray symbolsArray = parser.GetArray("data");
  177. int dbSymbolCount = symbolsArray.Size();
  178. for(int s = 0; s < ArraySize(symbols); s++)
  179. {
  180. string mt5Symbol = symbols[s];
  181. bool found = false;
  182. // Search for matching symbol in database
  183. for(int i = 0; i < dbSymbolCount; i++)
  184. {
  185. CJAson dbSymbol = symbolsArray.GetObject(i);
  186. string dbSymbolName = dbSymbol.GetString("symbol");
  187. if(dbSymbolName == mt5Symbol)
  188. {
  189. symbolIdMap[s] = (int)dbSymbol.GetInt("id");
  190. found = true;
  191. break;
  192. }
  193. }
  194. // Create symbol if not found
  195. if(!found)
  196. {
  197. int newId = CreateSymbol(mt5Symbol);
  198. if(newId > 0)
  199. {
  200. symbolIdMap[s] = newId;
  201. Print("Created new symbol: ", mt5Symbol, " (ID: ", newId, ")");
  202. }
  203. else
  204. {
  205. Print("Failed to create symbol: ", mt5Symbol);
  206. return false;
  207. }
  208. }
  209. }
  210. return true;
  211. }
  212. //+------------------------------------------------------------------+
  213. //| Create new symbol in database |
  214. //+------------------------------------------------------------------+
  215. int CreateSymbol(string symbol)
  216. {
  217. string url = ApiBaseUrl + "/api/symbols";
  218. string result;
  219. string headers = "Content-Type: application/json";
  220. if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
  221. // Extract exchange and instrument type from symbol name
  222. string parts[];
  223. StringSplit(symbol, '_', parts);
  224. string exchange = (ArraySize(parts) > 1) ? parts[0] : "MT5";
  225. string instrumentType = "forex"; // Default, can be improved
  226. // Prepare payload
  227. CJAson payload;
  228. payload.Add("symbol", symbol);
  229. payload.Add("exchange", exchange);
  230. payload.Add("instrumentType", instrumentType);
  231. payload.Add("isActive", true);
  232. int res = WebRequest("POST", url, headers, 5000, payload.GetJson(), result);
  233. if(res != 201)
  234. {
  235. Print("Error creating symbol: ", res, " - ", result);
  236. return -1;
  237. }
  238. // Parse response to get new ID
  239. CJAson parser;
  240. if(!parser.Parse(result))
  241. {
  242. Print("Failed to parse create symbol response");
  243. return -1;
  244. }
  245. return (int)parser.GetObject("data").GetInt("id");
  246. }
  247. //+------------------------------------------------------------------+
  248. //| Get symbol ID from map |
  249. //+------------------------------------------------------------------+
  250. int GetSymbolId(string symbol)
  251. {
  252. for(int i = 0; i < ArraySize(symbols); i++)
  253. {
  254. if(symbols[i] == symbol)
  255. {
  256. return symbolIdMap[i];
  257. }
  258. }
  259. return -1;
  260. }
  261. //+------------------------------------------------------------------+