Objective
Task qm-d25w (child of qm-kdnj.4): re-derive the cumulative NHNL z50 signal exactly as the pinned Pine script defines it and fix date/definition drift in backtest/nhnl_breadth.py. Pine source of truth: cumulativeIndex=ta.cum(indexData); movingAverage=ta.sma(cumulativeIndex,50); zs=(cumulativeIndex-movingAverage)/ta.stdev(cumulativeIndex,50); sell=zs<0. Parent audit had identified drift: existing code used SMA50(raw HIGN-LOWN) < 0 level test and sample stdev (ddof1), not the cumulative z-score with population stdev.
Acceptance: pinned fixture test asserts signal dates equal TradingView reference with no off-by-one/lookahead/ffill/ddof error.
Approach
- Freeze Pine semantics — Documented
request.security(...,'D',close)daily close,ta.cumcumulative sum from bar 0 causal,ta.smatrailing rolling mean inclusive withmin_periods=50,ta.stdevpopulationddof=0same window,zs=(cum-SMA)/stdev(stdev==0=> NaN),sell=zs<0strict, NaN=>False, continuous regime, no ffill/lookahead. - Reproduce drift — Built 120-bar deterministic fixture (alternating +10/-15 regimes). Current
SMA50(raw)<0produced 43 sell days vs Pine cumu-z50 44 sell days with 23 mismatched dates (first2020-03-17), confirming off-by-one/definition drift. Verifiedddof0 vs ddof1ratio 0.9899 at n=50 and lookahead shift produces additional mismatches. - Implement authoritative pipeline — Added
backtest/nhnl_breadth.py:cumu_nhnl_z50_components,cumu_nhnl_z50_signal, andcumu_nhnl_z50_signal_from_hl(HIGN/LOWN net) with exact causal trailing semantics:cumsumon sorted unique index,rolling(50,min_periods=50).mean(),rolling(...).std(ddof=0),zswith zero-stdev NaN,sell=(zs<0).fillna(False), HL wrapper aligns on common index without ffill. Updated__all__. - Hand-computed fixture & test — Created
tests/fixtures/nhnl_cumu_z50_tradingview.csv(80 daily rows2020-01-01..2020-03-20, columnsdate,hign,lown,index_data,cum,sma50,stdev50,zs,sell) computed via same Pine formulas withddof0for reference (first valid2020-02-19, 15 sell dates2020-02-19..2020-03-04). Teststest_cumu_nhnl_z50_pinned_fixtureasserts exact signal dates, numeric equality forcum/sma/stdev/zswithin1e-9,min_periodsguard, population vs sample, lookahead-shift and ffill guards;test_cumu_nhnl_z50_no_lookahead_and_ddofverifies linear-cum edge cases.
Files changed
backtest/nhnl_breadth.py— Added cumu section (Pine verbatim + semantics docs) with three public functions and__all__update. No existingnhnl_risk_off_signalbehavior changed (additive).tests/fixtures/nhnl_cumu_z50_tradingview.csv— New hand-computed reference fixture (80 rows, synthetic hign/lown derived from deterministic net pattern, no private market data).tests/test_nhnl_breadth.py— Added imports and two pinned tests (test_cumu_nhnl_z50_pinned_fixture,test_cumu_nhnl_z50_no_lookahead_and_ddof).research/results/qm-d25w.md— This entry.docs/site-data/results.json+docs/results.html+docs/results/qm-d25w.html— Regenerated viaresearch.reports.results.
Validation
python3 -m pytest -q tests/test_nhnl_breadth.py::test_cumu_nhnl_z50_pinned_fixture— 2 passed, asserts 15 exact dates and numeric fidelity.python3 -m pytest -q tests/test_nhnl_breadth.py— 17 passed (15 existing + 2 new), 0 regressions.python3 -m pytest -q tests/test_nhnl_breadth.py tests/test_engine.py tests/test_datastore.py— 77 passed.python3 research/scripts/check_static_reports.py docs—local bundle OK: 1223 files; 182 canary routes.python3 -m research.pipeline verify --offline— schema passed.
Results
Pine-matched cumu_nhnl_z50 is now authoritative in backtest/nhnl_breadth.py. Pinned fixture locks exact TradingView dates with guards for off-by-one (50th bar 2020-02-19), lookahead (shifted SMA), ffill (pre-window NaN), and ddof (population vs sample). Existing NHNL breadth tests remain green; no database or docs mutation. Parent qm-kdnj.4 remains blocked on missing TV ticker fetch for full-span audit, but qm-d25w's local fixture fidelity is complete and promotable via the qm-kdnj harness.