Anvil
Strategy SDK
The Strategy trait, the order-intent boundary, and how strategies are selected at compile time without touching the platform.
The SDK is the seam between Anvil's deterministic platform and proprietary strategy logic. A strategy observes state and emits order intents; it never holds an execution handle, reads the wall clock, or performs I/O. Everything it needs arrives through a context, and everything it wants to happen leaves as an intent for the risk gateway to dispose.
The Strategy trait
A strategy implements a single trait. On each event it receives an immutable
view of state and a context (deterministic time, configuration, instrument
metadata) and returns the intents it wants to propose. The shapes below are
representative of the SDK model; the canonical signatures live in the sdk
crate's generated API.
/// Implemented by every strategy. Pure with respect to (state, event):
/// the same inputs must always produce the same intents.
pub trait Strategy {
/// Called once at startup with validated configuration.
fn on_start(&mut self, ctx: &StrategyContext) -> Vec<OrderIntent>;
/// Called for each market event the strategy is subscribed to.
fn on_event(
&mut self,
state: &StrategyView,
event: &MarketEvent,
ctx: &StrategyContext,
) -> Vec<OrderIntent>;
}Key properties:
- No execution handle. The trait gives the strategy no way to send an order
except by returning an
OrderIntent. - No ambient time. Time and randomness are read from
ctx(deterministic clock, seeded RNG) so a replay reproduces the strategy's decisions exactly. - Read-only state.
StrategyViewis an immutable projection; the strategy cannot mutate platform state.
Order intents
An OrderIntent is a request, not a command. It describes what the strategy
wants (instrument, side, price, size, time-in-force) using the platform's typed
domain model — monetary values are rust_decimal-backed, never raw f64.
let intent = OrderIntent::limit(
instrument, // typed instrument id
Side::Buy,
price, // Decimal-backed Price
size, // Decimal-backed Quantity
TimeInForce::Gtc,
);Every intent is journaled, then crosses the multi-stage risk gateway (position, exposure, rate, sanity). A rejected intent never reaches an exchange adapter and is recorded with its rejection reason. See Runtime architecture → Risk architecture.
Compile-time selection
Strategies are selected at build time, not loaded dynamically. Each strategy is
a crate gated behind a Cargo feature (the strat_* family), and a compile-time
registry binds exactly one strategy into the runner. This keeps the active
strategy explicit, removes dynamic-dispatch nondeterminism, and lets the
compiler enforce the platform/strategy boundary.
# Build the runner with a single strategy compiled in.
cargo build -p anvil-runner --features strat_market_makingDeterminism checklist for strategy authors
- Read time and randomness only from
ctx. - Derive all decisions from
state,event, and configuration — never from global mutable state or external services. - Return intents; never attempt side effects.
- Keep
on_eventfree of allocation-heavy work on the hot path where latency matters; the platform calls it for every subscribed event.
A strategy that follows these rules replays bit-for-bit, which is what lets the same code run in backtest, paper, and live without divergence.