namespace Wickra.Examples; /// Summary statistics for a long-only equity curve. public sealed record EquityResult(double TotalReturnPct, double Sharpe, double MaxDrawdownPct, int Trades, double FinalEquity); /// /// Minimal long-only backtest helper: turn a stream of per-bar fractional /// returns into a PnL / Sharpe / max-drawdown summary. The strategy examples /// produce the returns; this aggregates them. /// public static class Backtest { /// Per-bar fractional returns (0.01 == +1%). /// Number of position entries. /// Annualisation factor for the Sharpe ratio. public static EquityResult Summarize(IReadOnlyList periodReturns, int trades, double periodsPerYear = 252.0) { double equity = 1.0, peak = 1.0, maxDrawdown = 0.0; foreach (var r in periodReturns) { equity *= 1.0 + r; peak = Math.Max(peak, equity); if (peak > 0) { maxDrawdown = Math.Max(maxDrawdown, (peak - equity) / peak); } } var mean = periodReturns.Count > 0 ? periodReturns.Average() : 0.0; var variance = periodReturns.Count > 1 ? periodReturns.Sum(x => (x - mean) * (x - mean)) / (periodReturns.Count - 1) : 0.0; var stdDev = Math.Sqrt(variance); var sharpe = stdDev > 1e-12 ? mean / stdDev * Math.Sqrt(periodsPerYear) : 0.0; return new EquityResult((equity - 1.0) * 100.0, sharpe, maxDrawdown * 100.0, trades, equity); } /// Prints a one-line summary. public static void Print(string name, EquityResult r) { Console.WriteLine( $"{name,-26} return={r.TotalReturnPct,8:F2}% sharpe={r.Sharpe,6:F2} maxDD={r.MaxDrawdownPct,6:F2}% trades={r.Trades}"); } }