Objective
Implement ATR and volatility-scaled trailing-stop adapters in backtest/generic_signals.py and wire them into the head-to-head experiment harness (same window, costs, one-bar lag) so the experiment detail delta table shows volatility-aware variants alongside fixed percent stops, with offline tests.
Approach
- Extended
backtest/generic_signals.pywith_true_range,_wilder_rma,atr_trailing_stop_signal(Wilder RMA of TR, Chandelier-style peak minus 3x ATR) andvol_trailing_stop_signal(rolling std of returns scaled by price, peak times 1 minus 2x vol), plus wrappersatr_trailing_weights,vol_trailing_weights; exposedtrailing_atrandtrailing_volviaall_generic_signalsandall_generic_weights; causal, UTC-naive, close-only fallback documented. - Updated
research/scripts/publish_s5fi_hma_qm_kdnj1.pyto computetrailing_atrandtrailing_volharness rows on the same SPY window (ensure_universe offline=True) withBacktestConfig.allocatesmartly(next_close +10bps) and one-bar lag viavalidate_weights, mirroringtrailing_15pct/trailing_20pctwiring. - Added
tests/test_trailing_stop_volatility_harness.py(11 offline tests, no network): determinism, close-only fallback, causality prefix, warmup, ATR/vol divergence vs fixed 10pct,all_generic_signalskeys, same-window harness deltas, no DB mutation, one-bar lag, payload/HTML containment. - Rebuilt deterministic public artifacts via
research.reports scaffoldandresearch.artifacts build --mode full --offline; verifieddocs/experiments/qm-kdnj.1.html9-row harness table and payload containtrailing_atr/trailing_vol.
Files changed
backtest/generic_signals.py— new ATR/vol trailing-stop signals and weights, extendedall_generic_signals/all_generic_weightsresearch/scripts/publish_s5fi_hma_qm_kdnj1.py— harness wiring fortrailing_atr/trailing_volrowstests/test_trailing_stop_volatility_harness.py— offline harness testsresearch/experiments/qm-kdnj.1.jsonanddocs/site-data/experiments/qm-kdnj.1.json/docs/experiments/qm-kdnj.1.html— 9-row delta tableresearch/results/qm-z7yi.md— this note;docs/site-data/results.json,docs/results.html,docs/results/qm-z7yi.htmlrebuilt
Validation
python3 -m pytest -q tests/test_trailing_stop_volatility_harness.py— 11 passed offline (deterministic, causal, divergence, same-window harness, no DB mutation, lag)python3 -m pytest -q tests/test_trailing_stop_volatility_harness.py -k 'causality or deterministic or warmup'— 3 passedbash -c 'grep -q trailing_atr docs/experiments/qm-kdnj.1.html && grep -q generic-exit-harness docs/experiments/qm-kdnj.1.html'— ok- Sibling harnesses
tests/test_s5fi_hma_generic_harness.py,test_vix_contango_generic_harness.py,test_adl_vol_ratio_generic_harness.py,test_fed_bal_roc_generic_harness.py— 29 passed python3 -m research.reports.resultsandpython3 research/scripts/check_static_reports.py docs— static site checks passpython3 -m research.artifacts check --mode full --offline— deterministic
Results
The experiment detail page docs/experiments/qm-kdnj.1.html now shows a 9-row generic-exit-harness table including trailing_atr (1.1628 generic-only → 1.8785 signal+generic, delta 61.54%) and trailing_vol (0.9774 → 1.5789, delta 61.54%) alongside fixed stops, on window 2007-01-03 → 2026-07-24 with same costs/lag. Offline harness proves no live fetch and no DB mutation. Changelog entry research/results/qm-z7yi.md is searchable via git grep.