//+------------------------------------------------------------------+ //| AlgoForge_Engine1_UnitTest.mq5 | //| Project : Algo Forge (refactor total) | //| Sumber inspirasi: SniperGold SMC Pro+ (c) Waseem Shahrukh | //| https://www.mql5.com/en/code/75466 | //+------------------------------------------------------------------+ //| Unit test minimal Engine 1 (Data Collector Bar MTF + cache). | //| Jalankan di Strategy Tester (mis. XAUUSDc M15, model 1-min OHLC).| //| Semua hasil dicetak di journal dengan prefix "AFTEST". | //| | //| CEK YANG DIJALANKAN: | //| T1 - cache ready & count>0 untuk tiap TF terdaftar | //| T2 - validitas OHLC setiap bar | //| T3 - urutan waktu strict (menurun dari index 0) | //| T4 - closed-bar lock: SEMUA bar di cache sudah tertutup | //| T5 - anti-freeze: CopyRates HANYA saat Bars() berubah | //| T6 - konsistensi Count() vs Bars() | //| T7 - independensi chart-TF: Engine memakai TF eksplisit | //| (PERIOD_CURRENT ditolak saat Register; verifikasi statis | //| dengan grep di kode sumber) | //+------------------------------------------------------------------+ #property copyright "Algo Forge - refactor SniperGold SMC Pro+ (Waseem Shahrukh, https://www.mql5.com/en/code/75466)" #property version "1.00" #property strict #property description "Unit test Engine 1: MTF bar collector + cache index reusable. Jalankan di Strategy Tester; hasil di journal (prefix AFTEST)." #include input group "===== ENGINE 1 UNIT TEST =====" input ENUM_TIMEFRAMES InpTF1 = PERIOD_M1; // TF uji #1 input ENUM_TIMEFRAMES InpTF2 = PERIOD_M5; // TF uji #2 input ENUM_TIMEFRAMES InpTF3 = PERIOD_M15; // TF uji #3 input ENUM_TIMEFRAMES InpTF4 = PERIOD_H1; // TF uji #4 input int InpMaxBars = 300; // Kapasitas cache per TF input bool InpVerbose = false; // Log detail per bar (5 bar terbaru tiap slot) AFEngine1MTF g_e1; int g_s[4]; long g_lastBars[4]; int g_histAtLast[4]; int g_pass=0, g_fail=0; bool g_initDone=false; bool g_verbose=false; //+------------------------------------------------------------------+ string SlotName(int i) { switch(i) { case 0: return EnumToString(InpTF1); case 1: return EnumToString(InpTF2); case 2: return EnumToString(InpTF3); case 3: return EnumToString(InpTF4); } return "?"; } void Pass(const string what) { g_pass++; if(g_verbose) Print("AFTEST PASS: "+what); } void Fail(const string what) { g_fail++; Print("AFTEST FAIL: "+what); } //+------------------------------------------------------------------+ //| Cek menyeluruh satu slot (dipanggil saat init & tiap bar baru) | //+------------------------------------------------------------------+ void CheckSlot(int slot,const string name) { if(slot<0) { Fail(name+": slot registrasi gagal"); return; } if(!g_e1.IsReady(slot)) { PrintFormat("AFTEST [%s] SKIP: cache belum ready (Bars=%d)",name,(int)Bars(_Symbol,g_e1.TfOf(slot))); return; } int cnt=g_e1.Count(slot); if(cnt<=0) { Fail(name+": T1 count<=0"); return; } Pass(name+": T1 ready, count="+IntegerToString(cnt)); bool sane=true, ordered=true, closed=true; datetime now=TimeCurrent(); datetime newest=g_e1.Time(slot,0); for(int i=0;i MathMin(b.open,b.close)+1e-9) sane=false; if(b.high < b.low-1e-9) sane=false; if(b.open<=0 || b.high<=0 || b.low<=0 || b.close<=0) sane=false; // T3 - urutan waktu strict (index bertambah = makin tua) if(i+10 && cnt<=barsAll) Pass(name+": T6 count<=Bars ("+IntegerToString(cnt)+"/"+IntegerToString(barsAll)+")"); else if(barsAll>0) Fail(name+": T6 count>Bars (tidak mungkin)"); if(g_verbose) PrintFormat("AFTEST [%s] stat: count=%d bars=%d historyCalls=%d refresh=%d", name,cnt,(int)barsAll,g_e1.HistoryCalls(slot),g_e1.RefreshCount(slot)); if(g_verbose) { for(int i=0;i=0) { g_lastBars[i]=g_e1.LastBars(g_s[i]); g_histAtLast[i]=g_e1.HistoryCalls(g_s[i]); } CheckSlot(g_s[i],SlotName(i)); } PrintFormat("AFTEST slot terdaftar: %d (max %d)",g_e1.SlotCount(),AF_E1_MAX_SLOTS); g_initDone=true; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Tick | //+------------------------------------------------------------------+ void OnTick() { if(!g_initDone) return; g_e1.Refresh(); // internal: skip slot tanpa bar baru for(int i=0;i<4;i++) { int s=g_s[i]; if(s<0 || !g_e1.IsReady(s)) continue; long lb=g_e1.LastBars(s); int hc=g_e1.HistoryCalls(s); if(lb!=g_lastBars[i]) // ada bar baru di TF ini { if(g_histAtLast[i]>0 && hc!=g_histAtLast[i]+1) Fail(SlotName(i)+": T5 jumlah panggilan History saat bar baru tidak tepat " +"(hc="+IntegerToString(hc)+" prev="+IntegerToString(g_histAtLast[i])+")"); g_lastBars[i]=lb; g_histAtLast[i]=hc; CheckSlot(s,SlotName(i)); // verifikasi ulang T2-T4 } else if(hc!=g_histAtLast[i]) // TANPA bar baru tapi ada baca History -> ANTI-FREEZE GAGAL { Fail(SlotName(i)+": T5 anti-freeze dilanggar - panggilan History tanpa bar baru"); g_histAtLast[i]=hc; } } } //+------------------------------------------------------------------+ //| Tester summary | //+------------------------------------------------------------------+ double OnTester() { Print("AFTEST ============================================================"); PrintFormat("AFTEST RESULT: PASS=%d FAIL=%d",g_pass,g_fail); Print("AFTEST RESULT: "+(g_fail==0 ? "SEMUA CEK LULUS" : "ADA CEK GAGAL")); for(int i=0;i<4;i++) if(g_s[i]>=0 && g_e1.IsReady(g_s[i])) PrintFormat("AFTEST final [%s]: count=%d historyCalls=%d",SlotName(i),g_e1.Count(g_s[i]),g_e1.HistoryCalls(g_s[i])); return (g_fail==0 ? 1000.0 : 0.0); }