SniperGold_ML/docs/FEATURE_CONTRACT.md

229 lines
8.8 KiB
Markdown
Raw Permalink Normal View History

# 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]<lvl -> dir=-1
bullish grab: !isHigh && low[b]<lvl && close[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)
```