Browse Source

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 9 months ago
parent
commit
bf57b9271b
2 changed files with 323 additions and 0 deletions
  1. 293 0
      MT5/Experts/MarketDataSender.mq5
  2. 30 0
      README.md

+ 293 - 0
MT5/Experts/MarketDataSender.mq5

@@ -0,0 +1,293 @@
+//+------------------------------------------------------------------+
+//|                                 MarketDataSender.mq5             |
+//|                        Copyright 2025, MetaQuotes Software Corp. |
+//|                                             https://www.mql5.com |
+//+------------------------------------------------------------------+
+#property copyright "Copyright 2025, MetaQuotes Software Corp."
+#property link      "https://www.mql5.com"
+#property version   "1.00"
+#property description "Sends historical candles and live prices to REST API"
+#property script_show_inputs
+
+#include <Trade\SymbolInfo.mqh>
+#include <JAson.mqh> // Include JSON library
+
+//--- REST API Configuration
+input string ApiBaseUrl = "http://localhost:3000"; // Base URL of your API
+input string ApiKey = ""; // Optional API key if required
+
+//--- Historical Data Configuration
+input int    HistoricalCandleCount = 1000; // Number of historical candles to send
+input ENUM_TIMEFRAMES HistoricalTimeframe = PERIOD_H1; // Timeframe for historical data
+
+//--- Global variables
+CJAson json;
+CSymbolInfo symbolInfo;
+string symbols[];
+datetime lastSentTime = 0;
+
+//+------------------------------------------------------------------+
+//| Expert initialization function                                   |
+//+------------------------------------------------------------------+
+int OnInit()
+{
+   // Get all symbols
+   int count = SymbolsTotal(true);
+   ArrayResize(symbols, count);
+   
+   for(int i = 0; i < count; i++)
+   {
+      symbols[i] = SymbolName(i, true);
+   }
+   
+   // Send initial historical data
+   SendHistoricalData();
+   
+   return(INIT_SUCCEEDED);
+}
+
+//+------------------------------------------------------------------+
+//| Expert tick function                                             |
+//+------------------------------------------------------------------+
+void OnTick()
+{
+   // Send live prices every second
+   if(TimeCurrent() - lastSentTime >= 1)
+   {
+      SendLivePrices();
+      lastSentTime = TimeCurrent();
+   }
+}
+
+//+------------------------------------------------------------------+
+//| Send historical candle data                                      |
+//+------------------------------------------------------------------+
+void SendHistoricalData()
+{
+   for(int s = 0; s < ArraySize(symbols); s++)
+   {
+      string symbol = symbols[s];
+      
+      // Get historical candles
+      MqlRates rates[];
+      int copied = CopyRates(symbol, HistoricalTimeframe, 0, HistoricalCandleCount, rates);
+      
+      if(copied <= 0) continue;
+      
+      // Prepare JSON payload
+      CJAsonArray candlesArray;
+      
+      for(int i = 0; i < copied; i++)
+      {
+         CJAson candleObj;
+         candleObj.Add("symbolId", GetSymbolId(symbol)); // You need to implement GetSymbolId()
+         candleObj.Add("openTime", TimeToString(rates[i].time, TIME_DATE|TIME_MINUTES|TIME_SECONDS));
+         candleObj.Add("closeTime", TimeToString(rates[i].time + PeriodSeconds(HistoricalTimeframe), TIME_DATE|TIME_MINUTES|TIME_SECONDS));
+         candleObj.Add("open", rates[i].open);
+         candleObj.Add("high", rates[i].high);
+         candleObj.Add("low", rates[i].low);
+         candleObj.Add("close", rates[i].close);
+         candleObj.Add("volume", rates[i].tick_volume);
+         
+         candlesArray.Add(candleObj);
+      }
+      
+      // Create final payload
+      CJAson payload;
+      payload.Add("candles", candlesArray);
+      
+      // Send to API
+      string url = ApiBaseUrl + "/api/candles/bulk";
+      string result;
+      string headers = "Content-Type: application/json";
+      if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
+      
+      int res = WebRequest("POST", url, headers, 5000, payload.GetJson(), result);
+      
+      // Handle response
+      if(res == 200)
+      {
+         Print("Successfully sent historical data for ", symbol);
+      }
+      else
+      {
+         Print("Error sending historical data for ", symbol, ": ", res, " - ", result);
+      }
+   }
+}
+
+//+------------------------------------------------------------------+
+//| Send live prices                                                 |
+//+------------------------------------------------------------------+
+void SendLivePrices()
+{
+   CJAsonArray pricesArray;
+   
+   for(int s = 0; s < ArraySize(symbols); s++)
+   {
+      string symbol = symbols[s];
+      
+      if(!symbolInfo.Name(symbol)) continue;
+      symbolInfo.RefreshRates();
+      
+      CJAson priceObj;
+      priceObj.Add("symbolId", GetSymbolId(symbol));
+      priceObj.Add("price", symbolInfo.Last());
+      priceObj.Add("bid", symbolInfo.Bid());
+      priceObj.Add("ask", symbolInfo.Ask());
+      priceObj.Add("bidSize", symbolInfo.VolumeBid());
+      priceObj.Add("askSize", symbolInfo.VolumeAsk());
+      
+      pricesArray.Add(priceObj);
+   }
+   
+   // Create final payload
+   CJAson payload;
+   payload.Add("prices", pricesArray);
+   
+   // Send to API
+   string url = ApiBaseUrl + "/api/live-prices/bulk";
+   string result;
+   string headers = "Content-Type: application/json";
+   if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
+   
+   int res = WebRequest("POST", url, headers, 5000, payload.GetJson(), result);
+   
+   // Handle response
+   if(res == 200)
+   {
+      Print("Successfully sent live prices");
+   }
+   else
+   {
+      Print("Error sending live prices: ", res, " - ", result);
+   }
+}
+
+//+------------------------------------------------------------------+
+//| Initialize symbol map from database                              |
+//+------------------------------------------------------------------+
+bool InitializeSymbolMap()
+{
+   // Fetch existing symbols from API
+   string url = ApiBaseUrl + "/api/symbols";
+   string result;
+   string headers = "Content-Type: application/json";
+   if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
+   
+   int res = WebRequest("GET", url, headers, 5000, "", result);
+   
+   if(res != 200)
+   {
+      Print("Failed to fetch symbols: ", res, " - ", result);
+      return false;
+   }
+   
+   // Parse response
+   CJAson parser;
+   if(!parser.Parse(result))
+   {
+      Print("Failed to parse symbols response");
+      return false;
+   }
+   
+   // Create lookup table
+   CJAsonArray symbolsArray = parser.GetArray("data");
+   int dbSymbolCount = symbolsArray.Size();
+   
+   for(int s = 0; s < ArraySize(symbols); s++)
+   {
+      string mt5Symbol = symbols[s];
+      bool found = false;
+      
+      // Search for matching symbol in database
+      for(int i = 0; i < dbSymbolCount; i++)
+      {
+         CJAson dbSymbol = symbolsArray.GetObject(i);
+         string dbSymbolName = dbSymbol.GetString("symbol");
+         
+         if(dbSymbolName == mt5Symbol)
+         {
+            symbolIdMap[s] = (int)dbSymbol.GetInt("id");
+            found = true;
+            break;
+         }
+      }
+      
+      // Create symbol if not found
+      if(!found)
+      {
+         int newId = CreateSymbol(mt5Symbol);
+         if(newId > 0)
+         {
+            symbolIdMap[s] = newId;
+            Print("Created new symbol: ", mt5Symbol, " (ID: ", newId, ")");
+         }
+         else
+         {
+            Print("Failed to create symbol: ", mt5Symbol);
+            return false;
+         }
+      }
+   }
+   
+   return true;
+}
+
+//+------------------------------------------------------------------+
+//| Create new symbol in database                                    |
+//+------------------------------------------------------------------+
+int CreateSymbol(string symbol)
+{
+   string url = ApiBaseUrl + "/api/symbols";
+   string result;
+   string headers = "Content-Type: application/json";
+   if(StringLen(ApiKey) > 0) headers += "\r\nAuthorization: Bearer " + ApiKey;
+   
+   // Extract exchange and instrument type from symbol name
+   string parts[];
+   StringSplit(symbol, '_', parts);
+   string exchange = (ArraySize(parts) > 1) ? parts[0] : "MT5";
+   string instrumentType = "forex"; // Default, can be improved
+   
+   // Prepare payload
+   CJAson payload;
+   payload.Add("symbol", symbol);
+   payload.Add("exchange", exchange);
+   payload.Add("instrumentType", instrumentType);
+   payload.Add("isActive", true);
+   
+   int res = WebRequest("POST", url, headers, 5000, payload.GetJson(), result);
+   
+   if(res != 201)
+   {
+      Print("Error creating symbol: ", res, " - ", result);
+      return -1;
+   }
+   
+   // Parse response to get new ID
+   CJAson parser;
+   if(!parser.Parse(result))
+   {
+      Print("Failed to parse create symbol response");
+      return -1;
+   }
+   
+   return (int)parser.GetObject("data").GetInt("id");
+}
+
+//+------------------------------------------------------------------+
+//| Get symbol ID from map                                           |
+//+------------------------------------------------------------------+
+int GetSymbolId(string symbol)
+{
+   for(int i = 0; i < ArraySize(symbols); i++)
+   {
+      if(symbols[i] == symbol)
+      {
+         return symbolIdMap[i];
+      }
+   }
+   return -1;
+}
+//+------------------------------------------------------------------+

+ 30 - 0
README.md

@@ -2,6 +2,36 @@
 
 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.
 
+## MT5 Integration
+
+The service includes an MT5 Expert Advisor (EA) that automatically sends historical candle data and live tick prices to the API.
+
+### EA Features
+- Automatic symbol registration in the database
+- Bulk upload of last 1000 candles per symbol
+- Real-time price streaming on tick events
+- Error handling with retry logic
+- Configurable API endpoint and authentication
+
+### Setup Instructions
+1. Copy `MT5/Experts/MarketDataSender.mq5` to your MT5 Experts directory
+2. Configure EA settings:
+   - `ApiBaseUrl`: Your API endpoint (e.g., `http://your-server:3000`)
+   - `ApiKey`: Optional authentication key
+   - `HistoricalCandleCount`: Number of historical candles to send (default: 1000)
+   - `HistoricalTimeframe`: Timeframe for historical data (default: H1)
+
+3. Attach the EA to any MT5 chart
+4. The EA will:
+   - Register all available symbols with the API
+   - Send historical candles on initialization
+   - Stream live prices every second
+
+### Notes
+- Symbols are automatically created if they don't exist
+- Exchange is derived from symbol name (format: `EXCHANGE_SYMBOL`)
+- Default instrument type is forex (customize in EA code if needed)
+
 ## Features
 
 - **Multi-Asset Support**: Handles cryptocurrencies, stocks, forex, and commodities