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:
@@ -8,6 +8,8 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
wickra "github.com/wickra-lib/wickra/bindings/go"
|
||||
)
|
||||
@@ -139,3 +141,85 @@ func Print(name string, r EquityResult) {
|
||||
fmt.Printf("%-26s return=%8.2f%% sharpe=%6.2f maxDD=%6.2f%% trades=%d\n",
|
||||
name, r.TotalReturnPct, r.Sharpe, r.MaxDrawdownPct, r.Trades)
|
||||
}
|
||||
|
||||
// BundledCandles loads one of the checked-in datasets under examples/data,
|
||||
// resolved relative to this source file so it works from any working directory.
|
||||
func BundledCandles(filename string) []Bar {
|
||||
_, self, _, _ := runtime.Caller(0)
|
||||
path := filepath.Join(filepath.Dir(self), "..", "..", "..", "data", filename)
|
||||
bars, err := LoadOhlcvCsv(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bars
|
||||
}
|
||||
|
||||
// PrintSummary prints the per-trade backtest summary shared verbatim with the
|
||||
// Rust, Python, Node and C example suites (same labels, same numbers).
|
||||
func PrintSummary(name string, firstPrice, lastPrice float64, bars int, closedTrades []float64, finalEquity float64, equityCurve []float64) {
|
||||
buyHold := lastPrice / firstPrice
|
||||
stratReturn := finalEquity - 1.0
|
||||
bhReturn := buyHold - 1.0
|
||||
wins, losses := 0, 0
|
||||
best, worst := 0.0, 0.0
|
||||
for i, r := range closedTrades {
|
||||
if r > 0 {
|
||||
wins++
|
||||
} else if r < 0 {
|
||||
losses++
|
||||
}
|
||||
if i == 0 || r > best {
|
||||
best = r
|
||||
}
|
||||
if i == 0 || r < worst {
|
||||
worst = r
|
||||
}
|
||||
}
|
||||
n := len(closedTrades)
|
||||
mean := 0.0
|
||||
if n > 0 {
|
||||
var sum float64
|
||||
for _, r := range closedTrades {
|
||||
sum += r
|
||||
}
|
||||
mean = sum / float64(n)
|
||||
}
|
||||
variance := 0.0
|
||||
if n > 1 {
|
||||
var ss float64
|
||||
for _, r := range closedTrades {
|
||||
ss += (r - mean) * (r - mean)
|
||||
}
|
||||
variance = ss / float64(n-1)
|
||||
}
|
||||
sharpe := 0.0
|
||||
if variance > 0 {
|
||||
sharpe = mean / math.Sqrt(variance)
|
||||
}
|
||||
peak, maxDD := 1.0, 0.0
|
||||
if len(equityCurve) > 0 {
|
||||
peak = equityCurve[0]
|
||||
}
|
||||
for _, eq := range equityCurve {
|
||||
if eq > peak {
|
||||
peak = eq
|
||||
}
|
||||
if dd := (peak - eq) / peak; dd > maxDD {
|
||||
maxDD = dd
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("=== %s ===\n", name)
|
||||
fmt.Printf("%-23s%d\n", "Bars:", bars)
|
||||
fmt.Printf("%-23s%d (W%d / L%d)\n", "Trades:", n, wins, losses)
|
||||
fmt.Printf("%-23s%+.2f%%\n", "Strategy return:", stratReturn*100)
|
||||
fmt.Printf("%-23s%+.2f%%\n", "Buy & Hold return:", bhReturn*100)
|
||||
fmt.Printf("%-23s%+.2f%%\n", "Excess over BH:", (stratReturn-bhReturn)*100)
|
||||
fmt.Printf("%-23s%.2f%%\n", "Max drawdown:", maxDD*100)
|
||||
fmt.Printf("%-23s%.2f (mean %+.4f, stddev %.4f)\n", "Per-trade Sharpe:", sharpe, mean, math.Sqrt(variance))
|
||||
fmt.Printf("%-23s%+.2f%% / %+.2f%%\n", "Best / worst trade:", best*100, worst*100)
|
||||
fmt.Println()
|
||||
fmt.Println("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