//+------------------------------------------------------------------+ //| MarketDataSender.mq5 (Final Fixed Version) | //+------------------------------------------------------------------+ #property strict #property description "Fetches all symbols' candles and live prices, sends to API." #include input string ApiBaseUrl = "http://market-price.insightbull.io"; input int HistoricalCandleCount = 1000; input ENUM_TIMEFRAMES HistoricalTimeframe = PERIOD_H1; input int LivePriceIntervalSeconds = 5; // Globals string symbols[]; int symbolIds[]; datetime lastSend = 0; //+------------------------------------------------------------------+ int OnInit() { Print("Initializing MarketDataSender EA..."); if(!InitializeSymbols()) { Print("❌ Failed to initialize symbols."); return(INIT_FAILED); } Print("✅ Symbols initialized: ", ArraySize(symbols)); SendAllHistoricalCandles(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ void OnTick() { if(TimeCurrent() - lastSend >= LivePriceIntervalSeconds) { SendLivePrices(); lastSend = TimeCurrent(); } } //+------------------------------------------------------------------+ bool InitializeSymbols() { int total = SymbolsTotal(true); if(total <= 0) { Print("❌ No symbols found!"); return false; } ArrayResize(symbols, total); ArrayResize(symbolIds, total); for(int i = 0; i < total; i++) { symbols[i] = SymbolName(i, true); symbolIds[i] = -1; } if(!SyncSymbolsWithDatabase()) { Print("❌ Failed to sync symbols with database"); return false; } return true; } //+------------------------------------------------------------------+ bool SyncSymbolsWithDatabase() { Print("Syncing symbols with database..."); string url = ApiBaseUrl + "/api/symbols"; string headers = "Content-Type: application/json\r\n"; string resultHeaders = ""; char result[]; char emptyData[]; // ✅ required placeholder for GET request ResetLastError(); // ✅ Correct GET request signature: includes empty data[] int res = WebRequest("GET", url, headers, 5000, emptyData, result, resultHeaders); if(res == -1) { int err = GetLastError(); Print("❌ WebRequest connection error: ", err, " URL=", url); return false; } if(res != 200) { Print("❌ Failed to fetch symbols from API: HTTP ", res, " Response: ", CharArrayToString(result)); return false; } string symbolsResponse = CharArrayToString(result); if(StringFind(symbolsResponse, "\"data\"") < 0) { Print("⚠️ Unexpected response format from symbols API: ", symbolsResponse); } for(int i = 0; i < ArraySize(symbols); i++) { string symbolName = symbols[i]; int symbolId = FindSymbolId(symbolsResponse, symbolName); if(symbolId > 0) { symbolIds[i] = symbolId; Print("✅ Found existing symbol: ", symbolName, " (ID: ", symbolId, ")"); } else { Sleep(300); // prevent overload (0.3 second delay) symbolId = CreateSymbolInDatabase(symbolName); if(symbolId > 0) { symbolIds[i] = symbolId; Print("✅ Created new symbol: ", symbolName, " (ID: ", symbolId, ")"); } else { Print("❌ Failed to create symbol: ", symbolName," (ID: ", symbolId, ")"); symbolIds[i] = -1; } } } return true; } //+------------------------------------------------------------------+ int FindSymbolId(string response, string symbolName) { string searchPattern = StringFormat("\"symbol\":\"%s\"", symbolName); int symbolPos = StringFind(response, searchPattern); if(symbolPos < 0) return -1; int idPos = StringFind(response, "\"id\":", symbolPos); if(idPos < 0) return -1; int startPos = idPos + 5; int endPos = StringFind(response, ",", startPos); if(endPos < 0) endPos = StringFind(response, "}", startPos); string idStr = StringSubstr(response, startPos, endPos - startPos); return (int)StringToInteger(idStr); } //+------------------------------------------------------------------+ int CreateSymbolInDatabase(string symbolName) { string baseAsset = ""; string quoteAsset = ""; string exchange = "MT5"; string instrumentType = "forex"; if(StringLen(symbolName) >= 6) { baseAsset = StringSubstr(symbolName, 0, 3); quoteAsset = StringSubstr(symbolName, 3, 3); } string json = StringFormat( "{\"symbol\":\"%s\",\"baseAsset\":\"%s\",\"quoteAsset\":\"%s\",\"exchange\":\"%s\",\"instrumentType\":\"%s\",\"isActive\":true}", symbolName, baseAsset, quoteAsset, exchange, instrumentType ); string url = ApiBaseUrl + "/api/symbols"; string headers = "Content-Type: application/json\r\n"; string resultHeaders = ""; char postData[]; StringToCharArray(json, postData, 0, CP_UTF8); // ✅ FIX: Remove trailing null terminator from JSON if(ArraySize(postData) > 0 && postData[ArraySize(postData) - 1] == 0) ArrayResize(postData, ArraySize(postData) - 1); char result[]; int res = WebRequest("POST", url, headers, 5000, postData, result, resultHeaders); if(res != 201 && res != 200) { Print("❌ Failed to create symbol: ", res, " Response: ", CharArrayToString(result)); return -1; } string createResponse = CharArrayToString(result); int idPos = StringFind(createResponse, "\"id\":"); if(idPos < 0) return -1; int startPos = idPos + 5; int endPos = StringFind(createResponse, ",", startPos); if(endPos < 0) endPos = StringFind(createResponse, "}", startPos); string idStr = StringSubstr(createResponse, startPos, endPos - startPos); return (int)StringToInteger(idStr); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Send all historical candles to the API (Fixed Version) | //+------------------------------------------------------------------+ void SendAllHistoricalCandles() { Print("Starting historical upload for ", ArraySize(symbols), " symbols..."); for(int i = 0; i < ArraySize(symbols); i++) { string sym = symbols[i]; // --- Ensure data is ready --- Sleep(300); int tries = 0; while(!SeriesInfoInteger(sym, HistoricalTimeframe, SERIES_SYNCHRONIZED) && tries < 10) { Print("⏳ Waiting for ", sym, " history to load..."); Sleep(500); tries++; } // --- Now copy candles --- MqlRates rates[]; ResetLastError(); int copied = CopyRates(sym, HistoricalTimeframe, 0, HistoricalCandleCount, rates); int err = GetLastError(); if(copied <= 0) { Print("⚠️ Failed to copy candles for ", sym, " (copied=", copied, ", err=", err, ")"); continue; } Print("✅ Copied ", copied, " candles for ", sym); // --- Print a few sample candles --- int sampleCount = MathMin(5, copied); // show up to 5 examples for(int j = 0; j < sampleCount; j++) { MqlRates r = rates[j]; string openTime = TimeToString(r.time, TIME_DATE|TIME_SECONDS); string closeTime = TimeToString(r.time + PeriodSeconds(HistoricalTimeframe), TIME_DATE|TIME_SECONDS); PrintFormat( "🕒 [%s] %s → %s | O=%.5f H=%.5f L=%.5f C=%.5f | Vol=%.2f", sym, openTime, closeTime, r.open, r.high, r.low, r.close, r.tick_volume ); } // --- Send candles in batches to API --- int sentTotal = 0; int batchSize = 200; for(int start = 0; start < copied; start += batchSize) { int size = MathMin(batchSize, copied - start); int symbolId = symbolIds[i]; if(symbolId <= 0) continue; string json = BuildCandleJSONFromRates(symbolId, rates, start, size); string url = ApiBaseUrl + "/api/candles/bulk"; string response; bool ok = SendJSON(url, json, response); if(!ok) { Print("❌ Failed to send candle batch for ", sym, " start=", start); break; } sentTotal += size; Print("📤 Sent candles for ", sym, ": ", sentTotal, "/", copied); } } Print("✅ Historical upload finished."); } //+------------------------------------------------------------------+ void SendLivePrices() { bool firstItem = true; string json = "{\"prices\":["; int sentCount = 0; for(int i = 0; i < ArraySize(symbols); i++) { string sym = symbols[i]; double bid = SymbolInfoDouble(sym, SYMBOL_BID); double ask = SymbolInfoDouble(sym, SYMBOL_ASK); double last = SymbolInfoDouble(sym, SYMBOL_LAST); if(bid <= 0 || ask <= 0 || last <= 0) continue; int symId = symbolIds[i]; if(symId <= 0) continue; string item = StringFormat("{\"symbolId\":%d,\"price\":%.8f,\"bid\":%.8f,\"ask\":%.8f,\"bidSize\":%.8f,\"askSize\":%.8f}", symId, last, bid, ask, 0.0, 0.0); if(!firstItem) json += ","; json += item; firstItem = false; sentCount++; } json += "]}"; if(sentCount == 0) { Print("No valid live prices to send right now."); return; } string url = ApiBaseUrl + "/api/live-prices/bulk"; string response; bool ok = SendJSON(url, json, response); if(ok) Print("✅ Sent ", sentCount, " live prices."); else Print("❌ Failed to send live prices (sent ", sentCount, " items). Response: ", response); } //+------------------------------------------------------------------+ string ToISO8601(datetime t) { MqlDateTime st; TimeToStruct(t, st); return StringFormat("%04d-%02d-%02dT%02d:%02d:%02d.000Z", st.year, st.mon, st.day, st.hour, st.min, st.sec); } string BuildCandleJSONFromRates(int symbolId, MqlRates &rates[], int startIndex, int count) { string json = "{\"candles\":["; bool first = true; int ratesSize = ArraySize(rates); for(int i = startIndex; i < startIndex + count && i < ratesSize; i++) { MqlRates r = rates[i]; if(r.time <= 0) continue; datetime open_dt = (datetime)r.time; datetime close_dt = (datetime)(r.time + (datetime)PeriodSeconds(HistoricalTimeframe)); string openTime = ToISO8601(open_dt); string closeTime = ToISO8601(close_dt); double volume = (r.tick_volume > 0 ? r.tick_volume : 1); double quoteVolume = (r.real_volume > 0 ? r.real_volume : volume); string one = StringFormat( "{\"symbolId\":%d,\"openTime\":\"%s\",\"closeTime\":\"%s\",\"open\":%.5f,\"high\":%.5f,\"low\":%.5f,\"close\":%.5f,\"volume\":%.5f,\"tradesCount\":%d,\"quoteVolume\":%.5f}", symbolId, openTime, closeTime, r.open, r.high, r.low, r.close, volume, (int)volume, quoteVolume ); if(!first) json += ","; json += one; first = false; } json += "]}"; return json; } //+------------------------------------------------------------------+ bool SendJSON(string url, string json, string &response) { ResetLastError(); char postData[]; StringToCharArray(json, postData, 0, CP_UTF8); // ✅ Remove trailing null terminator if(ArraySize(postData) > 0 && postData[ArraySize(postData) - 1] == 0) ArrayResize(postData, ArraySize(postData) - 1); if(ArraySize(postData) <= 0) { Print("❌ Empty postData for URL: ", url); return false; } char result[]; string headers = "Content-Type: application/json\r\n"; string resultHeaders = ""; int timeout = 15000; int res = WebRequest("POST", url, headers, timeout, postData, result, resultHeaders); if(res == -1) { int err = GetLastError(); Print("WebRequest error: ", err, " url=", url); return false; } response = CharArrayToString(result); if(res == 200 || res == 201) return true; Print("HTTP status ", res, " response: ", response); return false; } //+------------------------------------------------------------------+