Selaa lähdekoodia

feat: enhance MT5 expert advisor with timer-based sync and improved symbol parsing

- Add timer-based periodic candle synchronization every 60 seconds
- Improve symbol parsing to handle suffixes and better detect base/quote assets
- Enhance error handling and logging throughout the code
- Add cleanup functionality to remove old candles (keep last 1000)
- Improve history loading checks with timeout safety
- Better fallback for instrument type detection (crypto, metal, index, forex)
uzairrizwan1 3 kuukautta sitten
vanhempi
commit
ebecb8c792
1 muutettua tiedostoa jossa 135 lisäystä ja 15 poistoa
  1. 135 15
      MT5/Experts/MarketDataSender.mq5

+ 135 - 15
MT5/Experts/MarketDataSender.mq5

@@ -15,7 +15,7 @@ input int LivePriceIntervalSeconds = 5;
15
 string symbols[];
15
 string symbols[];
16
 int symbolIds[];
16
 int symbolIds[];
17
 datetime lastSend = 0;
17
 datetime lastSend = 0;
18
-
18
+datetime lastCandleSync = 0;
19
 //+------------------------------------------------------------------+
19
 //+------------------------------------------------------------------+
20
 int OnInit()
20
 int OnInit()
21
 {
21
 {
@@ -28,6 +28,8 @@ int OnInit()
28
 
28
 
29
    Print("✅ Symbols initialized: ", ArraySize(symbols));
29
    Print("✅ Symbols initialized: ", ArraySize(symbols));
30
    SendAllHistoricalCandles();
30
    SendAllHistoricalCandles();
31
+   EventSetTimer(60);  // ⏱️ Trigger OnTimer() every 30 minutes
32
+   Print("✅ Timer set: SendAllHistoricalCandles() will run every 30 minutes.");
31
    return(INIT_SUCCEEDED);
33
    return(INIT_SUCCEEDED);
32
 }
34
 }
33
 
35
 
@@ -202,11 +204,53 @@ int CreateSymbolInDatabase(string symbolName)
202
    string exchange = "MT5";
204
    string exchange = "MT5";
203
    string instrumentType = "forex";
205
    string instrumentType = "forex";
204
 
206
 
207
+   // --- Clean suffixes like ".pro", ".m", ".r", "_i" ---
208
+   int dotPos = StringFind(symbolName, ".");
209
+   if(dotPos > 0)
210
+      symbolName = StringSubstr(symbolName, 0, dotPos);
211
+
212
+   // --- Try basic split for 6-char pairs like EURUSD, GBPJPY, BTCUSD ---
205
    if(StringLen(symbolName) >= 6)
213
    if(StringLen(symbolName) >= 6)
206
    {
214
    {
207
       baseAsset = StringSubstr(symbolName, 0, 3);
215
       baseAsset = StringSubstr(symbolName, 0, 3);
208
       quoteAsset = StringSubstr(symbolName, 3, 3);
216
       quoteAsset = StringSubstr(symbolName, 3, 3);
209
    }
217
    }
218
+   else
219
+   {
220
+      // --- Try alternate detection ---
221
+      if(StringFind(symbolName, "USD") >= 0)
222
+      {
223
+         int pos = StringFind(symbolName, "USD");
224
+         baseAsset = StringSubstr(symbolName, 0, pos);
225
+         quoteAsset = "USD";
226
+      }
227
+      else if(StringFind(symbolName, "EUR") >= 0)
228
+      {
229
+         int pos = StringFind(symbolName, "EUR");
230
+         baseAsset = StringSubstr(symbolName, 0, pos);
231
+         quoteAsset = "EUR";
232
+      }
233
+      else
234
+      {
235
+         // Fallback safe defaults
236
+         baseAsset = symbolName;
237
+         quoteAsset = "USD";
238
+      }
239
+   }
240
+
241
+   // --- Final safety: ensure no empty fields ---
242
+   if(StringLen(baseAsset) == 0) baseAsset = "UNKNOWN";
243
+   if(StringLen(quoteAsset) == 0) quoteAsset = "USD";
244
+
245
+   // --- Decide instrument type ---
246
+   if(StringFind(symbolName, "BTC") == 0 || StringFind(symbolName, "ETH") == 0)
247
+      instrumentType = "crypto";
248
+   else if(StringFind(symbolName, "XAU") == 0 || StringFind(symbolName, "XAG") == 0)
249
+      instrumentType = "metal";
250
+   else if(StringFind(symbolName, "US30") == 0 || StringFind(symbolName, "NAS") == 0)
251
+      instrumentType = "index";
252
+   else
253
+      instrumentType = "forex";
210
 
254
 
211
    string json = StringFormat(
255
    string json = StringFormat(
212
       "{\"symbol\":\"%s\",\"baseAsset\":\"%s\",\"quoteAsset\":\"%s\",\"exchange\":\"%s\",\"instrumentType\":\"%s\",\"isActive\":true}",
256
       "{\"symbol\":\"%s\",\"baseAsset\":\"%s\",\"quoteAsset\":\"%s\",\"exchange\":\"%s\",\"instrumentType\":\"%s\",\"isActive\":true}",
@@ -219,18 +263,15 @@ int CreateSymbolInDatabase(string symbolName)
219
 
263
 
220
    char postData[];
264
    char postData[];
221
    StringToCharArray(json, postData, 0, CP_UTF8);
265
    StringToCharArray(json, postData, 0, CP_UTF8);
222
-
223
-   // ✅ FIX: Remove trailing null terminator from JSON
224
    if(ArraySize(postData) > 0 && postData[ArraySize(postData) - 1] == 0)
266
    if(ArraySize(postData) > 0 && postData[ArraySize(postData) - 1] == 0)
225
       ArrayResize(postData, ArraySize(postData) - 1);
267
       ArrayResize(postData, ArraySize(postData) - 1);
226
 
268
 
227
    char result[];
269
    char result[];
228
-
229
    int res = WebRequest("POST", url, headers, 5000, postData, result, resultHeaders);
270
    int res = WebRequest("POST", url, headers, 5000, postData, result, resultHeaders);
230
 
271
 
231
    if(res != 201 && res != 200)
272
    if(res != 201 && res != 200)
232
    {
273
    {
233
-      Print("❌ Failed to create symbol: ", res, " Response: ", CharArrayToString(result));
274
+      PrintFormat("❌ Failed to create symbol %s | HTTP %d | Response: %s", symbolName, res, CharArrayToString(result));
234
       return -1;
275
       return -1;
235
    }
276
    }
236
 
277
 
@@ -245,6 +286,7 @@ int CreateSymbolInDatabase(string symbolName)
245
    string idStr = StringSubstr(createResponse, startPos, endPos - startPos);
286
    string idStr = StringSubstr(createResponse, startPos, endPos - startPos);
246
    return (int)StringToInteger(idStr);
287
    return (int)StringToInteger(idStr);
247
 }
288
 }
289
+
248
 //+------------------------------------------------------------------+
290
 //+------------------------------------------------------------------+
249
 //| Fetch latest stored candle openTime from API                     |
291
 //| Fetch latest stored candle openTime from API                     |
250
 //+------------------------------------------------------------------+
292
 //+------------------------------------------------------------------+
@@ -298,6 +340,9 @@ datetime GetLatestCandleTime(int symbolId)
298
 //+------------------------------------------------------------------+
340
 //+------------------------------------------------------------------+
299
 //| Send all historical candles to the API (Fixed Version)           |
341
 //| Send all historical candles to the API (Fixed Version)           |
300
 //+------------------------------------------------------------------+
342
 //+------------------------------------------------------------------+
343
+//+------------------------------------------------------------------+
344
+//| Send all historical candles to the API (Fixed + Timeout Safe)    |
345
+//+------------------------------------------------------------------+
301
 void SendAllHistoricalCandles()
346
 void SendAllHistoricalCandles()
302
 {
347
 {
303
    Print("Starting historical upload for ", ArraySize(symbols), " symbols...");
348
    Print("Starting historical upload for ", ArraySize(symbols), " symbols...");
@@ -311,26 +356,42 @@ void SendAllHistoricalCandles()
311
       // --- Get last stored candle time ---
356
       // --- Get last stored candle time ---
312
       datetime latestApiTime = GetLatestCandleTime(symbolId);
357
       datetime latestApiTime = GetLatestCandleTime(symbolId);
313
 
358
 
314
-      // --- Ensure data is ready ---
359
+      // --- Ensure history data is available ---
315
       Sleep(300);
360
       Sleep(300);
316
       int tries = 0;
361
       int tries = 0;
317
-      while(!SeriesInfoInteger(sym, HistoricalTimeframe, SERIES_SYNCHRONIZED) && tries < 10)
362
+      bool historyReady = false;
363
+
364
+      while(tries < 10)
318
       {
365
       {
319
-         Print("⏳ Waiting for ", sym, " history to load...");
366
+         if(SeriesInfoInteger(sym, HistoricalTimeframe, SERIES_SYNCHRONIZED))
367
+         {
368
+            historyReady = true;
369
+            break;
370
+         }
371
+         PrintFormat("⏳ Waiting for %s history to load... (try %d/10)", sym, tries + 1);
320
          Sleep(500);
372
          Sleep(500);
321
          tries++;
373
          tries++;
322
       }
374
       }
323
 
375
 
376
+      if(!historyReady)
377
+      {
378
+         PrintFormat("⚠️ Skipping %s — history not loaded after 10 tries (~5s timeout).", sym);
379
+         continue;
380
+      }
381
+
382
+      // --- Copy rates ---
324
       MqlRates rates[];
383
       MqlRates rates[];
325
       ResetLastError();
384
       ResetLastError();
326
       int copied = CopyRates(sym, HistoricalTimeframe, 0, HistoricalCandleCount, rates);
385
       int copied = CopyRates(sym, HistoricalTimeframe, 0, HistoricalCandleCount, rates);
386
+
327
       if(copied <= 0)
387
       if(copied <= 0)
328
       {
388
       {
329
-         Print("⚠️ Failed to copy candles for ", sym);
389
+         int err = GetLastError();
390
+         PrintFormat("⚠️ Failed to copy candles for %s (error %d)", sym, err);
330
          continue;
391
          continue;
331
       }
392
       }
332
 
393
 
333
-      Print("✅ Copied ", copied, " candles for ", sym);
394
+      PrintFormat("✅ Copied %d candles for %s", copied, sym);
334
 
395
 
335
       // --- Filter new candles ---
396
       // --- Filter new candles ---
336
       int startIndex = 0;
397
       int startIndex = 0;
@@ -346,34 +407,39 @@ void SendAllHistoricalCandles()
346
       int newCount = copied - startIndex;
407
       int newCount = copied - startIndex;
347
       if(newCount <= 0)
408
       if(newCount <= 0)
348
       {
409
       {
349
-         Print("ℹ️ No new candles to send for ", sym);
410
+         PrintFormat("ℹ️ No new candles to send for %s", sym);
350
          continue;
411
          continue;
351
       }
412
       }
352
 
413
 
353
-      Print("🆕 Sending ", newCount, " new candles for ", sym, " after ", TimeToString(latestApiTime, TIME_DATE|TIME_SECONDS));
414
+      PrintFormat("🆕 Sending %d new candles for %s after %s", newCount, sym, TimeToString(latestApiTime, TIME_DATE|TIME_SECONDS));
354
 
415
 
355
       // --- Send new candles in batches ---
416
       // --- Send new candles in batches ---
356
       int batchSize = 200;
417
       int batchSize = 200;
357
       int sentTotal = 0;
418
       int sentTotal = 0;
419
+
358
       for(int start = startIndex; start < copied; start += batchSize)
420
       for(int start = startIndex; start < copied; start += batchSize)
359
       {
421
       {
360
          int size = MathMin(batchSize, copied - start);
422
          int size = MathMin(batchSize, copied - start);
361
          string json = BuildCandleJSONFromRates(symbolId, rates, start, size);
423
          string json = BuildCandleJSONFromRates(symbolId, rates, start, size);
362
          string url = ApiBaseUrl + "/api/candles/bulk";
424
          string url = ApiBaseUrl + "/api/candles/bulk";
363
          string response;
425
          string response;
426
+
364
          bool ok = SendJSON(url, json, response);
427
          bool ok = SendJSON(url, json, response);
365
          if(!ok)
428
          if(!ok)
366
          {
429
          {
367
-            Print("❌ Failed to send candle batch for ", sym, " start=", start);
430
+            PrintFormat("❌ Failed to send candle batch for %s (start=%d)", sym, start);
368
             break;
431
             break;
369
          }
432
          }
433
+
370
          sentTotal += size;
434
          sentTotal += size;
371
-         Print("📤 Sent ", sentTotal, "/", newCount, " new candles for ", sym);
435
+         PrintFormat("📤 Sent %d/%d new candles for %s", sentTotal, newCount, sym);
372
       }
436
       }
373
    }
437
    }
438
+
374
    Print("✅ Incremental candle upload finished.");
439
    Print("✅ Incremental candle upload finished.");
375
 }
440
 }
376
 
441
 
442
+
377
 //+------------------------------------------------------------------+
443
 //+------------------------------------------------------------------+
378
 //| Send live prices of all active symbols                           |
444
 //| Send live prices of all active symbols                           |
379
 //+------------------------------------------------------------------+
445
 //+------------------------------------------------------------------+
@@ -593,4 +659,58 @@ bool SendJSON(string url, string json, string &response)
593
    return false;
659
    return false;
594
 }
660
 }
595
 
661
 
596
-//+------------------------------------------------------------------+
662
+//+------------------------------------------------------------------+
663
+//+------------------------------------------------------------------+
664
+//| Cleanup old candles (keep only last 1000)                        |
665
+//+------------------------------------------------------------------+
666
+void CleanupOldCandles(int symbolId)
667
+{
668
+   string url = ApiBaseUrl + "/api/candles/cleanup/" + IntegerToString(symbolId) + "?keep=1000";
669
+   string headers = "Content-Type: application/json\r\n";
670
+   string resultHeaders = "";
671
+   char result[];
672
+   char emptyData[];
673
+
674
+   ResetLastError();
675
+   int res = WebRequest("DELETE", url, headers, 10000, emptyData, result, resultHeaders);
676
+
677
+   string response = CharArrayToString(result);
678
+   if(res == 200 || res == 204)
679
+      Print("🧹 Cleanup successful for symbolId=", symbolId, " → kept last 1000 candles.");
680
+   else
681
+      Print("⚠️ Cleanup failed for symbolId=", symbolId, " HTTP=", res, " Response=", response);
682
+}
683
+
684
+//+------------------------------------------------------------------+
685
+//| Timer event: runs every 60 seconds                               |
686
+//+------------------------------------------------------------------+
687
+void OnTimer()
688
+{
689
+   datetime now = TimeCurrent();
690
+
691
+   // ✅ Run full candle sync only once every minute
692
+   if(now - lastCandleSync >= 600)
693
+   {
694
+      Print("⏰ Running scheduled candle sync and cleanup...");
695
+      SendAllHistoricalCandles();
696
+
697
+      // ✅ After uploading candles, clean up old ones
698
+      for(int i = 0; i < ArraySize(symbols); i++)
699
+      {
700
+         int symId = symbolIds[i];
701
+         if(symId <= 0) continue;
702
+         CleanupOldCandles(symId);
703
+         Sleep(500); // small delay to avoid API overload
704
+      }
705
+
706
+      lastCandleSync = now;
707
+      Print("✅ Candle sync + cleanup cycle completed.");
708
+   }
709
+}
710
+
711
+
712
+
713
+void OnDeinit(const int reason)
714
+{
715
+   EventKillTimer();
716
+}