Objective
For qm-kdnj.1 (INDEX:S5FI HMA20 8.5/0 Pine fidelity already verified, INDEX:S5FI 4956 rows 2006-12-29..2026-08-11 tradingview daily persisted in data/market.sqlite), wire the qm-kdnj generic opposite-signal harness (SPX close<50DMA, <200DMA, 50/200 death-cross, trailing-stop 10/15/20% via backtest/generic_signals.py) as head-to-head indicator+generic vs generic-only deltas (ΔCAGR/ΔSharpe/ΔMaxDD, hit/false-signal) on same window/costs/lag (next_close, fee_bps=10). Acceptance: docs/site-data/experiments/qm-kdnj.1.json gains strategy-parity performance vs generic-only delta table, docs/experiments/qm-kdnj.1.html renders deltas in Research Narrative, offline repro via ensure_ohlcv offline=True, no hand-edited payloads.
Approach
- Created reusable publisher
research/scripts/publish_s5fi_hma_qm_kdnj1.py(compute_harness,build_manifestoffline): loads INDEX:S5FI viaread_ohlcv("INDEX:S5FI")4956 rows tradingview daily and SPY viaensure_universe(["SPY"], start="2007-01-01", end="2026-08-04", offline=True)4920 bars, prepares S5FI daily ffilled to SPY calendar, runsspx_breadth_buy_signalHMA20 8.5/0 crossover/crossunder OR +apply_holding_window20d fixed (18 triggers 224 active 4.55%), generates generics viabacktest/generic_signals.pysma_signal50/200,cross_signal50/200 death-cross,trailing_stop_signal0.10/0.15/0.20 expanding cummax,bh_signal, builds paired backtests per generic on same window withBacktestConfig.allocatesmartly()next_close 10bps via_StratOR overrideblended.loc[active, SPY]=1.0+validate_weights, computes metrics Δ% ΔCAGR ΔSharpe ΔMaxDD hit/false-signal, emitsresearch/experiments/qm-kdnj.1.jsonwithmetrics.generic_exit_harness+supplemental.generic_exit_harness/generic_exit_harness_rows7 rows +generic_exit_harness_detail+research_narrativeGeneric opposite-signal harness entry, provenancebacktest/generic_signals.pyhash.
- Existing
research/reports/scaffold.py:render_experiment_detailalready renders generic-exit harness table fromsupplemental.generic_exit_harness_rowsfallback to dict mapping (header Generic/Generic-only/Signal+Generic/Δ%/ΔCAGR/ΔSharpe, window note via BacktestConfig.allocatesmartly next_close 10bps); no scaffold change required — publisher rows drive deterministic rendering, parity payload remains canonical producer.
- Rebuilt site-data and static site via
python3 -m research.reports build --renderexporting 139/139 payloads, writingdocs/site-data/experiments/qm-kdnj.1.json(34071 bytes) with supplemental rows 7 anddocs/experiments/qm-kdnj.1.htmlwithdata-test=generic-exit-harnesstable (sma50 2.2269→3.9265 76.32%, sma200 5.1262→8.8577 72.79%, cross_50_200 44.93%, trailing_10pct 80.40%, trailing_15pct 73.65%, trailing_20pct 59.03%, bh 0%).
- Added
tests/test_s5fi_hma_generic_harness.py(7 tests offline no network no cache mutation): ensure_ohlcv offline S5FI 4956/SPY 4920, generic signals deterministic/causal trailing expanding, S5FI signal 18/224 tick-for-tick, harness offline deltas matching computed fixtures (sma200 72.79% etc), manifest+site-data+html contain harness with hit/false-signal, no DB WAL mutation, one-bar lag and costs via BacktestConfig.
Files changed
research/scripts/publish_s5fi_hma_qm_kdnj1.py— new offline harness publisher for qm-kdnj.1 (compute_harness/build_manifest, 7 generics 10/15/20% trailing via backtest/generic_signals.py, head-to-head same window/costs/lag OR override hw20, ΔCAGR/ΔSharpe/ΔMaxDD hit/false-signal)research/experiments/qm-kdnj.1.json— experiment manifest research-experiment/v1 id qm-kdnj.1 with metrics/supplemental generic_exit_harness + generic_exit_harness_rows 7 + generic_exit_harness_detail + research_narrative Generic opposite-signal harness, trigger_active_stats 18/224, window 2007-01-03→2026-07-24 valid_bars 4920, provenance includes backtest/generic_signals.pydocs/site-data/experiments/qm-kdnj.1.json— site-data mirror of experiment payload (deterministic via research.reports build, harness rows + stats)docs/experiments/qm-kdnj.1.html— static parity page with Research Narrative generic-exit-harness delta table (data-test generic-exit-harness, 7 rows sma50/sma200/cross/trailing10/15/20/bh Δ% ΔCAGR ΔSharpe)tests/test_s5fi_hma_generic_harness.py— 7 offline harness tests (ensure_ohlcv offline 4956, deterministic/causal, S5FI 18/224, deltas sma200 72.79% etc, manifest/site/html, no mutation, lag+costs)research/results/qm-pum2.md— this durable task note (YAML front matter id qm-pum2, category fallback, searchable via git grep).shared-artifact-manifest.json,research/catalog-canary/payloads/sixty-forty.json— rebuilt shared artifacts deterministic (11 artifacts) viaresearch.artifacts build --mode full --offlinedocs/site-data/results.json,docs/results.html,docs/results/qm-pum2.html— rebuilt changelog payload viapython3 -m research.reports.results(qm-pum2 newest)
Validation
python3 -m research.scripts.publish_s5fi_hma_qm_kdnj1 --write --offline— wrote research/experiments/qm-kdnj.1.json harness 4920 bars 2007-01-03→2026-07-24 SMA50 76.32% (2.2269→3.9265 CAGR +3.07% Sharpe +0.14 MaxDD +8.22% hit 198/false 26), SMA200 72.79% (5.1262→8.8577 CAGR +3.09% Sharpe +0.07 MaxDD -2.09% hit 204/false 20), cross 44.93%, trailing10 80.40%, trailing15 73.65%, trailing20 59.03%, BH 0% with hit/false per genericpython3 -m research.reports build --render— exported 139/139 payloads, scaffold wrote docs/site-data/experiments/qm-kdnj.1.json (34071 bytes rows 7) + docs/experiments/qm-kdnj.1.html with data-test generic-exit-harness table 7 rowspython3 -m pytest -q tests/test_s5fi_hma_generic_harness.py— 7 passed offline via ensure_ohlcv offline=True, deterministic signals, no DB mutation (file fingerprint stable), one-bar lag BacktestConfig.allocatesmartly next_close 10bpspython3 -m pytest -q tests/test_spx_breadth_low.py tests/test_datastore.py tests/test_engine.py— 73 passedpython3 -m research.artifacts check --mode full --offline— shared artifacts are deterministic and current after rebuild (built 11)python3 -m research.artifacts build --mode full --offline— built 11 deterministic shared artifacts (prior drift .shared-artifact-manifest.json fixed)python3 research/scripts/check_static_reports.py docs— local bundle OK: 1250 files; 178 canary routespython3 -m research.pipeline verify --offline— passedgrep -n generic-exit-harness docs/experiments/qm-kdnj.1.html— section id=generic-exit-harness data-test=generic-exit-harness renders Δ% ΔCAGR ΔSharpe with trailing_15pct/trailing_20pctpython3 -c "import json; d=json.load(open('docs/site-data/experiments/qm-kdnj.1.json')); print(d['experiment']['supplemental']['generic_exit_harness_rows'])"— 7 rows with delta_cagr/delta_sharpe/delta_maxDD hit/false_signalsqlite3 data/market.sqlite SELECT ticker,min(date),max(date),count(*) FROM ohlcv WHERE ticker='INDEX:S5FI'— INDEX:S5FI 2006-12-29→2026-08-11 4956 rows offline read_ohlcv succeeds without live fetchpython3 -m research.reports.results— rebuilt changelog with qm-pum2, n_entries incremented, docs/site-data/results.json includes qm-pum2 summary
Results
Offline harness verified: INDEX:S5FI 4956 rows 2006-12-29→2026-08-11 tradingview daily persisted in data/market.sqlite:instruments/ohlcv and verified offline via ensure_universe offline=True — signal HMA20 8.5/0 Pine tick-for-tick hw20 fixed yields 18 triggers /224 active 4.55% on same window 2007-01-03→2026-07-24. Generic harness paired each defensive head-to-head same window/costs/lag BacktestConfig.allocatesmartly next_close 10bps: SMA50 +76.32% (2.2269→3.9265 CAGR 4.19%→7.26% Δ+3.07% Sharpe 0.43→0.57 Δ+0.14 MaxDD -38.01%→-29.78% Δ+8.22% hit 198/false 26), SMA200 +72.79% (5.1262→8.8577 CAGR +3.09% Sharpe +0.07 MaxDD -20.93%→-23.02% Δ-2.09% hit 204/false 20), cross 50/200 +44.93% (5.3075→7.6923 CAGR +2.09% Sharpe +0.05 MaxDD -33.72%→-33.72% hit 179/false 45), trailing-stop 10% +80.40% (3.9991→7.2142 CAGR +3.29% Sharpe +0.10 MaxDD -22.84%→-23.48% hit 186/false 38), 15% +73.65% (3.3143→5.7555 CAGR +3.05% Sharpe +0.12 MaxDD -32.10%→-28.05% hit 160/false 64), 20% +59.03% (4.4288→7.0430 CAGR +2.60% Sharpe +0.09 MaxDD -38.22%→-31.50% hit 110/false 114), BH 0% (7.4701→7.4701 already max long). Delta table renders in Research Narrative generic-exit-harness section and site-data supplemental; offline repro deterministic without live fetch, no hand-edited payloads, no DB mutation, no private data.
Links
- Task:
qm-pum2(parent epicqm-kdnj, bead qm-kdnj.1 INDEX:S5FI HMA20 8.5/0) - Publisher:
research/scripts/publish_s5fi_hma_qm_kdnj1.py(compute_harness/build_manifest offline, 7 generics 10/15/20% trailing) - Manifest:
research/experiments/qm-kdnj.1.json+docs/site-data/experiments/qm-kdnj.1.json+docs/experiments/qm-kdnj.1.html(parity page Research Narrative delta table) - Generics:
backtest/generic_signals.py(sma_signal, cross_signal, trailing_stop_signal 10/15/20% expanding cummax, bh_signal, generic_weights) - Signal:
backtest/spx_breadth_low.py(hma/wma, spx_breadth_buy_signal crossover/crossunder OR, apply_holding_window hw20) - Data:
data/market.sqliteINDEX:S5FI 4956 2006-12-29→2026-08-11 tradingview + SPY 8428 1993-01-29→2026-07-24 - Tests:
tests/test_s5fi_hma_generic_harness.py(7 offline),tests/test_spx_breadth_low.py(pinned fixture) - Findings:
research/findings/spx-breadth-low-qm-4bx.18.md+research/findings/s5fi-breadth-buy-signal-qm-4bx.23.json