feat: net-of-cost backtest, gate ablation + time-exit sweeps, longer tails
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 57s
Deploy / deploy (push) Successful in 32s

Phase 1 of the strategy-measurement plan — report-only, no production
trading behavior changes:

- Cost haircut: every bucket/sweep now reports net_avg_r/net_total_r
  alongside gross (COST_PER_SIDE=0.1% of notional, converted to R via
  each setup's stop distance); params carry cost_per_side_pct.
- Gate ablation table: re-qualifies candidates at the current momentum
  cutoff with one floor removed per row (confidence / R:R / NEUTRAL /
  momentum-only) to show which floors earn their keep.
- Time-based exit sweep: hold 5/10/21/30 days with the initial ATR stop,
  exit at the day-N close — the classic momentum implementation, to
  disambiguate the wide-trailing result.
- TP sweep extended to +40/+50%, trailing to 25/30% so the optima are
  interior instead of starred at the sweep edge.
- BacktestPanel: Net Avg R columns everywhere, gate-ablation and
  time-exit tables, stars now mark best net avg R; stale cached reports
  still render (all new fields optional/guarded).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 07:50:37 +02:00
parent 84ce7c5c26
commit 29b1a9a28c
5 changed files with 505 additions and 24 deletions
+31 -1
View File
@@ -229,6 +229,9 @@ export interface BacktestBucket {
hit_rate: number | null;
avg_r: number | null;
total_r: number | null;
// Net of transaction costs — optional so a stale cached report still renders.
net_avg_r?: number | null;
net_total_r?: number | null;
}
export interface BacktestCalibrationRow {
@@ -249,6 +252,8 @@ export interface BacktestTakeProfitRow {
hit_rate: number | null;
avg_r: number | null;
total_r: number | null;
net_avg_r?: number | null;
net_total_r?: number | null;
}
export interface BacktestTrailingRow {
@@ -258,6 +263,23 @@ export interface BacktestTrailingRow {
win_rate: number | null;
avg_r: number | null;
total_r: number | null;
net_avg_r?: number | null;
net_total_r?: number | null;
}
export interface BacktestTimeExitRow {
hold_days: number;
total: number;
wins: number;
win_rate: number | null;
avg_r: number | null;
total_r: number | null;
net_avg_r?: number | null;
net_total_r?: number | null;
}
export interface BacktestGateAblationRow extends BacktestBucket {
variant: string;
}
export interface BacktestSignalEvalRow {
@@ -276,14 +298,22 @@ export interface BacktestReport {
tickers: number;
candidates: number;
qualified: number;
params: { step_days: number; horizon_days: number; min_lookback: number };
params: {
step_days: number;
horizon_days: number;
min_lookback: number;
cost_per_side_pct?: number;
};
overall_qualified: BacktestBucket;
overall_all: BacktestBucket;
by_direction: Record<string, BacktestBucket>;
min_momentum_percentile: number;
sweep: BacktestSweepRow[];
gate_ablation?: BacktestGateAblationRow[];
gate_ablation_note?: string;
take_profit_sweep?: BacktestTakeProfitRow[];
trailing_sweep?: BacktestTrailingRow[];
time_exit_sweep?: BacktestTimeExitRow[];
calibration: BacktestCalibrationRow[];
signal_eval?: BacktestSignalEvalRow[];
signal_eval_note?: string;