191 lines
8.1 KiB
MQL5
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);
|
||
|
|
}
|