Skip to content

4. Trading through the engine

The engine provides layers; a strategy is how you decide to use them. It is a plain file the engine loads, and the only place a trading decision is expressed.

So the set of strategies the engine can run is open. Adding one is a configuration change. The engine keeps no compiled-in list and no branch on a strategy name, and it refuses to start an asset with no strategy bound to it: it will not trade something it has no behaviour for.

4.1 What a strategy is

strategies/<name>.strategy is written in a dotted, indentation-based script language. The file opens with a name and version, declares the state it wants to remember, then lists the events it reacts to and the ordered steps each event drives:

strategy my_strategy
version 1

remembers:
  cycle.ok: bool
  my.active: bool

when book.updates:
  prepare:
    init.venue.state
    resolve.book
    cycle.ok = not $platform.price.nan

  gate:
    if cycle.ok:
      cycle.facts
      my.active = not $platform.oracle.halted

  act:
    if cycle.ok and my.active:
      buy.place.send

Triggers are events, with no polling loop. Each indented block under a label is a step, evaluated in order, and conditions act as guards that skip a step without ending the cycle. The script reads engine state through $platform facts such as price.nan, oracle.halted, and buy.capital.low, which keeps the interface a strategy sees a declared, validated surface.

Binding. Configuration names a strategy file per asset; the loader tries strategies/<name>.strategy and then strategies/<name>.json, the latter being the compiled artifact emitted by dio strategy compile. Binding is by filename convention and nothing else. A configuration naming a strategy file that does not exist is rejected at startup, with no fallback default, and an asset with no strategy entry at all is likewise a startup failure.

What a strategy reads. Facts fall into two groups. Market facts give the current realtime view: the top-of-book mid price and the wall clock. Engine facts give the state of the machine the strategy is running in: whether a decision record has published, whether the oracle is halted, resting and tracked order counts, whether a cancel or amend is in flight, quote and base affordability, fee availability, and balance freshness.

What a strategy calls. Steps invoke named actions from a registry. Each action declares whether it is a decision, an effectful operation, or a read, and carries a schema the file is validated against, so an unknown action name is caught at compile time.

The action registry is the extension point. The vocabulary a strategy draws on is not fixed by the engine's authors. The registry accepts new actions, and the shipped vocabulary is what the ladder in section 5 happens to use: venue and cycle preparation, book resolution, order placement and amendment, sell planning and finalization, order event application, cooldown and ghost-order handling, fee refresh, and position accumulation. Anything a future strategy needs beyond that is added by registering an action, and every strategy file can then call it. The engine is asset-agnostic and strategy-agnostic; what is expressible today is a function of what has been built, and the build is layered so that extending it does not mean restructuring it.

Tooling. dio strategy validate checks a file statically, dio strategy compile lowers it to the shared AST and can emit the JSON render for diffing, dio strategy lsp serves the same diagnostics over stdio to a companion VS Code extension, and a trace recorder supports behavioural diffing between the script path and the reference decision engine.

The language reference, including every trigger and fact, is in Strategy script language. The Action glossary lists the verbs the runtime actually handles and the older registered names that it does not. Your first strategy is the step-by-step walkthrough. Setting a file up in a running engine is covered in Authoring a strategy.

4.2 What a strategy consumes from the engine

A strategy does not decide how much capital it risks. That is the oracle's job, and the boundary between the two is a contract with exactly four fields: an activity verdict, a spacing, and two order sizes (section 3.1).

The strategy decides what to do with them. A ladder uses the spacing to place a rung and an offsetting sell. A different strategy could take the same quantity and spacing and use them to mean something else, hold the position, or decline to act on the activity verdict. The engine does not care which, because it executes orders and the strategy expresses intent.

Two properties of the contract matter. Values are published for inactive assets as well as active ones, so a strategy can react to being switched off. And there is no configuration fallback path, so a strategy can never find a second source of sizing truth by accident.

4.3 What the engine leaves to the strategy

Everything that defines the approach itself. Whether to quote passively or take liquidity. What order type to use. What a fill means for the position. When to hold, when to offset, when to stop. How to use the accumulation threshold. The engine provides the order path, the accounting, and the sizing brain, and it will faithfully execute what the strategy asks within the capital the oracle has committed.