Sfoglia il codice sorgente

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 mesi fa
parent
commit
ebecb8c792
1 ha cambiato i file con 135 aggiunte e 15 eliminazioni
  1. 135 15
      MT5/Experts/MarketDataSender.mq5

+ 135 - 15
MT5/Experts/MarketDataSender.mq5

@@ -15,7 +15,7 @@ input int LivePriceIntervalSeconds = 5;
15 15
 string symbols[];
16 16
 int symbolIds[];
17 17
 datetime lastSend = 0;
18
-
18
+datetime lastCandleSync = 0;
19 19
 //+------------------------------------------------------------------+
20 20
 int OnInit()
21 21
 {
@@ -28,6 +28,8 @@ int OnInit()
28 28
 
29 29
    Print("✅ Symbols initialized: ", ArraySize(symbols));
30 30
    SendAllHistoricalCandles();
31
+   EventSetTimer(60);  // ⏱️ Trigger OnTimer() every 30 minutes
32
+   Print("✅ Timer set: SendAllHistoricalCandles() will run every 30 minutes.");
31 33
    return(INIT_SUCCEEDED);
32 34
 }
33 35
 
@@ -202,11 +204,53 @@ int CreateSymbolInDatabase(string symbolName)
202 204
    string exchange = "MT5";
203 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 213
    if(StringLen(symbolName) >= 6)
206 214
    {
207 215
       baseAsset = StringSubstr(symbolName, 0, 3);
208 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 255
    string json = StringFormat(
212 256
       "{\"symbol\":\"%s\",\"baseAsset\":\"%s\",\"quoteAsset\":\"%s\",\"exchange\":\"%s\",\"instrumentType\":\"%s\",\"isActive\":true}",
@@ -219,18 +263,15 @@ int CreateSymbolInDatabase(string symbolName)
219 263
 
220 264
    char postData[];
221 265
    StringToCharArray(json, postData, 0, CP_UTF8);
222
-
223
-   // ✅ FIX: Remove trailing null terminator from JSON
224 266
    if(ArraySize(postData) > 0 && postData[ArraySize(postData) - 1] == 0)
225 267
       ArrayResize(postData, ArraySize(postData) - 1);
226 268
 
227 269
    char result[];
228
-
229 270
    int res = WebRequest("POST", url, headers, 5000, postData, result, resultHeaders);
230 271
 
231 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 275
       return -1;
235 276
    }
236 277
 
@@ -245,6 +286,7 @@ int CreateSymbolInDatabase(string symbolName)
245 286
    string idStr = StringSubstr(createResponse, startPos, endPos - startPos);
246 287
    return (int)StringToInteger(idStr);
247 288
 }
289
+
248 290
 //+------------------------------------------------------------------+
249 291
 //| Fetch latest stored candle openTime from API                     |
250 292
 //+------------------------------------------------------------------+
@@ -298,6 +340,9 @@ datetime GetLatestCandleTime(int symbolId)
298 340
 //+------------------------------------------------------------------+
299 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 346
 void SendAllHistoricalCandles()
302 347
 {
303 348
    Print("Starting historical upload for ", ArraySize(symbols), " symbols...");
@@ -311,26 +356,42 @@ void SendAllHistoricalCandles()
311 356
       // --- Get last stored candle time ---
312 357
       datetime latestApiTime = GetLatestCandleTime(symbolId);
313 358
 
314
-      // --- Ensure data is ready ---
359
+      // --- Ensure history data is available ---
315 360
       Sleep(300);
316 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 372
          Sleep(500);
321 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 383
       MqlRates rates[];
325 384
       ResetLastError();
326 385
       int copied = CopyRates(sym, HistoricalTimeframe, 0, HistoricalCandleCount, rates);
386
+
327 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 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 396
       // --- Filter new candles ---
336 397
       int startIndex = 0;
@@ -346,34 +407,39 @@ void SendAllHistoricalCandles()
346 407
       int newCount = copied - startIndex;
347 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 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 416
       // --- Send new candles in batches ---
356 417
       int batchSize = 200;
357 418
       int sentTotal = 0;
419
+
358 420
       for(int start = startIndex; start < copied; start += batchSize)
359 421
       {
360 422
          int size = MathMin(batchSize, copied - start);
361 423
          string json = BuildCandleJSONFromRates(symbolId, rates, start, size);
362 424
          string url = ApiBaseUrl + "/api/candles/bulk";
363 425
          string response;
426
+
364 427
          bool ok = SendJSON(url, json, response);
365 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 431
             break;
369 432
          }
433
+
370 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 439
    Print("✅ Incremental candle upload finished.");
375 440
 }
376 441
 
442
+
377 443
 //+------------------------------------------------------------------+
378 444
 //| Send live prices of all active symbols                           |
379 445
 //+------------------------------------------------------------------+
@@ -593,4 +659,58 @@ bool SendJSON(string url, string json, string &response)
593 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
+}