feat: add residual momentum to signal-edge backtest
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 55s
Deploy / deploy (push) Successful in 33s

Adds a research-only 12-1 residual momentum signal to the cross-sectional signal-evaluation harness. The signal estimates benchmark beta over the 12-1 formation window and ranks cumulative stock return minus beta-adjusted benchmark return; it only appears when benchmark closes are available.

No production qualification behavior changes. The Backtest signal table labels the new row as 12-1 residual momentum. Tests cover benchmark-gated emission and beta removal while keeping stock-specific drift.

Verification: 453 backend tests pass, ruff check app/ clean, frontend npm run build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 15:46:54 +02:00
parent 1e82dfad7f
commit 13374087db
4 changed files with 122 additions and 10 deletions
+4 -2
View File
@@ -60,8 +60,9 @@ def test_quintile_spread_none_when_too_few():
def test_signal_values_momentum_and_trend():
# Steadily rising series so every lookback is positive and trend is above SMA.
closes = [100.0 * (1.01 ** k) for k in range(300)]
dates = [date(2024, 1, 1) + timedelta(days=k) for k in range(300)]
i = 299
vals = bt._signal_values(closes, closes, i)
vals = bt._signal_values(dates, closes, closes, i)
assert vals["mom_12_1"] > 0 # up over the 12→1 month window
assert vals["trend_200"] > 0 # price above its 200-bar SMA in an uptrend
# 12-1 momentum skips the last month: close[i-21] / close[i-252] - 1
@@ -73,7 +74,8 @@ def test_signal_values_momentum_and_trend():
def test_signal_values_drops_signals_without_enough_history():
closes = [100.0 + k for k in range(80)] # only 80 bars
vals = bt._signal_values(closes, closes, 79)
dates = [date(2024, 1, 1) + timedelta(days=k) for k in range(80)]
vals = bt._signal_values(dates, closes, closes, 79)
assert "mom_3_1" in vals # needs 63 bars of lookback — present
assert "mom_6_1" not in vals # needs 126 — absent
assert "mom_12_1" not in vals # needs 252 — absent