IS-WFA-OOS strategy validation: the harness, its gates, and Lean vs. ours (#2582)
Date: 2026-07-28 · Status: module landed (experiments/lib/walk-forward.js, tested); Lean adoption evaluated and deferred with reasons.
The problem #2582 names
experiments/trader_walkforward.js proves the live signal engine has no bar-by-bar look-ahead — at bar t only bars [0..t] are visible. That is necessary but not sufficient. It still optimizes and reports on one window: the stop-width sweep (atr2/3/4) is chosen over the same ~30 days it is scored on, so the winning multiple is an in-sample fit. The question that actually gates capital is "does a rule chosen on the past hold on data it never saw," and the standard answer is walk-forward analysis (WFA).
What landed
experiments/lib/walk-forward.js — a reusable IS-WFA-OOS engine, decoupled from any strategy or data source (bars, simulate(bars, params) → trades, the param grid, and the score objective are all injected; no network, no broker, no app import):
makeFolds(total, isBars, oosBars, anchored)— sequential folds whose OOS segments tile the series with no overlap and no gaps (every out-of-sample bar scored exactly once).anchored= growing IS frommakeFolds(total, isBars, oosBars, anchored)(anchored WFA) vs a fixed-width sliding IS (rolling WFA).walkForward(cfg)— for each fold: optimize params on the IS window only, then evaluate the chosen params on the immediately-following OOS segment the optimizer never saw. The concatenated OOS trades are the one honest track record; Walk-Forward Efficiency (OOS score / IS score) says how much fitted edge survived.- Pre-committed gates (
DEFAULT_GATES) — a strategy passes only if ALL hold:minFolds ≥ 3,avgWFE ≥ 0.5(half the IS edge survives OOS),oosProfitFactor ≥ 1.0,positiveOosFraction ≥ 0.5(the edge shows in a majority of folds, not one lucky one),maxOosDrawdown ≤ 25%. Stated up front so a disappointing run cannot move the goalposts — the same discipline the Σ₀ eval ledger imposes on model marks.
Tested (experiments/lib/walk-forward.test.js, 9/9): fold tiling (anchored & rolling), the cross-fold no-look-ahead invariant (OOS always strictly after its IS), optimize-on-IS/evaluate-on-OOS, an edge that inverts OOS is caught (negative WFE, gate fails), a robust edge passes, and the OOS record is a strict subset of the series (never leaks IS).
Lean vs. ours — the adoption decision
QuantConnect Lean ships a mature WFA/optimization stack (OptimizerNodePacket, walk-forward via the CLI, parameter sweeps, a full backtest engine with corporate-actions/borrow/slippage models).
| Lean | this module | |
|---|---|---|
| WFA folds + optimize-on-IS | ✅ built-in | ✅ walkForward |
| Pre-committed pass/fail gates | ✋ you script them | ✅ DEFAULT_GATES in-repo |
| Strategy under test | Lean QCAlgorithm (C#/Python, rewrite) |
the existing JS simulate (signal-engine, kalshi, …) as-is |
| Data | Lean's data feeds / subscriptions | whatever bars you pass (Yahoo, cached, synthetic) |
| Fills / costs / borrow | rich, realistic models | the caller's simulate owns them (today: 0.10% round-trip, next-bar-open) |
| Runtime footprint | full engine (.NET/Docker) | one zero-dep Node file, unit-tested |
Decision: extend ours now; keep Lean as the escalation for realism, not methodology. The methodology gap (#2582) is walk-forward discipline + committed gates, and that is what this module adds directly over the strategies we already run — with no rewrite of the signal engine into a Lean QCAlgorithm and no .NET/data-subscription dependency. Lean's genuine edge is execution realism (borrow, corporate actions, richer slippage), which matters when a rule has passed WFA and needs a higher-fidelity fill model before capital — that is the point to spike Lean, not before. Adopting Lean to get walk-forward would import a large runtime to replace ~150 lines and still require scripting the gates by hand.
Follow-on
- Wire
trader_walkforward.js'ssimulateSymbolin as thesimulatecallback so the stop-width sweep runs throughwalkForward(needs the Yahoo fetch, so it stays a networked experiment, not a unit test). - The pre-committed gate thresholds are defaults; calibrate
minAvgWfe/maxOosDrawdownagainst a few real strategies before treating a pass as a green light.