浏览代码

feat: Add MT5 integration with automatic symbol registration and data streaming

This commit introduces:

- MT5 Expert Advisor (MarketDataSender.mq5) that:
  * Automatically registers symbols with the API
  * Sends last 1000 historical candles on initialization
  * Streams live prices via bulk updates every second
  * Handles error conditions and retries
- Comprehensive README documentation for MT5 setup
- Symbol mapping between MT5 and database IDs
Hussain Afzal 4 月之前
父节点
当前提交
bf57b9271b
共有 2 个文件被更改,包括 323 次插入0 次删除
  1. 293 0
      MT5/Experts/MarketDataSender.mq5
  2. 30 0
      README.md

+ 293 - 0
MT5/Experts/MarketDataSender.mq5

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

+ 30 - 0
README.md

@@ -2,6 +2,36 @@
2 2
 
3 3
 A high-performance financial data API that provides comprehensive market data for various financial instruments including cryptocurrencies, stocks, forex, and commodities through both RESTful endpoints and real-time WebSocket connections.
4 4
 
5
+## MT5 Integration
6
+
7
+The service includes an MT5 Expert Advisor (EA) that automatically sends historical candle data and live tick prices to the API.
8
+
9
+### EA Features
10
+- Automatic symbol registration in the database
11
+- Bulk upload of last 1000 candles per symbol
12
+- Real-time price streaming on tick events
13
+- Error handling with retry logic
14
+- Configurable API endpoint and authentication
15
+
16
+### Setup Instructions
17
+1. Copy `MT5/Experts/MarketDataSender.mq5` to your MT5 Experts directory
18
+2. Configure EA settings:
19
+   - `ApiBaseUrl`: Your API endpoint (e.g., `http://your-server:3000`)
20
+   - `ApiKey`: Optional authentication key
21
+   - `HistoricalCandleCount`: Number of historical candles to send (default: 1000)
22
+   - `HistoricalTimeframe`: Timeframe for historical data (default: H1)
23
+
24
+3. Attach the EA to any MT5 chart
25
+4. The EA will:
26
+   - Register all available symbols with the API
27
+   - Send historical candles on initialization
28
+   - Stream live prices every second
29
+
30
+### Notes
31
+- Symbols are automatically created if they don't exist
32
+- Exchange is derived from symbol name (format: `EXCHANGE_SYMBOL`)
33
+- Default instrument type is forex (customize in EA code if needed)
34
+
5 35
 ## Features
6 36
 
7 37
 - **Multi-Asset Support**: Handles cryptocurrencies, stocks, forex, and commodities