feat: robustness stats + dynamic recommendation; retire settled report sections
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 54s
Deploy / deploy (push) Successful in 32s

Robustness (answers 'is the edge just outliers?'):
- _bucket_stats gains median_net_r, profit_factor, and net_avg_r_ex_top5
  (expectancy with the top 5% of winners removed); shown as stat tiles.
- Portfolio sim gains per-calendar-year returns, shown in the sim table.

Dynamic recommendation ('What this backtest recommends' panel):
- _build_recommendation derives advice from the report's own numbers on
  every run — exit policy (target vs best hold, with sim CAGRs), which
  gate floors earn their keep (ablation Hold column), best momentum
  cutoff, book-vs-SPY verdict, and an outlier-dependence warning when
  the trimmed expectancy goes non-positive.

Retired (conclusions reached, tables removed from report + UI):
- Take-profit sweep (no interior optimum — fixed TP is the wrong tool
  for momentum), trailing sweep (converged to the hold-to-horizon exit),
  probability calibration (model is display-only by decision).
- _tp_primitives slimmed to _risk_and_stop_day; trailing machinery gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 12:33:22 +02:00
parent 0f43e755f4
commit 243e369e9a
4 changed files with 359 additions and 503 deletions
+12 -32
View File
@@ -236,41 +236,16 @@ export interface BacktestBucket {
worst_r?: number | null;
avg_hold_days?: number | null;
net_r_per_day?: number | null;
}
export interface BacktestCalibrationRow {
bucket: string;
n: number;
predicted_avg: number;
realized_hit_rate: number;
// Robustness: distribution shape, and expectancy without the top winners.
median_net_r?: number | null;
profit_factor?: number | null;
net_avg_r_ex_top5?: number | null;
}
export interface BacktestSweepRow extends BacktestBucket {
min_momentum_percentile: number;
}
export interface BacktestTakeProfitRow {
tp_pct: number;
total: number;
wins: number;
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 {
trail_pct: 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 BacktestTimeExitRow {
hold_days: number;
total: number;
@@ -304,10 +279,17 @@ export interface BacktestPortfolioPolicy {
avg_hold_days: number | null;
skipped_book_full: number;
spy_return_pct: number | null;
yearly_returns?: { year: number; return_pct: number | null }[];
start_date: string;
end_date: string;
}
export interface BacktestRecommendation {
headline: string | null;
items: { topic: string; text: string }[];
note?: string;
}
export interface BacktestPortfolioSim {
params: {
starting_capital: number;
@@ -359,11 +341,9 @@ export interface BacktestReport {
sweep: BacktestSweepRow[];
gate_ablation?: BacktestGateAblationRow[];
gate_ablation_note?: string;
take_profit_sweep?: BacktestTakeProfitRow[];
trailing_sweep?: BacktestTrailingRow[];
time_exit_sweep?: BacktestTimeExitRow[];
portfolio_sim?: BacktestPortfolioSim;
calibration: BacktestCalibrationRow[];
recommendation?: BacktestRecommendation;
signal_eval?: BacktestSignalEvalRow[];
signal_eval_note?: string;
note: string;