Skip to content

feat(features): residual-momentum family (W2, L4469) — data module owns features#356

Merged
cipher813 merged 2 commits into
mainfrom
feat/w2-residual-momentum-features
Jun 1, 2026
Merged

feat(features): residual-momentum family (W2, L4469) — data module owns features#356
cipher813 merged 2 commits into
mainfrom
feat/w2-residual-momentum-features

Conversation

@cipher813
Copy link
Copy Markdown
Owner

What

W2 of the predictor-improvement arc. Adds 3 per-ticker feature-store columns computed in features/feature_engineer.py — the single source of truth for features — reusing the existing beta-residualized log-return series (no beta recompute, no duplicated computation).

Column Units Compute
residual_momentum_ratio information ratio ∑residual_returns[t-252,t-21] / (σ_resid·√231) — reuses the same residual_returns series feeding idio_vol_60d (Blitz/Hanauer residual momentum)
mom_12_1_pct decimal return close.shift(21)/close.shift(252) - 1 (12-1 skip-month; the store only had 5/20/60/120d)
sector_mom_pct decimal return sector-ETF own 12-1 momentum (GKX industry momentum, absolute — distinct from sector_vs_spy_* relative)

Why this lives here (not in the predictor)

The "data module owns features" invariant. An earlier predictor-side draft re-derived a rolling market beta — duplicating exactly what feature_engineer.py already computes for beta_60d/idio_vol_60d, the drift hazard the invariant prevents. Corrected: the features live here; the predictor reads them as columns.

Contract

Full feature-store contract satisfied: FEATURES list + registry.CATALOG + SCHEMA.md §3, all three in sync; columns are units-suffix compliant (_ratio/_pct) so test_schema_contract.py passes with no grandfathering. A full features.compute run backfills all history; daily_append warms up like any rolling feature.

Consumer

alpha-engine-predictor #216 consumes these as read-only ArcticDB columns, observe-gated in the L2 until the standalone leak-free read validates the signal (not auto-promoted).

Tests

Full suite 1751 passing; new test_feature_engineer_residual_momentum.py (pure-beta → ~zero residual momentum, skip-month exclusion, NaN-when-SPY/sector-missing, warmup).

Sequencing note

The predictor reading these needs the columns materialized in ArcticDB — a full features.compute (DataPhase1) run after merge. Relevant for the Tue 6/2 off-cycle run: DataPhase1 → PredictorTraining → backtester chain.

🤖 Generated with Claude Code

…wns features (L4469)

W2 of the predictor-improvement arc. Adds 3 per-ticker feature-store columns,
computed in feature_engineer.py (the SINGLE source of truth for features) —
NOT in the predictor. Reuses the existing beta-residualized log-return series
(`residual_returns`, the same one feeding idio_vol_60d) — NO beta recompute,
no duplicated computation, drift invariant preserved.

- residual_momentum_ratio : vol-scaled cumulative residual log-return over the
  12-1 skip-month window (∑resid / (σ_resid·√231)) — Blitz/Hanauer residual
  momentum, the highest-EV signal. Information ratio → `_ratio`.
- mom_12_1_pct            : 12-1 skip-month raw price momentum (classic factor;
  the store only had 5/20/60/120d, none skipping the recent month).
- sector_mom_pct          : sector-ETF own 12-1 momentum (GKX industry momentum,
  absolute — distinct from the existing sector_vs_spy_* relative features).

Full feature-store contract: FEATURES + registry CATALOG + SCHEMA.md §3 +
units-suffix compliant (schema-contract test green). A full features.compute run
backfills all history; daily_append warms up like any rolling feature.

Predictor (alpha-engine-predictor #216) consumes these as read-only ArcticDB
columns, observe-gated in the L2 until the standalone leak-free read validates.

Suite 1751 passing (+ new test_feature_engineer_residual_momentum.py).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bust contrast

The pure-beta test (close == SPY exactly) produced a 0/0 information ratio
dominated by float jitter — value varied by pandas/numpy version (passed
locally on pandas 2.3.3, failed in CI). Replaced with the down-drift mirror of
the existing contrast test: market up + idiosyncratic drift down → residual
momentum NEGATIVE while raw price momentum POSITIVE. Strong drifts dominate
noise → version-independent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cipher813 cipher813 merged commit d7fa939 into main Jun 1, 2026
1 check passed
@cipher813 cipher813 deleted the feat/w2-residual-momentum-features branch June 1, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant