feat: portfolio simulation + per-trade stats (gaps, hold time, best/worst)
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 55s
Deploy / deploy (push) Successful in 38s

Per-trade additions to the report:
- Gap-through-stop fills: stops now fill at the worse of the stop or the
  bar's open across every exit model (target, TP, trailing, time), so a
  loss can exceed -1R; targets never fill better than their level.
- best_r / worst_r, avg holding days, and net R per day of capital
  deployed on the summary buckets and the time-exit sweep.

Portfolio simulation (the stats a per-setup replay cannot give):
- One capital-constrained book over the qualified setups: 10k start, max
  10 concurrent positions (one per ticker, best momentum first), 1%
  fixed-fractional risk with a 20% no-leverage notional cap, entries at
  the detection close, 0.1%/side costs, daily mark-to-market.
- Two exit policies compared: S/R target race vs hold-to-horizon.
- Equity-curve stats: final equity, total return, CAGR, max drawdown,
  annualized daily Sharpe, win rate, avg P&L, best/worst trade, avg
  hold, entries skipped on a full book, and SPY price return over the
  same window (benchmark history refreshed to cover the replay span).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 11:56:29 +02:00
parent 942a22ce65
commit 0f43e755f4
4 changed files with 634 additions and 33 deletions
+44
View File
@@ -232,6 +232,10 @@ export interface BacktestBucket {
// Net of transaction costs — optional so a stale cached report still renders.
net_avg_r?: number | null;
net_total_r?: number | null;
best_r?: number | null;
worst_r?: number | null;
avg_hold_days?: number | null;
net_r_per_day?: number | null;
}
export interface BacktestCalibrationRow {
@@ -276,6 +280,45 @@ export interface BacktestTimeExitRow {
total_r: number | null;
net_avg_r?: number | null;
net_total_r?: number | null;
best_r?: number | null;
worst_r?: number | null;
avg_hold_days?: number | null;
net_r_per_day?: number | null;
}
export interface BacktestPortfolioPolicy {
policy: string;
starting_capital: number;
final_equity: number;
total_return_pct: number;
cagr_pct: number | null;
max_drawdown_pct: number;
sharpe: number | null;
trades: number;
win_rate: number | null;
avg_trade_pnl: number | null;
best_trade_r: number | null;
worst_trade_r: number | null;
best_trade_pnl: number | null;
worst_trade_pnl: number | null;
avg_hold_days: number | null;
skipped_book_full: number;
spy_return_pct: number | null;
start_date: string;
end_date: string;
}
export interface BacktestPortfolioSim {
params: {
starting_capital: number;
max_positions: number;
risk_per_trade_pct: number;
notional_cap_pct: number;
cost_per_side_pct: number;
hold_days: number;
};
policies: BacktestPortfolioPolicy[];
note?: string;
}
export interface BacktestGateAblationRow extends BacktestBucket {
@@ -319,6 +362,7 @@ export interface BacktestReport {
take_profit_sweep?: BacktestTakeProfitRow[];
trailing_sweep?: BacktestTrailingRow[];
time_exit_sweep?: BacktestTimeExitRow[];
portfolio_sim?: BacktestPortfolioSim;
calibration: BacktestCalibrationRow[];
signal_eval?: BacktestSignalEvalRow[];
signal_eval_note?: string;