475 lines
18 KiB
MQL5
475 lines
18 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| AlgoForge_Engine2_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 2 (4 agen sinyal independen + agregator)|
|
|
//| Jalankan di Strategy Tester (XAUUSD, model "every tick"). |
|
|
//| Semua hasil dicetak di journal dengan prefix "AFTEST2". |
|
|
//| |
|
|
//| CEK YANG DIJALANKAN: |
|
|
//| T1 - validitas output tiap agen (buy/sell/bias/conf/dir) |
|
|
//| T2 - independensi: mengubah input agen X TIDAK mengubah output |
|
|
//| agen lain; determinisme (input sama -> output sama) |
|
|
//| T3 - closed-bar lock: semua bar yang dipakai sudah tertutup |
|
|
//| T4 - non-repaint: dalam bar yang sama, output identik |
|
|
//| T5 - validitas output agregator (dir/bias/score/levels) |
|
|
//| T6 - agregator dengan input sintetis (netral/buy/sell/campur) |
|
|
//+------------------------------------------------------------------+
|
|
#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 2: 4 agen sinyal independen (N/C/E/P) + agregator. Jalankan di Strategy Tester; hasil di journal (prefix AFTEST2)."
|
|
|
|
#include <AlgoForge\AF_Engine2_Aggregator.mqh>
|
|
#include <AlgoForge\AF_Engine2_Setup.mqh>
|
|
|
|
input group "===== ENGINE 2 UNIT TEST ====="
|
|
// TF agen HARDCODED (AF_E2_TF_S1..S4 = H4/M30/M15/M5) - tanpa input manual
|
|
input ENUM_TIMEFRAMES InpTamper1 = PERIOD_H1; // Slot tamper N (uji independensi; beda dari base H4)
|
|
input ENUM_TIMEFRAMES InpTamper2 = PERIOD_M20; // Slot tamper C (beda dari base M30)
|
|
input ENUM_TIMEFRAMES InpTamper3 = PERIOD_M12; // Slot tamper E (beda dari base M15)
|
|
input ENUM_TIMEFRAMES InpTamper4 = PERIOD_M6; // Slot tamper P (beda dari base M3)
|
|
input int InpMaxBars = 600; // Kapasitas cache per TF
|
|
input bool InpVerbose = false; // Log detail
|
|
|
|
AFEngine1MTF g_e1;
|
|
AFEngine2Signals g_eng2;
|
|
AFAgentNarrative g_agN;
|
|
AFAgentContext g_agC;
|
|
AFAgentEntry g_agE;
|
|
AFAgentPriceAction g_agP;
|
|
AFAggregator g_agg;
|
|
|
|
int g_s[4]; // slot N,C,E,P
|
|
int g_t[4]; // slot tamper
|
|
long g_lastBar[4];
|
|
AFSignalOut g_out[4];
|
|
AFAggOut g_aggOut;
|
|
|
|
int g_pass=0, g_fail=0;
|
|
bool g_verbose=false;
|
|
bool g_indepDone=false;
|
|
bool g_repaintPending=false;
|
|
|
|
//+------------------------------------------------------------------+
|
|
string AgentName(int i)
|
|
{
|
|
switch(i)
|
|
{
|
|
case 0: return "Narrative";
|
|
case 1: return "Context";
|
|
case 2: return "Entry";
|
|
case 3: return "PriceAction";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
void Pass(const string what){ g_pass++; if(g_verbose) Print("AFTEST2 PASS: "+what); }
|
|
void Fail(const string what){ g_fail++; Print("AFTEST2 FAIL: "+what); }
|
|
|
|
bool SameSignal(const AFSignalOut &a,const AFSignalOut &b)
|
|
{
|
|
if(MathAbs(a.buy-b.buy)>1e-12) return false;
|
|
if(MathAbs(a.sell-b.sell)>1e-12) return false;
|
|
if(a.dir!=b.dir) return false;
|
|
if(a.reason!=b.reason) return false;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| T1 - validitas output agen |
|
|
//+------------------------------------------------------------------+
|
|
void CheckAgentOut(int i)
|
|
{
|
|
AFSignalOut o=g_out[i];
|
|
bool ok=true;
|
|
if(o.buy<0.0 || o.buy>1.0 || o.sell<0.0 || o.sell>1.0) ok=false;
|
|
if(o.bias<-1.0-1e-9 || o.bias>1.0+1e-9) ok=false;
|
|
if(o.confidence<0.0 || o.confidence>1.0) ok=false;
|
|
if(o.dir==1 && o.bias<=AF_E2_DIR_TOL) ok=false;
|
|
if(o.dir==-1 && o.bias>=-AF_E2_DIR_TOL) ok=false;
|
|
if(o.dir==0 && MathAbs(o.bias)>AF_E2_DIR_TOL) ok=false;
|
|
if(o.name!=AgentName(i)) ok=false;
|
|
if(ok)
|
|
Pass("T1 "+AgentName(i)+" output valid (dir="+IntegerToString(o.dir)
|
|
+" bias="+DoubleToString(o.bias,2)+" conf="+DoubleToString(o.confidence,2)+")");
|
|
else
|
|
Fail("T1 "+AgentName(i)+" output INVALID (buy="+DoubleToString(o.buy,3)
|
|
+" sell="+DoubleToString(o.sell,3)+" bias="+DoubleToString(o.bias,3)
|
|
+" dir="+IntegerToString(o.dir)+")");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| T5 - validitas output agregator |
|
|
//+------------------------------------------------------------------+
|
|
void CheckAggOut()
|
|
{
|
|
AFAggOut o=g_aggOut;
|
|
bool ok=true;
|
|
if(o.buy<0.0 || o.buy>1.0 || o.sell<0.0 || o.sell>1.0) ok=false;
|
|
if(MathAbs(o.bias-(o.buy-o.sell))>1e-9) ok=false;
|
|
if(MathAbs(o.score-MathAbs(o.bias))>1e-9) ok=false;
|
|
if(o.confidence<0.0 || o.confidence>1.0) ok=false;
|
|
if(o.dir==1 && (o.bias<AF_AGG_BUY_TH || o.buy<AF_AGG_MIN_SUP)) ok=false;
|
|
if(o.dir==-1 && (o.bias>-AF_AGG_BUY_TH || o.sell<AF_AGG_MIN_SUP)) ok=false;
|
|
if(o.dir!=0)
|
|
{
|
|
if(!o.hasLevels || o.entry<=0.0) ok=false;
|
|
if(o.dir==1 && (o.sl>=o.entry || o.tp<=o.entry)) ok=false;
|
|
if(o.dir==-1 && (o.sl<=o.entry || o.tp>=o.entry)) ok=false;
|
|
}
|
|
if(o.reason=="") ok=false;
|
|
if(ok)
|
|
Pass("T5 agregator valid (dir="+IntegerToString(o.dir)
|
|
+" bias="+DoubleToString(o.bias,2)+" score="+DoubleToString(o.score,2)
|
|
+" conf="+DoubleToString(o.confidence,2)+")");
|
|
else
|
|
Fail("T5 agregator INVALID (dir="+IntegerToString(o.dir)
|
|
+" buy="+DoubleToString(o.buy,3)+" sell="+DoubleToString(o.sell,3)+")");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| T2 - independensi & determinisme |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckIndependence()
|
|
{
|
|
AFSignalOut base[4];
|
|
g_agN.Compute(g_e1,g_s[0],base[0]);
|
|
g_agC.Compute(g_e1,g_s[1],base[1]);
|
|
g_agE.Compute(g_e1,g_s[2],base[2]);
|
|
g_agP.Compute(g_e1,g_s[3],base[3]);
|
|
|
|
bool tamperReady=true;
|
|
for(int i=0;i<4;i++)
|
|
if(g_t[i]<0 || !g_e1.IsReady(g_t[i])) tamperReady=false;
|
|
if(!tamperReady)
|
|
{
|
|
Print("AFTEST2 T2 SKIP: slot tamper belum siap (akan dicoba di bar berikutnya)");
|
|
return false;
|
|
}
|
|
|
|
bool indepOK=true;
|
|
for(int t=0;t<4;t++)
|
|
{
|
|
AFSignalOut alt[4];
|
|
g_agN.Compute(g_e1,(t==0)? g_t[0] : g_s[0],alt[0]);
|
|
g_agC.Compute(g_e1,(t==1)? g_t[1] : g_s[1],alt[1]);
|
|
g_agE.Compute(g_e1,(t==2)? g_t[2] : g_s[2],alt[2]);
|
|
g_agP.Compute(g_e1,(t==3)? g_t[3] : g_s[3],alt[3]);
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
if(i==t) continue; // agen yang diubah BOLEH berbeda
|
|
if(!SameSignal(base[i],alt[i]))
|
|
{
|
|
Fail("T2 independensi rusak: ganti input "+AgentName(t)+" mengubah "+AgentName(i));
|
|
indepOK=false;
|
|
}
|
|
}
|
|
}
|
|
|
|
AFSignalOut chk[4];
|
|
g_agN.Compute(g_e1,g_s[0],chk[0]);
|
|
g_agC.Compute(g_e1,g_s[1],chk[1]);
|
|
g_agE.Compute(g_e1,g_s[2],chk[2]);
|
|
g_agP.Compute(g_e1,g_s[3],chk[3]);
|
|
bool det=true;
|
|
for(int i=0;i<4;i++) if(!SameSignal(base[i],chk[i])) det=false;
|
|
|
|
if(det && indepOK) Pass("T2 independensi & determinisme OK");
|
|
if(!det) Fail("T2 determinisme rusak (input sama -> output beda)"); return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| T4 - non-repaint (output identik dalam bar yang sama) |
|
|
//+------------------------------------------------------------------+
|
|
void CheckNonRepaint()
|
|
{
|
|
AFSignalOut chk[4];
|
|
g_agN.Compute(g_e1,g_s[0],chk[0]);
|
|
g_agC.Compute(g_e1,g_s[1],chk[1]);
|
|
g_agE.Compute(g_e1,g_s[2],chk[2]);
|
|
g_agP.Compute(g_e1,g_s[3],chk[3]);
|
|
bool ok=true;
|
|
for(int i=0;i<4;i++) if(!SameSignal(g_out[i],chk[i])) ok=false;
|
|
if(ok) Pass("T4 non-repaint: output identik dalam bar yang sama");
|
|
else Fail("T4 non-repaint dilanggar");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| T6 - agregator dengan input sintetis (logika murni) |
|
|
//+------------------------------------------------------------------+
|
|
void SyntheticAggTests()
|
|
{
|
|
AFSignalOut z, b, s;
|
|
AF_SignalInit(z,"Z","?");
|
|
AF_SignalInit(b,"B","?");
|
|
b.buy=0.9; b.sell=0.1; b.bias=0.8; b.confidence=0.9; b.dir=1;
|
|
AF_SignalInit(s,"S","?");
|
|
s.buy=0.1; s.sell=0.9; s.bias=-0.8; s.confidence=0.9; s.dir=-1;
|
|
|
|
AFAggOut o;
|
|
|
|
g_agg.Compute(g_e1,0,z,z,z,z,o);
|
|
if(o.dir==0) Pass("T6 agregator: semua netral -> WAIT");
|
|
else Fail("T6 agregator: semua netral harus WAIT");
|
|
|
|
g_agg.Compute(g_e1,0,b,b,b,b,o);
|
|
if(o.dir==1 && o.buy>0.5) Pass("T6 agregator: semua buy kuat -> BUY");
|
|
else Fail("T6 agregator: semua buy kuat harus BUY");
|
|
|
|
g_agg.Compute(g_e1,0,s,s,s,s,o);
|
|
if(o.dir==-1 && o.sell>0.5) Pass("T6 agregator: semua sell kuat -> SELL");
|
|
else Fail("T6 agregator: semua sell kuat harus SELL");
|
|
|
|
g_agg.Compute(g_e1,0,b,z,z,z,o);
|
|
if(o.dir==1) Pass("T6 agregator: N buy kuat saja -> BUY (bobot N)");
|
|
else Fail("T6 agregator: N buy kuat harus BUY");
|
|
|
|
g_agg.Compute(g_e1,0,b,s,z,z,o);
|
|
if(o.dir==0) Pass("T6 agregator: N buy vs C sell seimbang -> WAIT");
|
|
else Fail("T6 agregator: N buy vs C sell seimbang harus WAIT");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| T7 - F3 Candidate Setup layer (sintetis, semantic inputs) |
|
|
//| Port test dari spec_tests_candidate_setup_runtime.py oracle: |
|
|
//| T7a : rantai lengkap -> CANDIDATE_SETUP (F3-T01) |
|
|
//| T7b : H4 conflict -> BLOCKED (F3-T08) |
|
|
//| T7c : M3 contrary -> setup valid, M3_CONFIRMED=false (F3-T11) |
|
|
//| T7d : entry konsumsi -> CONSUMED, satu entry (F3-T18) |
|
|
//| T7e : score rendah tetap ada setup (F3-T25) |
|
|
//+------------------------------------------------------------------+
|
|
void SyntheticSetupTests()
|
|
{
|
|
AFSetupEngine eng;
|
|
AFSetupInputs inp;
|
|
AFSetupOutcome out;
|
|
|
|
// T7a: rantai lengkap bullish (mirror F3-T01)
|
|
eng.Reset();
|
|
inp.h4=1; inp.m30=1;
|
|
inp.sweep.onset=2; inp.sweep.dir=1;
|
|
inp.choch.onset=5; inp.choch.dir=1;
|
|
inp.zone.type=AF_SETUP_ZONE_OB; inp.zone.formation=4;
|
|
inp.zone.dir=1; inp.zone.mit=AF_ZONE_UNMITIGATED; inp.zone.invalidated=false;
|
|
inp.m15=1; inp.m3=0; inp.entry=false; inp.score=0.5;
|
|
// bar 6: sweep fresh (age 4), choch fresh (age 1), chain completes
|
|
eng.Step(6,inp,out);
|
|
if(out.setup_exists && out.setup_id==1 && out.state==AF_SETUP_CANDIDATE && out.dir==1)
|
|
Pass("T7a setup layer: rantai lengkap -> CANDIDATE_SETUP id=1");
|
|
else
|
|
Fail("T7a setup layer: rantai lengkap gagal (state="+EnumToString(out.state)+")");
|
|
|
|
// T7b: H4 conflict -> BLOCKED (mirror F3-T08)
|
|
eng.Reset();
|
|
inp.h4=-1; inp.m30=1;
|
|
inp.sweep.onset=2; inp.sweep.dir=1;
|
|
inp.choch.onset=3; inp.choch.dir=1;
|
|
inp.zone.type=AF_SETUP_ZONE_OB; inp.zone.formation=3;
|
|
inp.zone.dir=1; inp.zone.mit=AF_ZONE_UNMITIGATED; inp.zone.invalidated=false;
|
|
inp.m15=1; inp.m3=0; inp.entry=false; inp.score=0.5;
|
|
eng.Step(6,inp,out);
|
|
if(out.blocked && !out.setup_exists)
|
|
Pass("T7b setup layer: H4/M30 conflict -> BLOCKED");
|
|
else
|
|
Fail("T7b setup layer: H4/M30 conflict harus BLOCKED");
|
|
|
|
// T7c: M3 contrary -> setup valid, M3_CONFIRMED=false (mirror F3-T11)
|
|
eng.Reset();
|
|
inp.h4=1; inp.m30=1;
|
|
inp.sweep.onset=2; inp.sweep.dir=1;
|
|
inp.choch.onset=5; inp.choch.dir=1;
|
|
inp.zone.type=AF_SETUP_ZONE_OB; inp.zone.formation=4;
|
|
inp.zone.dir=1; inp.zone.mit=AF_ZONE_UNMITIGATED; inp.zone.invalidated=false;
|
|
inp.m15=1; inp.m3=-1; inp.entry=false; inp.score=0.5;
|
|
eng.Step(6,inp,out);
|
|
eng.Step(7,inp,out);
|
|
if(out.setup_exists && out.setup_id==1 && !out.m3_confirmed &&
|
|
out.state==AF_SETUP_CANDIDATE)
|
|
Pass("T7c setup layer: M3 contrary -> setup valid, M3_CONFIRMED=false");
|
|
else
|
|
Fail("T7c setup layer: M3 contrary harus tetap valid tanpa konfirmasi");
|
|
|
|
// T7d: entry konsumsi -> CONSUMED, satu entry per setup (mirror F3-T18)
|
|
eng.Reset();
|
|
inp.h4=1; inp.m30=1;
|
|
inp.sweep.onset=2; inp.sweep.dir=1;
|
|
inp.choch.onset=5; inp.choch.dir=1;
|
|
inp.zone.type=AF_SETUP_ZONE_OB; inp.zone.formation=4;
|
|
inp.zone.dir=1; inp.zone.mit=AF_ZONE_UNMITIGATED; inp.zone.invalidated=false;
|
|
inp.m15=1; inp.m3=0; inp.score=0.5;
|
|
inp.entry=false;
|
|
eng.Step(5,inp,out); // forming
|
|
inp.entry=true;
|
|
eng.Step(6,inp,out); // entry pada bar yang sama -> CONSUMED
|
|
inp.entry=false;
|
|
eng.Step(7,inp,out); // setup sudah terminal -> slot bebas
|
|
if(eng.EntryBindings()==1 && out.state==AF_SETUP_NONE)
|
|
Pass("T7d setup layer: satu entry per setup (CONSUMED)");
|
|
else
|
|
Fail("T7d setup layer: aturan satu-entry gagal (bindings="
|
|
+IntegerToString(eng.EntryBindings())+")");
|
|
|
|
// T7e: score rendah tetap ada setup (mirror F3-T25)
|
|
eng.Reset();
|
|
inp.h4=1; inp.m30=1;
|
|
inp.sweep.onset=2; inp.sweep.dir=1;
|
|
inp.choch.onset=5; inp.choch.dir=1;
|
|
inp.zone.type=AF_SETUP_ZONE_OB; inp.zone.formation=4;
|
|
inp.zone.dir=1; inp.zone.mit=AF_ZONE_UNMITIGATED; inp.zone.invalidated=false;
|
|
inp.m15=1; inp.m3=0; inp.entry=false; inp.score=0.05;
|
|
eng.Step(6,inp,out);
|
|
if(out.setup_exists && out.setup_id==1 && out.score==0.05)
|
|
Pass("T7e setup layer: setup ada walau score rendah (score != setup)");
|
|
else
|
|
Fail("T7e setup layer: score rendah harus tetap punya setup");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log verbose |
|
|
//+------------------------------------------------------------------+
|
|
void PrintAllVerbose()
|
|
{
|
|
for(int i=0;i<4;i++)
|
|
PrintFormat("AFTEST2 [%s] TF=%s dir=%d buy=%.3f sell=%.3f conf=%.3f | %s",
|
|
g_out[i].name,g_out[i].tfName,g_out[i].dir,g_out[i].buy,
|
|
g_out[i].sell,g_out[i].confidence,g_out[i].reason);
|
|
PrintFormat("AFTEST2 [AGG] %s",g_aggOut.reason);
|
|
if(g_aggOut.hasLevels)
|
|
PrintFormat("AFTEST2 [AGG] entry=%.2f sl=%.2f tp=%.2f",
|
|
g_aggOut.entry,g_aggOut.sl,g_aggOut.tp);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Semua cek pada bar tertutup baru (atau saat semua slot siap) |
|
|
//+------------------------------------------------------------------+
|
|
bool RunAllChecks()
|
|
{
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
if(g_s[i]<0 || !g_e1.IsReady(g_s[i]))
|
|
{
|
|
PrintFormat("AFTEST2 SKIP: slot %s belum siap",AgentName(i));
|
|
return false;
|
|
}
|
|
if(g_e1.Count(g_s[i])<AF_E2_MIN_BARS)
|
|
{
|
|
PrintFormat("AFTEST2 SKIP: data %s kurang (%d bar < %d)",
|
|
AgentName(i),g_e1.Count(g_s[i]),AF_E2_MIN_BARS);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// T3 - closed-bar lock: bar terbaru tiap slot sudah tertutup
|
|
datetime now=TimeCurrent();
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
datetime t0=g_e1.Time(g_s[i],0);
|
|
if(!g_e1.IsBarClosed(t0,g_e1.TfOf(g_s[i]),now))
|
|
Fail("T3 "+AgentName(i)+": bar terbaru belum tertutup ("+TimeToString(t0)+")");
|
|
}
|
|
|
|
// compute semua agen + agregator (via facade)
|
|
g_eng2.Compute(g_e1,g_s[0],g_s[1],g_s[2],g_s[3],
|
|
g_out[0],g_out[1],g_out[2],g_out[3],g_aggOut);
|
|
|
|
for(int i=0;i<4;i++) CheckAgentOut(i);
|
|
CheckAggOut();
|
|
|
|
if(!g_indepDone)
|
|
{
|
|
g_indepDone=CheckIndependence();
|
|
|
|
}
|
|
|
|
if(g_verbose) PrintAllVerbose();
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Init |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
Print("AFTEST2 ============================================================");
|
|
Print("AFTEST2 Algo Forge Engine2 UnitTest v"+AF_PROJECT_VERSION);
|
|
Print("AFTEST2 Atribusi: "+AF_SOURCE_TITLE+" (c) "+AF_SOURCE_AUTHOR+" - "+AF_SOURCE_URL);
|
|
g_verbose=InpVerbose;
|
|
|
|
ENUM_TIMEFRAMES tfs[4];
|
|
tfs[0]=AF_E2_TF_S1; // H4 (N)
|
|
tfs[1]=AF_E2_TF_S2; // M30 (C)
|
|
tfs[2]=AF_E2_TF_S3; // M15 (E)
|
|
tfs[3]=AF_E2_TF_S4; // M3 (P)
|
|
ENUM_TIMEFRAMES tft[4];
|
|
tft[0]=InpTamper1;
|
|
tft[1]=InpTamper2;
|
|
tft[2]=InpTamper3;
|
|
tft[3]=InpTamper4;
|
|
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
g_s[i]=g_e1.Register(tfs[i],InpMaxBars);
|
|
g_t[i]=g_e1.Register(tft[i],InpMaxBars);
|
|
g_lastBar[i]=-1;
|
|
if(g_s[i]<0) Fail("Register slot "+AgentName(i)+" gagal");
|
|
}
|
|
g_e1.Refresh();
|
|
|
|
SyntheticAggTests(); // T6 - murni logika, jalan tanpa data
|
|
SyntheticSetupTests(); // T7 - F3 setup layer, input semantik sintetis
|
|
|
|
PrintFormat("AFTEST2 slot: N=%d C=%d E=%d P=%d | tamper N=%d C=%d E=%d P=%d",
|
|
g_s[0],g_s[1],g_s[2],g_s[3],g_t[0],g_t[1],g_t[2],g_t[3]);
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tick |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
g_e1.Refresh();
|
|
|
|
bool barsChanged=false;
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
if(g_s[i]<0) continue;
|
|
if(!g_e1.IsReady(g_s[i])) continue;
|
|
long lb=g_e1.LastBars(g_s[i]);
|
|
if(lb!=g_lastBar[i])
|
|
{
|
|
barsChanged=true;
|
|
g_lastBar[i]=lb;
|
|
}
|
|
}
|
|
|
|
if(barsChanged)
|
|
{
|
|
if(RunAllChecks()) g_repaintPending=true;
|
|
else g_repaintPending=false;
|
|
}
|
|
else if(g_repaintPending)
|
|
{
|
|
CheckNonRepaint(); // bar sama -> output harus identik
|
|
g_repaintPending=false;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tester summary |
|
|
//+------------------------------------------------------------------+
|
|
double OnTester()
|
|
{
|
|
Print("AFTEST2 ============================================================");
|
|
PrintFormat("AFTEST2 RESULT: PASS=%d FAIL=%d",g_pass,g_fail);
|
|
Print("AFTEST2 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("AFTEST2 final [%s]: TF=%s count=%d historyCalls=%d",
|
|
AgentName(i),AF_E2_TfName(g_e1.TfOf(g_s[i])),
|
|
g_e1.Count(g_s[i]),g_e1.HistoryCalls(g_s[i]));
|
|
return (g_fail==0)? 1000.0 : 0.0;
|
|
}
|