Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

- Add exact stock-dividend, rights, and spin-off distributions with explicit basis allocation,
fractional rejection or cash-in-lieu policy, destination currency validation, target adjustment,
and complete journal attribution.
- Add stable-identity instrument lifecycle state for halt, resume, identifier/provider remapping,
expiration, and delisting with deterministic order cancellation and explicit terminal hold or
cash-out policy.
- Publish scenario/journal contract v12 and external strategy protocol v10 while preserving v11
and protocol v9 as frozen compatibility contracts.
- Add deterministic trade-date and settlement-date accounting, versioned business-date settlement
calendars, settled and unsettled cash and position attribution, explicit settlement buying-power
policies, and auditable settlement completion and failure events.
Expand Down
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ scenario slices and scheduled or external intents
maker/taker applicability, minimums, caps, rebates, and deterministic FX conversion
- Explicit multi-currency cash ledgers and complete per-slice FX marks in a base currency
- Explicit signed initial portfolios with cost basis, P&L and fee history, marks, and FX state
- Split and cash-dividend processing before matching, including target and order adjustment
- Splits, dividends, rights, spin-offs, fractional cash-in-lieu, and exact basis allocation
- Stable instrument identity with halt/resume, identifier changes, expiration, and delisting
- Effective-time short locates, availability clipping, borrow-rate accrual, recalls, and
deterministic close-out orders
- Per-currency credit/debit cash rates with explicit day-count and compounding policies
Expand All @@ -60,7 +61,7 @@ scenario slices and scheduled or external intents
fee-component attribution
- Deterministic event IDs, ordered causal references, and order-creation attribution
- Contract-selected compiled execution modules with versioned model-owned configuration and
capability descriptors; v11 currently exposes `completed_bar_v1` configuration v2
capability descriptors; v12 currently exposes `completed_bar_v1` configuration v2
- 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
Expand Down Expand Up @@ -90,23 +91,23 @@ Validate the included scenario with an in-memory replay:

```sh
opam exec -- dune exec trading-engine -- \
--input contracts/v11/fixtures/demo.scenario.json \
--input contracts/v12/fixtures/demo.scenario.json \
--validate-only
```

Run it and create a journal:

```sh
opam exec -- dune exec trading-engine -- \
--input contracts/v11/fixtures/demo.scenario.json \
--input contracts/v12/fixtures/demo.scenario.json \
--journal demo.journal.jsonl
```

For larger histories, validate and replay the equivalent stream one slice at a time:

```sh
opam exec -- dune exec trading-engine -- \
--input contracts/v11/fixtures/demo.scenario.jsonl \
--input contracts/v12/fixtures/demo.scenario.jsonl \
--input-format jsonl \
--journal demo.journal.jsonl
```
Expand All @@ -115,7 +116,7 @@ Run an external strategy against an empty-schedule scenario:

```sh
opam exec -- dune exec trading-engine -- \
--input contracts/strategy/v9/fixtures/external.scenario.json \
--input contracts/strategy/v10/fixtures/external.scenario.json \
--journal external.journal.jsonl \
--strategy-executable ./my-strategy \
--strategy-arg=config.toml \
Expand Down Expand Up @@ -184,7 +185,11 @@ slice whose start is not earlier than its creation time.
- 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.
active orders; distributions allocate basis and fractional cash exactly; cash dividends credit
longs and debit shorts in the quote-currency ledger.
- Lifecycle events update symbols and provider mappings without changing instrument identity.
Halts and terminal events cancel orders; expiration and delisting follow an explicit hold or
cash-out policy.
- Effective-time borrow observations control short availability and rates. New shorts are rejected
or clipped to their locate, recalls reject new shorts or create deterministic close-out orders,
and observed borrow charges accrue before matching.
Expand Down Expand Up @@ -226,19 +231,19 @@ do not provide reducer snapshots or restart recovery.
- [Diagnostic contract](docs/diagnostics.md)
- [Scenario contract](docs/scenario.md)
- [Contract conformance corpus](contracts/conformance/README.md)
- [Current contract v11 and conformance fixtures](contracts/v11/README.md)
- [Current contract v12 and conformance fixtures](contracts/v12/README.md)
- [Frozen contract v2](contracts/v2/README.md)
- [Historical contract v1](contracts/v1/README.md)
- [Scenario JSON Schema](contracts/v11/scenario.schema.json)
- [Scenario stream record JSON Schema](contracts/v11/scenario-stream.schema.json)
- [Journal record JSON Schema](contracts/v11/journal.schema.json)
- [External strategy protocol v9](contracts/strategy/v9/README.md)
- [Scenario JSON Schema](contracts/v12/scenario.schema.json)
- [Scenario stream record JSON Schema](contracts/v12/scenario-stream.schema.json)
- [Journal record JSON Schema](contracts/v12/journal.schema.json)
- [External strategy protocol v10](contracts/strategy/v10/README.md)
- [Historical strategy protocol v3](contracts/strategy/v3/README.md)
- [Historical strategy protocol v2](contracts/strategy/v2/README.md)
- [Historical strategy protocol v1](contracts/strategy/v1/README.md)
- [Persistra compatibility](docs/persistra.md)
- [Strategy message JSON Schema](contracts/strategy/v9/message.schema.json)
- [Strategy transcript JSON Schema](contracts/strategy/v9/transcript.schema.json)
- [Strategy message JSON Schema](contracts/strategy/v10/message.schema.json)
- [Strategy transcript JSON Schema](contracts/strategy/v10/transcript.schema.json)
- [Execution model](docs/execution-model.md)
- [OCaml coverage](docs/coverage.md)
- [Continuous integration and portability matrix](docs/continuous-integration.md)
Expand Down
84 changes: 84 additions & 0 deletions contracts/conformance/cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,90 @@
"mutations": [],
"schema_expectation": "accept",
"source": "strategy/v9/fixtures/external.strategy.jsonl"
},
{
"name": "scenario-v12-valid",
"artifact": "scenario-v12",
"kind": "scenario",
"source": "v12/fixtures/demo.scenario.json",
"mutations": [],
"schema_expectation": "accept",
"parser_expectation": "accept"
},
{
"name": "scenario-stream-v12-valid",
"artifact": "scenario-stream-v12",
"kind": "scenario_stream",
"source": "v12/fixtures/demo.scenario.jsonl",
"mutations": [],
"schema_expectation": "accept",
"parser_expectation": "accept"
},
{
"name": "strategy-ready-valid-v10",
"artifact": "strategy-message-v10",
"instance": {
"strategy_protocol_version": "10",
"strategy_sequence": "1",
"message_type": "ready",
"payload": { "strategy_name": "conformance", "strategy_version": null }
},
"mutations": [],
"schema_expectation": "accept",
"parser_expectation": "accept",
"parser_expected": "ready"
},
{
"name": "strategy-intents-valid-v10",
"artifact": "strategy-message-v10",
"instance": {
"strategy_protocol_version": "10",
"strategy_sequence": "2",
"message_type": "intents",
"payload": { "intents": [] }
},
"mutations": [],
"schema_expectation": "accept",
"parser_expectation": "accept",
"parser_expected": "intents"
},
{
"name": "strategy-error-valid-v10",
"artifact": "strategy-message-v10",
"instance": {
"strategy_protocol_version": "10",
"strategy_sequence": "7",
"message_type": "error",
"payload": { "message": "fixture failure" }
},
"mutations": [],
"schema_expectation": "accept"
},
{
"name": "strategy-v10-rejected-response-branch",
"artifact": "strategy-transcript-v10",
"instance": {
"strategy_diagnostic_version": "1",
"transcript_sequence": "2",
"record_type": "rejected_strategy_response",
"expected_strategy_sequence": "1",
"diagnostic": {
"diagnostic_version": "1",
"code": "strategy.protocol",
"phase": "strategy",
"message": "strategy initialization: invalid strategy response JSON",
"context": { "json_path": "$", "sequence": "1" },
"cause": null
},
"evidence": {
"encoding": "hex",
"prefix": "7b",
"observed_bytes": 1,
"truncated": false
}
},
"mutations": [],
"schema_expectation": "accept"
}
]
}
49 changes: 49 additions & 0 deletions contracts/conformance/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,55 @@
"sources": [
{ "path": "strategy/v9/fixtures/external.strategy.jsonl", "format": "jsonl" }
]
},
{
"name": "scenario-v12",
"schema": "v12/scenario.schema.json",
"version_field": "contract_version",
"version": "12",
"sources": [
{ "path": "v12/fixtures/demo.scenario.json", "format": "json" },
{ "path": "v12/fixtures/fill-clipped.scenario.json", "format": "json" },
{ "path": "strategy/v10/fixtures/external.scenario.json", "format": "json" }
]
},
{
"name": "scenario-stream-v12",
"schema": "v12/scenario-stream.schema.json",
"version_field": "contract_version",
"version": "12",
"sources": [
{ "path": "v12/fixtures/demo.scenario.jsonl", "format": "jsonl" },
{ "path": "strategy/v10/fixtures/external.scenario.jsonl", "format": "jsonl" }
]
},
{
"name": "journal-v12",
"schema": "v12/journal.schema.json",
"version_field": "contract_version",
"version": "12",
"sources": [
{ "path": "v12/fixtures/demo.journal.jsonl", "format": "jsonl" },
{ "path": "v12/fixtures/fill-clipped.journal.jsonl", "format": "jsonl" }
]
},
{
"name": "strategy-message-v10",
"schema": "strategy/v10/message.schema.json",
"version_field": "strategy_protocol_version",
"version": "10",
"sources": [
{ "path": "strategy/v10/fixtures/external.strategy.jsonl", "format": "jsonl", "extract": ["message"] }
]
},
{
"name": "strategy-transcript-v10",
"schema": "strategy/v10/transcript.schema.json",
"version_field": "strategy_protocol_version",
"version": "10",
"sources": [
{ "path": "strategy/v10/fixtures/external.strategy.jsonl", "format": "jsonl" }
]
}
]
}
59 changes: 59 additions & 0 deletions contracts/strategy/v10/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# External strategy protocol v10

Version 10 is a synchronous JSON Lines protocol over child-process standard input and output.
Trading Engine sends `initialize`, ordered `event` requests, and `shutdown`. The strategy answers
with `ready`, `intents`, and `stopped`. It may answer any request with `error`.
Protocol v9 remains available for scenario contract v11; earlier versions retain their frozen
shapes.

Every message repeats `strategy_protocol_version: "10"` and a positive canonical
`strategy_sequence`. A response must repeat the sequence of its request. Only one request is
outstanding. Trading Engine rejects unknown or duplicate fields, invalid canonical values,
oversized lines, a wrong version or sequence, unexpected response types, EOF, timeout, and a
nonzero process exit.

The event context contains the replay clock, a marked base-currency portfolio, deterministic group
exposure snapshots, all working orders, and the latest available bar for each instrument. Every
callback emitted for a market slice uses
that slice's `received_at` as `now` and uses its complete bars and FX vector. The portfolio reports
cash, equity, net, long, short, and gross market value plus every attributed cash ledger and
configured position. Position quantities and weights reflect applied fills. Weights are truncated
toward zero to six decimal places. `weights_available` is false and all weights are null when
equity is zero or negative.

The `initialize` request identifies scenario contract v12 and includes the exact `initial_portfolio`
snapshot alongside the legacy cash projection. It also carries the complete versioned venue
calendars, nested execution configuration, financing policy, and settlement policy, so a strategy
can construct DAY orders and reject incompatible execution, financing, or settlement state before
replay.

Matching pauses after each strategy callback. The engine applies the response against the exact
account and OMS state exposed by that callback before delivering another callback or considering
the next eligible order. Later same-slice contexts include the effects of earlier responses. The
eligible-order sequence is fixed at the start of matching, so newly submitted orders wait for a
later slice. Cancelling an order before its turn leaves its unused slice capacity available to the
next eligible order.

Event payloads cover completed market slices with effective-time borrow and cash-rate observations
plus explicit settlement failures, fills, order updates, and rejected intents. Portfolio contexts
include cash-interest attribution and settled and unsettled cash and position quantities. Response
intents use the scenario v12 intent shapes. Market-slice events include lifecycle transitions and
the expanded corporate-action catalog.

External replay requires an empty batch schedule and empty streamed intent batches. The engine
records accepted messages in both directions in a deterministic transcript. A response rejected
for invalid JSON, fields, version, sequence, EOF, or size is never stored as an accepted exchange.
Instead, the partial transcript ends with a `rejected_strategy_response` diagnostic record. Version
1 rejection diagnostics use the shared
[`diagnostic/v1`](../../diagnostic/v1/README.md) contract. The transcript schema narrows that
contract to the `strategy.protocol` and `resource.limit` codes in the `strategy` phase. The record
includes the structured rejection diagnostic and at most the first 256 raw response bytes encoded
as lowercase hexadecimal. `observed_bytes` counts bytes available when the engine rejected the
response, and `truncated` reports whether the prefix omits observed bytes. The transcript and audit
journal retain partial files after failure and finalize only after their respective success checks.

- `message.schema.json` validates individual requests and responses.
- `transcript.schema.json` validates accepted exchanges and rejected-response diagnostics.
- `fixtures/external.scenario.json` is the batch replay fixture.
- `fixtures/external.scenario.jsonl` is its bounded-memory stream form.
- `fixtures/external.strategy.jsonl` is the canonical protocol transcript.
15 changes: 15 additions & 0 deletions contracts/strategy/v10/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(install
(section share)
(package trading_engine)
(files
(message.schema.json as contracts/strategy/v10/message.schema.json)
(transcript.schema.json as contracts/strategy/v10/transcript.schema.json)
(fixtures/external.scenario.json
as
contracts/strategy/v10/fixtures/external.scenario.json)
(fixtures/external.scenario.jsonl
as
contracts/strategy/v10/fixtures/external.scenario.jsonl)
(fixtures/external.strategy.jsonl
as
contracts/strategy/v10/fixtures/external.strategy.jsonl)))
Loading
Loading