← Results index

Re-derive cumu_nhnl_z50 signal and add pinned-fixture test

2026-08-18T12:00:00Z Bug fix completed Fixed backtest/nhnl_breadth to implement Pine ta.cum->SMA50(cum)->stdev50 ddof0 zs<0 exactly and added hand-computed CSV fixture with date-exact test guarding off-by-one/lookahead/ffill/ddof errors.
nhnl pine tradingview cumu stdev-ddof0 fixture

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

  1. Freeze Pine semantics — Documented request.security(...,'D',close) daily close, ta.cum cumulative sum from bar 0 causal, ta.sma trailing rolling mean inclusive with min_periods=50, ta.stdev population ddof=0 same window, zs=(cum-SMA)/stdev (stdev==0 => NaN), sell=zs<0 strict, NaN=>False, continuous regime, no ffill/lookahead.
  2. Reproduce drift — Built 120-bar deterministic fixture (alternating +10/-15 regimes). Current SMA50(raw)<0 produced 43 sell days vs Pine cumu-z50 44 sell days with 23 mismatched dates (first 2020-03-17), confirming off-by-one/definition drift. Verified ddof0 vs ddof1 ratio 0.9899 at n=50 and lookahead shift produces additional mismatches.
  3. Implement authoritative pipeline — Added backtest/nhnl_breadth.py:cumu_nhnl_z50_components, cumu_nhnl_z50_signal, and cumu_nhnl_z50_signal_from_hl (HIGN/LOWN net) with exact causal trailing semantics: cumsum on sorted unique index, rolling(50,min_periods=50).mean(), rolling(...).std(ddof=0), zs with zero-stdev NaN, sell=(zs<0).fillna(False), HL wrapper aligns on common index without ffill. Updated __all__.
  4. Hand-computed fixture & test — Created tests/fixtures/nhnl_cumu_z50_tradingview.csv (80 daily rows 2020-01-01..2020-03-20, columns date,hign,lown,index_data,cum,sma50,stdev50,zs,sell) computed via same Pine formulas with ddof0 for reference (first valid 2020-02-19, 15 sell dates 2020-02-19..2020-03-04). Tests test_cumu_nhnl_z50_pinned_fixture asserts exact signal dates, numeric equality for cum/sma/stdev/zs within 1e-9, min_periods guard, population vs sample, lookahead-shift and ffill guards; test_cumu_nhnl_z50_no_lookahead_and_ddof verifies linear-cum edge cases.

Files changed

Validation

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.

Source artifact: research/results/qm-d25w.md