# FEATURE_CONTRACT.md — SNIPERGOLD_ML P2 SOURCE OF TRUTH Status: **AUTHORITATIVE (P2.4)** — one definition for MQL5 runtime and Python training. Version/hash: see §5 (computed during the P2.5 implementation). Date: 2026-08-21 --- ## 0. Temporal & window contract (applies to ALL features) ```text Row : M15 bar with open time t; features computed at bar CLOSE, tc = t + 900 s. Closed-bar : ALL inputs are only closed bars at tc (no forming bar). M15 window : 700-bar cache = [t-699, t]; ProcessStructure begin = max(100, total-600) = 100 -> 600 bars analyzed; absolute valid pivots [t-649, t-50]. HTF window : D1/H4/H1 250-bar cache; bias uses the 200 NEWEST bars ending at E_ea(t). E_ea(t) : last closed HTF bar at tc = searchsorted(ht, tc - period, 'right') - 1. ATR(t) : rolling 14 closed M15 bars ending at t (e1.ATR / atr_series). Normalization: features NOT normalized at the feature level; z-score with the exported model mean/std (SniperGold_ML.mqh) is done inside the model. Missing data : 0 (neutral) when input unavailable, per the runtime code. ``` --- ## 1. Feature table (19 features) ### f0 — htf1_bias (D1 bias) ```text Meaning : HTF D1 bias direction (BTTF fractal swing s=3 + break) Source timeframe : D1 Input bars : 200 newest D1 bars ending at E_ea(t) Lookback : 200 D1 bars Warm-up : need < 120 -> 0 (Neutral) Closed-bar rule : window only closed bars <= E_ea(t); loop break excludes the last window bar Formula : BTTFBias(h,l,c,200): s=3; i in [4,198]; pivot p=i-3 (p>=3) fractal ±3; break when cl[i] > up (bullish) / < dn (bearish); final trend (-1/0/+1) Normalization : -1/0/+1 (used raw) Missing-data : 0 when < 120 bars Runtime : BTTFBias(sD1) [fixed: 200 newest cache bars] Training : tf_bias_asof(hh,hl,hc, E_ea) [P2.1 corrected] Parity test : abs(runtime - training) <= 1e-9, all rows ``` ### f1 — htf2_bias (H4 bias) ```text Same as f0 with source timeframe H4, E_ea on H4 (period 14400). ``` ### f2 — htf3_bias (H1 bias) ```text Same as f0 with source timeframe H1, E_ea on H1 (period 3600). ``` ### f3 — swing_trend ```text Meaning : swing structure direction (BOS/CHoCH) at bar t Source timeframe : M15 Input bars : 700-bar cache; analysis region [t-599, t]; pivot feed [t-649, t-50] Lookback : 600 analysis bars (+100 cache warmup) Warm-up : cache bars < 100 not analyzed; MIN_BARS=160 (EA) Closed-bar rule : closed bars; forming bar dropped from the cache Formula : ProcessStructure(SWING_LEN=50, internal=false) -> final trend (-1/0/+1) Normalization : -1/0/+1 Missing-data : 0 Runtime : g_swTrend (ProcessStructure) Training : build_structure(window 700, begin=100) -> sw['trend'][-1] Parity test : 1e-9 ``` ### f4 — internal_trend ```text Same as f3 with INTERNAL_LEN=5, internal=true, swing-timeline confluence. ``` ### f5 — chart_bias ```text Meaning : combined swing+internal bias (last break) Formula : if inLastBreak >= swLastBreak && inTrend!=0 -> inTrend; else swTrend; if 0 -> swTrend if !=0 else inTrend Runtime : g_bias; Training: build_structure window -> same logic ``` ### f6 — eq_pos_norm ```text Meaning : price position relative to equilibrium (swing high/low) Formula : rng = sw_high - sw_low; eqPos = 2*(price - sw_low)/rng - 1; 0 if rng<=0 sw_high/sw_low : last swing HIGH/LOW pivot in [t-649, t-50]; 0 if none (P2.3) Normalization : continuous ([-1,1] typical); Missing: 0 Runtime : ComputeMLFeatures with g_swHigh/g_swLow; Training: windowed pivots ``` ### f7 — sweep_dir ```text Meaning : direction of the last liquidity grab (internal pivot) Formula : for each internal pivot (p,lvl,isHigh), window GRAB_WINDOW=8: bearish grab: isHigh && high[b]>lvl && close[b] dir=-1 bullish grab: !isHigh && low[b]lvl -> dir=+1 update if b > swpBar Runtime : DetectLiquidityGrabs; Training: inn['pivots'] + window 8 ``` ### f8 — choch_dir ```text Meaning : direction of the last CHoCH (internal structure) Formula : trend reversal at a break with swing confluence allow (SwingTrendAt) Runtime : g_chochDir; Training: inn['choch_dir'] ``` ### f9 — choch_confirm ```text Meaning : CHoCH confirms the sweep Formula : chochDir!=0 && chochBar >= swpBar && chochDir == swpDir -> 1 ``` ### f10 — eqh_swept (P2.2 corrected) ```text Meaning : equal-high level broken Input bars : swing pivot list window [t-649, t-50] Formula : for CONSECUTIVE pairs (p1,p2) in the list, both highs, |p2-p1|>=EQ_BARS(3), |pr2-pr1| <= EQ_TOL_ATR(0.10) x ATR(t): swept if any bar b in (p2, t] with high[b] > pr2 ATR basis : ATR of the CURRENT row bar (not pivot ATR) — legacy v4.4 Normalization : 0/1 ``` ### f11 — eql_swept (P2.2 corrected) ```text Same as f10 for lows: |pr2-pr1| <= 0.10 x ATR(t); swept if low[b] < pr2. ``` ### f12 — delta_sign ```text Meaning : body vs wick balance direction (delta proxy), 10 bars Formula : sum (bb-ss)/tt * vol / sumVol; sign Runtime : BTDelta(DELTA_BARS=10); Training: 10-bar loop ``` ### f13 — delta_mag ```text Formula : clamp(sum/sumVol, -1, 1); continuous ``` ### f14 — dist_high_atr ```text Meaning : distance of price to the swing high in ATR units Formula : clamp((sw_high - price)/ATR, -10, 10); 0 if sw_high=0 ``` ### f15 — dist_low_atr ```text Formula : clamp((price - sw_low)/ATR, -10, 10); 0 if sw_low=0 ``` ### f16 — mom20_atr ```text Meaning : 20-interval momentum Formula : (price - close[t-20]) / ATR; price = c[t]; guard t>=21 Runtime : (price - close[tcv-21])/g_atr with tc=tcv=total (parity verified 0.0000) ``` ### f17 — range_atr ```text Formula : (sw_high - sw_low)/ATR; 0 if rng<=0 ``` ### f18 — confluence ```text Meaning : heuristic confluence score (0..100) Formula : g_bias!=0 +10; hb==3||hr==3 +25 / hb>=2&&hr==0 +12; eqPos<0&&bias>0 +10; eqPos>0&&bias<0 +10; swpDir!=0 +15; chochDir==swpDir +15; (swpDir==1&&bias>0)||(swpDir==-1&&bias<0) +15; min(100, total) Runtime : ComputeMLFeatures; Training: confluence_feature(F) ``` --- ## 2. MANDATORY implementation changes (results of P2.1–P2.3) ```text RUNTIME (EA) : - BTTFBias : replace GetBar(slot, count-1-i) -> GetBar(slot, need-1-i) (window = the 200 NEWEST cache bars, not the 200 oldest) [P2.1] - DetectEQ / window / delta / mom : UNCHANGED (already contract-conformant) TRAINING (build_features) : - f0-f2 : as-of CLOSE mapping (E_ea = closed HTF bar at tc), not as-of open with lag-1; 200-newest window ending at E_ea [P2.1] - f10/f11 : consecutive-list pairs + tol = EQ_TOL_ATR * A[i] (row ATR) + pivot window [i-649, i-50] [P2.2] - f3-f9, f14, f15, f17 : structure from a 700-bar cache slice ending at i, begin=100 (sw_high/sw_low = last pivot in [i-649, i-50], 0 when empty) [P2.3] ``` --- ## 3. Parity test definition (P2.5) ```text - timestamp parity : join runtime dump (mode 2, corrected EA) vs training feed 100% (no missing/extra rows) - per-feature : abs(runtime - training) <= eps f0-f5,f7-f12,f18 : eps = 1e-9 (discrete) f6,f13-f17 : eps = 1e-6 (continuous) - prediction parity: SGMLProb(runtime features) vs Python model output, max/mean |dp| + mismatch count/rate (P2.6) ``` ## 4. Versioning ```text FEATURE_CONTRACT v1.0 (2026-08-21) — based on: docs/P2_1_HTF_FORENSIC.md (f0-f2, classification B, corrected tf_bias_asof(E_ea)) docs/P2_2_EQH_EQL_FORENSIC.md (f10/f11, legacy v4.4, ATR@row + window) docs/P2_3_CONTEXT_WINDOW.md (700-bar window, sw_high/sw_low [t-649,t-50]) EA: AlgoForge_Backtest_Baseline.mq5; Training: train_model.py build_features ``` ## 5. Hash/version (filled after the P2.5 implementation) ```text Feature contract hash : C44CC6F2B740C32D06F776BD7C3E669DC5A8A6DE0484230544EBFFCF517D38DD (FEATURE_CONTRACT.md) Training build : 4680F9057B6EF32FE311C5BA10DAC2A243E15F624F61921557C92EEC59FCCEA7 (build_features_p2.py) Runtime dump (fixed) : 5F8AB5CB6EFD4E1188D9922C8050B7D1301B842B4C1AF276B98C85EF8D134CE5 (AlgoForge_bt_features_fixed_XAUUSD_M15.csv) Parity feature : 94CBFB0C5E8784D926C4FDA8506BF3DE470D672B68F02EC47C821D8139022F04 (parity_p2.py) Parity prediction : 83B00528BB02D731FA8CEA853ADBBDD745796E778B6BD4177F6BEDB4A87535E7 (parity_prediction.py) EA runtime (fixed) : BTTFBias GetBar(need-1-i) compiled 2026-08-21 20:23 (ex5) ```