Trading Engine is a deterministic, event-driven OCaml execution engine. It runs a typed strategy through pre-trade risk, order management, synchronized completed-bar execution, exact accounting, valuation, and a hash-bound JSON Lines audit journal.
The engine is replay-first. Its pure kernel and explicit source, strategy, execution, and journal layers keep networking, files, and wall-clock state outside the reducer.
scenario slices and scheduled or external intents
│
▼
deterministic reducer
│
┌──────────┼──────────┐
▼ ▼ ▼
strategy risk + OMS slice simulator
│ │ │
└──────────┴──── fills┘
│
▼
accounting + valuation
│
▼
JSON Lines journal
- OCaml 5.5 and Dune 3.24 with a repository-local opam switch
- Opaque IDs and canonical checked fixed-point prices, weights, money, and quantities
- Synchronized market slices with one bar per configured instrument
- Separate market event, availability, receipt, slice, and engine ordering
- Pure strategy callbacks with causal, immutable context snapshots
- Pure suspend/resume strategy requests with equivalent scripted and external reducers
- Portfolio weight and quantity targets covering the complete instrument catalog
- Current-equity weight sizing at synchronized closing marks with lot rounding
- Persistent target reconciliation through bounded market-order attempts
- Direct market and limit orders, cancellations, and metrics
- Signed long/short position, order, lot, tick, gross-exposure, leverage, and margin risk
- Deterministic liquidation-first matching, then sell-before-buy and FIFO priority
- Shared per-instrument volume participation, partial fills, and GTC limits
- One-slice IOC market orders
- Risk-aware fractional-lot clipping with structured
margin_limitedrecords - Fixed and notional fees with explicit rounding
- Explicit multi-currency cash ledgers and complete per-slice FX marks in a base currency
- Split and cash-dividend processing before matching, including target and order adjustment
- Short borrow accrual, maintenance-margin calls, and deterministic liquidation orders
- Signed average-cost accounting, realized and unrealized P&L, and equity reconciliation
- Per-currency cash and per-instrument quantity, mark, value, basis, P&L, and fee attribution
- Deterministic event IDs, ordered causal references, and order-creation attribution
- Contract-selected compiled execution modules; v3 currently exposes
completed_bar_v1 - Strict batch JSON and bounded-memory JSON Lines scenario parsing with JSON Schemas
- Versioned synchronous JSON Lines strategy processes with per-request timeouts and strict lifecycle supervision
- Complete bidirectional strategy transcripts with exclusive partial and no-replace publication
- Scenario SHA-256 binding in
run_startedandrun_completed - Exclusive partial journal creation and atomic no-replace finalization
- Unit, schema-conformance, scenario, golden-contract, and property tests
The project uses a local switch and does not modify the default switch. The complete check also
uses Python's jsonschema package to validate the committed scenario and journal fixtures.
cd ~/trading-engine
opam switch set .
opam install . --deps-only --with-test --locked
make checkValidate the included scenario with an in-memory replay:
opam exec -- dune exec trading-engine -- \
--input contracts/v3/fixtures/demo.scenario.json \
--validate-onlyRun it and create a journal:
opam exec -- dune exec trading-engine -- \
--input contracts/v3/fixtures/demo.scenario.json \
--journal demo.journal.jsonlFor larger histories, validate and replay the equivalent stream one slice at a time:
opam exec -- dune exec trading-engine -- \
--input contracts/v3/fixtures/demo.scenario.jsonl \
--input-format jsonl \
--journal demo.journal.jsonlRun an external strategy against an empty-schedule scenario:
opam exec -- dune exec trading-engine -- \
--input contracts/strategy/v3/fixtures/external.scenario.json \
--journal external.journal.jsonl \
--strategy-executable ./my-strategy \
--strategy-arg=config.toml \
--strategy-timeout 30 \
--strategy-transcript external.strategy.jsonlThe engine launches the program directly without a shell. This supervises the child but does not
sandbox it; run strategy code with the same trust you give the invoking user. Protocol messages
own the child's standard input and output; strategy diagnostics belong on standard error. Only
one request is outstanding. Initialization must return ready, each event must return intents,
and shutdown must return stopped. Wrong versions or sequences, unknown or malformed fields,
oversized responses, EOF, timeout, extra output, and nonzero exit all fail the replay. The journal
and transcript retain partial artifacts for diagnosis.
Discover the executable version and machine-readable compatibility surface:
opam exec -- dune exec trading-engine -- --version
opam exec -- dune exec trading-engine -- --capabilitiesClients must confirm that both scenario_contract_versions and journal_contract_versions
contain the scenario's contract_version before starting a replay. External clients must also
require their version in strategy_protocol_versions.
The final and .partial journal paths must not already exist. Batch JSON hashes the same complete
document it parses. JSON Lines input is hashed and validated in a bounded-memory pass before the
journal is created, then replayed from the same open file and hashed again before publication. The
CLI binds that exact-byte hash into the journal. It writes to the partial path and publishes the
requested path only after run_completed is fully written and the partial file is closed. An
error preserves the partial artifact for diagnosis.
An order emitted after slice n cannot execute on slice n. It first becomes eligible on a later
slice whose start is not earlier than its creation time.
- Market orders attempt the next eligible open and cancel any remainder after that slice.
- Persistent portfolio targets submit a new bounded attempt after each miss until reached or superseded.
- Limit orders use deterministic gap improvement and optimistic intrabar touch rules.
- Eligible liquidation orders consume capacity before other orders. Within each origin class, sells precede buys and FIFO creation order breaks ties within a side.
- Corporate actions are applied before matching. Splits adjust positions, persistent targets, and active orders; cash dividends credit longs and debit shorts in the quote-currency ledger.
- Borrow fees accrue on open shorts for the slice interval before matching.
- Proposed fills are clipped to the largest permitted fractional-lot quantity at the actual fill price and never exceed the maximum order quantity. Increasing exposure must satisfy position, gross-exposure, leverage, and initial-margin limits; exposure-reducing fills remain available.
- A maintenance-margin breach cancels active orders, clears portfolio targets, and creates deterministic market orders that flatten positions in bounded lots across later slices.
- The engine emits exactly one valuation after each complete synchronized slice.
Read Execution model for the full phase, price, fee, cash, and accounting rules.
The current scope omits:
- Broker and streaming-market-data connectors
- External execution-report ingestion
- Exchange calendars and time-zone databases
- Durable reducer snapshots and broker reconciliation
fsyncand restart recovery for journals- Tick, trade, and order-book replay
The journal writer flushes each record, creates its partial file exclusively, and finalizes with an
exclusive hard link. It does not call fsync, so the journal is an audit artifact rather than a
production recovery log.
- Architecture
- Scenario contract
- Current contract v3 and conformance fixtures
- Frozen contract v2
- Historical contract v1
- Scenario JSON Schema
- Scenario stream record JSON Schema
- Journal record JSON Schema
- External strategy protocol v3
- Historical strategy protocol v2
- Historical strategy protocol v1
- Strategy message JSON Schema
- Strategy transcript JSON Schema
- Execution model
- Persistra integration
- Contributing