examples: fix and harmonize the strategy backtests across all languages (#324)
The strategy_* examples were only syntax-smoked in CI, never run, which hid two classes of problem: 1. Python strategy_macd_adx / strategy_bollinger_squeeze passed three separate arguments to the candle indicators ADX/ATR, whose .update() takes a single candle — a TypeError at runtime — and read the ADX tuple at index 0 (plus_di) instead of 2 (adx). Both fixed. 2. The Go / C# / R / Java strategies defaulted to synthetic data and used a different (annualised) one-line summary, so they printed wildly different numbers from the Rust/Python/Node/C/WASM suite. Rewrite them to the shared per-trade backtest (load the bundled BTCUSDT CSV by default, same entry/exit logic, same print_summary output). All nine runnable bindings now print byte-identical backtest summaries on the same data (MACD+ADX 246 trades / -47.19%, RSI 37 / -17.84%, Bollinger 1 / -7.82%), verified by diffing each language's output against the Python reference. WASM shares the same logic and bundled dataset (browser-rendered).
This commit is contained in:
@@ -42,4 +42,75 @@ public static class Backtest
|
||||
Console.WriteLine(
|
||||
$"{name,-26} return={r.TotalReturnPct,8:F2}% sharpe={r.Sharpe,6:F2} maxDD={r.MaxDrawdownPct,6:F2}% trades={r.Trades}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the per-trade backtest summary shared verbatim with the Rust,
|
||||
/// Python, Node, Go and C example suites (same labels, same numbers).
|
||||
/// </summary>
|
||||
public static void PrintSummary(string name, double firstPrice, double lastPrice, int bars,
|
||||
IReadOnlyList<double> closedTrades, double finalEquity, IReadOnlyList<double> equityCurve)
|
||||
{
|
||||
var ci = System.Globalization.CultureInfo.InvariantCulture;
|
||||
var buyHold = lastPrice / firstPrice;
|
||||
var stratReturn = finalEquity - 1.0;
|
||||
var bhReturn = buyHold - 1.0;
|
||||
int wins = 0, losses = 0;
|
||||
double best = 0.0, worst = 0.0;
|
||||
for (var i = 0; i < closedTrades.Count; i++)
|
||||
{
|
||||
var r = closedTrades[i];
|
||||
if (r > 0)
|
||||
{
|
||||
wins++;
|
||||
}
|
||||
else if (r < 0)
|
||||
{
|
||||
losses++;
|
||||
}
|
||||
|
||||
if (i == 0 || r > best)
|
||||
{
|
||||
best = r;
|
||||
}
|
||||
|
||||
if (i == 0 || r < worst)
|
||||
{
|
||||
worst = r;
|
||||
}
|
||||
}
|
||||
|
||||
var n = closedTrades.Count;
|
||||
var mean = n > 0 ? closedTrades.Average() : 0.0;
|
||||
var variance = n > 1 ? closedTrades.Sum(x => (x - mean) * (x - mean)) / (n - 1) : 0.0;
|
||||
var sharpe = variance > 0 ? mean / Math.Sqrt(variance) : 0.0;
|
||||
var peak = equityCurve.Count > 0 ? equityCurve[0] : 1.0;
|
||||
var maxDd = 0.0;
|
||||
foreach (var eq in equityCurve)
|
||||
{
|
||||
if (eq > peak)
|
||||
{
|
||||
peak = eq;
|
||||
}
|
||||
|
||||
var dd = (peak - eq) / peak;
|
||||
if (dd > maxDd)
|
||||
{
|
||||
maxDd = dd;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"=== {name} ===");
|
||||
Console.WriteLine(string.Create(ci, $"{"Bars:",-23}{bars}"));
|
||||
Console.WriteLine(string.Create(ci, $"{"Trades:",-23}{n} (W{wins} / L{losses})"));
|
||||
Console.WriteLine(string.Create(ci, $"{"Strategy return:",-23}{stratReturn * 100:+0.00;-0.00}%"));
|
||||
Console.WriteLine(string.Create(ci, $"{"Buy & Hold return:",-23}{bhReturn * 100:+0.00;-0.00}%"));
|
||||
Console.WriteLine(string.Create(ci, $"{"Excess over BH:",-23}{(stratReturn - bhReturn) * 100:+0.00;-0.00}%"));
|
||||
Console.WriteLine(string.Create(ci, $"{"Max drawdown:",-23}{maxDd * 100:0.00}%"));
|
||||
Console.WriteLine(string.Create(ci, $"{"Per-trade Sharpe:",-23}{sharpe:0.00} (mean {mean:+0.0000;-0.0000}, stddev {Math.Sqrt(variance):0.0000})"));
|
||||
Console.WriteLine(string.Create(ci, $"{"Best / worst trade:",-23}{best * 100:+0.00;-0.00}% / {worst * 100:+0.00;-0.00}%"));
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("NOTE: Educational example — fees, slippage, funding costs and tax " +
|
||||
"effects are simplified or omitted. Past performance is not " +
|
||||
"indicative of future results.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user