| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- //+------------------------------------------------------------------+
- //| MarketDataSender.mq5 (Final Fixed Version) |
- //+------------------------------------------------------------------+
- #property strict
- #property description "Fetches all symbols' candles and live prices, sends to API."
- #include <Trade\SymbolInfo.mqh>
- 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;
- }
- //+------------------------------------------------------------------+
|