13, Backtester

Backtester

An offline, dependency-free strategy backtester that sanity-checks the entry/exit/sizing logic in strategies/ against historical bars, it tests consistency, not performance.

The `backtest/` directory holds an offline strategy backtester used to sanity-check the rule logic in `strategies/`, the entry, exit, and sizing rules of mean-reversion and left-side accumulation, against historical bars. It runs on plain node with no `npm install`, no Python, and no Docker.

Its real job is confidence, not returns: it validates that the caps and stops (per-trade cap, the −8% per-position stop, concentration limits) behave the way they are written before those rules ever touch a live account.

The execution model

The engine is event-driven, long-only, and single-symbol, kept deliberately conservative to avoid look-ahead:

  • Daily bars only, no intraday. One decision per bar, evaluated on the close.
  • Next-bar-open fills: a signal generated on bar *i*'s close is filled at bar *i+1*'s open. This removes look-ahead, the desk never trades on information from the same bar it acts on.
  • Slippage: buys fill at open × (1 + slippageBps/1e4), sells at open × (1 − slippageBps/1e4).
  • Commission: a flat commissionUsd per fill (entry tranche, add, and exit).
  • Risk-based sizing: shares = floor((riskPct% of NAV) / per-share-risk), then clamped by the per-trade cap, the concentration cap, and available cash.
  • Hard stop: each position carries a stopPrice. If a bar closes at/below it, the position is flattened at the next open. For mean-reversion the stop is entry × (1 − stopLossPct%), the −8% per-position guardrail.
  • Left-side accumulation substitutes its own wider, risk-budgeted whole-position kill-stop, as that strategy prescribes (its 2% total-risk cap is what bounds the trade).
  • Single concurrent position. Mean-reversion is one entry per setup; left-side may add one planned tranche to the same position, the defined exception documented in Strategies & Risk.

Default parameters

The engine's DEFAULT_PARAMS mirror the caps in `strategies/README.md`:

ParameterDefaultMeaning
startingEquityUsd10000Starting NAV for the run.
riskPerTradePct1Risk budget per trade (mean-reversion default; raised to 2 for left-side).
perTradeCapPct15Per-trade notional cap, the strategies/README.md per-trade cap.
concentrationCapPct25Max position weight, the concentration cap.
stopLossPct8The −8% per-position hard stop (mean-reversion).
commissionUsd0Flat commission charged per fill.
slippageBps5Slippage applied to each fill, in basis points.

Layout

Everything is pure ES modules with no dependencies:

text
backtest/
├── engine.js                       # event-driven, long-only, single-symbol engine
├── indicators.js                   # indicator helpers (moving averages, RSI, etc.)
├── metrics.js                      # performance/consistency metrics
├── strategies/
│   ├── mean-reversion.js           # rule logic mirroring strategies/mean-reversion.md
│   └── left-side-accumulation.js   # planned scale-in (the defined averaging exception)
├── fixtures/
│   ├── uptrend.json                # synthetic bars for a rising regime
│   └── drawdown.json               # synthetic bars for a selloff regime
└── reports/                        # generated per-strategy report JSON

Add new strategies as self-contained, testable files in backtest/strategies/ alongside their written rule in `strategies/`.

Pure Node ESMNo npm installLong onlyDaily barsNext-bar-open fillsIllustrative