Skip to content
SlashClone
Made by@qtrader

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.

SkillTradingv1.0.0 (latest)Tested
Activity
New: be the first to try it
  1. 1---
  2. 2name: backtest-runner
  3. 3description: Backtest a trading strategy on daily or intraday OHLCV data from a CSV, with next-bar fills, slippage, commission, and no look-ahead. Built-in SMA crossover, RSI mean reversion, and buy-and-hold baselines, plus a simple plug-in file for custom strategies. Outputs an equity curve CSV, a trades CSV, and metrics (total return, CAGR, volatility, Sharpe, Sortino, max drawdown, win rate, exposure). Use when the user says "backtest this", "would this strategy have worked", "test an SMA crossover on SPY", "try RSI mean reversion", "compare to buy and hold", or hands over a price CSV and a trading rule. Not financial advice.
  4. 4license: MIT
  5. 5---
  6. 6
  7. 7# Backtest runner
  8. 8
  9. 9An event-driven backtester in one Node script. For each bar: fill yesterday's order at today's **open** (plus slippage and commission), mark equity at today's **close**, then ask the strategy for a new target using only bars up to today. That ordering is what keeps it honest.
  10. 11Backtests describe the past under simplified assumptions. Say so every time you report results, and never present a backtest as a prediction or as financial advice.
  11. 13## Inputs
  12. 151. **Prices:** a CSV with `date` (or `timestamp`), `open,high,low,close`, optional `volume`, one row per bar, one symbol. The `alpaca-connector` kit writes this format (`data/SPY-1Day.csv`). Use split and dividend adjusted prices, or splits will look like crashes.
  13. 162. **Strategy:** a built-in name or a plug-in file (below).
  14. 173. **Costs:** ask if the user has not said. Defaults: slippage 5 bps per side, commission 0, integer shares, long only, starting capital 100,000.
  15. 19## Steps
  16. 211. **Check the data.** Row count, date range, gaps, and that prices look adjusted. The script rejects bad rows (missing columns, high below close, duplicate dates) with the line number.
  17. 222. **Run the baseline first** so every result has a comparison:
  18. 24 ```bash
  19. 25 node scripts/backtest.mjs --data data/SPY-1Day.csv --strategy buy-hold --out out/buy-hold
  20. 26 ```
  21. 283. **Run the strategy.**
  22. 30 ```bash
  23. 31 node scripts/backtest.mjs --data data/SPY-1Day.csv --strategy sma-cross --param fast=20 --param slow=50 --slippage-bps 5 --commission-per-share 0.005 --out out/sma-20-50
  24. 32 node scripts/backtest.mjs --data data/SPY-1Day.csv --strategy rsi-revert --param period=14 --param entry=30 --param exit=50 --out out/rsi
  25. 33 node scripts/backtest.mjs --data data/SPY-1Day.csv --strategy-file strategies/my-idea.mjs --out out/my-idea
  26. 34 ```
  27. 36 Other flags: `--capital`, `--commission-bps`, `--fractional`, `--allow-short`, `--periods-per-year` (252 daily, 52 weekly, about 98280 for 1-minute regular hours), `--rf` (annual risk-free rate for Sharpe and Sortino, default 0).
  28. 374. **Read the outputs:** `<out>-metrics.json`, `<out>-trades.csv`, `<out>-equity.csv`.
  29. 385. **Report** with the template below, then point out the caveats that apply (next section).
  30. 396. If the user wants to tune parameters, run a small grid and show the whole table, not just the best row. Warn that picking the best of many runs overfits; suggest checking the winner on a later, untouched date range.
  31. 41## Built-in strategies
  32. 43| Name | Params (defaults) | Rule |
  33. 44| --- | --- | --- |
  34. 45| `buy-hold` | none | Long from the first possible fill to the end. |
  35. 46| `sma-cross` | `fast=20`, `slow=50` | Long when SMA(fast) > SMA(slow), else flat. |
  36. 47| `rsi-revert` | `period=14`, `entry=30`, `exit=50` | Wilder RSI. Buy when RSI < entry; sell when RSI > exit; otherwise hold. |
  37. 49## Writing a plug-in strategy
  38. 51Save to `strategies/<name>.mjs`. See `examples/strategies/donchian-breakout.mjs` for a full example.
  39. 53```js
  40. 54export default {
  41. 55 name: "my-idea",
  42. 56 params: { lookback: 20 }, // defaults; override with --param lookback=50
  43. 57 onBar({ i, bar, bars, closes, params, state, position, ind }) {
  44. 58 const avg = ind.sma(closes, params.lookback); // uses bars 0..i only
  45. 59 if (avg === null) return 0; // not enough history yet
  46. 60 return bar.close > avg ? 1 : 0; // 1 long, 0 flat, -1 short, null keep
  47. 61 },
  48. 62};
  49. 63```
  50. 65`bars` and `closes` end at today; reading `bars[i + 1]` gives `undefined`. Unknown `--param` names are rejected, so typos fail loudly.
  51. 67## Report template
  52. 69```markdown
  53. 70# Backtest: <strategy> <params> on <symbol>, <start> to <end>
  54. 72| Metric | Strategy | Buy and hold |
  55. 73| --- | ---: | ---: |
  56. 74| Total return | 0.37% | 9.36% |
  57. 75| CAGR | 0.29% | 7.32% |
  58. 76| Volatility (ann.) | 11.18% | 17.05% |
  59. 77| Sharpe | 0.08 | 0.50 |
  60. 78| Sortino | 0.11 | 0.70 |
  61. 79| Max drawdown | -16.60% | -22.40% |
  62. 80| Trades / win rate | 5 / 40% | 0 |
  63. 81| Exposure | 42.5% | 99.7% |
  64. 83Assumptions: next-open fills, 5 bps slippage, $0 commission, integer shares, long only.
  65. 84What stands out: <2 or 3 plain sentences>
  66. 85Not financial advice. Past results do not predict future returns.
  67. 86```
  68. 88## Caveats to call out
  69. 90- Fewer than about 30 trades: the win rate and Sharpe are noisy. Say so.
  70. 91- One symbol and one period: no survivorship check, no regime variety.
  71. 92- No dividends unless prices are adjusted. No borrow costs for shorts. No partial fills, gaps through stops, or market impact beyond the slippage setting.
  72. 93- Signals are computed on close and filled next open, so overnight gaps hit the fill price. That is realistic, not a bug.
  73. 95## Metric definitions
  74. 97Per-bar returns `r` from the close-to-close equity curve. Volatility = sample stdev(r) x sqrt(periods per year). Sharpe = (mean(r) - rf per bar) / stdev(r) x sqrt(ppy). Sortino uses downside deviation sqrt(sum(min(0, r - rf per bar)^2) / N) over all N bars. CAGR = (1 + total)^(ppy / N) - 1. Max drawdown is the worst fall from a running equity peak. Win rate counts closed trades with pnl > 0 after costs. Exposure is the share of bars ending with a position.
  75. 99## Guardrails
  76. 101- Never fetch prices yourself in this kit; use the user's CSV or the `alpaca-connector` kit.
  77. 102- Never claim a strategy "works" or recommend trading it. Report numbers and caveats.
  78. 103- Plug-in files are code the user owns. Only run strategy files the user wrote or asked you to write in `strategies/`.