Skip to content

The strategy contract

Strategies are code in this repo — one file per hypothesis under backend/ats/strategies/, registered at import time (ADR-001). The same code runs in a backtest and on a live account. This page is the conceptual overview; the canonical, copy-from-it contract with the exact field names and the traps that cost real debugging time is docs/STRATEGY_GUIDE.md.

Every strategy file has three parts:

  1. A frozen config (StrategyConfig) starting with instrument_id and bar_type, then your tunable parameters with sensible defaults, then a set of load-bearing shared fields (sizing mode, risk caps, and the rule-test machinery) whose names the sizing helper, the rule test, and the UI form generator all read.
  2. The @register(...) decorator with a unique key, a display name, a one-line description, the config class, and a search space (2–3 tunable dimensions for the optimizer — keep it small; every extra dimension is more room to overfit).
  3. The Strategy class with on_start (register indicators + subscribe to bars), on_bar (the logic), and the standard on_stop.

Two disciplines are mandatory and enforced by convention:

  • The entry decision goes through the rule-test switch (should_enter(self, <signal>)), so the rule test can replace your entries with random ones. Exits stay outside it (the rule test holds exits fixed).
  • Sizing goes through the shared helper (entry_quantity(self, price)), which applies the fixed/vol-target sizing and the risk caps.
  • Indicators update before on_bar fires — to compare against a channel/extreme that includes the current bar, store the previous bar’s value (no look-ahead either way for oscillators).
  • Never name an attribute _stop (it shadows the Nautilus FSM).
  • Nautilus scales: RelativeStrengthIndex.value is 0–1, not 0–100.
  • Be deterministic: no wall clocks, no unseeded randomness.
  • Single-instrument (the default) — sees one symbol’s bars and decides long/flat. All the library strategies are this shape.
  • Intraday — single-instrument, but subclass a small session harness that rebuilds per-RTH- session state (VWAP, opening range, minutes-since-open) and goes flat by the close. Designed for 1-MINUTE-LAST bars and judged net of the measured spread.

Cross-sectional and pairs strategies are a different shape — they rank/relate a whole universe and are vectorized engines, not registry Strategy subclasses. See multi-instrument engines.

  • By hand — copy one of the skeletons named in the guide and follow the contract.
  • From a description — the Studio turns plain English into a strategy that follows this same contract, validated and sandboxed.

Either way, a registered strategy automatically appears in the Backtests strategy picker and can be run through the gauntlet. To test one, tests/test_strategies.py parametrizes over every registry key (schema hygiene, “it actually trades on the fixture”, and “rule mode is byte-identical to default”).