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.
What a strategy provides
Section titled “What a strategy provides”Every strategy file has three parts:
- A frozen config (
StrategyConfig) starting withinstrument_idandbar_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. - 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). - The
Strategyclass withon_start(register indicators + subscribe to bars),on_bar(the logic), and the standardon_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.
The traps (see the guide for detail)
Section titled “The traps (see the guide for detail)”- Indicators update before
on_barfires — 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.valueis 0–1, not 0–100. - Be deterministic: no wall clocks, no unseeded randomness.
Two strategy shapes
Section titled “Two strategy shapes”- 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-LASTbars 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
Strategysubclasses. See multi-instrument engines.
Two ways to author one
Section titled “Two ways to author one”- 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”).