//+------------------------------------------------------------------+ //| AlgoForge_Engine3_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 3 (display). Jalankan di Strategy | //| Tester (XAUUSD, model "every tick", Visual=1 agar objek chart | //| bisa dibuat). Semua hasil dicetak di journal (prefix AFTEST3). | //| | //| CEK YANG DIJALANKAN: | //| T1 - data display valid (struktur/zona/level/bias) | //| T2 - non-repaint: dalam bar yang sama data identik | //| T3 - closed-bar lock: semua bar yang dipakai sudah tertutup | //| T4 - render: AFR_DrawAll menciptakan objek chart (prefix) | //| T5 - determinisme render: render ulang bar sama -> jumlah sama | //+------------------------------------------------------------------+ #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 3: display struktur/zona/sinyal/dashboard (hanya baca output Engine 1 & 2). Jalankan di Strategy Tester; hasil di journal (prefix AFTEST3)." #include input group "===== ENGINE 3 UNIT TEST =====" // TF agen HARDCODED (AF_E2_TF_S1..S4 = H4/M30/M15/M3) - tanpa input manual input ENUM_TIMEFRAMES InpDispTF = PERIOD_CURRENT; // TF display (CURRENT = chart) input int InpMaxBars = 600; // Kapasitas cache per slot input bool InpVerbose = false; // Log detail AFEngine1MTF g_e1; AFEngine2Signals g_eng2; AFDisplayData g_dd, g_ddChk; AFSignalOut g_n, g_c, g_e, g_p; AFAggOut g_agg; AFRenderCfg g_cfg; int g_sN, g_sC, g_sE, g_sP, g_sDisp, g_sD1, g_sW1; long g_lastBar[7]; bool g_repaintPending=false; int g_pass=0, g_fail=0; bool g_verbose=false; bool g_objSupported=false; bool g_objChecked=false; int g_lastObjCount=-1; //+------------------------------------------------------------------+ void Pass(const string what){ g_pass++; if(g_verbose) Print("AFTEST3 PASS: "+what); } void Fail(const string what){ g_fail++; Print("AFTEST3 FAIL: "+what); } int SlotIdx(int i) { switch(i) { case 0: return g_sN; case 1: return g_sC; case 2: return g_sE; case 3: return g_sP; case 4: return g_sDisp; case 5: return g_sD1; case 6: return g_sW1; } return -1; } string SlotName(int i) { switch(i) { case 0: return "N"; case 1: return "C"; case 2: return "E"; case 3: return "P"; case 4: return "DISP"; case 5: return "D1"; case 6: return "W1"; } return "?"; } int CountObjectsWithPrefix(string pfx) { int n=0; int total=ObjectsTotal(0,0,-1); for(int i=0;id.swLow && (d.rangePos<0.0 || d.rangePos>1.0)) { ok=false; Fail("T1 rangePos di luar [0,1]"); } if(d.nBias=="" || d.cBias=="" || d.eBias=="" || d.pBias=="") { ok=false; Fail("T1 bias agen kosong"); } int nP=ArraySize(d.pivots); for(int i=0;id.structLines[i].t2 || (d.structLines[i].lbl!="BOS" && d.structLines[i].lbl!="CHoCH")) { ok=false; Fail("T1 garis struktur invalid ("+d.structLines[i].lbl+")"); break; } } int nO=ArraySize(d.ob); for(int i=0;i0 bila slot D1/W1 punya >=2 bar int s1=SlotIdx(5); int s2=SlotIdx(6); if(g_e1.IsReady(s1) && g_e1.Count(s1)>=2) { if(d.pdh<=0.0 || d.pdl<=0.0) { ok=false; Fail("T1 PDH/PDL = 0 padahal D1 siap (count="+IntegerToString(g_e1.Count(s1))+")"); } } if(g_e1.IsReady(s2) && g_e1.Count(s2)>=2) { if(d.pwh<=0.0 || d.pwl<=0.0) { ok=false; Fail("T1 PWH/PWL = 0 padahal W1 siap (count="+IntegerToString(g_e1.Count(s2))+")"); } } // agregator sanity (dari Engine 2) AFAggOut a=g_agg; if(a.buy<0.0 || a.buy>1.0 || a.sell<0.0 || a.sell>1.0) ok=false; if(MathAbs(a.bias-(a.buy-a.sell))>1e-9) ok=false; if(ok) Pass("T1 data display valid (structure="+d.structure+" eqPos="+d.eqPos +" HH/HL/LH/LL="+IntegerToString(d.HH)+"/"+IntegerToString(d.HL)+"/" +IntegerToString(d.LH)+"/"+IntegerToString(d.LL) +" piv="+IntegerToString(nP)+" lines="+IntegerToString(nL) +" ob="+IntegerToString(nO)+" fvg="+IntegerToString(nF)+")"); } //+------------------------------------------------------------------+ //| T3 - closed-bar lock | //+------------------------------------------------------------------+ void CheckClosedBar() { datetime now=TimeCurrent(); bool ok=true; for(int i=0;i<5;i++) // N,C,E,P,DISP { int s=SlotIdx(i); if(s<0 || !g_e1.IsReady(s)) continue; datetime t0=g_e1.Time(s,0); if(!g_e1.IsBarClosed(t0,g_e1.TfOf(s),now)) { ok=false; Fail("T3 "+SlotName(i)+": bar terbaru belum tertutup ("+TimeToString(t0)+")"); } } if(ok) Pass("T3 closed-bar lock: semua slot siap memakai bar tertutup"); } //+------------------------------------------------------------------+ //| T2 - non-repaint (data identik dalam bar sama) | //+------------------------------------------------------------------+ void CheckNonRepaint() { AFDisplayData chk; chk.Reset(); AF_BuildDisplayData(g_e1,g_sN,g_sC,g_sE,g_sP,g_sDisp,g_sD1,g_sW1, g_cfg.obCount,g_cfg.fvgCount, g_n,g_c,g_e,g_p,chk); if(AF_SameDisplayData(g_dd,chk)) Pass("T2 non-repaint: data display identik dalam bar yang sama"); else Fail("T2 non-repaint dilanggar (data display berubah dalam bar sama)"); } //+------------------------------------------------------------------+ //| T4/T5 - render (buat objek, determinisme) | //+------------------------------------------------------------------+ void CheckRender() { // cek dukungan objek chart (sekali) if(!g_objChecked) { g_objChecked=true; string nm=g_cfg.prefix+"_PROBE"; g_objSupported=ObjectCreate(0,nm,OBJ_TREND,0,TimeCurrent(),0,TimeCurrent(),0); if(g_objSupported) ObjectDelete(0,nm); if(!g_objSupported) Print("AFTEST3 T4 SKIP: pembuatan objek chart tidak didukung (jalankan dengan Visual=1)"); } if(!g_objSupported) return; AFR_Clear(g_cfg); int before=CountObjectsWithPrefix(g_cfg.prefix); AFR_DrawAll(g_cfg,g_e1,g_sDisp,g_dd,g_n,g_c,g_e,g_p,g_agg); int after=CountObjectsWithPrefix(g_cfg.prefix); if(after>before && after>0) Pass("T4 render: "+IntegerToString(after)+" objek dibuat (prefix "+g_cfg.prefix+")"); else Fail("T4 render: tidak ada objek dibuat (before="+IntegerToString(before) +" after="+IntegerToString(after)+")"); // panel dashboard harus ada bila showDashboard if(g_cfg.showDashboard) { if(ObjectFind(0,g_cfg.prefix+"P_BIAS")>=0) Pass("T4 dashboard: panel P_BIAS ada"); else Fail("T4 dashboard: panel P_BIAS tidak ditemukan"); } // T5 - determinisme render: render ulang pada bar sama AFR_Clear(g_cfg); AFR_DrawAll(g_cfg,g_e1,g_sDisp,g_dd,g_n,g_c,g_e,g_p,g_agg); int after2=CountObjectsWithPrefix(g_cfg.prefix); if(after2==after) Pass("T5 render deterministik: jumlah objek sama ("+IntegerToString(after2)+")"); else Fail("T5 render tidak deterministik: "+IntegerToString(after)+" vs "+IntegerToString(after2)); g_lastObjCount=after2; } //+------------------------------------------------------------------+ //| Log verbose | //+------------------------------------------------------------------+ void PrintAllVerbose() { PrintFormat("AFTEST3 [DISP] TF=%s structure=%s eqPos=%s HH=%d HL=%d LH=%d LL=%d", AF_E2_TfName(g_e1.TfOf(g_sDisp)),g_dd.structure,g_dd.eqPos, g_dd.HH,g_dd.HL,g_dd.LH,g_dd.LL); PrintFormat("AFTEST3 [DISP] pivots=%d lines=%d ob=%d fvg=%d pdh=%.2f pdl=%.2f pwh=%.2f pwl=%.2f", ArraySize(g_dd.pivots),ArraySize(g_dd.structLines), ArraySize(g_dd.ob),ArraySize(g_dd.fvg), g_dd.pdh,g_dd.pdl,g_dd.pwh,g_dd.pwl); PrintFormat("AFTEST3 [AGENTS] N=%s C=%s E=%s P=%s",g_dd.nBias,g_dd.cBias,g_dd.eBias,g_dd.pBias); PrintFormat("AFTEST3 [AGG] %s",g_agg.reason); } //+------------------------------------------------------------------+ bool RunAllChecks() { for(int i=0;i<4;i++) { int s=SlotIdx(i); if(s<0 || !g_e1.IsReady(s) || g_e1.Count(s) data harus identik g_repaintPending=false; } } //+------------------------------------------------------------------+ double OnTester() { Print("AFTEST3 ============================================================"); PrintFormat("AFTEST3 RESULT: PASS=%d FAIL=%d",g_pass,g_fail); Print("AFTEST3 RESULT: "+(g_fail==0? "SEMUA CEK LULUS" : "ADA CEK GAGAL")); PrintFormat("AFTEST3 final: DISP TF=%s count=%d historyCalls=%d", AF_E2_TfName(g_e1.TfOf(g_sDisp)),g_e1.Count(g_sDisp), g_e1.HistoryCalls(g_sDisp)); PrintFormat("AFTEST3 final: D1 count=%d historyCalls=%d | W1 count=%d historyCalls=%d", g_e1.Count(g_sD1),g_e1.HistoryCalls(g_sD1), g_e1.Count(g_sW1),g_e1.HistoryCalls(g_sW1)); PrintFormat("AFTEST3 final: TotalHistoryCalls=%d",g_e1.TotalHistoryCalls()); return (g_fail==0)? 1000.0 : 0.0; } //+------------------------------------------------------------------+