using Marathon.Domain.Entities; using Marathon.Domain.Enums; using Marathon.Domain.ValueObjects; namespace Marathon.Domain.Backtesting; /// /// Aggregate output of one simulation run. Contains both the headline numbers /// the user looks at (final bankroll, ROI, max drawdown) and the per-bet /// trace needed to draw an equity curve. /// /// Echoed from the strategy for the UI. /// Bankroll after the last simulated bet settled. /// FinalBankroll − StartingBankroll. /// /// NetProfit / TotalStaked × 100. Null when no bets were placed /// (no anomaly met the threshold, or the bankroll went to zero before any /// stake could be sized). /// /// Sum of stake sizes across every settled bet. /// Sum of gross returns across every settled bet. /// /// Largest peak-to-trough drop in bankroll observed during the run, as an /// absolute amount. Always ≥ 0. /// /// /// as a percentage of the peak that preceded it. /// Null when there were no draws (no bets or no losses). /// /// Total bets the strategy actually placed. /// Settled bets whose post-flip favourite won. /// Settled bets whose post-flip favourite lost. /// /// Total anomalies inspected but skipped. Equals /// + + /// . Surfaced separately so the UI can /// distinguish a strategy choice ("threshold too high") from a real-world /// signal ("bankroll empty") or a data-quality issue. /// /// /// Skipped because Anomaly.Score < strategy.MinScore — pure strategy choice. /// /// /// Skipped because the evidence parsed but the post-flip favourite has no /// rate / probability, or because a two-way market produced a Draw winner. /// Strategy-orthogonal — these would be skipped under any rule. /// /// /// Skipped because the sized stake was non-positive (Kelly returned no edge, /// or bankroll was depleted) or exceeded the current bankroll. /// /// Longest run of consecutive wins. /// Longest run of consecutive losses. /// /// Per-bet records in chronological order — drives the equity curve. /// /// /// Pre-shaped "Side1Name vs Side2Name" strings keyed by event id, for /// every event in . Carried alongside the result so the UI /// projection does not need a second pass over IEventRepository. /// Missing events (pruned by retention) are absent from the map; consumers /// fall back to EventId.Value. /// public sealed record BacktestResult( decimal StartingBankroll, decimal FinalBankroll, decimal NetProfit, decimal? RoiPercent, decimal TotalStaked, decimal TotalReturned, decimal MaxDrawdown, decimal? MaxDrawdownPercent, int BetsPlaced, int Wins, int Losses, int Skipped, int SkippedByThreshold, int SkippedByDataQuality, int SkippedByBankroll, int MaxWinStreak, int MaxLossStreak, IReadOnlyList Trace, IReadOnlyDictionary EventTitles); /// /// One settled simulated bet. Carries enough metadata to surface a /// drill-down row and a point on the equity curve. /// /// Source anomaly for the link-back affordance. /// Event being bet on. /// When the anomaly was originally detected. /// Confidence score of the anomaly. /// Sport metadata if available — null when the event is missing. /// Side bet on (the post-suspension favourite). /// Rate at which the simulator "bought" the bet (post-flip rate). /// Stake sized for this bet. /// Actual winner of the event. /// true if the post-flip favourite was the winner. /// Gross return — Stake × Rate for a win, 0 for a loss. /// Bankroll after this bet settled — equity-curve y-axis. public sealed record BacktestTrace( Guid AnomalyId, EventId EventId, DateTimeOffset DetectedAt, decimal Score, SportCode? Sport, Side PostFlipFavourite, decimal TakenRate, decimal Stake, Side WinnerSide, bool IsWin, decimal Payout, decimal BankrollAfter);