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:
kingchenc
2026-06-17 17:56:22 +02:00
committed by GitHub
parent 2e07c07a40
commit 75eefbbd08
20 changed files with 886 additions and 196 deletions
+39 -12
View File
@@ -1,20 +1,47 @@
# Mean reversion: go long when RSI(14) drops below 30, exit when it recovers above 50.
library(wickra)
# Strategy example: RSI(14) mean-reversion.
#
# Go long when RSI(14) drops below 30 (oversold), exit when it recovers above 70
# (overbought). 0.1% fees per trade. The R counterpart of
# examples/python/strategy_rsi_mean_reversion.py, printing the same summary. Uses
# the checked-in examples/data/btcusdt-1h.csv dataset (pass a CSV path to override).
suppressPackageStartupMessages(library(wickra))
source("_common.R")
FEE <- 0.001
OVERSOLD <- 30
OVERBOUGHT <- 70
args <- commandArgs(trailingOnly = TRUE)
bars <- if (length(args) >= 1) load_ohlcv_csv(args[1]) else synthetic_candles(2000)
bars <- if (length(args) >= 1) load_ohlcv_csv(args[1]) else bundled_candles("btcusdt-1h.csv")
closes <- bars$close
n_bars <- length(closes)
rsi <- Rsi(14)
returns <- numeric(0); trades <- 0L; in_pos <- FALSE; entry <- 0
for (i in seq_len(nrow(bars))) {
cl <- bars$close[i]
value <- update(rsi, cl)
in_pos <- FALSE; entry_price <- 0; closed <- numeric(0); equity <- 1
equity_curve <- numeric(n_bars)
for (i in seq_len(n_bars)) {
value <- update(rsi, closes[i])
price <- closes[i]
equity_curve[i] <- if (in_pos) equity * (price / entry_price) else equity
if (!is.finite(value)) next
if (!in_pos && value < 30) {
in_pos <- TRUE; entry <- cl; trades <- trades + 1L
} else if (in_pos && value > 50) {
returns <- c(returns, (cl - entry) / entry); in_pos <- FALSE
if (!in_pos && value < OVERSOLD) {
entry_price <- price; equity <- equity * (1 - FEE); in_pos <- TRUE
} else if (in_pos && value > OVERBOUGHT) {
trade_ret <- price / entry_price - 1
closed <- c(closed, trade_ret)
equity <- equity * (1 + trade_ret) * (1 - FEE)
in_pos <- FALSE
}
}
print_equity("RSI mean-reversion", summarize_equity(returns, trades))
if (in_pos) {
trade_ret <- closes[n_bars] / entry_price - 1
closed <- c(closed, trade_ret)
equity <- equity * (1 + trade_ret) * (1 - FEE)
}
print_summary("RSI Mean-Reversion (1h, BTCUSDT)",
closes[1], closes[n_bars], n_bars, closed, equity, equity_curve)