Backtest runner
@qtrader/backtest-runner
Backtest a trading idea on an OHLCV CSV with honest assumptions: next-bar fills, slippage, commission, no look-ahead. Built-in SMA crossover, RSI mean reversion, and buy-and-hold, plus a one-file plug-in format for your own strategies. Writes equity curve and trades CSVs and metrics (CAGR, volatility, Sharpe, Sortino, max drawdown, win rate, exposure). Not financial advice.
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1// Example plug-in strategy for backtest-runner: Donchian channel breakout.
- 2// Buy when the close breaks above the highest high of the previous `entry` bars.
- 3// Exit when the close falls below the lowest low of the previous `exit` bars.
- 4// Run: node scripts/backtest.mjs --data examples/prices.csv --strategy-file examples/strategies/donchian-breakout.mjs --param entry=30
- 5//
- 6// The contract:
- 7// export default { name, params, onBar(ctx) }
- 8// ctx.i index of the bar that just closed
- 9// ctx.bar that bar { date, open, high, low, close, volume }
- 10// ctx.bars bars 0..i only (later bars read as undefined, so no look-ahead)
- 11// ctx.closes closes 0..i only
- 12// ctx.params defaults below, overridden by --param name=value
- 13// ctx.state an object you can keep values in between bars
- 14// ctx.position { qty, target }
- 15// ctx.ind { sma, rsi, highest, lowest } helpers: ind.sma(values, n) uses values up to i
- 16// Return 1 (long), 0 (flat), -1 (short, needs --allow-short), or null to keep the current target.
- 17// Whatever you return fills at the NEXT bar's open, with slippage and commission.
- 18export default {
- 19 name: "donchian-breakout",
- 20 params: { entry: 20, exit: 10 },
- 21 onBar({ i, bars, bar, params, position }) {
- 22 if (i < params.entry) return 0;
- 23 const highs = [];
- 24 const lows = [];
- 25 for (let k = i - params.entry; k < i; k++) highs.push(bars[k].high);
- 26 for (let k = i - params.exit; k < i; k++) lows.push(bars[k].low);
- 27 if (position.target <= 0 && bar.close > Math.max(...highs)) return 1;
- 28 if (position.target > 0 && bar.close < Math.min(...lows)) return 0;
- 29 return null;
- 30 },
- 31};
