namespace Marathon.Domain.Backtesting;
///
/// Parameters fed to . The strategy is "for every
/// SuspensionFlip anomaly with score ≥ , stake
/// according to on the post-flip favourite at the
/// post-flip rate, then settle against the actual EventResult."
///
///
/// Initial bankroll for compounding stake rules. Must be positive.
///
///
/// Lower bound on Anomaly.Score — only anomalies at or above this
/// threshold are bet on. Must be in [0, 1].
///
/// How to size each bet — see the enum docs.
///
/// Used when is .
/// Must be positive.
///
///
/// Used when is
/// . Expressed as a
/// fraction in (0, 1]. e.g. 0.02 = 2 % of bankroll.
///
///
/// Used when is .
/// Multiplier on the raw Kelly fraction; in (0, 1]. 0.25 (quarter-Kelly) is
/// the conservative default.
///
public sealed record BacktestStrategy(
decimal StartingBankroll,
decimal MinScore,
StakeRule StakeRule,
decimal FlatStake,
decimal PercentOfBankroll,
decimal KellyFraction)
{
public decimal StartingBankroll { get; } = StartingBankroll > 0m
? StartingBankroll
: throw new ArgumentOutOfRangeException(nameof(StartingBankroll),
StartingBankroll, "StartingBankroll must be positive.");
public decimal MinScore { get; } = MinScore is >= 0m and <= 1m
? MinScore
: throw new ArgumentOutOfRangeException(nameof(MinScore),
MinScore, "MinScore must be in [0, 1].");
public decimal FlatStake { get; } = FlatStake > 0m
? FlatStake
: throw new ArgumentOutOfRangeException(nameof(FlatStake),
FlatStake, "FlatStake must be positive.");
public decimal PercentOfBankroll { get; } = PercentOfBankroll is > 0m and <= 1m
? PercentOfBankroll
: throw new ArgumentOutOfRangeException(nameof(PercentOfBankroll),
PercentOfBankroll, "PercentOfBankroll must be in (0, 1].");
public decimal KellyFraction { get; } = KellyFraction is > 0m and <= 1m
? KellyFraction
: throw new ArgumentOutOfRangeException(nameof(KellyFraction),
KellyFraction, "KellyFraction must be in (0, 1].");
/// Sensible defaults — flat-stake, score ≥ 0.45, ¼-Kelly waiting in the wings.
public static BacktestStrategy Default { get; } = new(
StartingBankroll: 1000m,
MinScore: 0.45m,
StakeRule: StakeRule.Flat,
FlatStake: 50m,
PercentOfBankroll: 0.02m,
KellyFraction: 0.25m);
}