Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4, risk 0.25%, one chart per index with a shared Magic. - System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the expanding vol percentile (no 1024-bar stdlib ceiling) - System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and Friday flat, shared through terminal globals by Magic - CWarriorExpert: guard on every tick; a transient open failure retries the bar - CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid - SignalDipBuy: no entry vote while holding (a still-dipping time exit never closed, and Processing re-entered on the exit bar); no entry on a stop bar - WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk - TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ - SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills) - research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL (AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5,5 КиБ
NN meta-label on the vol-gated dip-z: cross-index / market-state features (pre-registration)
Written 2026-09-23, before any model was fitted or any result looked at. The only
thing inspected before this was the trade count per year of the gated strategy
(2021 91, 2022 238, 2023 43, 2024 60, 2025 119, 2026 102; win rate 62.6 %), which was
needed to size the walk-forward blocks. Code: research/nn_cross_index.py.
Universe and label
- Trades: the production rule exactly as
vol_filter_test.collect_filtered(..., "vol pct >= 0.50")produces them (SP500, NAS100, US30, DAX40; H4; z20 <= -1.5; GK sigma(30) expanding percentile >= 0.50; exit close >= SMA20 or 10 bars; stop 3 x ATR14; next-open fill; spread from the bar file). Generatorbacktest.simulatereused unchanged. - Label (classifier): net R-multiple > 0. Secondary label (regressor, sizing only): R.
- Primary training universe = the gated trades. Secondary (reported, NOT part of the pass bar): train on the UNGATED dip-z trades (1,243, twice the data), score the gated ones.
Timing / leakage control
All four H4 files sit on the same 00/04/08/12/16/20 server-time grid. For a signal bar of symbol s opening at t (closing t+4h), another index's features are taken from its last bar with open time <= t, which closes at or before t+4h — i.e. already closed when the signal bar closes. When DAX is shut (US evening) this is its last closed bar (stale, causal). Correlation/dispersion use closes forward-filled onto the union grid with the same rule.
Feature sets (all at the signal bar i = entry_i - 1)
Per index k (fixed order SP500, NAS100, US30, DAX40): z20_k, vp_k (GK sigma30 expanding
pctile), r5_k = (c-c[-5])/ATR14, r30_k = (c-c[-30])/ATR14.
CROSS (primary) = the 16 per-index columns above + breadth15 (# indices with z20<=-1.5)
breadth10(# with z20<=-1.0) + owndd120((120-bar high - close)/ATR14) + ownslope200((SMA200 - SMA200[-20])/ATR14) +corr60(mean pairwise correlation of the 4 indices' H4 log returns over the last 60 union-grid bars) +disp20(cross-sectional std of the 4 indices' 20-bar log returns) + index one-hot (4). = 26 columns.
BAR (the old in-terminal set, baseline): z20, (c-SMA200)/ATR, ret1/ATR, ret5/ATR, down streak, position in 20-bar range, range/ATR, close-in-bar, gap/ATR, ATR14/ATR100, efficiency ratio(10), variance ratio (4-bar vs 1-bar over 60), regime (c>SMA200), dow, hour + one-hot.
BOTH = CROSS ∪ BAR (measures the increment).
Models (hyper-parameters fixed now, not tuned)
- MLP (primary): StandardScaler -> MLPClassifier(hidden=(16,), alpha=1.0, solver=lbfgs, max_iter=2000), mean of 5 seeds. Median imputation from the training block.
- RF: 500 trees, min_samples_leaf=15, max_features=sqrt.
- GB: GradientBoosting(150 trees, depth 2, lr 0.05, subsample 0.7).
- LR: L2 logistic, C=0.1 (linear reference).
- Regressor for the R-sizing secondary: MLPRegressor, same architecture.
Walk-forward
Expanding window, yearly refit (6-monthly would give blocks of 20-60 trades). Validation blocks: 2023, 2024, 2025, 2026-01..08. Training = every trade whose signal bar is before the block start AND whose exit bar is >= 12 bars (of its own symbol) before the block start (purge + embargo 12 bars). Pooled model with index one-hot.
Statistics
- Per block: AUC and validation n. Mean AUC over blocks with n >= 100 (expected: only 2025 and 2026 qualify — this is stated in advance).
- Pooled OOS AUC over all 2023-26 validation rows, 95 % bootstrap CI (2,000 resamples) and a label-permutation p-value (1,000).
- Sanity: (1) labels shuffled inside every training block -> AUC must be ~0.5;
(2) canary: CROSS +
canary= first-bar return of the trade (c - o of the fill bar)/ATR, i.e. future data -> AUC must be clearly high, proving the harness sees signal. - Permutation importance of the primary model: drop in pooled OOS AUC when a column is shuffled within its block (20 repeats).
Portfolio (OOS 2023-01-01 .. 2026-08-31, halves split at 2024-11-01)
Rebuilt from the per-bar P (every gated signal bar gets a P from its block's model), so a
skipped signal lets the symbol take a later signal exactly as the EA would: entries =
gated & (P >= cut) -> backtest.simulate. Risk 0.25 %/trade, open-risk cap 0.75 %
(sum of open stop-risk). Friday flat and swap are NOT modelled (the Python tooling does
not; it affects all variants alike).
- (a) baseline gated.
- (b) filter P >= cut, cuts 0.45 / 0.50 (primary) / 0.55.
- (c) sizer: multiplier 0.5 / 1.0 / 1.5 by tercile of P, tercile edges from time-ordered 3-fold out-of-fold predictions on the training block (causal). ret/DD is ~scale-free, so average-risk drift does not bias the comparison; realized mean multiplier is reported.
- Secondary: sizer from the MLP regressor's predicted R terciles. Per half: trades, trades/month, return (compounded at exit), maxDD exit-based AND mark-to-market (open positions marked at every H4 close, additive), ret/DD = return / MTM maxDD.
PASS BAR (primary = MLP on CROSS, gated universe)
AUC criterion: pooled OOS AUC >= 0.55 with bootstrap 95 % CI lower bound > 0.50, AND mean per-block AUC over blocks with n >= 100 >= 0.55.
- Filter PASS = AUC criterion AND cut-0.50 filter ret/DD (MTM) > baseline in BOTH halves AND >= 2 trades/month in both halves.
- Sizer PASS = AUC criterion AND tercile sizer ret/DD (MTM) > baseline in BOTH halves. Anything else is FAIL. Other models / cuts / the ungated-trained variant are reported for context and cannot turn a FAIL into a PASS.