SniperGold_ML/MQL5/Experts/AlgoForge_Engine1_UnitTest.mq5

191 lines
8.1 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 <AlgoForge\AF_Engine1_MTFData.mqh>
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<cnt;i++)
{
AFBar b;
if(!g_e1.GetBar(slot,i,b)) { sane=false; continue; }
// T2 - validitas OHLC
if(b.high < MathMax(b.open,b.close)-1e-9) sane=false;
if(b.low > 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+1<cnt)
{
AFBar b2;
if(g_e1.GetBar(slot,i+1,b2) && b.time<=b2.time) ordered=false;
}
// T4 - closed-bar lock
if(!g_e1.IsBarClosed(b.time,g_e1.TfOf(slot),now)) closed=false;
}
if(sane) Pass(name+": T2 OHLC valid"); else Fail(name+": T2 OHLC invalid");
if(ordered) Pass(name+": T3 urutan waktu strict"); else Fail(name+": T3 urutan waktu rusak");
if(closed) Pass(name+": T4 semua bar tertutup (newest="+TimeToString(newest)+")");
else Fail(name+": T4 ada bar terbuka di cache");
// T6 - konsistensi dengan Bars()
long barsAll=Bars(_Symbol,g_e1.TfOf(slot));
if(barsAll>0 && 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<MathMin(5,cnt);i++)
{
AFBar b;
if(g_e1.GetBar(slot,i,b))
PrintFormat("AFTEST [%s] bar[%d] %s O=%.5f H=%.5f L=%.5f C=%.5f V=%d",
name,i,TimeToString(b.time),b.open,b.high,b.low,b.close,b.tick_volume);
}
}
}
//+------------------------------------------------------------------+
//| Init |
//+------------------------------------------------------------------+
int OnInit()
{
Print("AFTEST ============================================================");
Print("AFTEST Algo Forge Engine1 UnitTest v"+AF_PROJECT_VERSION);
Print("AFTEST Atribusi: "+AF_SOURCE_TITLE+" (c) "+AF_SOURCE_AUTHOR+" - "+AF_SOURCE_URL);
g_verbose=InpVerbose;
g_s[0]=g_e1.Register(InpTF1,InpMaxBars);
g_s[1]=g_e1.Register(InpTF2,InpMaxBars);
g_s[2]=g_e1.Register(InpTF3,InpMaxBars);
g_s[3]=g_e1.Register(InpTF4,InpMaxBars);
g_e1.Refresh(); // build awal (sekali per TF)
for(int i=0;i<4;i++)
{
if(g_s[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);
}