//+------------------------------------------------------------------+ //| AlgoForge_Backtest_Baseline.mq5 | //| Project : Algo Forge (refactor total) | //+------------------------------------------------------------------+ //| ATRIBUSI SUMBER (WAJIB - jangan dihapus): | //| SniperGold SMC Pro+ | //| https://www.mql5.com/en/code/75466 | //| Copyright (c) Waseem Shahrukh | //| Proyek ini adalah refaktor total dengan arsitektur baru; | //| atribusi sumber wajib dicantumkan (lihat AF_Attribution.mqh). | //+------------------------------------------------------------------+ //| BACKTEST BASELINE (Tahap 5 Algo Forge): | //| - Model : SniperGold_ML.mqh (MLP 19->12->2, freeze | //| v20260821_2head, AUC long 0.6270 / short 0.6207). | //| - Fitur : 19 fitur SMC dihitung INTERNAL di EA (identik dgn | //| SniperGold_SMC_ProPlus_v4_4.mq5 ComputeMLFeatures + | //| dependensinya) -- BUKAN iCustom (rapuh). | //| - Data : HANYA dari Engine 1 (AFEngine1MTF) -- closed-bar | //| lock, cache index reusable, anti-freeze. | //| - Mode 0 : tulis prob long/short per bar M15 tertutup ke CSV | //| (untuk parity/konsistensi runtime-training + AUC). | //| - Mode 1 : trading net-of-spread di Strategy Tester | //| (OrderSend market; spread aktual dari tester). | //+------------------------------------------------------------------+ #property copyright "Algo Forge (refactor total) - atribusi wajib: https://www.mql5.com/en/code/75466 (Waseem Shahrukh)" #property version "1.00" #property description "Backtest baseline MLP 19->12->2 (freeze v20260821_2head): fitur internal (bukan iCustom), data via Engine 1. Mode 0 = CSV prob/bar (AUC/parity), Mode 1 = trade net-of-spread." #include #include #include #include //================================================================== // INPUTS //================================================================== input group "===== MODE =====" input int InpMode = 1; // 0 = CSV prob per bar (parity/AUC), 1 = trade, 2 = CSV 19 fitur (parity), 3 = debug HTF cache input bool InpLogCsv = true; // Tulis CSV audit (mode 0: prob; mode 1: trades) input group "===== SINYAL (baseline MLP 0.627/0.621) =====" input double InpThreshLong = 0.60; // Ambang prob long untuk sinyal BUY input double InpThreshShort = 0.60; // Ambang prob short untuk sinyal SELL input group "===== TRADE (mode 1) =====" input double InpSL_ATR = 1.0; // SL (x ATR) dari entry input double InpTP_ATR = 1.5; // TP (x ATR) dari entry input int InpMaxHoldBars = 24; // Exit maksimum (bar M15; = horizon label baseline) input double InpLot = 0.01; // Volume lot per posisi input group "===== ENGINE 1 =====" input int InpMaxBars = 700; // Kapasitas cache M15 (>= 2*SwingLen + Lookback) //================================================================== // PARAMETER FITUR (konsisten dgn training v4.4 default -- jangan // diubah; model dilatih dgn konfigurasi ini) //================================================================== #define AF_BT_SWING_LEN 50 // InpSwingLen (v4.4) #define AF_BT_INTERNAL_LEN 5 // InpInternalLen (v4.4) #define AF_BT_LOOKBACK 600 // InpLookbackBars (v4.4) #define AF_BT_GRAB_WIN 8 // InpGrabWindow (v4.4) #define AF_BT_SEQ_WINDOW 40 // InpSeqWindow (v4.4): sweep -> CHoCH -> entry chain window // = event validity/expiry utk f7 (P3-S.0 f7 event lifecycle fix) #define AF_BT_EQ_THR 0.10 // InpEQThreshold (v4.4) #define AF_BT_EQ_BARS 3 // InpEQBarsConfirm (v4.4) #define AF_BT_DELTA_BARS 10 // InpDeltaBars (v4.4) #define AF_BT_HTF1 PERIOD_D1 // InpHTF1 (v4.4) #define AF_BT_HTF2 PERIOD_H4 // InpHTF2 (v4.4) #define AF_BT_HTF3 PERIOD_H1 // InpHTF3 (v4.4) #define AF_BT_MIN_BARS 160 // minimal bar M15 sebelum analisis #define AF_BT_MAGIC 20260821 //================================================================== // STATE //================================================================== struct BTPivot { int bar; double price; bool isHigh; }; BTPivot g_sp[]; int g_spN=0; // swing pivots (utk EQH/EQL) BTPivot g_ip[]; int g_ipN=0; // internal pivots (utk liquidity grab) int g_stBar[]; int g_stVal[]; int g_stN=0; // trend timeline (swing) double g_swHigh=0,g_swLow=0; int g_swHighBar=-1,g_swLowBar=-1,g_swTrend=0; int g_inTrend=0,g_inLastBreak=-1,g_swLastBreak=-1,g_bias=0; int g_swpDir=0; int g_swpBar=-1; double g_swpLevel=0; int g_chochDir=0; int g_chochBar=-1; int g_dDir=0; double g_dNorm=0; bool g_eqhSwept=false,g_eqlSwept=false; double g_atr=0; string g_bTF1="Neutral",g_bTF2="Neutral",g_bTF3="Neutral"; double g_feat[SGML_NF]; double g_mlProb=0.5, g_mlProbBear=0.5; //================================================================== // F1 EVENT CONTRACT (P3-S.11) — canonical EVENT semantics (frozen // contract docs/SNIPERGOLD_CANONICAL_SETUP_CONTRACT_v1.md §K/§M, // adjudicated P3-S.10 OD-3): // EVENT { ts, dir, valid_until, superseded, source } // - W_sweep = W_choch = AF_BT_SEQ_WINDOW (40) — NOT optimized. // - active iff onset <= decisionBar <= onset + W (EXPIRED at W+1). // - superseded : a newer onset of the same type replaced it (permanent; // the stored latest onset is by construction the non-superseded one). // - chain : sweep onset <= CHoCH onset <= sweep onset + W_sweep, // both fresh at the decision bar, directions equal. // Consumers (f9/f18) must NOT read raw persistent state without expiry // (BUG-P3S2-001 / BUG-P3S3-001). Detection semantics // (DetectLiquidityGrabs / ProcessStructure) are UNCHANGED. //================================================================== struct AFEventSweep { int bar; // onset bar (M15 feature frame, 0 = oldest) int dir; // +1 bullish / -1 bearish int valid_until; // bar + AF_BT_SEQ_WINDOW bool superseded; // false for the stored latest onset string source; // "DetectLiquidityGrabs" }; struct AFEventChoch { int bar; int dir; int valid_until; bool superseded; string source; // "ProcessStructure(internal)" }; AFEventSweep g_evSweep; AFEventChoch g_evChoch; void AFEventSweepReset() { g_evSweep.bar=-1; g_evSweep.dir=0; g_evSweep.valid_until=-1; g_evSweep.superseded=false; g_evSweep.source="DetectLiquidityGrabs"; } void AFEventChochReset() { g_evChoch.bar=-1; g_evChoch.dir=0; g_evChoch.valid_until=-1; g_evChoch.superseded=false; g_evChoch.source="ProcessStructure(internal)"; } // Latest sweep EVENT active at the decision bar (contract boundary: age <= W) bool AFBT_SweepActive(int decisionBar) { if(g_evSweep.bar<0) return false; return (decisionBar-g_evSweep.bar)<=AF_BT_SEQ_WINDOW; } // Latest CHoCH EVENT active at the decision bar (contract boundary: age <= W) bool AFBT_ChochActive(int decisionBar) { if(g_evChoch.bar<0) return false; return (decisionBar-g_evChoch.bar)<=AF_BT_SEQ_WINDOW; } // Canonical setup chain (frozen §D/§M + OD-4): latest active sweep AND latest // active CHoCH, CHoCH after the sweep and within the sweep validity chain, // directions equal. NO arithmetic voting at the setup layer. bool AFBT_ChainValid(int decisionBar) { if(g_evSweep.dir==0 || g_evChoch.dir==0) return false; if(!AFBT_SweepActive(decisionBar)) return false; if(!AFBT_ChochActive(decisionBar)) return false; if(g_evChoch.barAF_BT_SEQ_WINDOW) return false; // within sweep chain if(g_evChoch.dir!=g_evSweep.dir) return false; return true; } AFEngine1MTF e1; int sM15=-1,sH1=-1,sH4=-1,sD1=-1; bool g_ready=false; datetime g_lastM15=0; // ---- trade state (mode 1) ---- ulong g_ticket=0; int g_posDir=0; double g_entryPrice=0; datetime g_entryTime=0; // ---- CSV ---- int g_fh=-1; int g_nBars=0; // ---- debug HTF (mode 3) : timestamp deterministik utk full-cache dump ---- datetime g_dbgPicks[] = { D'2026.01.05 12:00', D'2026.03.18 01:00', D'2026.04.01 00:00', D'2026.06.16 13:30', D'2026.07.20 15:45' }; //================================================================== // INIT / DEINIT //================================================================== int OnInit() { int mb=MathMax(InpMaxBars, 2*AF_BT_SWING_LEN + AF_BT_LOOKBACK); sM15=e1.Register(PERIOD_M15,mb); sH1 =e1.Register(PERIOD_H1,250); sH4 =e1.Register(PERIOD_H4,250); sD1 =e1.Register(PERIOD_D1,250); if(sM15<0||sH1<0||sH4<0||sD1<0){ Print("AFBT: Register gagal"); return(INIT_FAILED); } if(InpLogCsv) { string fn = (InpMode==0) ? "AlgoForge_bt_prob_"+_Symbol+"_M15.csv" : (InpMode==2) ? "AlgoForge_bt_features_"+_Symbol+"_M15.csv" : (InpMode==3) ? "AlgoForge_htf_debug_"+_Symbol+"_M15.csv" : "AlgoForge_bt_trades_"+_Symbol+"_M15.csv"; g_fh=FileOpen(fn,FILE_WRITE|FILE_CSV|FILE_ANSI); if(g_fh==INVALID_HANDLE){ Print("AFBT: FileOpen gagal ",fn," err=",GetLastError()); } else { if(InpMode==0) FileWrite(g_fh,"time","close","atr","pLong","pShort"); else if(InpMode==2) FileWrite(g_fh,"time","close","atr","f0_htf1","f1_htf2","f2_htf3","f3_swing", "f4_internal","f5_bias","f6_eqpos","f7_sweep","f8_choch","f9_chochok", "f10_eqh","f11_eql","f12_dsign","f13_dmag","f14_dhigh","f15_dlow", "f16_mom20","f17_range","f18_conf"); else if(InpMode==3) FileWrite(g_fh,"time","cntH1","cntH4","cntD1","needH1","needH4","needD1","biasH1","biasH4","biasD1"); else FileWrite(g_fh,"time","dir","entry","sl","tp","exitTime","exitPrice","pnl","reason"); } } g_lastM15=0; g_ready=false; return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if(g_fh!=INVALID_HANDLE) { FileClose(g_fh); g_fh=-1; } } //================================================================== // MAIN //================================================================== void OnTick() { if(!g_ready) { e1.Refresh(); if(!(e1.IsReady(sM15)&&e1.IsReady(sH1)&&e1.IsReady(sH4)&&e1.IsReady(sD1))) return; if(e1.Count(sM15)=total) return false; double v=h[idx]; for(int k=1;k<=len;k++){ if(h[idx-k]>v) return false; if(h[idx+k]>v) return false; } return true; } bool IsPivotLow(const double &l[],int idx,int len,int total) { if(idx-len<0||idx+len>=total) return false; double v=l[idx]; for(int k=1;k<=len;k++){ if(l[idx-k]=len) { if(IsPivotHigh(high,p,len,total)){ upTarget=high[p]; upBar=p; lastHighBar=p; lastHigh=high[p]; if(!internal) AddSwingPivot(p,high[p],true); else AddInternalPivot(p,high[p],true); } if(IsPivotLow(low,p,len,total)){ dnTarget=low[p]; dnBar=p; lastLowBar=p; lastLow=low[p]; if(!internal) AddSwingPivot(p,low[p],false); else AddInternalPivot(p,low[p],false); } } // bullish break if(upBar>=0 && close[i]>upTarget) { bool choch=(trend<0); bool allow = !internal || SwingTrendAt(i)>=0; // InpConfluenceFilter=true (default v4.4) trend=1; if(!internal){ g_swTrend=1; g_swLastBreak=i; AddTrend(i,1); } else { g_inTrend=1; g_inLastBreak=i; if(allow&&choch){ g_chochDir=1; g_chochBar=i; } } upTarget=DBL_MAX; upBar=-1; } // bearish break if(dnBar>=0 && close[i]0); bool allow = !internal || SwingTrendAt(i)<=0; trend=-1; if(!internal){ g_swTrend=-1; g_swLastBreak=i; AddTrend(i,-1); } else { g_inTrend=-1; g_inLastBreak=i; if(allow&&choch){ g_chochDir=-1; g_chochBar=i; } } dnTarget=-DBL_MAX; dnBar=-1; } } if(!internal) { if(lastHighBar>=0){ g_swHigh=lastHigh; g_swHighBar=lastHighBar; } if(lastLowBar>=0){ g_swLow=lastLow; g_swLowBar=lastLowBar; } } } // DetectEQ: EQH/EQL swept (fitur 10/11) void DetectEQ(const datetime &time[],const double &high[],const double &low[],int total) { g_eqhSwept=false; g_eqlSwept=false; double tol=AF_BT_EQ_THR*g_atr; for(int i=1;ig_sp[i].price){ g_eqhSwept=true; break; } } if(!g_sp[i].isHigh&&!g_sp[i-1].isHigh&&MathAbs(g_sp[i].price-g_sp[i-1].price)<=tol) { for(int b=g_sp[i].bar+1;b buy-side swept (bearish intent) if(g_ip[s].isHigh && high[b]>lvl && close[b]g_swpBar){ g_swpBar=b; g_swpDir=-1; g_swpLevel=lvl; } break; } // bullish grab: wick below internal low, close back above -> sell-side swept (bullish intent) if(!g_ip[s].isHigh && low[b]lvl) { if(b>g_swpBar){ g_swpBar=b; g_swpDir=1; g_swpLevel=lvl; } break; } } } } // TFBias: fractal swing (s=3) + break, dari Engine 1 (closed-bar lock) string BTTFBias(int slot) { int need=MathMin(200,e1.Count(slot)); if(need<120) return "Neutral"; double h[],l[],cl[]; ArrayResize(h,need); ArrayResize(l,need); ArrayResize(cl,need); for(int i=0;i 0) if(!e1.GetBar(slot,need-1-i,b)) return "Neutral"; h[i]=b.high; l[i]=b.low; cl[i]=b.close; } int s=3, m=need-1; // m excludes forming bar (Engine 1 sudah closed-bar) double up=DBL_MAX, dn=-DBL_MAX; int upb=-1, dnb=-1, trend=0; for(int i=s+1;i=s) { bool isH=true,isL=true; for(int k=1;k<=s;k++){ if(h[p]<=h[p+k]||h[p]<=h[p-k])isH=false; if(l[p]>=l[p+k]||l[p]>=l[p-k])isL=false; } if(isH){ up=h[p]; upb=p; } if(isL){ dn=l[p]; dnb=p; } } if(upb>=0 && cl[i]>up){ trend=1; up=DBL_MAX; upb=-1; } if(dnb>=0 && cl[i]0)?"Bullish":(trend<0)?"Bearish":"Neutral"; } // debug (mode 3): dump full cache H1/H4/D1 (bar time+OHLC) ke CSV void DumpHTFCache(datetime t) { if(g_fh==INVALID_HANDLE) return; string slots[3]={"H1","H4","D1"}; int ids[3] ={sH1,sH4,sD1}; for(int si=0;si<3;si++) { int cnt=e1.Count(ids[si]); FileWrite(g_fh,"=== FULLDUMP",slots[si],TimeToString(t,TIME_DATE|TIME_MINUTES),"count="+IntegerToString(cnt)); for(int i=0;i=open[j]){ bb=body+lw; ss=uw; } else { ss=body+uw; bb=lw; } double tt=bb+ss; if(tt<=0) tt=_Point; sum += (bb-ss)/tt * (double)vol[j]; sumVol += (double)vol[j]; } g_dDir=(sum>0?1:(sum<0?-1:0)); g_dNorm=(sumVol>0)?MathMax(-1.0,MathMin(1.0,sum/sumVol)):0.0; } // ComputeMLFeatures: 19 fitur identik v4.4 / train_model.py void ComputeMLFeatures(double price,const double &close[],int tc) { int b1=(g_bTF1=="Bullish")?1:(g_bTF1=="Bearish"?-1:0); int b2=(g_bTF2=="Bullish")?1:(g_bTF2=="Bearish"?-1:0); int b3=(g_bTF3=="Bullish")?1:(g_bTF3=="Bearish"?-1:0); double rng=(g_swHigh>0&&g_swLow>0)?(g_swHigh-g_swLow):0.0; double eqPos=(rng>0)?2.0*(price-g_swLow)/rng-1.0:0.0; double dHigh=(g_swHigh>0)?MathMax(-10.0,MathMin(10.0,(g_swHigh-price)/g_atr)):0.0; double dLow =(g_swLow >0)?MathMax(-10.0,MathMin(10.0,(price-g_swLow)/g_atr)):0.0; double mom =(tc>=21)?(price-close[tc-21])/g_atr:0.0; // 20-bar: tc param = tcv = total -> close[total-21] = 20 interval dari price=c[total-1] (parity v4.4 & train_model.py; REVERT SB-06 percobaan, tc-20 ternyata 19-bar) double rA =(rng>0)?rng/g_atr:0.0; // F1 (P3-S.11): choch_confirms = canonical chain under the EVENT contract — // latest active sweep AND latest active CHoCH; CHoCH after the sweep and // within the sweep validity chain; directions equal. (BUG-P3S2-001 / // BUG-P3S3-001 fixed: stale sweep/CHoCH no longer confirm. Detection // semantics unchanged; validity boundary = frozen contract age <= W.) int decisionBar=tc; // newest closed M15 bar index bool swpValid =AFBT_SweepActive(decisionBar); bool chochOK =AFBT_ChainValid(decisionBar); g_feat[0] =b1; // htf1_bias g_feat[1] =b2; // htf2_bias g_feat[2] =b3; // htf3_bias g_feat[3] =g_swTrend; // swing_trend g_feat[4] =g_inTrend; // internal_trend g_feat[5] =g_bias; // chart_bias g_feat[6] =eqPos; // eq_pos_norm // P3-S.0 f7 event lifecycle fix: sweep_dir hanya aktif selama event valid // (onset .. onset+SeqWindow); setelah expired -> NO_SWEEP (0). Tidak mengubah // trigger condition/close-back/pivot; g_swpDir internal tetap utk f9/f18. g_feat[7] =(g_swpBar>=0 && (tc-1-g_swpBar)<=AF_BT_SEQ_WINDOW)? g_swpDir : 0; // sweep_dir g_feat[8] =g_chochDir; // choch_dir g_feat[9] =chochOK?1.0:0.0; // choch_confirms g_feat[10]=g_eqhSwept?1.0:0.0; // eqh_swept g_feat[11]=g_eqlSwept?1.0:0.0; // eql_swept g_feat[12]=g_dDir; // delta_sign g_feat[13]=g_dNorm; // delta_mag g_feat[14]=dHigh; // dist_high_atr g_feat[15]=dLow; // dist_low_atr g_feat[16]=mom; // mom20_atr g_feat[17]=rA; // range_atr // fitur 18: skor konfluensi heuristik (identik v4.4) int hb=0,hr=0; if(b1>0) hb++; else if(b1<0) hr++; if(b2>0) hb++; else if(b2<0) hr++; if(b3>0) hb++; else if(b3<0) hr++; int cb=0; if(g_bias!=0) cb+=10; if(hb==3 || hr==3) cb+=25; else if((hb>=2&&hr==0)||(hr>=2&&hb==0)) cb+=12; if(eqPos<0 && g_bias>0) cb+=10; if(eqPos>0 && g_bias<0) cb+=10; if(swpValid && g_swpDir!=0) cb+=15; // F1: valid sweep only if(chochOK) cb+=15; // F1: valid chain only if(swpValid && ((g_swpDir==1&&g_bias>0)||(g_swpDir==-1&&g_bias<0))) cb+=15; // F1: valid sweep only int g_confScore=MathMin(100,cb); g_feat[18]=g_confScore; // confluence g_mlProb=SGMLProb(g_feat); g_mlProbBear=SGMLProbShort(g_feat); } // orkestrasi: hitung semua fitur pada bar tertutup terbaru M15 void ComputeAll() { int total=e1.Count(sM15); // = N-1 (semua tertutup; forming dibuang Engine 1) int tcv=total; // setara tc v4.4 (= N-1) int tc=total-1; // bar tertutup terakhir (setara close[tc-1] v4.4) double o[],h[],l[],c[]; long v[]; datetime tm[]; if(!FillM15(o,h,l,c,v,tm)) return; g_spN=0; ArrayResize(g_sp,0); g_ipN=0; ArrayResize(g_ip,0); g_stN=0; ArrayResize(g_stBar,0); ArrayResize(g_stVal,0); g_swTrend=0; g_inTrend=0; g_swLastBreak=-1; g_inLastBreak=-1; g_bias=0; g_swpDir=0; g_swpBar=-1; g_swpLevel=0; g_chochDir=0; g_chochBar=-1; g_eqhSwept=false; g_eqlSwept=false; g_atr=e1.ATR(sM15,14); int startBar=MathMax(2*AF_BT_SWING_LEN,tcv-AF_BT_LOOKBACK); ProcessStructure(AF_BT_SWING_LEN,false,tm,o,h,l,c,v,total,startBar); ProcessStructure(AF_BT_INTERNAL_LEN,true,tm,o,h,l,c,v,total,startBar); g_bias=(g_inLastBreak>=g_swLastBreak && g_inTrend!=0) ? g_inTrend : g_swTrend; if(g_bias==0) g_bias=(g_swTrend!=0?g_swTrend:g_inTrend); DetectEQ(tm,h,l,total); DetectLiquidityGrabs(tm,h,l,c,total); BTDelta(h,l,o,c,v,tcv); // F1 (P3-S.11): maintain the canonical EVENT representation from the // UNCHANGED detector state. The stored latest onset IS the active // (non-superseded) event; an older onset is replaced (superseded) by // construction (DetectLiquidityGrabs: b>g_swpBar; ProcessStructure: // each qualifying break overwrites). valid_until = onset + W. AFEventSweepReset(); AFEventChochReset(); if(g_swpBar>=0) { g_evSweep.bar=g_swpBar; g_evSweep.dir=g_swpDir; g_evSweep.valid_until=g_swpBar+AF_BT_SEQ_WINDOW; } if(g_chochBar>=0) { g_evChoch.bar=g_chochBar; g_evChoch.dir=g_chochDir; g_evChoch.valid_until=g_chochBar+AF_BT_SEQ_WINDOW; } g_bTF1=BTTFBias(sD1); g_bTF2=BTTFBias(sH4); g_bTF3=BTTFBias(sH1); ComputeMLFeatures(c[tc],c,tcv); } //================================================================== // TRADE (mode 1) //================================================================== void OpenPosition(bool buy) { if(g_ticket>0) return; if(g_atr<=0) return; double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double entry=buy?ask:bid; double sl =buy?entry-InpSL_ATR*g_atr : entry+InpSL_ATR*g_atr; double tp =buy?entry+InpTP_ATR*g_atr : entry-InpTP_ATR*g_atr; MqlTradeRequest req={}; MqlTradeResult res={}; req.action =TRADE_ACTION_DEAL; req.symbol =_Symbol; req.volume =InpLot; req.type =buy?ORDER_TYPE_BUY:ORDER_TYPE_SELL; req.price =entry; req.sl =sl; req.tp =tp; req.deviation=50; req.magic =AF_BT_MAGIC; req.comment =buy?"AFBT_B":"AFBT_S"; if(!OrderSend(req,res)) { PrintFormat("AFBT OrderSend gagal ret=%d err=%d",res.retcode,GetLastError()); return; } if(res.retcode==TRADE_RETCODE_DONE || res.retcode==TRADE_RETCODE_PLACED || res.retcode==TRADE_RETCODE_DONE_PARTIAL) { g_ticket=res.order; g_posDir=buy?1:-1; g_entryPrice=res.price; g_entryTime=g_lastM15; if(g_fh!=INVALID_HANDLE) FileWrite(g_fh,TimeToString(g_entryTime,TIME_DATE|TIME_MINUTES), buy?"BUY":"SELL", DoubleToString(g_entryPrice,_Digits), DoubleToString(sl,_Digits), DoubleToString(tp,_Digits),"","","","open"); } } // Select posisi by ticket tanpa ambiguity overload (build 6093) bool BTSelectTicket(ulong ticket) { int total=PositionsTotal(); for(int i=0;i0)?SymbolInfoDouble(_Symbol,SYMBOL_BID):SymbolInfoDouble(_Symbol,SYMBOL_ASK); double pnl=PositionGetDouble(POSITION_PROFIT); datetime exTime=TimeCurrent(); MqlTradeRequest req={}; MqlTradeResult res={}; req.action =TRADE_ACTION_DEAL; req.symbol =_Symbol; req.volume =vol; req.type =(g_posDir>0)?ORDER_TYPE_SELL:ORDER_TYPE_BUY; req.price =price; req.deviation=50; req.magic =AF_BT_MAGIC; req.comment ="AFBT_X"; if(OrderSend(req,res)) { if(g_fh!=INVALID_HANDLE) FileWrite(g_fh,TimeToString(g_entryTime,TIME_DATE|TIME_MINUTES), (g_posDir>0)?"BUY":"SELL", DoubleToString(g_entryPrice,_Digits), "","", TimeToString(exTime,TIME_DATE|TIME_MINUTES), DoubleToString(price,_Digits), DoubleToString(pnl,2), reason); } g_ticket=0; g_posDir=0; } void ManageSignal() { bool buySig =(g_mlProb >=InpThreshLong); bool sellSig=(g_mlProbBear >=InpThreshShort); if(buySig && !sellSig) { if(g_ticket>0 && g_posDir<0) ClosePosition("reverse"); if(g_ticket==0) OpenPosition(true); } else if(sellSig && !buySig) { if(g_ticket>0 && g_posDir>0) ClosePosition("reverse"); if(g_ticket==0) OpenPosition(false); } } void ManageMaxHold() { if(g_ticket==0) return; int ib=e1.FindBarIndex(sM15,g_entryTime); if(ib<0 || ib>=InpMaxHoldBars) ClosePosition("maxhold"); } //================================================================== // TESTER //================================================================== double OnTester() { if(g_fh!=INVALID_HANDLE) { FileClose(g_fh); g_fh=-1; } if(InpMode==1) { double profit=TesterStatistics(STAT_PROFIT); double dd =TesterStatistics(STAT_BALANCE_DD); double trades =TesterStatistics(STAT_TRADES); PrintFormat("AFBT SUMMARY: trades=%.0f profit=%.2f dd=%.2f profitFactor=%.2f", trades,profit,dd,TesterStatistics(STAT_PROFIT_FACTOR)); PrintFormat("AFBT MODEL: MLP 19->12->2 %s | thresh L=%.2f S=%.2f | SL=%.1fATR TP=%.1fATR hold=%d", SGML_VER,InpThreshLong,InpThreshShort,InpSL_ATR,InpTP_ATR,InpMaxHoldBars); } else { PrintFormat("AFBT CSV DONE: bars=%d mode=%d (parity/AUC via Python)",g_nBars,InpMode); } return(0.0); }