/** * Base rates for the strategy as it actually runs. * * The per-target `probability` on a setup answers "will price touch this S/R * level?" — a question about a level we never exit at. These numbers answer the * question the user is really asking ("what tends to happen when I take one of * these?") and they are measured under the *real* exit policy, from the same * backtest report the Track Record page already consumes. */ interface MonitorRun { strategy?: string; lookback?: string; is_production?: boolean; sharpe?: number | null; cagr_pct?: number | null; max_drawdown_pct?: number | null; trades?: number | null; win_rate?: number | null; avg_hold_days?: number | null; best_trade_r?: number | null; worst_trade_r?: number | null; exit_reasons?: Record | null; } export interface BaseRates { lookbackLabel: string; trades: number; winRate: number; avgHoldDays: number | null; bestR: number | null; worstR: number | null; sharpe: number | null; /** How trades actually ended, as shares of the total (0-1). */ exits: { reason: string; label: string; count: number; share: number }[]; } const EXIT_LABELS: Record = { stop: 'initial stop', trailing_stop: 'trailing stop', time: 'max hold', target: 'target', }; /** * Pull the production strategy's full-history row out of a backtest report. * Returns null when the report hasn't run or has no production row. */ export function productionBaseRates(report: unknown): BaseRates | null { const monitor = (report as { portfolio_monitor?: { production_strategy?: string; runs?: MonitorRun[] } }) ?.portfolio_monitor; if (!monitor?.runs?.length) return null; const strategy = monitor.production_strategy; const row = monitor.runs.find((r) => r.strategy === strategy && r.lookback === 'all') ?? monitor.runs.find((r) => r.is_production && r.lookback === 'all'); if (!row || !row.trades) return null; const reasons = row.exit_reasons ?? {}; const total = Object.values(reasons).reduce((a, b) => a + b, 0); const exits = Object.entries(reasons) .map(([reason, count]) => ({ reason, label: EXIT_LABELS[reason] ?? reason, count, share: total > 0 ? count / total : 0, })) .sort((a, b) => b.count - a.count); return { lookbackLabel: 'all history', trades: row.trades, winRate: row.win_rate ?? 0, avgHoldDays: row.avg_hold_days ?? null, bestR: row.best_trade_r ?? null, worstR: row.worst_trade_r ?? null, sharpe: row.sharpe ?? null, exits, }; }