add cross-sectional signal evaluation (factor rank-IC) to the backtest
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 40s
Deploy / deploy (push) Successful in 26s

The per-setup hit-rate report can't tell whether a signal predicts returns —
only how a target/stop structure built on one performs. This adds a
cross-sectional factor-IC pass: each week the universe is ranked by a price-only
signal and graded by its rank correlation (Spearman IC) and top-minus-bottom-
quintile spread against the forward 30-day return.

Candidate signals (point-in-time from price; sentiment/fundamentals have no
history in the replay): 12-1/6-1/3-1 month momentum, 1-month reversal,
price-vs-200d SMA, proximity to the 52-week high (George/Hwang), and 126-day
realized volatility (low-vol anomaly).

Reuses the existing per-ticker replay loop (no new data, no second DB pass);
results land in the cached backtest_report as `signal_eval` and render as a
"Signal edge" table in BacktestPanel beside the calibration curve.

330 backend tests pass (10 new in test_signal_eval); frontend build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 17:58:40 +02:00
parent c34f3cb1a4
commit 402025692a
5 changed files with 432 additions and 1 deletions
+12
View File
@@ -224,6 +224,16 @@ export interface BacktestSweepRow extends BacktestBucket {
min_expected_value: number;
}
export interface BacktestSignalEvalRow {
signal: string;
weeks: number;
avg_cross_section: number | null;
mean_ic: number;
ic_t_stat: number | null;
ic_positive_pct: number;
mean_quintile_spread: number | null;
}
export interface BacktestReport {
generated_at: string;
tickers: number;
@@ -236,6 +246,8 @@ export interface BacktestReport {
min_expected_value: number;
sweep: BacktestSweepRow[];
calibration: BacktestCalibrationRow[];
signal_eval?: BacktestSignalEvalRow[];
signal_eval_note?: string;
note: string;
}