Objective
Re-derive the VIX contango signal exactly as the pinned Pine defines it (VIX/VIX3M ratio EMA50 vs EMA252, continuous regime sell=short>long), re-audit existing experiment qm-4bx.15 for signal-date fidelity, fix any date/definition drift (ratio construction, EMA adjust/span/seed, signal-date alignment, 4-point curve ensemble divergence), persist inputs offline, and republish under the qm-kdnj experiment-as-strategy contract with strategy parity plus notebook narrative.
Approach
Froze Pine verbatim: vix_data=request.security(VIX,D,close), vix3m_data=request.security(VIX3M,D,close), vix_ratio_long_ema=ta.ema(vix_data/vix3m_data,252), vix_ratio_short_ema=ta.ema(vix_data/vix3m_data,50), sell=short>long strict, buy=not sell continuous. Documented ta.ema as alpha 2/(len+1) SMA-seed NaN until len (not pandas ewm adjust=False), request.security D close as daily close with next_close execution (+1 bar), timezone America/New_York trading days. Corrected backtest/vix_contango.py (new file) to use SMA-seed EMA with leading-NaN handling (first consecutive valid window), ratio, continuous sell/buy, VixContangoRegime.generate_weights via validate_weights. Prior backtest/vix_backwardation.py 4-point ensemble retained as reference and excluded. Pinned 300-row fixture research/fixtures/vix-contango-pine-fixture.csv (50/252, hand-verified TV seed) plus SMA-seed and leading-NaN unit tests give tick-for-tick offline guarantee. Provenance: Yahoo ^VIX 1990-01-02→2026-08-04 9215 rows, ^VIX3M 2006-07-17→2026-07-17 5032 rows data/market.sqlite:ohlcv close_only (TV alternate CBOE:VIX/CBOE:VIX3M same depth-capped per probe), publication lag 0 (available at close, traded next bar), warmup 252 → first valid 2007-07-17, pre-2006 synthetic NO-GO (rejected prior OLS __VIX3M_SYNTH__). Offline replay via read_ohlcv verified.
Files changed
backtest/vix_contango.py— new Pine-verbatim module (ratio, ema SMA-seed, vix_contango_signals, VixContangoRegime, provenance_record)research/fixtures/vix-contango-pine-fixture.csv— pinned 300-row 50/252 TV reference for tick-for-tick asserttests/test_vix_contango.py— 6 tests (EMA seed, leading NaN, zero handling, pinned fixture exact dates, unavailable, continuous regime)research/findings/vix-contango-pine-audit-qm-kdnj.3.md/.json— audit report with date-fidelity delta, robustness, equity impactresearch/experiments/qm-kdnj.3.json— qm-kdnj experiment-as-strategy manifest (rejected disposition, frozen metrics, parity)research/reports/experiments.py— SCOPE_IDS +qm-kdnj.3for experiments payload parityresearch/results/qm-kdnj.3.md— this durable task note (searchable viagit grep)
Validation
python3 -m pytest -q tests/test_vix_contango.py tests/test_datastore.py tests/test_engine.py— 66 passed offline (EMA SMA-seed, tick-for-tick fixture, unavailable, continuous, datastore, engine)python3 research/scripts/check_static_reports.py docs— local bundle OK: 1225 files; 182 canary routespython3 -m research.pipeline validate— passed (28 nodes 38 edges graph_hash sha256:6802fabca6429d058c3d9169876dfd67d7e39a541cc129a06787bba78d0e1086)python3 -m research.pipeline verify --offline— passedsqlite3 data/market.sqlite SELECT ticker,start_date,end_date,n_rows FROM instruments WHERE ticker in ('^VIX','^VIX3M')— 9215 and 5032 rows, offline read_ohlcv succeeds without live fetch- No DB mutation from read paths, no secret/private leakage, no synthetic pre-2006 history invented
Results
Fidelity frozen and validated: 4785 valid bars 2007-07-17→2026-07-22, 43.2% active, 88 transitions, stable across subperiods (pre 43.6% post 42.9%) and adjacent EMAs (40/200 42.8% 60/300 43.5%). Date-fidelity audit fixes 31.9% mismatch vs prior sma10 quantile ensemble (1528 dates, prior 12.2% active, root causes ensemble vs 2-ticker, quantile vs EMA crossover, pandas ewm vs SMA seed, ffill vs strict NaN). Equity overlay SPY long/cash next_close 10bps CAGR 4.24% vol 10.9% Sharpe 0.435 vs B&H 10.58% vol 19.8% Sharpe 0.608, forward 21d active 1.28% vs inactive 0.76% diff +52bp — insufficient to overcome whipsaw/costs. Disposition rejected — do not promote to risk-off-indicators/v1; published as qm-kdnj experiment with frozen implementation, provenance, and negative result retained. No synthetic history invented, offline repro succeeds without live fetches.
Links
- Task:
qm-kdnj.3(parent epicqm-kdnj) - Pinned Pine:
vix_ratio_long_ema=ta.ema(vix_data/vix3m_data,252); vix_ratio_short_ema=ta.ema(vix_data/vix3m_data,50); sell=short>long(verbatim in findings) - Prior:
research/findings/vix-backwardation-qm-4bx.15.md/.json(NO-SHIP ensemble) - Code:
backtest/vix_contango.py(ema SMA-seed + vix_contango_signals + VixContangoRegime) - Data:
data/market.sqlite:ohlcv^VIX / ^VIX3M