From 3253b3101364d87afabe26000ebc11d53099e5a0 Mon Sep 17 00:00:00 2001 From: James Mallette Date: Sat, 22 Aug 2026 17:56:08 -0400 Subject: [PATCH 1/2] feat: add typed strategy metrics --- CHANGELOG.md | 3 + README.md | 24 +- contracts/conformance/cases.json | 119 + contracts/conformance/manifest.json | 103 + contracts/strategy/v14/README.md | 64 + contracts/strategy/v14/dune | 15 + .../v14/fixtures/external.scenario.json | 308 +++ .../v14/fixtures/external.scenario.jsonl | 4 + .../v14/fixtures/external.strategy.jsonl | 14 + contracts/strategy/v14/message.schema.json | 302 ++ contracts/strategy/v14/transcript.schema.json | 82 + contracts/v16/README.md | 122 + contracts/v16/dune | 36 + contracts/v16/fixtures/demo.journal.jsonl | 29 + contracts/v16/fixtures/demo.scenario.json | 468 ++++ contracts/v16/fixtures/demo.scenario.jsonl | 6 + .../v16/fixtures/fill-clipped.journal.jsonl | 13 + .../v16/fixtures/fill-clipped.scenario.json | 273 ++ .../v16/fixtures/order-book.journal.jsonl | 13 + .../v16/fixtures/order-book.scenario.json | 378 +++ .../v16/fixtures/order-book.scenario.jsonl | 4 + .../v16/fixtures/quote-trade.journal.jsonl | 13 + .../v16/fixtures/quote-trade.scenario.json | 319 +++ .../v16/fixtures/quote-trade.scenario.jsonl | 4 + contracts/v16/journal.schema.json | 2441 +++++++++++++++++ contracts/v16/scenario-stream.schema.json | 78 + contracts/v16/scenario.schema.json | 888 ++++++ docs/api-reference.md | 2 +- docs/continuous-integration.md | 2 +- docs/execution-model.md | 4 +- docs/persistra.md | 6 +- docs/scenario.md | 22 +- lib/audit.ml | 2 +- lib/audit.mli | 2 +- lib/codec.ml | 92 +- lib/codec.mli | 1 + lib/contract.ml | 10 +- lib/engine.ml | 11 +- lib/engine.mli | 11 + lib/execution_model.ml | 23 +- lib/external_replay.ml | 4 +- lib/market_slice.ml | 2 + lib/market_slice.mli | 17 + lib/metric.ml | 101 + lib/metric.mli | 28 + lib/replay.ml | 4 +- lib/resource_limits.ml | 12 + lib/resource_limits.mli | 6 + lib/scenario.ml | 131 +- lib/scenario_shape.ml | 30 +- lib/scenario_validation.ml | 20 +- lib/strategy.ml | 2 +- lib/strategy.mli | 2 +- lib/strategy_protocol.ml | 44 +- mkdocs.yml | 5 +- scripts/check-deterministic-journals | 20 + scripts/check-documentation.py | 4 +- scripts/release_artifacts.py | 12 +- test/cli.t | 2 +- test/dune | 15 + test/test_diagnostic.ml | 17 +- test/test_domain.ml | 46 + test/test_reducer.ml | 4 +- test/test_reducer_properties.ml | 7 +- test/test_scenario.ml | 45 +- test/test_strategy_protocol.ml | 54 +- 66 files changed, 6775 insertions(+), 170 deletions(-) create mode 100644 contracts/strategy/v14/README.md create mode 100644 contracts/strategy/v14/dune create mode 100644 contracts/strategy/v14/fixtures/external.scenario.json create mode 100644 contracts/strategy/v14/fixtures/external.scenario.jsonl create mode 100644 contracts/strategy/v14/fixtures/external.strategy.jsonl create mode 100644 contracts/strategy/v14/message.schema.json create mode 100644 contracts/strategy/v14/transcript.schema.json create mode 100644 contracts/v16/README.md create mode 100644 contracts/v16/dune create mode 100644 contracts/v16/fixtures/demo.journal.jsonl create mode 100644 contracts/v16/fixtures/demo.scenario.json create mode 100644 contracts/v16/fixtures/demo.scenario.jsonl create mode 100644 contracts/v16/fixtures/fill-clipped.journal.jsonl create mode 100644 contracts/v16/fixtures/fill-clipped.scenario.json create mode 100644 contracts/v16/fixtures/order-book.journal.jsonl create mode 100644 contracts/v16/fixtures/order-book.scenario.json create mode 100644 contracts/v16/fixtures/order-book.scenario.jsonl create mode 100644 contracts/v16/fixtures/quote-trade.journal.jsonl create mode 100644 contracts/v16/fixtures/quote-trade.scenario.json create mode 100644 contracts/v16/fixtures/quote-trade.scenario.jsonl create mode 100644 contracts/v16/journal.schema.json create mode 100644 contracts/v16/scenario-stream.schema.json create mode 100644 contracts/v16/scenario.schema.json create mode 100644 lib/metric.ml create mode 100644 lib/metric.mli diff --git a/CHANGELOG.md b/CHANGELOG.md index d9352a3..125542c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Publish scenario/journal contract v16 and strategy protocol v14 with typed, dimensioned strategy + metrics while retaining string-only metric compatibility through v15 and protocol v13. + - Add bounded level-two order-book replay with fresh snapshots, contiguous absolute updates, multi-level marketable depth, deterministic passive queue position, and locked-book support. - Publish scenario/journal contract v15 and external strategy protocol v13 while preserving v14 diff --git a/README.md b/README.md index 00905de..1a1b207 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ scenario slices and scheduled or external intents - 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 +- Typed strategy metrics with exact numeric, string, and boolean values, bounded dimensions, + units, and aggregation metadata - 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 @@ -98,7 +100,7 @@ Validate the included scenario with an in-memory replay: ```sh opam exec -- dune exec trading-engine -- \ - --input contracts/v15/fixtures/demo.scenario.json \ + --input contracts/v16/fixtures/demo.scenario.json \ --validate-only ``` @@ -106,7 +108,7 @@ Run it and create a journal: ```sh opam exec -- dune exec trading-engine -- \ - --input contracts/v15/fixtures/demo.scenario.json \ + --input contracts/v16/fixtures/demo.scenario.json \ --journal demo.journal.jsonl ``` @@ -114,7 +116,7 @@ For larger histories, validate and replay the equivalent stream one slice at a t ```sh opam exec -- dune exec trading-engine -- \ - --input contracts/v15/fixtures/demo.scenario.jsonl \ + --input contracts/v16/fixtures/demo.scenario.jsonl \ --input-format jsonl \ --journal demo.journal.jsonl ``` @@ -123,7 +125,7 @@ Run an external strategy against an empty-schedule scenario: ```sh opam exec -- dune exec trading-engine -- \ - --input contracts/strategy/v13/fixtures/external.scenario.json \ + --input contracts/strategy/v14/fixtures/external.scenario.json \ --journal external.journal.jsonl \ --strategy-executable ./my-strategy \ --strategy-arg=config.toml \ @@ -238,19 +240,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 v15 and conformance fixtures](contracts/v15/README.md) +- [Current contract v16 and conformance fixtures](contracts/v16/README.md) - [Frozen contract v2](contracts/v2/README.md) - [Historical contract v1](contracts/v1/README.md) -- [Scenario JSON Schema](contracts/v15/scenario.schema.json) -- [Scenario stream record JSON Schema](contracts/v15/scenario-stream.schema.json) -- [Journal record JSON Schema](contracts/v15/journal.schema.json) -- [External strategy protocol v13](contracts/strategy/v13/README.md) +- [Scenario JSON Schema](contracts/v16/scenario.schema.json) +- [Scenario stream record JSON Schema](contracts/v16/scenario-stream.schema.json) +- [Journal record JSON Schema](contracts/v16/journal.schema.json) +- [External strategy protocol v14](contracts/strategy/v14/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/v13/message.schema.json) -- [Strategy transcript JSON Schema](contracts/strategy/v13/transcript.schema.json) +- [Strategy message JSON Schema](contracts/strategy/v14/message.schema.json) +- [Strategy transcript JSON Schema](contracts/strategy/v14/transcript.schema.json) - [Execution model](docs/execution-model.md) - [OCaml coverage](docs/coverage.md) - [Continuous integration and portability matrix](docs/continuous-integration.md) diff --git a/contracts/conformance/cases.json b/contracts/conformance/cases.json index 0430ba0..ded2c74 100644 --- a/contracts/conformance/cases.json +++ b/contracts/conformance/cases.json @@ -983,6 +983,68 @@ "schema_expectation": "accept", "runtime_expectation": "accept", "rule": "structural" + }, + { + "name": "scenario-v16-valid", + "artifact": "scenario-v16", + "kind": "scenario", + "source": "v16/fixtures/demo.scenario.json", + "mutations": [], + "schema_expectation": "accept", + "runtime_expectation": "accept", + "rule": "structural" + }, + { + "name": "scenario-v16-order-book-valid", + "artifact": "scenario-v16", + "kind": "scenario", + "source": "v16/fixtures/order-book.scenario.json", + "mutations": [], + "schema_expectation": "accept", + "runtime_expectation": "accept", + "rule": "structural" + }, + { + "name": "scenario-stream-v16-valid", + "artifact": "scenario-stream-v16", + "kind": "scenario_stream", + "source": "v16/fixtures/order-book.scenario.jsonl", + "mutations": [], + "schema_expectation": "accept", + "runtime_expectation": "accept", + "rule": "structural" + }, + { + "name": "strategy-ready-valid-v14", + "artifact": "strategy-message-v14", + "kind": "strategy_response", + "source": "strategy/v14/fixtures/external.strategy.jsonl", + "record": 2, + "extract": [ + "message" + ], + "expected_sequence": "1", + "protocol_version": "14", + "mutations": [], + "schema_expectation": "accept", + "runtime_expectation": "accept", + "rule": "structural" + }, + { + "name": "strategy-intents-valid-v14", + "artifact": "strategy-message-v14", + "kind": "strategy_response", + "source": "strategy/v14/fixtures/external.strategy.jsonl", + "record": 4, + "extract": [ + "message" + ], + "expected_sequence": "2", + "protocol_version": "14", + "mutations": [], + "schema_expectation": "accept", + "runtime_expectation": "accept", + "rule": "structural" } ], "schema_only_cases": [ @@ -1625,6 +1687,63 @@ }, "mutations": [], "schema_expectation": "accept" + }, + { + "name": "strategy-stopped-valid-v14", + "artifact": "strategy-message-v14", + "instance": { + "strategy_protocol_version": "14", + "strategy_sequence": "7", + "message_type": "stopped", + "payload": {} + }, + "mutations": [], + "schema_expectation": "accept", + "parser_expectation": "accept", + "parser_expected": "stopped" + }, + { + "name": "strategy-error-valid-v14", + "artifact": "strategy-message-v14", + "instance": { + "strategy_protocol_version": "14", + "strategy_sequence": "7", + "message_type": "error", + "payload": { + "message": "fixture failure" + } + }, + "mutations": [], + "schema_expectation": "accept" + }, + { + "name": "strategy-v14-rejected-response-branch", + "artifact": "strategy-transcript-v14", + "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" } ] } diff --git a/contracts/conformance/manifest.json b/contracts/conformance/manifest.json index c68ce46..6e7d864 100644 --- a/contracts/conformance/manifest.json +++ b/contracts/conformance/manifest.json @@ -1139,6 +1139,109 @@ "format": "jsonl" } ] + }, + { + "name": "scenario-v16", + "schema": "v16/scenario.schema.json", + "version_field": "contract_version", + "version": "16", + "sources": [ + { + "path": "v16/fixtures/demo.scenario.json", + "format": "json" + }, + { + "path": "v16/fixtures/fill-clipped.scenario.json", + "format": "json" + }, + { + "path": "v16/fixtures/quote-trade.scenario.json", + "format": "json" + }, + { + "path": "v16/fixtures/order-book.scenario.json", + "format": "json" + }, + { + "path": "strategy/v14/fixtures/external.scenario.json", + "format": "json" + } + ] + }, + { + "name": "scenario-stream-v16", + "schema": "v16/scenario-stream.schema.json", + "version_field": "contract_version", + "version": "16", + "sources": [ + { + "path": "v16/fixtures/demo.scenario.jsonl", + "format": "jsonl" + }, + { + "path": "v16/fixtures/quote-trade.scenario.jsonl", + "format": "jsonl" + }, + { + "path": "v16/fixtures/order-book.scenario.jsonl", + "format": "jsonl" + }, + { + "path": "strategy/v14/fixtures/external.scenario.jsonl", + "format": "jsonl" + } + ] + }, + { + "name": "journal-v16", + "schema": "v16/journal.schema.json", + "version_field": "contract_version", + "version": "16", + "sources": [ + { + "path": "v16/fixtures/demo.journal.jsonl", + "format": "jsonl" + }, + { + "path": "v16/fixtures/fill-clipped.journal.jsonl", + "format": "jsonl" + }, + { + "path": "v16/fixtures/quote-trade.journal.jsonl", + "format": "jsonl" + }, + { + "path": "v16/fixtures/order-book.journal.jsonl", + "format": "jsonl" + } + ] + }, + { + "name": "strategy-message-v14", + "schema": "strategy/v14/message.schema.json", + "version_field": "strategy_protocol_version", + "version": "14", + "sources": [ + { + "path": "strategy/v14/fixtures/external.strategy.jsonl", + "format": "jsonl", + "extract": [ + "message" + ] + } + ] + }, + { + "name": "strategy-transcript-v14", + "schema": "strategy/v14/transcript.schema.json", + "version_field": "strategy_protocol_version", + "version": "14", + "sources": [ + { + "path": "strategy/v14/fixtures/external.strategy.jsonl", + "format": "jsonl" + } + ] } ] } diff --git a/contracts/strategy/v14/README.md b/contracts/strategy/v14/README.md new file mode 100644 index 0000000..1164118 --- /dev/null +++ b/contracts/strategy/v14/README.md @@ -0,0 +1,64 @@ +# External strategy protocol v14 + +Version 14 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 v13 remains available for scenario contract v15; earlier versions retain their frozen +shapes. + +Every message repeats `strategy_protocol_version: "14"` 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. + +Intent batches use the scenario v16 typed metric contract. `emit_metric.value` declares a numeric, +string, or boolean value, with optional unit, aggregation, and bounded dimensions. + +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 v16 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 v16 intent shapes. Market-slice events include lifecycle transitions and +the expanded corporate-action catalog, plus causally ordered quote/trade market events. +Protocol v14 also carries bounded order-book snapshots and incrementals and advertises the +`order_book_v1` configuration, including its maximum depth. + +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. diff --git a/contracts/strategy/v14/dune b/contracts/strategy/v14/dune new file mode 100644 index 0000000..999eb39 --- /dev/null +++ b/contracts/strategy/v14/dune @@ -0,0 +1,15 @@ +(install + (section share) + (package trading_engine) + (files + (message.schema.json as contracts/strategy/v14/message.schema.json) + (transcript.schema.json as contracts/strategy/v14/transcript.schema.json) + (fixtures/external.scenario.json + as + contracts/strategy/v14/fixtures/external.scenario.json) + (fixtures/external.scenario.jsonl + as + contracts/strategy/v14/fixtures/external.scenario.jsonl) + (fixtures/external.strategy.jsonl + as + contracts/strategy/v14/fixtures/external.strategy.jsonl))) diff --git a/contracts/strategy/v14/fixtures/external.scenario.json b/contracts/strategy/v14/fixtures/external.scenario.json new file mode 100644 index 0000000..9fa4d9c --- /dev/null +++ b/contracts/strategy/v14/fixtures/external.scenario.json @@ -0,0 +1,308 @@ +{ + "contract_version": "16", + "metadata": { + "producer": "strategy-protocol-fixture" + }, + "run_id": "external-demo", + "base_currency": "USD", + "initial_portfolio": { + "cash": [ + { + "currency": "USD", + "amount": "10000" + } + ], + "positions": [], + "marks": [], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ] + }, + "instruments": [ + { + "instrument_id": "demo-equity-acme", + "symbol": "ACME", + "quote_currency": "USD", + "tick_size": "0.01", + "lot_size": "1" + } + ], + "venue_calendars": [ + { + "calendar_id": "demo-xnas-2026", + "calendar_version": "1", + "venue_id": "XNAS", + "instrument_ids": [ + "demo-equity-acme" + ], + "sessions": [ + { + "session_date": "2026-01-01", + "policy": "holiday", + "phases": [] + }, + { + "session_date": "2026-01-02", + "policy": "regular", + "phases": [ + { + "phase": "premarket", + "opens_at": "2026-01-02T09:00:00Z", + "closes_at": "2026-01-02T14:25:00Z" + }, + { + "phase": "opening_auction", + "opens_at": "2026-01-02T14:25:00Z", + "closes_at": "2026-01-02T14:30:00Z" + }, + { + "phase": "regular", + "opens_at": "2026-01-02T14:30:00Z", + "closes_at": "2026-01-02T20:55:00Z" + }, + { + "phase": "closing_auction", + "opens_at": "2026-01-02T20:55:00Z", + "closes_at": "2026-01-02T21:00:00Z" + }, + { + "phase": "postmarket", + "opens_at": "2026-01-02T21:00:00Z", + "closes_at": "2026-01-03T01:00:00Z" + } + ] + }, + { + "session_date": "2026-01-05", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-05T14:30:00Z", + "closes_at": "2026-01-05T21:00:00Z" + } + ] + }, + { + "session_date": "2026-01-06", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-06T14:30:00Z", + "closes_at": "2026-01-06T21:00:00Z" + } + ] + }, + { + "session_date": "2026-01-07", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-07T14:30:00Z", + "closes_at": "2026-01-07T21:00:00Z" + } + ] + }, + { + "session_date": "2026-01-08", + "policy": "early_close", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-08T14:30:00Z", + "closes_at": "2026-01-08T18:00:00Z" + } + ] + } + ] + } + ], + "risk": { + "max_gross_exposure": "1000000", + "max_leverage": "2", + "short_borrow_bps": 0, + "instrument_policies": [ + { + "instrument_id": "demo-equity-acme", + "max_order_quantity": "1000", + "max_long_position": "1000", + "max_short_position": "1000", + "max_notional_exposure": "1000000", + "initial_margin_bps": 5000, + "maintenance_margin_bps": 2500, + "shorting_allowed": true + } + ], + "groups": [] + }, + "execution": { + "model": "completed_bar_v1", + "configuration": { + "version": "2", + "participation_bps": 5000, + "fee_schedules": [ + { + "schedule_id": "external-acme-fees-v1", + "instrument_id": "demo-equity-acme", + "settlement_currency": "USD", + "minimum": null, + "maximum": null, + "components": [ + { + "name": "broker", + "currency": "USD", + "kind": "fixed", + "value": "0.25", + "rounding": "up", + "applies_to": "any" + }, + { + "name": "exchange", + "currency": "USD", + "kind": "notional_bps", + "value": 10, + "rounding": "up", + "applies_to": "any" + } + ] + } + ] + } + }, + "max_internal_events": 1000, + "schedule": [], + "slices": [ + { + "slice_sequence": "1", + "start_at": "2026-01-02T14:30:00Z", + "end_at": "2026-01-02T21:00:00Z", + "available_at": "2026-01-02T21:00:01Z", + "received_at": "2026-01-02T21:00:02Z", + "bars": [ + { + "instrument_id": "demo-equity-acme", + "open": "100", + "high": "105", + "low": "99", + "close": "104", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "demo-equity-acme", + "effective_at": "2026-01-02T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-01-02T14:30:00Z", + "credit_rate_bps": 0, + "debit_rate_bps": 0 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + }, + { + "slice_sequence": "2", + "start_at": "2026-01-05T14:30:00Z", + "end_at": "2026-01-05T21:00:00Z", + "available_at": "2026-01-05T21:00:01Z", + "received_at": "2026-01-05T21:00:02Z", + "bars": [ + { + "instrument_id": "demo-equity-acme", + "open": "103", + "high": "108", + "low": "102", + "close": "107", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "demo-equity-acme", + "effective_at": "2026-01-05T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-01-05T14:30:00Z", + "credit_rate_bps": 0, + "debit_rate_bps": 0 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + } + ], + "financing": { + "day_count": "actual_365", + "compounding": "simple", + "borrow_missing_data": "reject", + "cash_missing_data": "reject", + "locate_policy": "clip_fill", + "recall_policy": "close_out" + }, + "settlement": { + "cash_buying_power": "total_cash", + "position_availability": "total_positions", + "calendars": [ + { + "calendar_id": "default-settlement", + "version": "1", + "business_dates": [ + "2026-01-02", + "2026-01-05", + "2026-01-06", + "2026-01-07", + "2026-01-08", + "2026-01-09", + "2026-02-02", + "2026-02-03", + "2026-02-04", + "2026-02-05" + ] + } + ], + "rules": [ + { + "instrument_id": "demo-equity-acme", + "calendar_id": "default-settlement", + "lag_business_days": 1 + } + ] + } +} diff --git a/contracts/strategy/v14/fixtures/external.scenario.jsonl b/contracts/strategy/v14/fixtures/external.scenario.jsonl new file mode 100644 index 0000000..894bf5a --- /dev/null +++ b/contracts/strategy/v14/fixtures/external.scenario.jsonl @@ -0,0 +1,4 @@ +{"contract_version":"16","scenario_sequence":"1","record_type":"scenario_header","payload":{"metadata":{"producer":"strategy-protocol-fixture"},"run_id":"external-demo","base_currency":"USD","initial_portfolio":{"cash":[{"currency":"USD","amount":"10000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"instruments":[{"instrument_id":"demo-equity-acme","symbol":"ACME","quote_currency":"USD","tick_size":"0.01","lot_size":"1"}],"venue_calendars":[{"calendar_id":"demo-xnas-2026","calendar_version":"1","venue_id":"XNAS","instrument_ids":["demo-equity-acme"],"sessions":[{"session_date":"2026-01-01","policy":"holiday","phases":[]},{"session_date":"2026-01-02","policy":"regular","phases":[{"phase":"premarket","opens_at":"2026-01-02T09:00:00Z","closes_at":"2026-01-02T14:25:00Z"},{"phase":"opening_auction","opens_at":"2026-01-02T14:25:00Z","closes_at":"2026-01-02T14:30:00Z"},{"phase":"regular","opens_at":"2026-01-02T14:30:00Z","closes_at":"2026-01-02T20:55:00Z"},{"phase":"closing_auction","opens_at":"2026-01-02T20:55:00Z","closes_at":"2026-01-02T21:00:00Z"},{"phase":"postmarket","opens_at":"2026-01-02T21:00:00Z","closes_at":"2026-01-03T01:00:00Z"}]},{"session_date":"2026-01-05","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-05T14:30:00Z","closes_at":"2026-01-05T21:00:00Z"}]},{"session_date":"2026-01-06","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-06T14:30:00Z","closes_at":"2026-01-06T21:00:00Z"}]},{"session_date":"2026-01-07","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-07T14:30:00Z","closes_at":"2026-01-07T21:00:00Z"}]},{"session_date":"2026-01-08","policy":"early_close","phases":[{"phase":"regular","opens_at":"2026-01-08T14:30:00Z","closes_at":"2026-01-08T18:00:00Z"}]}]}],"risk":{"max_gross_exposure":"1000000","max_leverage":"2","short_borrow_bps":0,"instrument_policies":[{"instrument_id":"demo-equity-acme","max_order_quantity":"1000","max_long_position":"1000","max_short_position":"1000","max_notional_exposure":"1000000","initial_margin_bps":5000,"maintenance_margin_bps":2500,"shorting_allowed":true}],"groups":[]},"execution":{"model":"completed_bar_v1","configuration":{"version":"2","participation_bps":5000,"fee_schedules":[{"schedule_id":"external-acme-fees-v1","instrument_id":"demo-equity-acme","settlement_currency":"USD","minimum":null,"maximum":null,"components":[{"name":"broker","currency":"USD","kind":"fixed","value":"0.25","rounding":"up","applies_to":"any"},{"name":"exchange","currency":"USD","kind":"notional_bps","value":10,"rounding":"up","applies_to":"any"}]}]}},"max_internal_events":1000,"financing":{"day_count":"actual_365","compounding":"simple","borrow_missing_data":"reject","cash_missing_data":"reject","locate_policy":"clip_fill","recall_policy":"close_out"},"settlement":{"cash_buying_power":"total_cash","position_availability":"total_positions","calendars":[{"calendar_id":"default-settlement","version":"1","business_dates":["2026-01-02","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-02-02","2026-02-03","2026-02-04","2026-02-05"]}],"rules":[{"instrument_id":"demo-equity-acme","calendar_id":"default-settlement","lag_business_days":1}]}}} +{"contract_version":"16","payload":{"intents":[],"market_slice":{"available_at":"2026-01-02T21:00:01Z","bars":[{"close":"104","high":"105","instrument_id":"demo-equity-acme","low":"99","open":"100","volume":"100"}],"corporate_actions":[],"end_at":"2026-01-02T21:00:00Z","fx_rates":[{"currency":"USD","rate":"1"}],"received_at":"2026-01-02T21:00:02Z","slice_sequence":"1","start_at":"2026-01-02T14:30:00Z","borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-02T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-02T14:30:00Z","credit_rate_bps":0,"debit_rate_bps":0}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}},"record_type":"market_slice","scenario_sequence":"2"} +{"contract_version":"16","payload":{"intents":[],"market_slice":{"available_at":"2026-01-05T21:00:01Z","bars":[{"close":"107","high":"108","instrument_id":"demo-equity-acme","low":"102","open":"103","volume":"100"}],"corporate_actions":[],"end_at":"2026-01-05T21:00:00Z","fx_rates":[{"currency":"USD","rate":"1"}],"received_at":"2026-01-05T21:00:02Z","slice_sequence":"2","start_at":"2026-01-05T14:30:00Z","borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-05T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-05T14:30:00Z","credit_rate_bps":0,"debit_rate_bps":0}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}},"record_type":"market_slice","scenario_sequence":"3"} +{"contract_version":"16","payload":{"slice_count":"2"},"record_type":"scenario_end","scenario_sequence":"4"} diff --git a/contracts/strategy/v14/fixtures/external.strategy.jsonl b/contracts/strategy/v14/fixtures/external.strategy.jsonl new file mode 100644 index 0000000..ceed5d3 --- /dev/null +++ b/contracts/strategy/v14/fixtures/external.strategy.jsonl @@ -0,0 +1,14 @@ +{"strategy_protocol_version":"14","transcript_sequence":"1","direction":"engine_to_strategy","message":{"strategy_protocol_version":"14","strategy_sequence":"1","message_type":"initialize","payload":{"engine_version":"1.0.0","scenario_contract_version":"16","scenario_sha256":"6809a3638fe668a2a11e56c42e9bac7e506c0064eb2cb0a3e71ba09d92839ee7","run_id":"external-demo","base_currency":"USD","initial_cash":[{"currency":"USD","amount":"10000"}],"initial_portfolio":{"cash":[{"currency":"USD","amount":"10000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"venue_calendars":[{"calendar_id":"demo-xnas-2026","calendar_version":"1","venue_id":"XNAS","instrument_ids":["demo-equity-acme"],"sessions":[{"session_date":"2026-01-01","policy":"holiday","phases":[]},{"session_date":"2026-01-02","policy":"regular","phases":[{"phase":"premarket","opens_at":"2026-01-02T09:00:00.000000Z","closes_at":"2026-01-02T14:25:00.000000Z"},{"phase":"opening_auction","opens_at":"2026-01-02T14:25:00.000000Z","closes_at":"2026-01-02T14:30:00.000000Z"},{"phase":"regular","opens_at":"2026-01-02T14:30:00.000000Z","closes_at":"2026-01-02T20:55:00.000000Z"},{"phase":"closing_auction","opens_at":"2026-01-02T20:55:00.000000Z","closes_at":"2026-01-02T21:00:00.000000Z"},{"phase":"postmarket","opens_at":"2026-01-02T21:00:00.000000Z","closes_at":"2026-01-03T01:00:00.000000Z"}]},{"session_date":"2026-01-05","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-05T14:30:00.000000Z","closes_at":"2026-01-05T21:00:00.000000Z"}]},{"session_date":"2026-01-06","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-06T14:30:00.000000Z","closes_at":"2026-01-06T21:00:00.000000Z"}]},{"session_date":"2026-01-07","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-07T14:30:00.000000Z","closes_at":"2026-01-07T21:00:00.000000Z"}]},{"session_date":"2026-01-08","policy":"early_close","phases":[{"phase":"regular","opens_at":"2026-01-08T14:30:00.000000Z","closes_at":"2026-01-08T18:00:00.000000Z"}]}]}],"financing":{"day_count":"actual_365","compounding":"simple","borrow_missing_data":"reject","cash_missing_data":"reject","locate_policy":"clip_fill","recall_policy":"close_out"},"settlement":{"cash_buying_power":"total_cash","position_availability":"total_positions","calendars":[{"calendar_id":"default-settlement","version":"1","business_dates":["2026-01-02","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-02-02","2026-02-03","2026-02-04","2026-02-05"]}],"rules":[{"instrument_id":"demo-equity-acme","calendar_id":"default-settlement","lag_business_days":1}]},"instruments":[{"instrument_id":"demo-equity-acme","symbol":"ACME","quote_currency":"USD","tick_size":"0.01","lot_size":"1"}],"risk":{"max_gross_exposure":"1000000","max_leverage":"2","short_borrow_bps":0,"instrument_policies":[{"instrument_id":"demo-equity-acme","max_order_quantity":"1000","max_long_position":"1000","max_short_position":"1000","max_notional_exposure":"1000000","initial_margin_bps":5000,"maintenance_margin_bps":2500,"shorting_allowed":true}],"groups":[]},"execution":{"model":"completed_bar_v1","configuration":{"version":"2","participation_bps":5000,"fee_schedules":[{"schedule_id":"external-acme-fees-v1","instrument_id":"demo-equity-acme","settlement_currency":"USD","minimum":null,"maximum":null,"components":[{"name":"broker","currency":"USD","kind":"fixed","value":"0.25","rounding":"up","applies_to":"any"},{"name":"exchange","currency":"USD","kind":"notional_bps","value":10,"rounding":"up","applies_to":"any"}]}]}},"metadata":{"producer":"strategy-protocol-fixture"}}}} +{"strategy_protocol_version":"14","transcript_sequence":"2","direction":"strategy_to_engine","message":{"strategy_protocol_version":"14","strategy_sequence":"1","message_type":"ready","payload":{"strategy_name":"fixture-strategy","strategy_version":"1"}}} +{"strategy_protocol_version":"14","transcript_sequence":"3","direction":"engine_to_strategy","message":{"strategy_protocol_version":"14","strategy_sequence":"2","message_type":"event","payload":{"context":{"now":"2026-01-02T21:00:02.000000Z","portfolio":{"base_currency":"USD","cash":"10000","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","equity":"10000","weights_available":true,"cash_weight":"1","cash_balances":[{"currency":"USD","amount":"10000","fx_rate":"1","base_value":"10000","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"0","base_settled_value":"10000","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"0","mark":"104","base_market_value":"0","weight":"0","settled_quantity":"0","unsettled_quantity":"0"}],"group_exposures":[]},"working_orders":[],"latest_bars":[{"instrument_id":"demo-equity-acme","open":"100","high":"105","low":"99","close":"104","volume":"100"}]},"event":{"type":"market_slice_closed","market_slice":{"slice_sequence":"1","start_at":"2026-01-02T14:30:00.000000Z","end_at":"2026-01-02T21:00:00.000000Z","available_at":"2026-01-02T21:00:01.000000Z","received_at":"2026-01-02T21:00:02.000000Z","bars":[{"instrument_id":"demo-equity-acme","open":"100","high":"105","low":"99","close":"104","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-02T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-02T14:30:00.000000Z","credit_rate_bps":0,"debit_rate_bps":0}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}}}}} +{"strategy_protocol_version":"14","transcript_sequence":"4","direction":"strategy_to_engine","message":{"strategy_protocol_version":"14","strategy_sequence":"2","message_type":"intents","payload":{"intents":[{"type":"target_quantities","targets":[{"instrument_id":"demo-equity-acme","quantity":"2"}]},{"type":"emit_metric","name":"fixture_signal","value":{"type":"numeric","value":"2"},"unit":"score","dimensions":{"source":"fixture"},"aggregation":"last"}]}}} +{"strategy_protocol_version":"14","transcript_sequence":"5","direction":"engine_to_strategy","message":{"strategy_protocol_version":"14","strategy_sequence":"3","message_type":"event","payload":{"context":{"now":"2026-01-02T21:00:02.000000Z","portfolio":{"base_currency":"USD","cash":"10000","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","equity":"10000","weights_available":true,"cash_weight":"1","cash_balances":[{"currency":"USD","amount":"10000","fx_rate":"1","base_value":"10000","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"0","base_settled_value":"10000","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"0","mark":"104","base_market_value":"0","weight":"0","settled_quantity":"0","unsettled_quantity":"0"}],"group_exposures":[]},"working_orders":[{"order_id":"external-demo-order-000000000001","instrument_id":"demo-equity-acme","side":"buy","quantity":"2","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"target_rebalance","created_event_id":"external-demo-event-000000000007","updated_event_id":"external-demo-event-000000000007","created_sequence":"7","created_at":"2026-01-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}],"latest_bars":[{"instrument_id":"demo-equity-acme","open":"100","high":"105","low":"99","close":"104","volume":"100"}]},"event":{"type":"order_updated","order":{"order_id":"external-demo-order-000000000001","instrument_id":"demo-equity-acme","side":"buy","quantity":"2","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"target_rebalance","created_event_id":"external-demo-event-000000000007","updated_event_id":"external-demo-event-000000000007","created_sequence":"7","created_at":"2026-01-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}}}}} +{"strategy_protocol_version":"14","transcript_sequence":"6","direction":"strategy_to_engine","message":{"strategy_protocol_version":"14","strategy_sequence":"3","message_type":"intents","payload":{"intents":[]}}} +{"strategy_protocol_version":"14","transcript_sequence":"7","direction":"engine_to_strategy","message":{"strategy_protocol_version":"14","strategy_sequence":"4","message_type":"event","payload":{"context":{"now":"2026-01-05T21:00:02.000000Z","portfolio":{"base_currency":"USD","cash":"9793.544","net_market_value":"214","long_market_value":"214","short_market_value":"0","gross_exposure":"214","equity":"10007.544","weights_available":true,"cash_weight":"0.978616","cash_balances":[{"currency":"USD","amount":"9793.544","fx_rate":"1","base_value":"9793.544","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"-206.456","base_settled_value":"10000","base_unsettled_value":"-206.456"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"2","mark":"107","base_market_value":"214","weight":"0.021383","settled_quantity":"0","unsettled_quantity":"2"}],"group_exposures":[]},"working_orders":[],"latest_bars":[{"instrument_id":"demo-equity-acme","open":"103","high":"108","low":"102","close":"107","volume":"100"}]},"event":{"type":"fill_received","fill":{"fill_id":"external-demo-fill-000000000001","order_id":"external-demo-order-000000000001","instrument_id":"demo-equity-acme","quote_currency":"USD","side":"buy","quantity":"2","price":"103","notional":"206","fee":"0.456","executed_at":"2026-01-05T14:30:00.000000Z","slice_sequence":"2","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.25","quote_amount":"0.25"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.206","quote_amount":"0.206"}]}}}}} +{"strategy_protocol_version":"14","transcript_sequence":"8","direction":"strategy_to_engine","message":{"strategy_protocol_version":"14","strategy_sequence":"4","message_type":"intents","payload":{"intents":[]}}} +{"strategy_protocol_version":"14","transcript_sequence":"9","direction":"engine_to_strategy","message":{"strategy_protocol_version":"14","strategy_sequence":"5","message_type":"event","payload":{"context":{"now":"2026-01-05T21:00:02.000000Z","portfolio":{"base_currency":"USD","cash":"9793.544","net_market_value":"214","long_market_value":"214","short_market_value":"0","gross_exposure":"214","equity":"10007.544","weights_available":true,"cash_weight":"0.978616","cash_balances":[{"currency":"USD","amount":"9793.544","fx_rate":"1","base_value":"9793.544","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"-206.456","base_settled_value":"10000","base_unsettled_value":"-206.456"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"2","mark":"107","base_market_value":"214","weight":"0.021383","settled_quantity":"0","unsettled_quantity":"2"}],"group_exposures":[]},"working_orders":[],"latest_bars":[{"instrument_id":"demo-equity-acme","open":"103","high":"108","low":"102","close":"107","volume":"100"}]},"event":{"type":"order_updated","order":{"order_id":"external-demo-order-000000000001","instrument_id":"demo-equity-acme","side":"buy","quantity":"2","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"target_rebalance","created_event_id":"external-demo-event-000000000007","updated_event_id":"external-demo-event-000000000007","created_sequence":"7","created_at":"2026-01-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"2","filled_notional":"206","status":"filled","rejection_reason":null}}}}} +{"strategy_protocol_version":"14","transcript_sequence":"10","direction":"strategy_to_engine","message":{"strategy_protocol_version":"14","strategy_sequence":"5","message_type":"intents","payload":{"intents":[]}}} +{"strategy_protocol_version":"14","transcript_sequence":"11","direction":"engine_to_strategy","message":{"strategy_protocol_version":"14","strategy_sequence":"6","message_type":"event","payload":{"context":{"now":"2026-01-05T21:00:02.000000Z","portfolio":{"base_currency":"USD","cash":"9793.544","net_market_value":"214","long_market_value":"214","short_market_value":"0","gross_exposure":"214","equity":"10007.544","weights_available":true,"cash_weight":"0.978616","cash_balances":[{"currency":"USD","amount":"9793.544","fx_rate":"1","base_value":"9793.544","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"-206.456","base_settled_value":"10000","base_unsettled_value":"-206.456"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"2","mark":"107","base_market_value":"214","weight":"0.021383","settled_quantity":"0","unsettled_quantity":"2"}],"group_exposures":[]},"working_orders":[],"latest_bars":[{"instrument_id":"demo-equity-acme","open":"103","high":"108","low":"102","close":"107","volume":"100"}]},"event":{"type":"market_slice_closed","market_slice":{"slice_sequence":"2","start_at":"2026-01-05T14:30:00.000000Z","end_at":"2026-01-05T21:00:00.000000Z","available_at":"2026-01-05T21:00:01.000000Z","received_at":"2026-01-05T21:00:02.000000Z","bars":[{"instrument_id":"demo-equity-acme","open":"103","high":"108","low":"102","close":"107","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-05T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-05T14:30:00.000000Z","credit_rate_bps":0,"debit_rate_bps":0}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}}}}} +{"strategy_protocol_version":"14","transcript_sequence":"12","direction":"strategy_to_engine","message":{"strategy_protocol_version":"14","strategy_sequence":"6","message_type":"intents","payload":{"intents":[]}}} +{"strategy_protocol_version":"14","transcript_sequence":"13","direction":"engine_to_strategy","message":{"strategy_protocol_version":"14","strategy_sequence":"7","message_type":"shutdown","payload":{}}} +{"strategy_protocol_version":"14","transcript_sequence":"14","direction":"strategy_to_engine","message":{"strategy_protocol_version":"14","strategy_sequence":"7","message_type":"stopped","payload":{}}} diff --git a/contracts/strategy/v14/message.schema.json b/contracts/strategy/v14/message.schema.json new file mode 100644 index 0000000..97d8a98 --- /dev/null +++ b/contracts/strategy/v14/message.schema.json @@ -0,0 +1,302 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/fallblu/trading-engine/contracts/strategy/v14/message.schema.json", + "title": "Trading Engine external strategy protocol v14 message", + "description": "One strict request or response in the synchronous JSON Lines strategy protocol. The engine additionally enforces direction, sequence pairing, canonical values, response limits, and lifecycle order.", + "oneOf": [ + { "$ref": "#/$defs/initialize" }, + { "$ref": "#/$defs/ready" }, + { "$ref": "#/$defs/event" }, + { "$ref": "#/$defs/intents" }, + { "$ref": "#/$defs/shutdown" }, + { "$ref": "#/$defs/stopped" }, + { "$ref": "#/$defs/error" } + ], + "$defs": { + "sequence": { + "type": "string", + "pattern": "^[1-9][0-9]*$" + }, + "base": { + "type": "object", + "additionalProperties": false, + "required": ["strategy_protocol_version", "strategy_sequence", "message_type", "payload"], + "properties": { + "strategy_protocol_version": { "const": "14" }, + "strategy_sequence": { "$ref": "#/$defs/sequence" }, + "message_type": { "type": "string" }, + "payload": { "type": "object" } + } + }, + "initialize": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "properties": { + "message_type": { "const": "initialize" }, + "payload": { "$ref": "#/$defs/initializePayload" } + } + } + ] + }, + "initializePayload": { + "type": "object", + "additionalProperties": false, + "required": ["engine_version", "scenario_contract_version", "scenario_sha256", "run_id", "base_currency", "initial_cash", "initial_portfolio", "instruments", "venue_calendars", "risk", "execution", "financing", "settlement", "metadata"], + "properties": { + "engine_version": { "type": "string", "minLength": 1 }, + "scenario_contract_version": { "const": "16" }, + "scenario_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "run_id": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/identifier" }, + "base_currency": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/identifier" }, + "initial_cash": { + "type": "array", + "minItems": 1, + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/cashBalance" } + }, + "initial_portfolio": { + "oneOf": [ + { "type": "null" }, + { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/initialPortfolio" } + ] + }, + "instruments": { + "type": "array", + "minItems": 1, + "maxItems": 4096, + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/instrument" } + }, + "venue_calendars": { + "type": "array", + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/venueCalendar" } + }, + "risk": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/risk" }, + "execution": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/execution" }, + "financing": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/financing" }, + "settlement": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/settlement" }, + "metadata": { "type": "object" } + } + }, + "ready": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "properties": { + "message_type": { "const": "ready" }, + "payload": { "$ref": "#/$defs/readyPayload" } + } + } + ] + }, + "readyPayload": { + "type": "object", + "additionalProperties": false, + "required": ["strategy_name", "strategy_version"], + "properties": { + "strategy_name": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/identifier" }, + "strategy_version": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1, "pattern": "^\\S(?:.*\\S)?$" } + ] + } + } + }, + "event": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "properties": { + "message_type": { "const": "event" }, + "payload": { "$ref": "#/$defs/eventPayload" } + } + } + ] + }, + "eventPayload": { + "type": "object", + "additionalProperties": false, + "required": ["context", "event"], + "properties": { + "context": { "$ref": "#/$defs/context" }, + "event": { "$ref": "#/$defs/strategyEvent" } + } + }, + "context": { + "type": "object", + "additionalProperties": false, + "required": ["now", "portfolio", "working_orders", "latest_bars"], + "properties": { + "now": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/timestamp" }, + "portfolio": { "$ref": "#/$defs/portfolio" }, + "working_orders": { + "type": "array", + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/journal.schema.json#/$defs/order" } + }, + "latest_bars": { + "type": "array", + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/bar" } + } + } + }, + "portfolio": { + "type": "object", + "additionalProperties": false, + "required": ["base_currency", "cash", "net_market_value", "long_market_value", "short_market_value", "gross_exposure", "equity", "weights_available", "cash_weight", "cash_balances", "positions", "group_exposures"], + "properties": { + "base_currency": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/identifier" }, + "cash": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" }, + "net_market_value": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" }, + "long_market_value": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/unsignedDecimal" }, + "short_market_value": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/unsignedDecimal" }, + "gross_exposure": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/unsignedDecimal" }, + "equity": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" }, + "weights_available": { "type": "boolean" }, + "cash_weight": { "$ref": "#/$defs/optionalWeight" }, + "cash_balances": { + "type": "array", + "minItems": 1, + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/journal.schema.json#/$defs/cashAttribution" } + }, + "positions": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/markedPosition" } + }, + "group_exposures": { + "type": "array", + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/journal.schema.json#/$defs/groupExposure" } + } + } + }, + "optionalWeight": { + "oneOf": [ + { "type": "null" }, + { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" } + ] + }, + "markedPosition": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "quantity", "settled_quantity", "unsettled_quantity", "mark", "base_market_value", "weight"], + "properties": { + "instrument_id": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/identifier" }, + "quantity": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" }, + "settled_quantity": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" }, + "unsettled_quantity": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" }, + "mark": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/positiveDecimal" }, + "base_market_value": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/signedDecimal" }, + "weight": { "$ref": "#/$defs/optionalWeight" } + } + }, + "strategyEvent": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["type", "market_slice"], + "properties": { + "type": { "const": "market_slice_closed" }, + "market_slice": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/marketSlice" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "fill"], + "properties": { + "type": { "const": "fill_received" }, + "fill": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/journal.schema.json#/$defs/fill" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "order"], + "properties": { + "type": { "const": "order_updated" }, + "order": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/journal.schema.json#/$defs/order" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "reason"], + "properties": { + "type": { "const": "intent_rejected" }, + "reason": { "type": "string", "minLength": 1 } + } + } + ] + }, + "intents": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "properties": { + "message_type": { "const": "intents" }, + "payload": { "$ref": "#/$defs/intentsPayload" } + } + } + ] + }, + "intentsPayload": { + "type": "object", + "additionalProperties": false, + "required": ["intents"], + "properties": { + "intents": { + "type": "array", + "maxItems": 4096, + "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/intent" } + } + } + }, + "shutdown": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "properties": { + "message_type": { "const": "shutdown" }, + "payload": { "$ref": "#/$defs/emptyPayload" } + } + } + ] + }, + "stopped": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "properties": { + "message_type": { "const": "stopped" }, + "payload": { "$ref": "#/$defs/emptyPayload" } + } + } + ] + }, + "emptyPayload": { + "type": "object", + "additionalProperties": false, + "maxProperties": 0 + }, + "error": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "properties": { + "message_type": { "const": "error" }, + "payload": { "$ref": "#/$defs/errorPayload" } + } + } + ] + }, + "errorPayload": { + "type": "object", + "additionalProperties": false, + "required": ["message"], + "properties": { + "message": { "type": "string", "minLength": 1, "pattern": "^\\S(?:.*\\S)?$" } + } + } + } +} diff --git a/contracts/strategy/v14/transcript.schema.json b/contracts/strategy/v14/transcript.schema.json new file mode 100644 index 0000000..4635866 --- /dev/null +++ b/contracts/strategy/v14/transcript.schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/fallblu/trading-engine/contracts/strategy/v14/transcript.schema.json", + "title": "Trading Engine external strategy protocol v14 transcript record", + "description": "One accepted exchange or rejected-response diagnostic retained from a supervised stdio strategy session.", + "oneOf": [ + { "$ref": "#/$defs/exchange" }, + { "$ref": "#/$defs/rejectedResponse" } + ], + "$defs": { + "canonicalSequence": { + "type": "string", + "pattern": "^[1-9][0-9]*$" + }, + "exchange": { + "type": "object", + "additionalProperties": false, + "required": ["strategy_protocol_version", "transcript_sequence", "direction", "message"], + "properties": { + "strategy_protocol_version": { "const": "14" }, + "transcript_sequence": { "$ref": "#/$defs/canonicalSequence" }, + "direction": { + "enum": ["engine_to_strategy", "strategy_to_engine"] + }, + "message": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/strategy/v14/message.schema.json" + } + } + }, + "rejectedResponse": { + "type": "object", + "additionalProperties": false, + "required": [ + "strategy_diagnostic_version", + "transcript_sequence", + "record_type", + "expected_strategy_sequence", + "diagnostic", + "evidence" + ], + "properties": { + "strategy_diagnostic_version": { "const": "1" }, + "transcript_sequence": { "$ref": "#/$defs/canonicalSequence" }, + "record_type": { "const": "rejected_strategy_response" }, + "expected_strategy_sequence": { "$ref": "#/$defs/canonicalSequence" }, + "diagnostic": { "$ref": "#/$defs/diagnostic" }, + "evidence": { "$ref": "#/$defs/evidence" } + } + }, + "diagnostic": { + "allOf": [ + { + "$ref": "https://github.com/fallblu/trading-engine/contracts/diagnostic/v1/diagnostic.schema.json" + }, + { + "properties": { + "code": { "enum": ["strategy.protocol", "resource.limit"] }, + "phase": { "const": "strategy" } + } + } + ] + }, + "evidence": { + "type": "object", + "additionalProperties": false, + "required": ["encoding", "prefix", "observed_bytes", "truncated"], + "properties": { + "encoding": { "const": "hex" }, + "prefix": { + "type": "string", + "pattern": "^(?:[0-9a-f]{2}){0,256}$" + }, + "observed_bytes": { + "type": "integer", + "minimum": 0, + "maximum": 1048577 + }, + "truncated": { "type": "boolean" } + } + } + } +} diff --git a/contracts/v16/README.md b/contracts/v16/README.md new file mode 100644 index 0000000..03ac9da --- /dev/null +++ b/contracts/v16/README.md @@ -0,0 +1,122 @@ +# Trading Engine contract v16 + +This directory is the authoritative v16 process and file contract shared by Trading Engine and its +clients. Versions 14 through 3 remain readable during their client transitions. + +- `scenario.schema.json` validates batch replay inputs. +- `scenario-stream.schema.json` validates each JSON Lines scenario-stream record. +- `journal.schema.json` validates each JSON Lines audit record. +- The files under `fixtures/` form the canonical valid conformance corpus. +- `fill-clipped.scenario.json` and its journal exercise a leverage-limited partial fill. + +Version 7 requires exactly one explicit risk policy per catalog instrument. Each policy defines +order, signed-position, notional, initial-margin, maintenance-margin, and shorting limits. Versioned +risk groups have explicit membership, may overlap, and can constrain gross, long, short, absolute +net, and gross-to-equity concentration exposure. + +Runtime validation requires exact currency, position-mark, and FX coverage; known instruments; +lot-aligned quantities; tick-aligned positive marks; basis with the same sign as quantity; +nonnegative fee histories; the base FX rate equal to one; instrument, group, aggregate exposure, +leverage, and initial-margin limits. Signed cash is valid. A successful v16 run emits `initial_state` +immediately after `run_started`, followed by a reconciled initial `valuation`, before market data. + +Admission and fill clipping include working-order reservations. When multiple groups limit the same +fill, lexical group identity is the deterministic tie breaker. Valuations and strategy contexts +carry group exposure snapshots, and clipping thresholds identify the exact instrument or group. + +Every v16 scenario, stream record, and journal record carries `"contract_version": "16"`. + +Version 8 adds explicit `market`, `limit`, `stop`, and `stop_limit` orders with `gtc`, `ioc`, +`fok`, `day`, and `gtd` time-in-force policies. `day` orders identify both their venue and the +exact versioned calendar; `gtd` orders carry an absolute expiry timestamp. Older contracts retain +their frozen mapping: market orders are IOC and limit orders are GTC. + +Stops evaluate only completed OHLCV bars. A gap through the trigger records the bar start as the +trigger time; an intrabar touch records the bar end. Trigger state and slice sequence are journaled, +and an activated order cannot execute before the following slice. A stop becomes a market order; +a stop-limit becomes its configured limit order. Splits adjust both trigger and limit prices. + +IOC orders cancel any remainder after their first eligible slice. FOK orders fill only when the +full remaining quantity fits both execution capacity and risk capacity, otherwise they cancel with +no fill. DAY orders cancel after matching the slice that reaches the selected session's final +phase close. GTD orders cancel before matching any completed bar whose end reaches or passes the +expiry, avoiding ambiguous partial-bar execution. + +The v10 `execution` object uses `completed_bar_v1` configuration version `"2"`: participation basis +points plus exactly one composable fee schedule per instrument. Named fixed, notional-basis-point, +and per-unit components declare currency, rounding, and maker/taker applicability. Optional +per-fill minimums and caps use the schedule settlement currency; negative components represent +rebates. Fills and valuations retain every native, quote, and base-currency attribution. Runtime +capabilities also advertise frozen configuration version `"1"` for older scenario contracts. + +Version 10 adds a required `financing` policy and effective-time observations on every market +slice. Borrow observations provide per-instrument locate availability, signed annual rates, and +recall state. Cash observations provide separate annual credit and debit rates per currency. +Policies select Actual/365 or Actual/360 day count, simple or daily compounding, missing-data +handling, locate rejection or fill clipping, and recall rejection or deterministic close-out. + +Borrow availability is enforced when a fill would create or increase a short. Recalls cancel +active sells and may submit priority IOC covers until the position is flat. Borrow charges and cash +interest use the exact slice interval, update native ledgers deterministically, and emit dedicated +journal records. Valuations report cash interest separately and include it in aggregate realized +P&L. Version 9 and earlier retain their frozen fixed-borrow behavior and wire shapes. + +Version 12 separates trade-date economic accounting from settlement-date availability. A required +settlement policy selects total or settled cash buying power and total or settled position +availability. Versioned calendars enumerate canonical business dates, and each instrument has an +explicit business-day lag. Every fill creates a deterministic settlement instruction containing +its cash and position movements, trade date, and due date. A due instruction either settles on the +first eligible slice or records a named failure supplied by that slice. + +Valuations and strategy contexts report settled and unsettled cash and quantities without changing +economic equity. Journals include instruction-created, completed, and failed events. Scenario v10 +and strategy protocol v8 retain their frozen immediate-settlement wire behavior. + +Version 12 adds exact stock-dividend, rights, and spin-off distributions. Each distribution names +its destination instrument, exact entitlement ratio, basis allocation in basis points, and either +rejects fractional entitlements or converts them to cash at an explicit price and currency. +Stock dividends adjust persistent targets and eligible working orders; every distribution journals +delivered quantity, fractional quantity, allocated basis, fractional basis, and cash in lieu. + +Lifecycle events keep stable instrument identity separate from mutable symbol and provider +mappings. Halt and resume transitions control tradability. Expiration and delisting are terminal, +cancel active orders, clear target exposure, and require an explicit hold or cash-out policy. +Cash-out specifies its terminal price and currency. Every transition journals the source event, +resulting listing state, provider provenance, liquidated quantity, and cash attribution. + +Version 14 adds `completed_bar_next_open_v1` and `completed_bar_adverse_touch_v1` without changing +the frozen `completed_bar_v1` semantics. Next-open limits require a marketable later open; +adverse-touch limits require a one-tick trade-through before a maker fill is eligible. Both models +declare fixed half-spread and linear participation-impact catalogs, including an explicit policy +for missing bar volume. Price costs round away from the reference price to instrument ticks and +cannot violate a limit. An `execution_price_selected` audit record attributes the reference price, +spread adjustment, impact adjustment, and final executable price before each fill. + +Version 14 adds `quote_trade_v1` and causally ordered `market_events`. Quotes expose bid/ask price +and displayed size. Trades expose price, size, and buy, sell, or unknown aggressor side. Each event +records economic, availability, and receipt timestamps plus a positive ingest sequence. Replay +orders events by availability, receipt, and ingest sequence. Marketable orders consume only +displayed quote liquidity; passive orders require appropriately aggressed trade evidence, and an +unknown aggressor never fills them. Event capacity is shared deterministically across order +priority and fills retain the event's economic timestamp. Completed bars remain the valuation +boundary. The `quote-trade` batch, stream, and journal fixtures demonstrate equivalent replay. + +Version 15 added `order_book_v1` and bounded level-two `order_book_events`. Every per-instrument +slice bundle starts with a complete snapshot and continues with contiguous absolute set, delete, +and aggressor-classified trade updates. Snapshots and updates reject crossed books, missing +deletes, sequence gaps, tick or lot misalignment, and depth beyond the configured limit; locked +books are valid. State is rebuilt from each slice snapshot, so replay never depends on hidden data +from a prior slice. + +Marketable orders walk observable opposite-side depth in price priority. Passive limit orders join +behind displayed same-price quantity and earlier engine orders. Reductions decrease quantity ahead, +adds join behind, and only appropriately aggressed trades consume the queue and fill the order. +Partial fills and cancellations therefore remain deterministic. Book liquidity is independent of +bar and quote/trade execution semantics, while completed bars remain the valuation boundary. The +`order-book` batch, stream, and journal fixtures demonstrate cancellation, queue depletion, maker +fills, bounded state, and batch/stream equivalence. + +Version 16 replaces string-only metrics with typed observations. Numeric values use canonical +decimal strings; string and boolean values retain their JSON types. Optional units, a closed +aggregation enum, and up to 16 unique dimensions are bounded at ingestion. Dimension keys are +sorted before journal encoding for deterministic downstream reconciliation. diff --git a/contracts/v16/dune b/contracts/v16/dune new file mode 100644 index 0000000..0e7e927 --- /dev/null +++ b/contracts/v16/dune @@ -0,0 +1,36 @@ +(install + (section share) + (package trading_engine) + (files + (journal.schema.json as contracts/v16/journal.schema.json) + (scenario-stream.schema.json as contracts/v16/scenario-stream.schema.json) + (scenario.schema.json as contracts/v16/scenario.schema.json) + (fixtures/demo.journal.jsonl as contracts/v16/fixtures/demo.journal.jsonl) + (fixtures/demo.scenario.json as contracts/v16/fixtures/demo.scenario.json) + (fixtures/demo.scenario.jsonl + as + contracts/v16/fixtures/demo.scenario.jsonl) + (fixtures/fill-clipped.journal.jsonl + as + contracts/v16/fixtures/fill-clipped.journal.jsonl) + (fixtures/fill-clipped.scenario.json + as + contracts/v16/fixtures/fill-clipped.scenario.json) + (fixtures/quote-trade.journal.jsonl + as + contracts/v16/fixtures/quote-trade.journal.jsonl) + (fixtures/quote-trade.scenario.json + as + contracts/v16/fixtures/quote-trade.scenario.json) + (fixtures/quote-trade.scenario.jsonl + as + contracts/v16/fixtures/quote-trade.scenario.jsonl) + (fixtures/order-book.journal.jsonl + as + contracts/v16/fixtures/order-book.journal.jsonl) + (fixtures/order-book.scenario.json + as + contracts/v16/fixtures/order-book.scenario.json) + (fixtures/order-book.scenario.jsonl + as + contracts/v16/fixtures/order-book.scenario.jsonl))) diff --git a/contracts/v16/fixtures/demo.journal.jsonl b/contracts/v16/fixtures/demo.journal.jsonl new file mode 100644 index 0000000..2590d37 --- /dev/null +++ b/contracts/v16/fixtures/demo.journal.jsonl @@ -0,0 +1,29 @@ +{"contract_version":"16","engine_sequence":"1","event_id":"demo-event-000000000001","causation_ids":[],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"run_started","payload":{"scenario_sha256":"a56b9b38f18d93e2953f90c8b052026d174e91c01a9465f8070a22040820a78d","execution_model":"completed_bar_adverse_touch_v1"}} +{"contract_version":"16","engine_sequence":"2","event_id":"demo-event-000000000002","causation_ids":["demo-event-000000000001"],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"initial_state","payload":{"portfolio":{"cash":[{"currency":"USD","amount":"10000"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"1","cost_basis":"90","realized_pnl":"5","dividend_pnl":"1","execution_fees":"0.5","borrow_fees":"0.25"}],"marks":[{"instrument_id":"demo-equity-acme","price":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}]},"valuation":{"base_currency":"USD","cash":"10000","net_market_value":"100","long_market_value":"100","short_market_value":"0","gross_exposure":"100","cost_basis":"90","realized_pnl":"5","unrealized_pnl":"10","equity":"10100","dividend_pnl":"1","execution_fees":"0.5","borrow_fees":"0.25","total_fees":"0.75","cash_balances":[{"currency":"USD","amount":"10000","fx_rate":"1","base_value":"10000","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"0","base_settled_value":"10000","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quote_currency":"USD","quantity":"1","mark":"100","fx_rate":"1","market_value":"100","base_market_value":"100","cost_basis":"90","base_cost_basis":"90","realized_pnl":"5","base_realized_pnl":"5","unrealized_pnl":"10","base_unrealized_pnl":"10","dividend_pnl":"1","base_dividend_pnl":"1","execution_fees":"0.5","base_execution_fees":"0.5","borrow_fees":"0.25","base_borrow_fees":"0.25","total_fees":"0.75","base_total_fees":"0.75","execution_fee_components":[],"settled_quantity":"1","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0","settled_cash":"10000","unsettled_cash":"0","margin":{"initial_requirement":"50","maintenance_requirement":"25","initial_excess":"10050","maintenance_excess":"10075","margin_call":false},"group_exposures":[]}}} +{"contract_version":"16","engine_sequence":"3","event_id":"demo-event-000000000003","causation_ids":["demo-event-000000000002"],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"10000","net_market_value":"100","long_market_value":"100","short_market_value":"0","gross_exposure":"100","cost_basis":"90","realized_pnl":"5","unrealized_pnl":"10","equity":"10100","dividend_pnl":"1","execution_fees":"0.5","borrow_fees":"0.25","total_fees":"0.75","cash_balances":[{"currency":"USD","amount":"10000","fx_rate":"1","base_value":"10000","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"0","base_settled_value":"10000","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quote_currency":"USD","quantity":"1","mark":"100","fx_rate":"1","market_value":"100","base_market_value":"100","cost_basis":"90","base_cost_basis":"90","realized_pnl":"5","base_realized_pnl":"5","unrealized_pnl":"10","base_unrealized_pnl":"10","dividend_pnl":"1","base_dividend_pnl":"1","execution_fees":"0.5","base_execution_fees":"0.5","borrow_fees":"0.25","base_borrow_fees":"0.25","total_fees":"0.75","base_total_fees":"0.75","execution_fee_components":[],"settled_quantity":"1","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0","settled_cash":"10000","unsettled_cash":"0","margin":{"initial_requirement":"50","maintenance_requirement":"25","initial_excess":"10050","maintenance_excess":"10075","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"4","event_id":"demo-event-000000000004","causation_ids":[],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"1","start_at":"2026-01-02T14:30:00.000000Z","end_at":"2026-01-02T21:00:00.000000Z","available_at":"2026-01-02T21:00:01.000000Z","received_at":"2026-01-02T21:00:02.000000Z","bars":[{"instrument_id":"demo-equity-acme","open":"100","high":"105","low":"99","close":"104","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-02T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"5","event_id":"demo-event-000000000005","causation_ids":["demo-event-000000000004"],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-01-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"10000","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-01-02T14:30:00.000000Z","period_end":"2026-01-02T21:00:00.000000Z","amount":"0.074201","closing_balance":"10000.074201"}} +{"contract_version":"16","engine_sequence":"6","event_id":"demo-event-000000000006","causation_ids":["demo-event-000000000004"],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"target_portfolio_requested","payload":{"basis":"weights","targets":[{"instrument_id":"demo-equity-acme","weight":"0.1","quantity":"9.715","reference_price":"104"}]}} +{"contract_version":"16","engine_sequence":"7","event_id":"demo-event-000000000007","causation_ids":["demo-event-000000000004"],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"metric_emitted","payload":{"name":"desired_weight","value":{"type":"numeric","value":"0.1"},"unit":"ratio","dimensions":{"instrument":"demo-equity-acme","source":"strategy"},"aggregation":"last"}} +{"contract_version":"16","engine_sequence":"8","event_id":"demo-event-000000000008","causation_ids":["demo-event-000000000004","demo-event-000000000006"],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"order_accepted","payload":{"order_id":"demo-order-000000000001","instrument_id":"demo-equity-acme","side":"buy","quantity":"8.715","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"target_rebalance","created_event_id":"demo-event-000000000008","updated_event_id":"demo-event-000000000008","created_sequence":"8","created_at":"2026-01-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}} +{"contract_version":"16","engine_sequence":"9","event_id":"demo-event-000000000009","causation_ids":["demo-event-000000000004"],"run_id":"demo","recorded_at":"2026-01-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"10000.074201","net_market_value":"104","long_market_value":"104","short_market_value":"0","gross_exposure":"104","cost_basis":"90","realized_pnl":"5.074201","unrealized_pnl":"14","equity":"10104.074201","dividend_pnl":"1","execution_fees":"0.5","borrow_fees":"0.25","total_fees":"0.75","cash_balances":[{"currency":"USD","amount":"10000.074201","fx_rate":"1","base_value":"10000.074201","interest":"0.074201","base_interest":"0.074201","settled_amount":"10000.074201","unsettled_amount":"0","base_settled_value":"10000.074201","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quote_currency":"USD","quantity":"1","mark":"104","fx_rate":"1","market_value":"104","base_market_value":"104","cost_basis":"90","base_cost_basis":"90","realized_pnl":"5","base_realized_pnl":"5","unrealized_pnl":"14","base_unrealized_pnl":"14","dividend_pnl":"1","base_dividend_pnl":"1","execution_fees":"0.5","base_execution_fees":"0.5","borrow_fees":"0.25","base_borrow_fees":"0.25","total_fees":"0.75","base_total_fees":"0.75","execution_fee_components":[],"settled_quantity":"1","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0.074201","settled_cash":"10000.074201","unsettled_cash":"0","margin":{"initial_requirement":"52","maintenance_requirement":"26","initial_excess":"10052.074201","maintenance_excess":"10078.074201","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"10","event_id":"demo-event-000000000010","causation_ids":[],"run_id":"demo","recorded_at":"2026-01-05T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"2","start_at":"2026-01-05T14:30:00.000000Z","end_at":"2026-01-05T21:00:00.000000Z","available_at":"2026-01-05T21:00:01.000000Z","received_at":"2026-01-05T21:00:02.000000Z","bars":[{"instrument_id":"demo-equity-acme","open":"103","high":"108","low":"102","close":"107","volume":"13"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-05T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-05T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"11","event_id":"demo-event-000000000011","causation_ids":["demo-event-000000000010"],"run_id":"demo","recorded_at":"2026-01-05T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-01-05T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"10000.074201","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-01-05T14:30:00.000000Z","period_end":"2026-01-05T21:00:00.000000Z","amount":"0.074201","closing_balance":"10000.148402"}} +{"contract_version":"16","engine_sequence":"12","event_id":"demo-event-000000000012","causation_ids":["demo-event-000000000008","demo-event-000000000010"],"run_id":"demo","recorded_at":"2026-01-05T21:00:02.000000Z","event_type":"execution_price_selected","payload":{"order_id":"demo-order-000000000001","instrument_id":"demo-equity-acme","side":"buy","reference_price":"103","spread_adjustment":"0.06","impact_adjustment":"0.13","final_price":"103.19"}} +{"contract_version":"16","engine_sequence":"13","event_id":"demo-event-000000000013","causation_ids":["demo-event-000000000012"],"run_id":"demo","recorded_at":"2026-01-05T21:00:02.000000Z","event_type":"fill_applied","payload":{"fill_id":"demo-fill-000000000001","order_id":"demo-order-000000000001","instrument_id":"demo-equity-acme","quote_currency":"USD","side":"buy","quantity":"6.5","price":"103.19","notional":"670.735","fee":"0.920735","executed_at":"2026-01-05T14:30:00.000000Z","slice_sequence":"2","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.25","quote_amount":"0.25"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.670735","quote_amount":"0.670735"}]}} +{"contract_version":"16","engine_sequence":"14","event_id":"demo-event-000000000014","causation_ids":["demo-event-000000000008","demo-event-000000000010"],"run_id":"demo","recorded_at":"2026-01-05T21:00:02.000000Z","event_type":"order_cancelled","payload":{"order":{"order_id":"demo-order-000000000001","instrument_id":"demo-equity-acme","side":"buy","quantity":"8.715","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"target_rebalance","created_event_id":"demo-event-000000000008","updated_event_id":"demo-event-000000000008","created_sequence":"8","created_at":"2026-01-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"6.5","filled_notional":"670.735","status":"cancelled","rejection_reason":null},"reason":"market_ioc"}} +{"contract_version":"16","engine_sequence":"15","event_id":"demo-event-000000000015","causation_ids":["demo-event-000000000006","demo-event-000000000010"],"run_id":"demo","recorded_at":"2026-01-05T21:00:02.000000Z","event_type":"order_accepted","payload":{"order_id":"demo-order-000000000002","instrument_id":"demo-equity-acme","side":"buy","quantity":"2.215","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"target_rebalance","created_event_id":"demo-event-000000000015","updated_event_id":"demo-event-000000000015","created_sequence":"15","created_at":"2026-01-05T21:00:02.000000Z","eligible_after_slice_sequence":"2","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}} +{"contract_version":"16","engine_sequence":"16","event_id":"demo-event-000000000016","causation_ids":["demo-event-000000000010"],"run_id":"demo","recorded_at":"2026-01-05T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"9328.492667","net_market_value":"802.5","long_market_value":"802.5","short_market_value":"0","gross_exposure":"802.5","cost_basis":"761.655735","realized_pnl":"5.148402","unrealized_pnl":"40.844265","equity":"10130.992667","dividend_pnl":"1","execution_fees":"1.420735","borrow_fees":"0.25","total_fees":"1.670735","cash_balances":[{"currency":"USD","amount":"9328.492667","fx_rate":"1","base_value":"9328.492667","interest":"0.148402","base_interest":"0.148402","settled_amount":"9328.492667","unsettled_amount":"0","base_settled_value":"9328.492667","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quote_currency":"USD","quantity":"7.5","mark":"107","fx_rate":"1","market_value":"802.5","base_market_value":"802.5","cost_basis":"761.655735","base_cost_basis":"761.655735","realized_pnl":"5","base_realized_pnl":"5","unrealized_pnl":"40.844265","base_unrealized_pnl":"40.844265","dividend_pnl":"1","base_dividend_pnl":"1","execution_fees":"1.420735","base_execution_fees":"1.420735","borrow_fees":"0.25","base_borrow_fees":"0.25","total_fees":"1.670735","base_total_fees":"1.670735","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.25","quote_currency":"USD","quote_amount":"0.25","base_amount":"0.25"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.670735","quote_currency":"USD","quote_amount":"0.670735","base_amount":"0.670735"}],"settled_quantity":"7.5","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.25","quote_currency":"USD","quote_amount":"0.25","base_amount":"0.25"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.670735","quote_currency":"USD","quote_amount":"0.670735","base_amount":"0.670735"}],"cash_interest":"0.148402","settled_cash":"9328.492667","unsettled_cash":"0","margin":{"initial_requirement":"401.25","maintenance_requirement":"200.625","initial_excess":"9729.742667","maintenance_excess":"9930.367667","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"17","event_id":"demo-event-000000000017","causation_ids":[],"run_id":"demo","recorded_at":"2026-01-06T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"3","start_at":"2026-01-06T14:30:00.000000Z","end_at":"2026-01-06T21:00:00.000000Z","available_at":"2026-01-06T21:00:01.000000Z","received_at":"2026-01-06T21:00:02.000000Z","bars":[{"instrument_id":"demo-equity-acme","open":"107","high":"109","low":"104","close":"105","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-06T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-06T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"18","event_id":"demo-event-000000000018","causation_ids":["demo-event-000000000017"],"run_id":"demo","recorded_at":"2026-01-06T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-01-06T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"9328.492667","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-01-06T14:30:00.000000Z","period_end":"2026-01-06T21:00:00.000000Z","amount":"0.069218","closing_balance":"9328.561885"}} +{"contract_version":"16","engine_sequence":"19","event_id":"demo-event-000000000019","causation_ids":["demo-event-000000000015","demo-event-000000000017"],"run_id":"demo","recorded_at":"2026-01-06T21:00:02.000000Z","event_type":"execution_price_selected","payload":{"order_id":"demo-order-000000000002","instrument_id":"demo-equity-acme","side":"buy","reference_price":"107","spread_adjustment":"0.06","impact_adjustment":"0.01","final_price":"107.07"}} +{"contract_version":"16","engine_sequence":"20","event_id":"demo-event-000000000020","causation_ids":["demo-event-000000000019"],"run_id":"demo","recorded_at":"2026-01-06T21:00:02.000000Z","event_type":"fill_applied","payload":{"fill_id":"demo-fill-000000000002","order_id":"demo-order-000000000002","instrument_id":"demo-equity-acme","quote_currency":"USD","side":"buy","quantity":"2.215","price":"107.07","notional":"237.16005","fee":"0.487161","executed_at":"2026-01-06T14:30:00.000000Z","slice_sequence":"3","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.25","quote_amount":"0.25"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.237161","quote_amount":"0.237161"}]}} +{"contract_version":"16","engine_sequence":"21","event_id":"demo-event-000000000021","causation_ids":["demo-event-000000000017"],"run_id":"demo","recorded_at":"2026-01-06T21:00:02.000000Z","event_type":"target_portfolio_requested","payload":{"basis":"quantities","targets":[{"instrument_id":"demo-equity-acme","weight":null,"quantity":"2.5","reference_price":null}]}} +{"contract_version":"16","engine_sequence":"22","event_id":"demo-event-000000000022","causation_ids":["demo-event-000000000017","demo-event-000000000021"],"run_id":"demo","recorded_at":"2026-01-06T21:00:02.000000Z","event_type":"order_accepted","payload":{"order_id":"demo-order-000000000003","instrument_id":"demo-equity-acme","side":"sell","quantity":"7.215","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"target_rebalance","created_event_id":"demo-event-000000000022","updated_event_id":"demo-event-000000000022","created_sequence":"22","created_at":"2026-01-06T21:00:02.000000Z","eligible_after_slice_sequence":"3","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}} +{"contract_version":"16","engine_sequence":"23","event_id":"demo-event-000000000023","causation_ids":["demo-event-000000000017"],"run_id":"demo","recorded_at":"2026-01-06T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"9090.914674","net_market_value":"1020.075","long_market_value":"1020.075","short_market_value":"0","gross_exposure":"1020.075","cost_basis":"999.302946","realized_pnl":"5.21762","unrealized_pnl":"20.772054","equity":"10110.989674","dividend_pnl":"1","execution_fees":"1.907896","borrow_fees":"0.25","total_fees":"2.157896","cash_balances":[{"currency":"USD","amount":"9090.914674","fx_rate":"1","base_value":"9090.914674","interest":"0.21762","base_interest":"0.21762","settled_amount":"9090.914674","unsettled_amount":"0","base_settled_value":"9090.914674","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quote_currency":"USD","quantity":"9.715","mark":"105","fx_rate":"1","market_value":"1020.075","base_market_value":"1020.075","cost_basis":"999.302946","base_cost_basis":"999.302946","realized_pnl":"5","base_realized_pnl":"5","unrealized_pnl":"20.772054","base_unrealized_pnl":"20.772054","dividend_pnl":"1","base_dividend_pnl":"1","execution_fees":"1.907896","base_execution_fees":"1.907896","borrow_fees":"0.25","base_borrow_fees":"0.25","total_fees":"2.157896","base_total_fees":"2.157896","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.5","quote_currency":"USD","quote_amount":"0.5","base_amount":"0.5"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.907896","quote_currency":"USD","quote_amount":"0.907896","base_amount":"0.907896"}],"settled_quantity":"9.715","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.5","quote_currency":"USD","quote_amount":"0.5","base_amount":"0.5"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.907896","quote_currency":"USD","quote_amount":"0.907896","base_amount":"0.907896"}],"cash_interest":"0.21762","settled_cash":"9090.914674","unsettled_cash":"0","margin":{"initial_requirement":"510.0375","maintenance_requirement":"255.01875","initial_excess":"9600.952174","maintenance_excess":"9855.970924","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"24","event_id":"demo-event-000000000024","causation_ids":[],"run_id":"demo","recorded_at":"2026-01-07T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"4","start_at":"2026-01-07T14:30:00.000000Z","end_at":"2026-01-07T21:00:00.000000Z","available_at":"2026-01-07T21:00:01.000000Z","received_at":"2026-01-07T21:00:02.000000Z","bars":[{"instrument_id":"demo-equity-acme","open":"105","high":"107","low":"103","close":"106","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-07T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-07T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"25","event_id":"demo-event-000000000025","causation_ids":["demo-event-000000000024"],"run_id":"demo","recorded_at":"2026-01-07T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-01-07T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"9090.914674","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-01-07T14:30:00.000000Z","period_end":"2026-01-07T21:00:00.000000Z","amount":"0.067455","closing_balance":"9090.982129"}} +{"contract_version":"16","engine_sequence":"26","event_id":"demo-event-000000000026","causation_ids":["demo-event-000000000022","demo-event-000000000024"],"run_id":"demo","recorded_at":"2026-01-07T21:00:02.000000Z","event_type":"execution_price_selected","payload":{"order_id":"demo-order-000000000003","instrument_id":"demo-equity-acme","side":"sell","reference_price":"105","spread_adjustment":"0.06","impact_adjustment":"0.02","final_price":"104.92"}} +{"contract_version":"16","engine_sequence":"27","event_id":"demo-event-000000000027","causation_ids":["demo-event-000000000026"],"run_id":"demo","recorded_at":"2026-01-07T21:00:02.000000Z","event_type":"fill_applied","payload":{"fill_id":"demo-fill-000000000003","order_id":"demo-order-000000000003","instrument_id":"demo-equity-acme","quote_currency":"USD","side":"sell","quantity":"7.215","price":"104.92","notional":"756.9978","fee":"1","executed_at":"2026-01-07T14:30:00.000000Z","slice_sequence":"4","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.25","quote_amount":"0.25"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"0.756998","quote_amount":"0.756998"},{"name":"maximum_adjustment","kind":"maximum_adjustment","currency":"USD","amount":"-0.006998","quote_amount":"-0.006998"}]}} +{"contract_version":"16","engine_sequence":"28","event_id":"demo-event-000000000028","causation_ids":["demo-event-000000000024"],"run_id":"demo","recorded_at":"2026-01-07T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"9846.979929","net_market_value":"265","long_market_value":"265","short_market_value":"0","gross_exposure":"265","cost_basis":"257.154644","realized_pnl":"19.134573","unrealized_pnl":"7.845356","equity":"10111.979929","dividend_pnl":"1","execution_fees":"2.907896","borrow_fees":"0.25","total_fees":"3.157896","cash_balances":[{"currency":"USD","amount":"9846.979929","fx_rate":"1","base_value":"9846.979929","interest":"0.285075","base_interest":"0.285075","settled_amount":"9846.979929","unsettled_amount":"0","base_settled_value":"9846.979929","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quote_currency":"USD","quantity":"2.5","mark":"106","fx_rate":"1","market_value":"265","base_market_value":"265","cost_basis":"257.154644","base_cost_basis":"257.154644","realized_pnl":"18.849498","base_realized_pnl":"18.849498","unrealized_pnl":"7.845356","base_unrealized_pnl":"7.845356","dividend_pnl":"1","base_dividend_pnl":"1","execution_fees":"2.907896","base_execution_fees":"2.907896","borrow_fees":"0.25","base_borrow_fees":"0.25","total_fees":"3.157896","base_total_fees":"3.157896","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.75","quote_currency":"USD","quote_amount":"0.75","base_amount":"0.75"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"1.664894","quote_currency":"USD","quote_amount":"1.664894","base_amount":"1.664894"},{"name":"maximum_adjustment","kind":"maximum_adjustment","currency":"USD","amount":"-0.006998","quote_currency":"USD","quote_amount":"-0.006998","base_amount":"-0.006998"}],"settled_quantity":"2.5","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.75","quote_currency":"USD","quote_amount":"0.75","base_amount":"0.75"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"1.664894","quote_currency":"USD","quote_amount":"1.664894","base_amount":"1.664894"},{"name":"maximum_adjustment","kind":"maximum_adjustment","currency":"USD","amount":"-0.006998","quote_currency":"USD","quote_amount":"-0.006998","base_amount":"-0.006998"}],"cash_interest":"0.285075","settled_cash":"9846.979929","unsettled_cash":"0","margin":{"initial_requirement":"132.5","maintenance_requirement":"66.25","initial_excess":"9979.479929","maintenance_excess":"10045.729929","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"29","event_id":"demo-event-000000000029","causation_ids":["demo-event-000000000028"],"run_id":"demo","recorded_at":"2026-01-07T21:00:02.000000Z","event_type":"run_completed","payload":{"scenario_sha256":"a56b9b38f18d93e2953f90c8b052026d174e91c01a9465f8070a22040820a78d","execution_model":"completed_bar_adverse_touch_v1","valuation":{"base_currency":"USD","cash":"9846.979929","net_market_value":"265","long_market_value":"265","short_market_value":"0","gross_exposure":"265","cost_basis":"257.154644","realized_pnl":"19.134573","unrealized_pnl":"7.845356","equity":"10111.979929","dividend_pnl":"1","execution_fees":"2.907896","borrow_fees":"0.25","total_fees":"3.157896","cash_balances":[{"currency":"USD","amount":"9846.979929","fx_rate":"1","base_value":"9846.979929","interest":"0.285075","base_interest":"0.285075","settled_amount":"9846.979929","unsettled_amount":"0","base_settled_value":"9846.979929","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quote_currency":"USD","quantity":"2.5","mark":"106","fx_rate":"1","market_value":"265","base_market_value":"265","cost_basis":"257.154644","base_cost_basis":"257.154644","realized_pnl":"18.849498","base_realized_pnl":"18.849498","unrealized_pnl":"7.845356","base_unrealized_pnl":"7.845356","dividend_pnl":"1","base_dividend_pnl":"1","execution_fees":"2.907896","base_execution_fees":"2.907896","borrow_fees":"0.25","base_borrow_fees":"0.25","total_fees":"3.157896","base_total_fees":"3.157896","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.75","quote_currency":"USD","quote_amount":"0.75","base_amount":"0.75"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"1.664894","quote_currency":"USD","quote_amount":"1.664894","base_amount":"1.664894"},{"name":"maximum_adjustment","kind":"maximum_adjustment","currency":"USD","amount":"-0.006998","quote_currency":"USD","quote_amount":"-0.006998","base_amount":"-0.006998"}],"settled_quantity":"2.5","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"0.75","quote_currency":"USD","quote_amount":"0.75","base_amount":"0.75"},{"name":"exchange","kind":"notional_bps","currency":"USD","amount":"1.664894","quote_currency":"USD","quote_amount":"1.664894","base_amount":"1.664894"},{"name":"maximum_adjustment","kind":"maximum_adjustment","currency":"USD","amount":"-0.006998","quote_currency":"USD","quote_amount":"-0.006998","base_amount":"-0.006998"}],"cash_interest":"0.285075","settled_cash":"9846.979929","unsettled_cash":"0","margin":{"initial_requirement":"132.5","maintenance_requirement":"66.25","initial_excess":"9979.479929","maintenance_excess":"10045.729929","margin_call":false},"group_exposures":[]},"order_counts":{"total":3,"active":0,"filled":2,"rejected":0,"cancelled":1}}} diff --git a/contracts/v16/fixtures/demo.scenario.json b/contracts/v16/fixtures/demo.scenario.json new file mode 100644 index 0000000..b8b8fb3 --- /dev/null +++ b/contracts/v16/fixtures/demo.scenario.json @@ -0,0 +1,468 @@ +{ + "contract_version": "16", + "metadata": { + "producer": "trading-engine-demo", + "purpose": "deterministic conformance fixture" + }, + "run_id": "demo", + "base_currency": "USD", + "initial_portfolio": { + "cash": [ + { + "currency": "USD", + "amount": "10000" + } + ], + "positions": [ + { + "instrument_id": "demo-equity-acme", + "quantity": "1", + "cost_basis": "90", + "realized_pnl": "5", + "dividend_pnl": "1", + "execution_fees": "0.5", + "borrow_fees": "0.25" + } + ], + "marks": [ + { + "instrument_id": "demo-equity-acme", + "price": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ] + }, + "instruments": [ + { + "instrument_id": "demo-equity-acme", + "symbol": "ACME", + "quote_currency": "USD", + "tick_size": "0.01", + "lot_size": "0.001" + } + ], + "venue_calendars": [ + { + "calendar_id": "demo-xnas-2026", + "calendar_version": "1", + "venue_id": "XNAS", + "instrument_ids": [ + "demo-equity-acme" + ], + "sessions": [ + { + "session_date": "2026-01-01", + "policy": "holiday", + "phases": [] + }, + { + "session_date": "2026-01-02", + "policy": "regular", + "phases": [ + { + "phase": "premarket", + "opens_at": "2026-01-02T09:00:00Z", + "closes_at": "2026-01-02T14:25:00Z" + }, + { + "phase": "opening_auction", + "opens_at": "2026-01-02T14:25:00Z", + "closes_at": "2026-01-02T14:30:00Z" + }, + { + "phase": "regular", + "opens_at": "2026-01-02T14:30:00Z", + "closes_at": "2026-01-02T20:55:00Z" + }, + { + "phase": "closing_auction", + "opens_at": "2026-01-02T20:55:00Z", + "closes_at": "2026-01-02T21:00:00Z" + }, + { + "phase": "postmarket", + "opens_at": "2026-01-02T21:00:00Z", + "closes_at": "2026-01-03T01:00:00Z" + } + ] + }, + { + "session_date": "2026-01-05", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-05T14:30:00Z", + "closes_at": "2026-01-05T21:00:00Z" + } + ] + }, + { + "session_date": "2026-01-06", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-06T14:30:00Z", + "closes_at": "2026-01-06T21:00:00Z" + } + ] + }, + { + "session_date": "2026-01-07", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-07T14:30:00Z", + "closes_at": "2026-01-07T21:00:00Z" + } + ] + }, + { + "session_date": "2026-01-08", + "policy": "early_close", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-01-08T14:30:00Z", + "closes_at": "2026-01-08T18:00:00Z" + } + ] + } + ] + } + ], + "risk": { + "max_gross_exposure": "1000000", + "max_leverage": "2", + "short_borrow_bps": 100, + "instrument_policies": [ + { + "instrument_id": "demo-equity-acme", + "max_order_quantity": "1000", + "max_long_position": "1000", + "max_short_position": "1000", + "max_notional_exposure": "1000000", + "initial_margin_bps": 5000, + "maintenance_margin_bps": 2500, + "shorting_allowed": true + } + ], + "groups": [] + }, + "execution": { + "model": "completed_bar_adverse_touch_v1", + "configuration": { + "version": "1", + "participation_bps": 5000, + "fee_schedules": [ + { + "schedule_id": "demo-acme-fees-v1", + "instrument_id": "demo-equity-acme", + "settlement_currency": "USD", + "minimum": "0.3", + "maximum": "1", + "components": [ + { + "name": "broker", + "currency": "USD", + "kind": "fixed", + "value": "0.25", + "rounding": "up", + "applies_to": "any" + }, + { + "name": "exchange", + "currency": "USD", + "kind": "notional_bps", + "value": 10, + "rounding": "up", + "applies_to": "taker" + }, + { + "name": "maker_rebate", + "currency": "USD", + "kind": "notional_bps", + "value": -2, + "rounding": "nearest", + "applies_to": "maker" + } + ] + } + ], + "spread_model": { + "model": "fixed_half_spread_v1", + "half_spread_bps": 5 + }, + "impact_model": { + "model": "linear_participation_v1", + "coefficient_bps": 25, + "missing_volume_policy": "reject" + } + } + }, + "max_internal_events": 1000, + "schedule": [ + { + "after_slice_sequence": "1", + "intents": [ + { + "type": "target_weights", + "targets": [ + { + "instrument_id": "demo-equity-acme", + "weight": "0.1" + } + ] + }, + { + "type": "emit_metric", + "name": "desired_weight", + "value": { "type": "numeric", "value": "0.1" }, + "unit": "ratio", + "dimensions": { "source": "strategy", "instrument": "demo-equity-acme" }, + "aggregation": "last" + } + ] + }, + { + "after_slice_sequence": "3", + "intents": [ + { + "type": "target_quantities", + "targets": [ + { + "instrument_id": "demo-equity-acme", + "quantity": "2.5" + } + ] + } + ] + } + ], + "slices": [ + { + "slice_sequence": "1", + "start_at": "2026-01-02T14:30:00Z", + "end_at": "2026-01-02T21:00:00Z", + "available_at": "2026-01-02T21:00:01Z", + "received_at": "2026-01-02T21:00:02Z", + "bars": [ + { + "instrument_id": "demo-equity-acme", + "open": "100", + "high": "105", + "low": "99", + "close": "104", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "demo-equity-acme", + "effective_at": "2026-01-02T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-01-02T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + }, + { + "slice_sequence": "2", + "start_at": "2026-01-05T14:30:00Z", + "end_at": "2026-01-05T21:00:00Z", + "available_at": "2026-01-05T21:00:01Z", + "received_at": "2026-01-05T21:00:02Z", + "bars": [ + { + "instrument_id": "demo-equity-acme", + "open": "103", + "high": "108", + "low": "102", + "close": "107", + "volume": "13" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "demo-equity-acme", + "effective_at": "2026-01-05T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-01-05T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + }, + { + "slice_sequence": "3", + "start_at": "2026-01-06T14:30:00Z", + "end_at": "2026-01-06T21:00:00Z", + "available_at": "2026-01-06T21:00:01Z", + "received_at": "2026-01-06T21:00:02Z", + "bars": [ + { + "instrument_id": "demo-equity-acme", + "open": "107", + "high": "109", + "low": "104", + "close": "105", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "demo-equity-acme", + "effective_at": "2026-01-06T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-01-06T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + }, + { + "slice_sequence": "4", + "start_at": "2026-01-07T14:30:00Z", + "end_at": "2026-01-07T21:00:00Z", + "available_at": "2026-01-07T21:00:01Z", + "received_at": "2026-01-07T21:00:02Z", + "bars": [ + { + "instrument_id": "demo-equity-acme", + "open": "105", + "high": "107", + "low": "103", + "close": "106", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "demo-equity-acme", + "effective_at": "2026-01-07T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-01-07T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + } + ], + "financing": { + "day_count": "actual_365", + "compounding": "simple", + "borrow_missing_data": "reject", + "cash_missing_data": "reject", + "locate_policy": "clip_fill", + "recall_policy": "close_out" + }, + "settlement": { + "cash_buying_power": "total_cash", + "position_availability": "total_positions", + "calendars": [ + { + "calendar_id": "default-settlement", + "version": "1", + "business_dates": [ + "2026-01-02", + "2026-01-05", + "2026-01-06", + "2026-01-07", + "2026-01-08", + "2026-01-09", + "2026-02-02", + "2026-02-03", + "2026-02-04", + "2026-02-05" + ] + } + ], + "rules": [ + { + "instrument_id": "demo-equity-acme", + "calendar_id": "default-settlement", + "lag_business_days": 1 + } + ] + } +} diff --git a/contracts/v16/fixtures/demo.scenario.jsonl b/contracts/v16/fixtures/demo.scenario.jsonl new file mode 100644 index 0000000..28012c0 --- /dev/null +++ b/contracts/v16/fixtures/demo.scenario.jsonl @@ -0,0 +1,6 @@ +{"contract_version":"16","scenario_sequence":"1","record_type":"scenario_header","payload":{"metadata":{"producer":"trading-engine-demo","purpose":"deterministic conformance fixture"},"run_id":"demo","base_currency":"USD","initial_portfolio":{"cash":[{"currency":"USD","amount":"10000"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"1","cost_basis":"90","realized_pnl":"5","dividend_pnl":"1","execution_fees":"0.5","borrow_fees":"0.25"}],"marks":[{"instrument_id":"demo-equity-acme","price":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}]},"instruments":[{"instrument_id":"demo-equity-acme","symbol":"ACME","quote_currency":"USD","tick_size":"0.01","lot_size":"0.001"}],"venue_calendars":[{"calendar_id":"demo-xnas-2026","calendar_version":"1","venue_id":"XNAS","instrument_ids":["demo-equity-acme"],"sessions":[{"session_date":"2026-01-01","policy":"holiday","phases":[]},{"session_date":"2026-01-02","policy":"regular","phases":[{"phase":"premarket","opens_at":"2026-01-02T09:00:00Z","closes_at":"2026-01-02T14:25:00Z"},{"phase":"opening_auction","opens_at":"2026-01-02T14:25:00Z","closes_at":"2026-01-02T14:30:00Z"},{"phase":"regular","opens_at":"2026-01-02T14:30:00Z","closes_at":"2026-01-02T20:55:00Z"},{"phase":"closing_auction","opens_at":"2026-01-02T20:55:00Z","closes_at":"2026-01-02T21:00:00Z"},{"phase":"postmarket","opens_at":"2026-01-02T21:00:00Z","closes_at":"2026-01-03T01:00:00Z"}]},{"session_date":"2026-01-05","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-05T14:30:00Z","closes_at":"2026-01-05T21:00:00Z"}]},{"session_date":"2026-01-06","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-06T14:30:00Z","closes_at":"2026-01-06T21:00:00Z"}]},{"session_date":"2026-01-07","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-07T14:30:00Z","closes_at":"2026-01-07T21:00:00Z"}]},{"session_date":"2026-01-08","policy":"early_close","phases":[{"phase":"regular","opens_at":"2026-01-08T14:30:00Z","closes_at":"2026-01-08T18:00:00Z"}]}]}],"risk":{"max_gross_exposure":"1000000","max_leverage":"2","short_borrow_bps":100,"instrument_policies":[{"instrument_id":"demo-equity-acme","max_order_quantity":"1000","max_long_position":"1000","max_short_position":"1000","max_notional_exposure":"1000000","initial_margin_bps":5000,"maintenance_margin_bps":2500,"shorting_allowed":true}],"groups":[]},"execution":{"model":"completed_bar_adverse_touch_v1","configuration":{"version":"1","participation_bps":5000,"fee_schedules":[{"schedule_id":"demo-acme-fees-v1","instrument_id":"demo-equity-acme","settlement_currency":"USD","minimum":"0.3","maximum":"1","components":[{"name":"broker","currency":"USD","kind":"fixed","value":"0.25","rounding":"up","applies_to":"any"},{"name":"exchange","currency":"USD","kind":"notional_bps","value":10,"rounding":"up","applies_to":"taker"},{"name":"maker_rebate","currency":"USD","kind":"notional_bps","value":-2,"rounding":"nearest","applies_to":"maker"}]}],"spread_model":{"model":"fixed_half_spread_v1","half_spread_bps":5},"impact_model":{"model":"linear_participation_v1","coefficient_bps":25,"missing_volume_policy":"reject"}}},"max_internal_events":1000,"financing":{"day_count":"actual_365","compounding":"simple","borrow_missing_data":"reject","cash_missing_data":"reject","locate_policy":"clip_fill","recall_policy":"close_out"},"settlement":{"cash_buying_power":"total_cash","position_availability":"total_positions","calendars":[{"calendar_id":"default-settlement","version":"1","business_dates":["2026-01-02","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-02-02","2026-02-03","2026-02-04","2026-02-05"]}],"rules":[{"instrument_id":"demo-equity-acme","calendar_id":"default-settlement","lag_business_days":1}]}}} +{"contract_version":"16","payload":{"intents":[{"targets":[{"instrument_id":"demo-equity-acme","weight":"0.1"}],"type":"target_weights"},{"aggregation":"last","dimensions":{"instrument":"demo-equity-acme","source":"strategy"},"name":"desired_weight","type":"emit_metric","unit":"ratio","value":{"type":"numeric","value":"0.1"}}],"market_slice":{"available_at":"2026-01-02T21:00:01Z","bars":[{"close":"104","high":"105","instrument_id":"demo-equity-acme","low":"99","open":"100","volume":"100"}],"corporate_actions":[],"end_at":"2026-01-02T21:00:00Z","fx_rates":[{"currency":"USD","rate":"1"}],"received_at":"2026-01-02T21:00:02Z","slice_sequence":"1","start_at":"2026-01-02T14:30:00Z","borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-02T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-02T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}},"record_type":"market_slice","scenario_sequence":"2"} +{"contract_version":"16","payload":{"intents":[],"market_slice":{"available_at":"2026-01-05T21:00:01Z","bars":[{"close":"107","high":"108","instrument_id":"demo-equity-acme","low":"102","open":"103","volume":"13"}],"corporate_actions":[],"end_at":"2026-01-05T21:00:00Z","fx_rates":[{"currency":"USD","rate":"1"}],"received_at":"2026-01-05T21:00:02Z","slice_sequence":"2","start_at":"2026-01-05T14:30:00Z","borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-05T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-05T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}},"record_type":"market_slice","scenario_sequence":"3"} +{"contract_version":"16","payload":{"intents":[{"targets":[{"instrument_id":"demo-equity-acme","quantity":"2.5"}],"type":"target_quantities"}],"market_slice":{"available_at":"2026-01-06T21:00:01Z","bars":[{"close":"105","high":"109","instrument_id":"demo-equity-acme","low":"104","open":"107","volume":"100"}],"corporate_actions":[],"end_at":"2026-01-06T21:00:00Z","fx_rates":[{"currency":"USD","rate":"1"}],"received_at":"2026-01-06T21:00:02Z","slice_sequence":"3","start_at":"2026-01-06T14:30:00Z","borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-06T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-06T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}},"record_type":"market_slice","scenario_sequence":"4"} +{"contract_version":"16","payload":{"intents":[],"market_slice":{"available_at":"2026-01-07T21:00:01Z","bars":[{"close":"106","high":"107","instrument_id":"demo-equity-acme","low":"103","open":"105","volume":"100"}],"corporate_actions":[],"end_at":"2026-01-07T21:00:00Z","fx_rates":[{"currency":"USD","rate":"1"}],"received_at":"2026-01-07T21:00:02Z","slice_sequence":"4","start_at":"2026-01-07T14:30:00Z","borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-07T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-07T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}},"record_type":"market_slice","scenario_sequence":"5"} +{"contract_version":"16","payload":{"slice_count":"4"},"record_type":"scenario_end","scenario_sequence":"6"} diff --git a/contracts/v16/fixtures/fill-clipped.journal.jsonl b/contracts/v16/fixtures/fill-clipped.journal.jsonl new file mode 100644 index 0000000..3419c5a --- /dev/null +++ b/contracts/v16/fixtures/fill-clipped.journal.jsonl @@ -0,0 +1,13 @@ +{"contract_version":"16","engine_sequence":"1","event_id":"fill-clipped-event-000000000001","causation_ids":[],"run_id":"fill-clipped","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"run_started","payload":{"scenario_sha256":"dd1ca1913eb2ef4e0070887065763fb75e81a1e27b4497bf2afd09546975bd07","execution_model":"completed_bar_v1"}} +{"contract_version":"16","engine_sequence":"2","event_id":"fill-clipped-event-000000000002","causation_ids":["fill-clipped-event-000000000001"],"run_id":"fill-clipped","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"initial_state","payload":{"portfolio":{"cash":[{"currency":"USD","amount":"550"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"valuation":{"base_currency":"USD","cash":"550","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0","unrealized_pnl":"0","equity":"550","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"550","fx_rate":"1","base_value":"550","interest":"0","base_interest":"0","settled_amount":"550","unsettled_amount":"0","base_settled_value":"550","base_unsettled_value":"0"}],"positions":[],"execution_fee_components":[],"cash_interest":"0","settled_cash":"550","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"550","maintenance_excess":"550","margin_call":false},"group_exposures":[]}}} +{"contract_version":"16","engine_sequence":"3","event_id":"fill-clipped-event-000000000003","causation_ids":["fill-clipped-event-000000000002"],"run_id":"fill-clipped","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"550","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0","unrealized_pnl":"0","equity":"550","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"550","fx_rate":"1","base_value":"550","interest":"0","base_interest":"0","settled_amount":"550","unsettled_amount":"0","base_settled_value":"550","base_unsettled_value":"0"}],"positions":[],"execution_fee_components":[],"cash_interest":"0","settled_cash":"550","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"550","maintenance_excess":"550","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"4","event_id":"fill-clipped-event-000000000004","causation_ids":[],"run_id":"fill-clipped","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"1","start_at":"2026-02-02T14:30:00.000000Z","end_at":"2026-02-02T21:00:00.000000Z","available_at":"2026-02-02T21:00:01.000000Z","received_at":"2026-02-02T21:00:02.000000Z","bars":[{"instrument_id":"clip-equity","open":"50","high":"50","low":"50","close":"50","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-02T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"5","event_id":"fill-clipped-event-000000000005","causation_ids":["fill-clipped-event-000000000004"],"run_id":"fill-clipped","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-02-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"550","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-02-02T14:30:00.000000Z","period_end":"2026-02-02T21:00:00.000000Z","amount":"0.004081","closing_balance":"550.004081"}} +{"contract_version":"16","engine_sequence":"6","event_id":"fill-clipped-event-000000000006","causation_ids":["fill-clipped-event-000000000004"],"run_id":"fill-clipped","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"order_accepted","payload":{"order_id":"fill-clipped-order-000000000001","instrument_id":"clip-equity","side":"buy","quantity":"10","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"direct","created_event_id":"fill-clipped-event-000000000006","updated_event_id":"fill-clipped-event-000000000006","created_sequence":"6","created_at":"2026-02-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}} +{"contract_version":"16","engine_sequence":"7","event_id":"fill-clipped-event-000000000007","causation_ids":["fill-clipped-event-000000000004"],"run_id":"fill-clipped","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"550.004081","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0.004081","unrealized_pnl":"0","equity":"550.004081","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"550.004081","fx_rate":"1","base_value":"550.004081","interest":"0.004081","base_interest":"0.004081","settled_amount":"550.004081","unsettled_amount":"0","base_settled_value":"550.004081","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"0","mark":"50","fx_rate":"1","market_value":"0","base_market_value":"0","cost_basis":"0","base_cost_basis":"0","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"0","base_unrealized_pnl":"0","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"0","base_execution_fees":"0","borrow_fees":"0","base_borrow_fees":"0","total_fees":"0","base_total_fees":"0","execution_fee_components":[],"settled_quantity":"0","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0.004081","settled_cash":"550.004081","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"550.004081","maintenance_excess":"550.004081","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"8","event_id":"fill-clipped-event-000000000008","causation_ids":[],"run_id":"fill-clipped","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"2","start_at":"2026-02-03T14:30:00.000000Z","end_at":"2026-02-03T21:00:00.000000Z","available_at":"2026-02-03T21:00:01.000000Z","received_at":"2026-02-03T21:00:02.000000Z","bars":[{"instrument_id":"clip-equity","open":"100","high":"100","low":"100","close":"100","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-03T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-03T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"9","event_id":"fill-clipped-event-000000000009","causation_ids":["fill-clipped-event-000000000008"],"run_id":"fill-clipped","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-02-03T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"550.004081","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-02-03T14:30:00.000000Z","period_end":"2026-02-03T21:00:00.000000Z","amount":"0.004081","closing_balance":"550.008162"}} +{"contract_version":"16","engine_sequence":"10","event_id":"fill-clipped-event-000000000010","causation_ids":["fill-clipped-event-000000000006","fill-clipped-event-000000000008"],"run_id":"fill-clipped","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"fill_clipped","payload":{"reason":{"version":"1","policy":"max_leverage","threshold":{"unit":"ratio","value":"1"}},"order_id":"fill-clipped-order-000000000001","instrument_id":"clip-equity","proposed_quantity":"10","permitted_quantity":"0","price":"100"}} +{"contract_version":"16","engine_sequence":"11","event_id":"fill-clipped-event-000000000011","causation_ids":["fill-clipped-event-000000000006","fill-clipped-event-000000000008"],"run_id":"fill-clipped","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"order_cancelled","payload":{"order":{"order_id":"fill-clipped-order-000000000001","instrument_id":"clip-equity","side":"buy","quantity":"10","order_kind":"market","trigger_price":null,"limit_price":null,"time_in_force":"ioc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"direct","created_event_id":"fill-clipped-event-000000000006","updated_event_id":"fill-clipped-event-000000000006","created_sequence":"6","created_at":"2026-02-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"cancelled","rejection_reason":null},"reason":"market_ioc"}} +{"contract_version":"16","engine_sequence":"12","event_id":"fill-clipped-event-000000000012","causation_ids":["fill-clipped-event-000000000008"],"run_id":"fill-clipped","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"550.008162","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0.008162","unrealized_pnl":"0","equity":"550.008162","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"550.008162","fx_rate":"1","base_value":"550.008162","interest":"0.008162","base_interest":"0.008162","settled_amount":"550.008162","unsettled_amount":"0","base_settled_value":"550.008162","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"0","mark":"100","fx_rate":"1","market_value":"0","base_market_value":"0","cost_basis":"0","base_cost_basis":"0","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"0","base_unrealized_pnl":"0","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"0","base_execution_fees":"0","borrow_fees":"0","base_borrow_fees":"0","total_fees":"0","base_total_fees":"0","execution_fee_components":[],"settled_quantity":"0","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0.008162","settled_cash":"550.008162","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"550.008162","maintenance_excess":"550.008162","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"13","event_id":"fill-clipped-event-000000000013","causation_ids":["fill-clipped-event-000000000012"],"run_id":"fill-clipped","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"run_completed","payload":{"scenario_sha256":"dd1ca1913eb2ef4e0070887065763fb75e81a1e27b4497bf2afd09546975bd07","execution_model":"completed_bar_v1","valuation":{"base_currency":"USD","cash":"550.008162","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0.008162","unrealized_pnl":"0","equity":"550.008162","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"550.008162","fx_rate":"1","base_value":"550.008162","interest":"0.008162","base_interest":"0.008162","settled_amount":"550.008162","unsettled_amount":"0","base_settled_value":"550.008162","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"0","mark":"100","fx_rate":"1","market_value":"0","base_market_value":"0","cost_basis":"0","base_cost_basis":"0","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"0","base_unrealized_pnl":"0","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"0","base_execution_fees":"0","borrow_fees":"0","base_borrow_fees":"0","total_fees":"0","base_total_fees":"0","execution_fee_components":[],"settled_quantity":"0","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0.008162","settled_cash":"550.008162","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"550.008162","maintenance_excess":"550.008162","margin_call":false},"group_exposures":[]},"order_counts":{"total":1,"active":0,"filled":0,"rejected":0,"cancelled":1}}} diff --git a/contracts/v16/fixtures/fill-clipped.scenario.json b/contracts/v16/fixtures/fill-clipped.scenario.json new file mode 100644 index 0000000..eea4e6d --- /dev/null +++ b/contracts/v16/fixtures/fill-clipped.scenario.json @@ -0,0 +1,273 @@ +{ + "contract_version": "16", + "metadata": { + "producer": "trading-engine", + "purpose": "fill clipping conformance fixture" + }, + "run_id": "fill-clipped", + "base_currency": "USD", + "initial_portfolio": { + "cash": [ + { + "currency": "USD", + "amount": "550" + } + ], + "positions": [], + "marks": [], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ] + }, + "instruments": [ + { + "instrument_id": "clip-equity", + "symbol": "CLIP", + "quote_currency": "USD", + "tick_size": "0.01", + "lot_size": "1" + } + ], + "venue_calendars": [ + { + "calendar_id": "clip-xnas-2026", + "calendar_version": "1", + "venue_id": "XNAS", + "instrument_ids": [ + "clip-equity" + ], + "sessions": [ + { + "session_date": "2026-02-02", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-02T14:30:00Z", + "closes_at": "2026-02-02T21:00:00Z" + } + ] + }, + { + "session_date": "2026-02-03", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-03T14:30:00Z", + "closes_at": "2026-02-03T21:00:00Z" + } + ] + }, + { + "session_date": "2026-02-04", + "policy": "early_close", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-04T14:30:00Z", + "closes_at": "2026-02-04T18:00:00Z" + } + ] + } + ] + } + ], + "risk": { + "max_gross_exposure": "1000000000", + "max_leverage": "1", + "short_borrow_bps": 100, + "instrument_policies": [ + { + "instrument_id": "clip-equity", + "max_order_quantity": "1000", + "max_long_position": "1000", + "max_short_position": "1000", + "max_notional_exposure": "1000000000", + "initial_margin_bps": 5000, + "maintenance_margin_bps": 2500, + "shorting_allowed": true + } + ], + "groups": [] + }, + "execution": { + "model": "completed_bar_v1", + "configuration": { + "version": "2", + "participation_bps": 10000, + "fee_schedules": [ + { + "schedule_id": "clip-fees-v1", + "instrument_id": "clip-equity", + "settlement_currency": "USD", + "minimum": null, + "maximum": null, + "components": [ + { + "name": "broker", + "currency": "USD", + "kind": "fixed", + "value": "10", + "rounding": "up", + "applies_to": "any" + } + ] + } + ] + } + }, + "max_internal_events": 1000, + "schedule": [ + { + "after_slice_sequence": "1", + "intents": [ + { + "type": "submit_order", + "instrument_id": "clip-equity", + "side": "buy", + "quantity": "10", + "order_kind": "market", + "trigger_price": null, + "limit_price": null, + "time_in_force": "ioc", + "venue_id": null, + "calendar_id": null, + "expires_at": null + } + ] + } + ], + "slices": [ + { + "slice_sequence": "1", + "start_at": "2026-02-02T14:30:00Z", + "end_at": "2026-02-02T21:00:00Z", + "available_at": "2026-02-02T21:00:01Z", + "received_at": "2026-02-02T21:00:02Z", + "bars": [ + { + "instrument_id": "clip-equity", + "open": "50", + "high": "50", + "low": "50", + "close": "50", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "clip-equity", + "effective_at": "2026-02-02T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-02-02T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + }, + { + "slice_sequence": "2", + "start_at": "2026-02-03T14:30:00Z", + "end_at": "2026-02-03T21:00:00Z", + "available_at": "2026-02-03T21:00:01Z", + "received_at": "2026-02-03T21:00:02Z", + "bars": [ + { + "instrument_id": "clip-equity", + "open": "100", + "high": "100", + "low": "100", + "close": "100", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "clip-equity", + "effective_at": "2026-02-03T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-02-03T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + } + ], + "financing": { + "day_count": "actual_365", + "compounding": "simple", + "borrow_missing_data": "reject", + "cash_missing_data": "reject", + "locate_policy": "clip_fill", + "recall_policy": "close_out" + }, + "settlement": { + "cash_buying_power": "total_cash", + "position_availability": "total_positions", + "calendars": [ + { + "calendar_id": "default-settlement", + "version": "1", + "business_dates": [ + "2026-01-02", + "2026-01-05", + "2026-01-06", + "2026-01-07", + "2026-01-08", + "2026-01-09", + "2026-02-02", + "2026-02-03", + "2026-02-04", + "2026-02-05" + ] + } + ], + "rules": [ + { + "instrument_id": "clip-equity", + "calendar_id": "default-settlement", + "lag_business_days": 1 + } + ] + } +} diff --git a/contracts/v16/fixtures/order-book.journal.jsonl b/contracts/v16/fixtures/order-book.journal.jsonl new file mode 100644 index 0000000..684543d --- /dev/null +++ b/contracts/v16/fixtures/order-book.journal.jsonl @@ -0,0 +1,13 @@ +{"contract_version":"16","engine_sequence":"1","event_id":"order-book-event-000000000001","causation_ids":[],"run_id":"order-book","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"run_started","payload":{"scenario_sha256":"10f1592feb856e3acc16a2eb3ebd23ff2c7641e092b88307e44efcd9f2072d74","execution_model":"order_book_v1"}} +{"contract_version":"16","engine_sequence":"2","event_id":"order-book-event-000000000002","causation_ids":["order-book-event-000000000001"],"run_id":"order-book","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"initial_state","payload":{"portfolio":{"cash":[{"currency":"USD","amount":"2000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"valuation":{"base_currency":"USD","cash":"2000","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0","unrealized_pnl":"0","equity":"2000","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"2000","fx_rate":"1","base_value":"2000","interest":"0","base_interest":"0","settled_amount":"2000","unsettled_amount":"0","base_settled_value":"2000","base_unsettled_value":"0"}],"positions":[],"execution_fee_components":[],"cash_interest":"0","settled_cash":"2000","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"2000","maintenance_excess":"2000","margin_call":false},"group_exposures":[]}}} +{"contract_version":"16","engine_sequence":"3","event_id":"order-book-event-000000000003","causation_ids":["order-book-event-000000000002"],"run_id":"order-book","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"2000","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0","unrealized_pnl":"0","equity":"2000","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"2000","fx_rate":"1","base_value":"2000","interest":"0","base_interest":"0","settled_amount":"2000","unsettled_amount":"0","base_settled_value":"2000","base_unsettled_value":"0"}],"positions":[],"execution_fee_components":[],"cash_interest":"0","settled_cash":"2000","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"2000","maintenance_excess":"2000","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"4","event_id":"order-book-event-000000000004","causation_ids":[],"run_id":"order-book","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"1","start_at":"2026-02-02T14:30:00.000000Z","end_at":"2026-02-02T21:00:00.000000Z","available_at":"2026-02-02T21:00:01.000000Z","received_at":"2026-02-02T21:00:02.000000Z","bars":[{"instrument_id":"clip-equity","open":"50","high":"50","low":"50","close":"50","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-02T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[{"type":"snapshot","instrument_id":"clip-equity","event_at":"2026-02-02T14:31:00.000000Z","available_at":"2026-02-02T14:31:01.000000Z","received_at":"2026-02-02T14:31:02.000000Z","ingest_sequence":"1","book_sequence":"1","bids":[{"price":"49","quantity":"20"}],"asks":[{"price":"51","quantity":"20"}]}]}} +{"contract_version":"16","engine_sequence":"5","event_id":"order-book-event-000000000005","causation_ids":["order-book-event-000000000004"],"run_id":"order-book","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-02-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"2000","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-02-02T14:30:00.000000Z","period_end":"2026-02-02T21:00:00.000000Z","amount":"0.01484","closing_balance":"2000.01484"}} +{"contract_version":"16","engine_sequence":"6","event_id":"order-book-event-000000000006","causation_ids":["order-book-event-000000000004"],"run_id":"order-book","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"order_accepted","payload":{"order_id":"order-book-order-000000000001","instrument_id":"clip-equity","side":"buy","quantity":"10","order_kind":"limit","trigger_price":null,"limit_price":"100","time_in_force":"gtc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"direct","created_event_id":"order-book-event-000000000006","updated_event_id":"order-book-event-000000000006","created_sequence":"6","created_at":"2026-02-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}} +{"contract_version":"16","engine_sequence":"7","event_id":"order-book-event-000000000007","causation_ids":["order-book-event-000000000004"],"run_id":"order-book","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"2000.01484","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0.01484","unrealized_pnl":"0","equity":"2000.01484","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"2000.01484","fx_rate":"1","base_value":"2000.01484","interest":"0.01484","base_interest":"0.01484","settled_amount":"2000.01484","unsettled_amount":"0","base_settled_value":"2000.01484","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"0","mark":"50","fx_rate":"1","market_value":"0","base_market_value":"0","cost_basis":"0","base_cost_basis":"0","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"0","base_unrealized_pnl":"0","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"0","base_execution_fees":"0","borrow_fees":"0","base_borrow_fees":"0","total_fees":"0","base_total_fees":"0","execution_fee_components":[],"settled_quantity":"0","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0.01484","settled_cash":"2000.01484","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"2000.01484","maintenance_excess":"2000.01484","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"8","event_id":"order-book-event-000000000008","causation_ids":[],"run_id":"order-book","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"2","start_at":"2026-02-03T14:30:00.000000Z","end_at":"2026-02-03T21:00:00.000000Z","available_at":"2026-02-03T21:00:01.000000Z","received_at":"2026-02-03T21:00:02.000000Z","bars":[{"instrument_id":"clip-equity","open":"100","high":"100","low":"100","close":"100","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-03T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-03T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[{"type":"snapshot","instrument_id":"clip-equity","event_at":"2026-02-03T14:31:00.000000Z","available_at":"2026-02-03T14:31:01.000000Z","received_at":"2026-02-03T14:31:02.000000Z","ingest_sequence":"1","book_sequence":"1","bids":[{"price":"100","quantity":"5"}],"asks":[{"price":"101","quantity":"20"}]},{"type":"set","instrument_id":"clip-equity","event_at":"2026-02-03T14:32:00.000000Z","available_at":"2026-02-03T14:32:01.000000Z","received_at":"2026-02-03T14:32:02.000000Z","ingest_sequence":"2","book_sequence":"2","side":"bid","price":"100","quantity":"3"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:33:00.000000Z","available_at":"2026-02-03T14:33:01.000000Z","received_at":"2026-02-03T14:33:02.000000Z","ingest_sequence":"3","book_sequence":"3","price":"100","quantity":"3","aggressor_side":"sell"},{"type":"set","instrument_id":"clip-equity","event_at":"2026-02-03T14:34:00.000000Z","available_at":"2026-02-03T14:34:01.000000Z","received_at":"2026-02-03T14:34:02.000000Z","ingest_sequence":"4","book_sequence":"4","side":"bid","price":"100","quantity":"10"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:35:00.000000Z","available_at":"2026-02-03T14:35:01.000000Z","received_at":"2026-02-03T14:35:02.000000Z","ingest_sequence":"5","book_sequence":"5","price":"100","quantity":"4","aggressor_side":"sell"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:36:00.000000Z","available_at":"2026-02-03T14:36:01.000000Z","received_at":"2026-02-03T14:36:02.000000Z","ingest_sequence":"6","book_sequence":"6","price":"100","quantity":"6","aggressor_side":"sell"}]}} +{"contract_version":"16","engine_sequence":"9","event_id":"order-book-event-000000000009","causation_ids":["order-book-event-000000000008"],"run_id":"order-book","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-02-03T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"2000.01484","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-02-03T14:30:00.000000Z","period_end":"2026-02-03T21:00:00.000000Z","amount":"0.01484","closing_balance":"2000.02968"}} +{"contract_version":"16","engine_sequence":"10","event_id":"order-book-event-000000000010","causation_ids":["order-book-event-000000000006","order-book-event-000000000008"],"run_id":"order-book","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"fill_applied","payload":{"fill_id":"order-book-fill-000000000001","order_id":"order-book-order-000000000001","instrument_id":"clip-equity","quote_currency":"USD","side":"buy","quantity":"4","price":"100","notional":"400","fee":"10","executed_at":"2026-02-03T14:35:00.000000Z","slice_sequence":"2","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"10","quote_amount":"10"}]}} +{"contract_version":"16","engine_sequence":"11","event_id":"order-book-event-000000000011","causation_ids":["order-book-event-000000000006","order-book-event-000000000008"],"run_id":"order-book","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"fill_applied","payload":{"fill_id":"order-book-fill-000000000002","order_id":"order-book-order-000000000001","instrument_id":"clip-equity","quote_currency":"USD","side":"buy","quantity":"6","price":"100","notional":"600","fee":"10","executed_at":"2026-02-03T14:36:00.000000Z","slice_sequence":"2","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"10","quote_amount":"10"}]}} +{"contract_version":"16","engine_sequence":"12","event_id":"order-book-event-000000000012","causation_ids":["order-book-event-000000000008"],"run_id":"order-book","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"980.02968","net_market_value":"1000","long_market_value":"1000","short_market_value":"0","gross_exposure":"1000","cost_basis":"1020","realized_pnl":"0.02968","unrealized_pnl":"-20","equity":"1980.02968","dividend_pnl":"0","execution_fees":"20","borrow_fees":"0","total_fees":"20","cash_balances":[{"currency":"USD","amount":"980.02968","fx_rate":"1","base_value":"980.02968","interest":"0.02968","base_interest":"0.02968","settled_amount":"980.02968","unsettled_amount":"0","base_settled_value":"980.02968","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"10","mark":"100","fx_rate":"1","market_value":"1000","base_market_value":"1000","cost_basis":"1020","base_cost_basis":"1020","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"-20","base_unrealized_pnl":"-20","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"20","base_execution_fees":"20","borrow_fees":"0","base_borrow_fees":"0","total_fees":"20","base_total_fees":"20","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"settled_quantity":"10","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"cash_interest":"0.02968","settled_cash":"980.02968","unsettled_cash":"0","margin":{"initial_requirement":"500","maintenance_requirement":"250","initial_excess":"1480.02968","maintenance_excess":"1730.02968","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"13","event_id":"order-book-event-000000000013","causation_ids":["order-book-event-000000000012"],"run_id":"order-book","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"run_completed","payload":{"scenario_sha256":"10f1592feb856e3acc16a2eb3ebd23ff2c7641e092b88307e44efcd9f2072d74","execution_model":"order_book_v1","valuation":{"base_currency":"USD","cash":"980.02968","net_market_value":"1000","long_market_value":"1000","short_market_value":"0","gross_exposure":"1000","cost_basis":"1020","realized_pnl":"0.02968","unrealized_pnl":"-20","equity":"1980.02968","dividend_pnl":"0","execution_fees":"20","borrow_fees":"0","total_fees":"20","cash_balances":[{"currency":"USD","amount":"980.02968","fx_rate":"1","base_value":"980.02968","interest":"0.02968","base_interest":"0.02968","settled_amount":"980.02968","unsettled_amount":"0","base_settled_value":"980.02968","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"10","mark":"100","fx_rate":"1","market_value":"1000","base_market_value":"1000","cost_basis":"1020","base_cost_basis":"1020","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"-20","base_unrealized_pnl":"-20","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"20","base_execution_fees":"20","borrow_fees":"0","base_borrow_fees":"0","total_fees":"20","base_total_fees":"20","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"settled_quantity":"10","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"cash_interest":"0.02968","settled_cash":"980.02968","unsettled_cash":"0","margin":{"initial_requirement":"500","maintenance_requirement":"250","initial_excess":"1480.02968","maintenance_excess":"1730.02968","margin_call":false},"group_exposures":[]},"order_counts":{"total":1,"active":0,"filled":1,"rejected":0,"cancelled":0}}} diff --git a/contracts/v16/fixtures/order-book.scenario.json b/contracts/v16/fixtures/order-book.scenario.json new file mode 100644 index 0000000..83e22e0 --- /dev/null +++ b/contracts/v16/fixtures/order-book.scenario.json @@ -0,0 +1,378 @@ +{ + "contract_version": "16", + "metadata": { + "producer": "trading-engine", + "purpose": "bounded order-book replay conformance fixture" + }, + "run_id": "order-book", + "base_currency": "USD", + "initial_portfolio": { + "cash": [ + { + "currency": "USD", + "amount": "2000" + } + ], + "positions": [], + "marks": [], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ] + }, + "instruments": [ + { + "instrument_id": "clip-equity", + "symbol": "CLIP", + "quote_currency": "USD", + "tick_size": "0.01", + "lot_size": "1" + } + ], + "venue_calendars": [ + { + "calendar_id": "clip-xnas-2026", + "calendar_version": "1", + "venue_id": "XNAS", + "instrument_ids": [ + "clip-equity" + ], + "sessions": [ + { + "session_date": "2026-02-02", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-02T14:30:00Z", + "closes_at": "2026-02-02T21:00:00Z" + } + ] + }, + { + "session_date": "2026-02-03", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-03T14:30:00Z", + "closes_at": "2026-02-03T21:00:00Z" + } + ] + }, + { + "session_date": "2026-02-04", + "policy": "early_close", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-04T14:30:00Z", + "closes_at": "2026-02-04T18:00:00Z" + } + ] + } + ] + } + ], + "risk": { + "max_gross_exposure": "1000000000", + "max_leverage": "1", + "short_borrow_bps": 100, + "instrument_policies": [ + { + "instrument_id": "clip-equity", + "max_order_quantity": "1000", + "max_long_position": "1000", + "max_short_position": "1000", + "max_notional_exposure": "1000000000", + "initial_margin_bps": 5000, + "maintenance_margin_bps": 2500, + "shorting_allowed": true + } + ], + "groups": [] + }, + "execution": { + "model": "order_book_v1", + "configuration": { + "version": "1", + "participation_bps": 10000, + "fee_schedules": [ + { + "schedule_id": "clip-fees-v1", + "instrument_id": "clip-equity", + "settlement_currency": "USD", + "minimum": null, + "maximum": null, + "components": [ + { + "name": "broker", + "currency": "USD", + "kind": "fixed", + "value": "10", + "rounding": "up", + "applies_to": "any" + } + ] + } + ], + "max_depth_levels": 10 + } + }, + "max_internal_events": 1000, + "schedule": [ + { + "after_slice_sequence": "1", + "intents": [ + { + "type": "submit_order", + "instrument_id": "clip-equity", + "side": "buy", + "quantity": "10", + "order_kind": "limit", + "trigger_price": null, + "limit_price": "100", + "time_in_force": "gtc", + "venue_id": null, + "calendar_id": null, + "expires_at": null + } + ] + } + ], + "slices": [ + { + "slice_sequence": "1", + "start_at": "2026-02-02T14:30:00Z", + "end_at": "2026-02-02T21:00:00Z", + "available_at": "2026-02-02T21:00:01Z", + "received_at": "2026-02-02T21:00:02Z", + "bars": [ + { + "instrument_id": "clip-equity", + "open": "50", + "high": "50", + "low": "50", + "close": "50", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "clip-equity", + "effective_at": "2026-02-02T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-02-02T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [ + { + "type": "snapshot", + "instrument_id": "clip-equity", + "event_at": "2026-02-02T14:31:00Z", + "available_at": "2026-02-02T14:31:01Z", + "received_at": "2026-02-02T14:31:02Z", + "ingest_sequence": "1", + "book_sequence": "1", + "bids": [ + { + "price": "49", + "quantity": "20" + } + ], + "asks": [ + { + "price": "51", + "quantity": "20" + } + ] + } + ] + }, + { + "slice_sequence": "2", + "start_at": "2026-02-03T14:30:00Z", + "end_at": "2026-02-03T21:00:00Z", + "available_at": "2026-02-03T21:00:01Z", + "received_at": "2026-02-03T21:00:02Z", + "bars": [ + { + "instrument_id": "clip-equity", + "open": "100", + "high": "100", + "low": "100", + "close": "100", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "clip-equity", + "effective_at": "2026-02-03T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-02-03T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [ + { + "type": "snapshot", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:31:00Z", + "available_at": "2026-02-03T14:31:01Z", + "received_at": "2026-02-03T14:31:02Z", + "ingest_sequence": "1", + "book_sequence": "1", + "bids": [ + { + "price": "100", + "quantity": "5" + } + ], + "asks": [ + { + "price": "101", + "quantity": "20" + } + ] + }, + { + "type": "set", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:32:00Z", + "available_at": "2026-02-03T14:32:01Z", + "received_at": "2026-02-03T14:32:02Z", + "ingest_sequence": "2", + "book_sequence": "2", + "side": "bid", + "price": "100", + "quantity": "3" + }, + { + "type": "trade", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:33:00Z", + "available_at": "2026-02-03T14:33:01Z", + "received_at": "2026-02-03T14:33:02Z", + "ingest_sequence": "3", + "book_sequence": "3", + "price": "100", + "quantity": "3", + "aggressor_side": "sell" + }, + { + "type": "set", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:34:00Z", + "available_at": "2026-02-03T14:34:01Z", + "received_at": "2026-02-03T14:34:02Z", + "ingest_sequence": "4", + "book_sequence": "4", + "side": "bid", + "price": "100", + "quantity": "10" + }, + { + "type": "trade", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:35:00Z", + "available_at": "2026-02-03T14:35:01Z", + "received_at": "2026-02-03T14:35:02Z", + "ingest_sequence": "5", + "book_sequence": "5", + "price": "100", + "quantity": "4", + "aggressor_side": "sell" + }, + { + "type": "trade", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:36:00Z", + "available_at": "2026-02-03T14:36:01Z", + "received_at": "2026-02-03T14:36:02Z", + "ingest_sequence": "6", + "book_sequence": "6", + "price": "100", + "quantity": "6", + "aggressor_side": "sell" + } + ] + } + ], + "financing": { + "day_count": "actual_365", + "compounding": "simple", + "borrow_missing_data": "reject", + "cash_missing_data": "reject", + "locate_policy": "clip_fill", + "recall_policy": "close_out" + }, + "settlement": { + "cash_buying_power": "total_cash", + "position_availability": "total_positions", + "calendars": [ + { + "calendar_id": "default-settlement", + "version": "1", + "business_dates": [ + "2026-01-02", + "2026-01-05", + "2026-01-06", + "2026-01-07", + "2026-01-08", + "2026-01-09", + "2026-02-02", + "2026-02-03", + "2026-02-04", + "2026-02-05" + ] + } + ], + "rules": [ + { + "instrument_id": "clip-equity", + "calendar_id": "default-settlement", + "lag_business_days": 1 + } + ] + } +} diff --git a/contracts/v16/fixtures/order-book.scenario.jsonl b/contracts/v16/fixtures/order-book.scenario.jsonl new file mode 100644 index 0000000..e8aa5d3 --- /dev/null +++ b/contracts/v16/fixtures/order-book.scenario.jsonl @@ -0,0 +1,4 @@ +{"contract_version":"16","scenario_sequence":"1","record_type":"scenario_header","payload":{"metadata":{"producer":"trading-engine","purpose":"bounded order-book replay conformance fixture"},"run_id":"order-book","base_currency":"USD","initial_portfolio":{"cash":[{"currency":"USD","amount":"2000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"instruments":[{"instrument_id":"clip-equity","symbol":"CLIP","quote_currency":"USD","tick_size":"0.01","lot_size":"1"}],"venue_calendars":[{"calendar_id":"clip-xnas-2026","calendar_version":"1","venue_id":"XNAS","instrument_ids":["clip-equity"],"sessions":[{"session_date":"2026-02-02","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-02-02T14:30:00Z","closes_at":"2026-02-02T21:00:00Z"}]},{"session_date":"2026-02-03","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-02-03T14:30:00Z","closes_at":"2026-02-03T21:00:00Z"}]},{"session_date":"2026-02-04","policy":"early_close","phases":[{"phase":"regular","opens_at":"2026-02-04T14:30:00Z","closes_at":"2026-02-04T18:00:00Z"}]}]}],"risk":{"max_gross_exposure":"1000000000","max_leverage":"1","short_borrow_bps":100,"instrument_policies":[{"instrument_id":"clip-equity","max_order_quantity":"1000","max_long_position":"1000","max_short_position":"1000","max_notional_exposure":"1000000000","initial_margin_bps":5000,"maintenance_margin_bps":2500,"shorting_allowed":true}],"groups":[]},"execution":{"model":"order_book_v1","configuration":{"version":"1","participation_bps":10000,"fee_schedules":[{"schedule_id":"clip-fees-v1","instrument_id":"clip-equity","settlement_currency":"USD","minimum":null,"maximum":null,"components":[{"name":"broker","currency":"USD","kind":"fixed","value":"10","rounding":"up","applies_to":"any"}]}],"max_depth_levels":10}},"max_internal_events":1000,"financing":{"day_count":"actual_365","compounding":"simple","borrow_missing_data":"reject","cash_missing_data":"reject","locate_policy":"clip_fill","recall_policy":"close_out"},"settlement":{"cash_buying_power":"total_cash","position_availability":"total_positions","calendars":[{"calendar_id":"default-settlement","version":"1","business_dates":["2026-01-02","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-02-02","2026-02-03","2026-02-04","2026-02-05"]}],"rules":[{"instrument_id":"clip-equity","calendar_id":"default-settlement","lag_business_days":1}]}}} +{"contract_version":"16","scenario_sequence":"2","record_type":"market_slice","payload":{"intents":[{"type":"submit_order","instrument_id":"clip-equity","side":"buy","quantity":"10","order_kind":"limit","trigger_price":null,"limit_price":"100","time_in_force":"gtc","venue_id":null,"calendar_id":null,"expires_at":null}],"market_slice":{"slice_sequence":"1","start_at":"2026-02-02T14:30:00Z","end_at":"2026-02-02T21:00:00Z","available_at":"2026-02-02T21:00:01Z","received_at":"2026-02-02T21:00:02Z","bars":[{"instrument_id":"clip-equity","open":"50","high":"50","low":"50","close":"50","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-02T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-02T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[{"type":"snapshot","instrument_id":"clip-equity","event_at":"2026-02-02T14:31:00Z","available_at":"2026-02-02T14:31:01Z","received_at":"2026-02-02T14:31:02Z","ingest_sequence":"1","book_sequence":"1","bids":[{"price":"49","quantity":"20"}],"asks":[{"price":"51","quantity":"20"}]}]}}} +{"contract_version":"16","scenario_sequence":"3","record_type":"market_slice","payload":{"intents":[],"market_slice":{"slice_sequence":"2","start_at":"2026-02-03T14:30:00Z","end_at":"2026-02-03T21:00:00Z","available_at":"2026-02-03T21:00:01Z","received_at":"2026-02-03T21:00:02Z","bars":[{"instrument_id":"clip-equity","open":"100","high":"100","low":"100","close":"100","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-03T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-03T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[{"type":"snapshot","instrument_id":"clip-equity","event_at":"2026-02-03T14:31:00Z","available_at":"2026-02-03T14:31:01Z","received_at":"2026-02-03T14:31:02Z","ingest_sequence":"1","book_sequence":"1","bids":[{"price":"100","quantity":"5"}],"asks":[{"price":"101","quantity":"20"}]},{"type":"set","instrument_id":"clip-equity","event_at":"2026-02-03T14:32:00Z","available_at":"2026-02-03T14:32:01Z","received_at":"2026-02-03T14:32:02Z","ingest_sequence":"2","book_sequence":"2","side":"bid","price":"100","quantity":"3"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:33:00Z","available_at":"2026-02-03T14:33:01Z","received_at":"2026-02-03T14:33:02Z","ingest_sequence":"3","book_sequence":"3","price":"100","quantity":"3","aggressor_side":"sell"},{"type":"set","instrument_id":"clip-equity","event_at":"2026-02-03T14:34:00Z","available_at":"2026-02-03T14:34:01Z","received_at":"2026-02-03T14:34:02Z","ingest_sequence":"4","book_sequence":"4","side":"bid","price":"100","quantity":"10"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:35:00Z","available_at":"2026-02-03T14:35:01Z","received_at":"2026-02-03T14:35:02Z","ingest_sequence":"5","book_sequence":"5","price":"100","quantity":"4","aggressor_side":"sell"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:36:00Z","available_at":"2026-02-03T14:36:01Z","received_at":"2026-02-03T14:36:02Z","ingest_sequence":"6","book_sequence":"6","price":"100","quantity":"6","aggressor_side":"sell"}]}}} +{"contract_version":"16","scenario_sequence":"4","record_type":"scenario_end","payload":{"slice_count":"2"}} diff --git a/contracts/v16/fixtures/quote-trade.journal.jsonl b/contracts/v16/fixtures/quote-trade.journal.jsonl new file mode 100644 index 0000000..b6e48b2 --- /dev/null +++ b/contracts/v16/fixtures/quote-trade.journal.jsonl @@ -0,0 +1,13 @@ +{"contract_version":"16","engine_sequence":"1","event_id":"quote-trade-event-000000000001","causation_ids":[],"run_id":"quote-trade","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"run_started","payload":{"scenario_sha256":"0e57b52243f3fc9fe37b94dc8fbde9a8b69c6c680bd5369e4d77c63060b853e8","execution_model":"quote_trade_v1"}} +{"contract_version":"16","engine_sequence":"2","event_id":"quote-trade-event-000000000002","causation_ids":["quote-trade-event-000000000001"],"run_id":"quote-trade","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"initial_state","payload":{"portfolio":{"cash":[{"currency":"USD","amount":"2000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"valuation":{"base_currency":"USD","cash":"2000","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0","unrealized_pnl":"0","equity":"2000","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"2000","fx_rate":"1","base_value":"2000","interest":"0","base_interest":"0","settled_amount":"2000","unsettled_amount":"0","base_settled_value":"2000","base_unsettled_value":"0"}],"positions":[],"execution_fee_components":[],"cash_interest":"0","settled_cash":"2000","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"2000","maintenance_excess":"2000","margin_call":false},"group_exposures":[]}}} +{"contract_version":"16","engine_sequence":"3","event_id":"quote-trade-event-000000000003","causation_ids":["quote-trade-event-000000000002"],"run_id":"quote-trade","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"2000","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0","unrealized_pnl":"0","equity":"2000","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"2000","fx_rate":"1","base_value":"2000","interest":"0","base_interest":"0","settled_amount":"2000","unsettled_amount":"0","base_settled_value":"2000","base_unsettled_value":"0"}],"positions":[],"execution_fee_components":[],"cash_interest":"0","settled_cash":"2000","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"2000","maintenance_excess":"2000","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"4","event_id":"quote-trade-event-000000000004","causation_ids":[],"run_id":"quote-trade","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"1","start_at":"2026-02-02T14:30:00.000000Z","end_at":"2026-02-02T21:00:00.000000Z","available_at":"2026-02-02T21:00:01.000000Z","received_at":"2026-02-02T21:00:02.000000Z","bars":[{"instrument_id":"clip-equity","open":"50","high":"50","low":"50","close":"50","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-02T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"5","event_id":"quote-trade-event-000000000005","causation_ids":["quote-trade-event-000000000004"],"run_id":"quote-trade","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-02-02T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"2000","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-02-02T14:30:00.000000Z","period_end":"2026-02-02T21:00:00.000000Z","amount":"0.01484","closing_balance":"2000.01484"}} +{"contract_version":"16","engine_sequence":"6","event_id":"quote-trade-event-000000000006","causation_ids":["quote-trade-event-000000000004"],"run_id":"quote-trade","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"order_accepted","payload":{"order_id":"quote-trade-order-000000000001","instrument_id":"clip-equity","side":"buy","quantity":"10","order_kind":"limit","trigger_price":null,"limit_price":"100","time_in_force":"gtc","venue_id":null,"calendar_id":null,"expires_at":null,"origin":"direct","created_event_id":"quote-trade-event-000000000006","updated_event_id":"quote-trade-event-000000000006","created_sequence":"6","created_at":"2026-02-02T21:00:02.000000Z","eligible_after_slice_sequence":"1","triggered_at":null,"triggered_slice_sequence":null,"filled_quantity":"0","filled_notional":"0","status":"working","rejection_reason":null}} +{"contract_version":"16","engine_sequence":"7","event_id":"quote-trade-event-000000000007","causation_ids":["quote-trade-event-000000000004"],"run_id":"quote-trade","recorded_at":"2026-02-02T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"2000.01484","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","cost_basis":"0","realized_pnl":"0.01484","unrealized_pnl":"0","equity":"2000.01484","dividend_pnl":"0","execution_fees":"0","borrow_fees":"0","total_fees":"0","cash_balances":[{"currency":"USD","amount":"2000.01484","fx_rate":"1","base_value":"2000.01484","interest":"0.01484","base_interest":"0.01484","settled_amount":"2000.01484","unsettled_amount":"0","base_settled_value":"2000.01484","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"0","mark":"50","fx_rate":"1","market_value":"0","base_market_value":"0","cost_basis":"0","base_cost_basis":"0","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"0","base_unrealized_pnl":"0","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"0","base_execution_fees":"0","borrow_fees":"0","base_borrow_fees":"0","total_fees":"0","base_total_fees":"0","execution_fee_components":[],"settled_quantity":"0","unsettled_quantity":"0"}],"execution_fee_components":[],"cash_interest":"0.01484","settled_cash":"2000.01484","unsettled_cash":"0","margin":{"initial_requirement":"0","maintenance_requirement":"0","initial_excess":"2000.01484","maintenance_excess":"2000.01484","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"8","event_id":"quote-trade-event-000000000008","causation_ids":[],"run_id":"quote-trade","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"market_slice_received","payload":{"slice_sequence":"2","start_at":"2026-02-03T14:30:00.000000Z","end_at":"2026-02-03T21:00:00.000000Z","available_at":"2026-02-03T21:00:01.000000Z","received_at":"2026-02-03T21:00:02.000000Z","bars":[{"instrument_id":"clip-equity","open":"100","high":"100","low":"100","close":"100","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-03T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-03T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[{"type":"quote","instrument_id":"clip-equity","event_at":"2026-02-03T14:31:00.000000Z","available_at":"2026-02-03T14:31:01.000000Z","received_at":"2026-02-03T14:31:02.000000Z","ingest_sequence":"1","bid_price":"99","bid_quantity":"20","ask_price":"101","ask_quantity":"20"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:32:00.000000Z","available_at":"2026-02-03T14:32:01.000000Z","received_at":"2026-02-03T14:32:02.000000Z","ingest_sequence":"2","price":"100","quantity":"5","aggressor_side":"unknown"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:33:00.000000Z","available_at":"2026-02-03T14:33:01.000000Z","received_at":"2026-02-03T14:33:02.000000Z","ingest_sequence":"3","price":"99","quantity":"4","aggressor_side":"sell"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:34:00.000000Z","available_at":"2026-02-03T14:34:01.000000Z","received_at":"2026-02-03T14:34:02.000000Z","ingest_sequence":"4","price":"100","quantity":"10","aggressor_side":"sell"}],"order_book_events":[]}} +{"contract_version":"16","engine_sequence":"9","event_id":"quote-trade-event-000000000009","causation_ids":["quote-trade-event-000000000008"],"run_id":"quote-trade","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"cash_interest_applied","payload":{"observation":{"currency":"USD","effective_at":"2026-02-03T14:30:00.000000Z","credit_rate_bps":100,"debit_rate_bps":200},"opening_balance":"2000.01484","applied_rate_bps":100,"day_count":"actual_365","compounding":"simple","period_start":"2026-02-03T14:30:00.000000Z","period_end":"2026-02-03T21:00:00.000000Z","amount":"0.01484","closing_balance":"2000.02968"}} +{"contract_version":"16","engine_sequence":"10","event_id":"quote-trade-event-000000000010","causation_ids":["quote-trade-event-000000000006","quote-trade-event-000000000008"],"run_id":"quote-trade","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"fill_applied","payload":{"fill_id":"quote-trade-fill-000000000001","order_id":"quote-trade-order-000000000001","instrument_id":"clip-equity","quote_currency":"USD","side":"buy","quantity":"4","price":"99","notional":"396","fee":"10","executed_at":"2026-02-03T14:33:00.000000Z","slice_sequence":"2","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"10","quote_amount":"10"}]}} +{"contract_version":"16","engine_sequence":"11","event_id":"quote-trade-event-000000000011","causation_ids":["quote-trade-event-000000000006","quote-trade-event-000000000008"],"run_id":"quote-trade","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"fill_applied","payload":{"fill_id":"quote-trade-fill-000000000002","order_id":"quote-trade-order-000000000001","instrument_id":"clip-equity","quote_currency":"USD","side":"buy","quantity":"6","price":"100","notional":"600","fee":"10","executed_at":"2026-02-03T14:34:00.000000Z","slice_sequence":"2","fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"10","quote_amount":"10"}]}} +{"contract_version":"16","engine_sequence":"12","event_id":"quote-trade-event-000000000012","causation_ids":["quote-trade-event-000000000008"],"run_id":"quote-trade","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"valuation","payload":{"base_currency":"USD","cash":"984.02968","net_market_value":"1000","long_market_value":"1000","short_market_value":"0","gross_exposure":"1000","cost_basis":"1016","realized_pnl":"0.02968","unrealized_pnl":"-16","equity":"1984.02968","dividend_pnl":"0","execution_fees":"20","borrow_fees":"0","total_fees":"20","cash_balances":[{"currency":"USD","amount":"984.02968","fx_rate":"1","base_value":"984.02968","interest":"0.02968","base_interest":"0.02968","settled_amount":"984.02968","unsettled_amount":"0","base_settled_value":"984.02968","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"10","mark":"100","fx_rate":"1","market_value":"1000","base_market_value":"1000","cost_basis":"1016","base_cost_basis":"1016","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"-16","base_unrealized_pnl":"-16","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"20","base_execution_fees":"20","borrow_fees":"0","base_borrow_fees":"0","total_fees":"20","base_total_fees":"20","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"settled_quantity":"10","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"cash_interest":"0.02968","settled_cash":"984.02968","unsettled_cash":"0","margin":{"initial_requirement":"500","maintenance_requirement":"250","initial_excess":"1484.02968","maintenance_excess":"1734.02968","margin_call":false},"group_exposures":[]}} +{"contract_version":"16","engine_sequence":"13","event_id":"quote-trade-event-000000000013","causation_ids":["quote-trade-event-000000000012"],"run_id":"quote-trade","recorded_at":"2026-02-03T21:00:02.000000Z","event_type":"run_completed","payload":{"scenario_sha256":"0e57b52243f3fc9fe37b94dc8fbde9a8b69c6c680bd5369e4d77c63060b853e8","execution_model":"quote_trade_v1","valuation":{"base_currency":"USD","cash":"984.02968","net_market_value":"1000","long_market_value":"1000","short_market_value":"0","gross_exposure":"1000","cost_basis":"1016","realized_pnl":"0.02968","unrealized_pnl":"-16","equity":"1984.02968","dividend_pnl":"0","execution_fees":"20","borrow_fees":"0","total_fees":"20","cash_balances":[{"currency":"USD","amount":"984.02968","fx_rate":"1","base_value":"984.02968","interest":"0.02968","base_interest":"0.02968","settled_amount":"984.02968","unsettled_amount":"0","base_settled_value":"984.02968","base_unsettled_value":"0"}],"positions":[{"instrument_id":"clip-equity","quote_currency":"USD","quantity":"10","mark":"100","fx_rate":"1","market_value":"1000","base_market_value":"1000","cost_basis":"1016","base_cost_basis":"1016","realized_pnl":"0","base_realized_pnl":"0","unrealized_pnl":"-16","base_unrealized_pnl":"-16","dividend_pnl":"0","base_dividend_pnl":"0","execution_fees":"20","base_execution_fees":"20","borrow_fees":"0","base_borrow_fees":"0","total_fees":"20","base_total_fees":"20","execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"settled_quantity":"10","unsettled_quantity":"0"}],"execution_fee_components":[{"name":"broker","kind":"fixed","currency":"USD","amount":"20","quote_currency":"USD","quote_amount":"20","base_amount":"20"}],"cash_interest":"0.02968","settled_cash":"984.02968","unsettled_cash":"0","margin":{"initial_requirement":"500","maintenance_requirement":"250","initial_excess":"1484.02968","maintenance_excess":"1734.02968","margin_call":false},"group_exposures":[]},"order_counts":{"total":1,"active":0,"filled":1,"rejected":0,"cancelled":0}}} diff --git a/contracts/v16/fixtures/quote-trade.scenario.json b/contracts/v16/fixtures/quote-trade.scenario.json new file mode 100644 index 0000000..baf05f8 --- /dev/null +++ b/contracts/v16/fixtures/quote-trade.scenario.json @@ -0,0 +1,319 @@ +{ + "contract_version": "16", + "metadata": { + "producer": "trading-engine", + "purpose": "bounded quote and trade replay fixture" + }, + "run_id": "quote-trade", + "base_currency": "USD", + "initial_portfolio": { + "cash": [ + { + "currency": "USD", + "amount": "2000" + } + ], + "positions": [], + "marks": [], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ] + }, + "instruments": [ + { + "instrument_id": "clip-equity", + "symbol": "CLIP", + "quote_currency": "USD", + "tick_size": "0.01", + "lot_size": "1" + } + ], + "venue_calendars": [ + { + "calendar_id": "clip-xnas-2026", + "calendar_version": "1", + "venue_id": "XNAS", + "instrument_ids": [ + "clip-equity" + ], + "sessions": [ + { + "session_date": "2026-02-02", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-02T14:30:00Z", + "closes_at": "2026-02-02T21:00:00Z" + } + ] + }, + { + "session_date": "2026-02-03", + "policy": "regular", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-03T14:30:00Z", + "closes_at": "2026-02-03T21:00:00Z" + } + ] + }, + { + "session_date": "2026-02-04", + "policy": "early_close", + "phases": [ + { + "phase": "regular", + "opens_at": "2026-02-04T14:30:00Z", + "closes_at": "2026-02-04T18:00:00Z" + } + ] + } + ] + } + ], + "risk": { + "max_gross_exposure": "1000000000", + "max_leverage": "1", + "short_borrow_bps": 100, + "instrument_policies": [ + { + "instrument_id": "clip-equity", + "max_order_quantity": "1000", + "max_long_position": "1000", + "max_short_position": "1000", + "max_notional_exposure": "1000000000", + "initial_margin_bps": 5000, + "maintenance_margin_bps": 2500, + "shorting_allowed": true + } + ], + "groups": [] + }, + "execution": { + "model": "quote_trade_v1", + "configuration": { + "version": "1", + "participation_bps": 10000, + "fee_schedules": [ + { + "schedule_id": "clip-fees-v1", + "instrument_id": "clip-equity", + "settlement_currency": "USD", + "minimum": null, + "maximum": null, + "components": [ + { + "name": "broker", + "currency": "USD", + "kind": "fixed", + "value": "10", + "rounding": "up", + "applies_to": "any" + } + ] + } + ] + } + }, + "max_internal_events": 1000, + "schedule": [ + { + "after_slice_sequence": "1", + "intents": [ + { + "type": "submit_order", + "instrument_id": "clip-equity", + "side": "buy", + "quantity": "10", + "order_kind": "limit", + "trigger_price": null, + "limit_price": "100", + "time_in_force": "gtc", + "venue_id": null, + "calendar_id": null, + "expires_at": null + } + ] + } + ], + "slices": [ + { + "slice_sequence": "1", + "start_at": "2026-02-02T14:30:00Z", + "end_at": "2026-02-02T21:00:00Z", + "available_at": "2026-02-02T21:00:01Z", + "received_at": "2026-02-02T21:00:02Z", + "bars": [ + { + "instrument_id": "clip-equity", + "open": "50", + "high": "50", + "low": "50", + "close": "50", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "clip-equity", + "effective_at": "2026-02-02T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-02-02T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [], + "order_book_events": [] + }, + { + "slice_sequence": "2", + "start_at": "2026-02-03T14:30:00Z", + "end_at": "2026-02-03T21:00:00Z", + "available_at": "2026-02-03T21:00:01Z", + "received_at": "2026-02-03T21:00:02Z", + "bars": [ + { + "instrument_id": "clip-equity", + "open": "100", + "high": "100", + "low": "100", + "close": "100", + "volume": "100" + } + ], + "fx_rates": [ + { + "currency": "USD", + "rate": "1" + } + ], + "corporate_actions": [], + "borrow_observations": [ + { + "instrument_id": "clip-equity", + "effective_at": "2026-02-03T14:30:00Z", + "available_quantity": "1000", + "annual_rate_bps": 100, + "recalled": false + } + ], + "cash_rate_observations": [ + { + "currency": "USD", + "effective_at": "2026-02-03T14:30:00Z", + "credit_rate_bps": 100, + "debit_rate_bps": 200 + } + ], + "settlement_failures": [], + "lifecycle_events": [], + "market_events": [ + { + "type": "quote", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:31:00Z", + "available_at": "2026-02-03T14:31:01Z", + "received_at": "2026-02-03T14:31:02Z", + "ingest_sequence": "1", + "bid_price": "99", + "bid_quantity": "20", + "ask_price": "101", + "ask_quantity": "20" + }, + { + "type": "trade", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:32:00Z", + "available_at": "2026-02-03T14:32:01Z", + "received_at": "2026-02-03T14:32:02Z", + "ingest_sequence": "2", + "price": "100", + "quantity": "5", + "aggressor_side": "unknown" + }, + { + "type": "trade", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:33:00Z", + "available_at": "2026-02-03T14:33:01Z", + "received_at": "2026-02-03T14:33:02Z", + "ingest_sequence": "3", + "price": "99", + "quantity": "4", + "aggressor_side": "sell" + }, + { + "type": "trade", + "instrument_id": "clip-equity", + "event_at": "2026-02-03T14:34:00Z", + "available_at": "2026-02-03T14:34:01Z", + "received_at": "2026-02-03T14:34:02Z", + "ingest_sequence": "4", + "price": "100", + "quantity": "10", + "aggressor_side": "sell" + } + ], + "order_book_events": [] + } + ], + "financing": { + "day_count": "actual_365", + "compounding": "simple", + "borrow_missing_data": "reject", + "cash_missing_data": "reject", + "locate_policy": "clip_fill", + "recall_policy": "close_out" + }, + "settlement": { + "cash_buying_power": "total_cash", + "position_availability": "total_positions", + "calendars": [ + { + "calendar_id": "default-settlement", + "version": "1", + "business_dates": [ + "2026-01-02", + "2026-01-05", + "2026-01-06", + "2026-01-07", + "2026-01-08", + "2026-01-09", + "2026-02-02", + "2026-02-03", + "2026-02-04", + "2026-02-05" + ] + } + ], + "rules": [ + { + "instrument_id": "clip-equity", + "calendar_id": "default-settlement", + "lag_business_days": 1 + } + ] + } +} diff --git a/contracts/v16/fixtures/quote-trade.scenario.jsonl b/contracts/v16/fixtures/quote-trade.scenario.jsonl new file mode 100644 index 0000000..842d0e7 --- /dev/null +++ b/contracts/v16/fixtures/quote-trade.scenario.jsonl @@ -0,0 +1,4 @@ +{"contract_version":"16","scenario_sequence":"1","record_type":"scenario_header","payload":{"metadata":{"producer":"trading-engine","purpose":"bounded quote and trade replay fixture"},"run_id":"quote-trade","base_currency":"USD","initial_portfolio":{"cash":[{"currency":"USD","amount":"2000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"instruments":[{"instrument_id":"clip-equity","symbol":"CLIP","quote_currency":"USD","tick_size":"0.01","lot_size":"1"}],"venue_calendars":[{"calendar_id":"clip-xnas-2026","calendar_version":"1","venue_id":"XNAS","instrument_ids":["clip-equity"],"sessions":[{"session_date":"2026-02-02","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-02-02T14:30:00Z","closes_at":"2026-02-02T21:00:00Z"}]},{"session_date":"2026-02-03","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-02-03T14:30:00Z","closes_at":"2026-02-03T21:00:00Z"}]},{"session_date":"2026-02-04","policy":"early_close","phases":[{"phase":"regular","opens_at":"2026-02-04T14:30:00Z","closes_at":"2026-02-04T18:00:00Z"}]}]}],"risk":{"max_gross_exposure":"1000000000","max_leverage":"1","short_borrow_bps":100,"instrument_policies":[{"instrument_id":"clip-equity","max_order_quantity":"1000","max_long_position":"1000","max_short_position":"1000","max_notional_exposure":"1000000000","initial_margin_bps":5000,"maintenance_margin_bps":2500,"shorting_allowed":true}],"groups":[]},"execution":{"model":"quote_trade_v1","configuration":{"version":"1","participation_bps":10000,"fee_schedules":[{"schedule_id":"clip-fees-v1","instrument_id":"clip-equity","settlement_currency":"USD","minimum":null,"maximum":null,"components":[{"name":"broker","currency":"USD","kind":"fixed","value":"10","rounding":"up","applies_to":"any"}]}]}},"max_internal_events":1000,"financing":{"day_count":"actual_365","compounding":"simple","borrow_missing_data":"reject","cash_missing_data":"reject","locate_policy":"clip_fill","recall_policy":"close_out"},"settlement":{"cash_buying_power":"total_cash","position_availability":"total_positions","calendars":[{"calendar_id":"default-settlement","version":"1","business_dates":["2026-01-02","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-02-02","2026-02-03","2026-02-04","2026-02-05"]}],"rules":[{"instrument_id":"clip-equity","calendar_id":"default-settlement","lag_business_days":1}]}}} +{"contract_version":"16","scenario_sequence":"2","record_type":"market_slice","payload":{"market_slice":{"slice_sequence":"1","start_at":"2026-02-02T14:30:00Z","end_at":"2026-02-02T21:00:00Z","available_at":"2026-02-02T21:00:01Z","received_at":"2026-02-02T21:00:02Z","bars":[{"instrument_id":"clip-equity","open":"50","high":"50","low":"50","close":"50","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-02T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-02T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]},"intents":[{"type":"submit_order","instrument_id":"clip-equity","side":"buy","quantity":"10","order_kind":"limit","trigger_price":null,"limit_price":"100","time_in_force":"gtc","venue_id":null,"calendar_id":null,"expires_at":null}]}} +{"contract_version":"16","scenario_sequence":"3","record_type":"market_slice","payload":{"market_slice":{"slice_sequence":"2","start_at":"2026-02-03T14:30:00Z","end_at":"2026-02-03T21:00:00Z","available_at":"2026-02-03T21:00:01Z","received_at":"2026-02-03T21:00:02Z","bars":[{"instrument_id":"clip-equity","open":"100","high":"100","low":"100","close":"100","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"clip-equity","effective_at":"2026-02-03T14:30:00Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-02-03T14:30:00Z","credit_rate_bps":100,"debit_rate_bps":200}],"settlement_failures":[],"lifecycle_events":[],"market_events":[{"type":"quote","instrument_id":"clip-equity","event_at":"2026-02-03T14:31:00Z","available_at":"2026-02-03T14:31:01Z","received_at":"2026-02-03T14:31:02Z","ingest_sequence":"1","bid_price":"99","bid_quantity":"20","ask_price":"101","ask_quantity":"20"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:32:00Z","available_at":"2026-02-03T14:32:01Z","received_at":"2026-02-03T14:32:02Z","ingest_sequence":"2","price":"100","quantity":"5","aggressor_side":"unknown"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:33:00Z","available_at":"2026-02-03T14:33:01Z","received_at":"2026-02-03T14:33:02Z","ingest_sequence":"3","price":"99","quantity":"4","aggressor_side":"sell"},{"type":"trade","instrument_id":"clip-equity","event_at":"2026-02-03T14:34:00Z","available_at":"2026-02-03T14:34:01Z","received_at":"2026-02-03T14:34:02Z","ingest_sequence":"4","price":"100","quantity":"10","aggressor_side":"sell"}],"order_book_events":[]},"intents":[]}} +{"contract_version":"16","scenario_sequence":"4","record_type":"scenario_end","payload":{"slice_count":"2"}} diff --git a/contracts/v16/journal.schema.json b/contracts/v16/journal.schema.json new file mode 100644 index 0000000..2315fa1 --- /dev/null +++ b/contracts/v16/journal.schema.json @@ -0,0 +1,2441 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/fallblu/trading-engine/contracts/v16/journal.schema.json", + "title": "Trading Engine v16 audit journal record", + "type": "object", + "additionalProperties": false, + "required": [ + "contract_version", + "engine_sequence", + "event_id", + "causation_ids", + "run_id", + "recorded_at", + "event_type", + "payload" + ], + "properties": { + "contract_version": { + "const": "16" + }, + "engine_sequence": { + "$ref": "#/$defs/sequence" + }, + "event_id": { + "$ref": "#/$defs/identifier" + }, + "causation_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "run_id": { + "$ref": "#/$defs/identifier" + }, + "recorded_at": { + "$ref": "#/$defs/timestamp" + }, + "event_type": { + "enum": [ + "run_started", + "initial_state", + "market_slice_received", + "target_portfolio_requested", + "order_accepted", + "order_rejected", + "order_triggered", + "order_cancelled", + "split_applied", + "cash_dividend_applied", + "distribution_applied", + "lifecycle_applied", + "order_adjusted", + "execution_price_selected", + "fill_applied", + "settlement_instruction_created", + "settlement_completed", + "settlement_failed", + "fill_clipped", + "borrow_fee_applied", + "borrow_charge_applied", + "borrow_recall_received", + "cash_interest_applied", + "margin_call", + "margin_restored", + "intent_rejected", + "metric_emitted", + "valuation", + "run_completed" + ] + }, + "payload": { + "type": "object" + } + }, + "allOf": [ + { + "if": { "properties": { "event_type": { "const": "execution_price_selected" } } }, + "then": { "properties": { "payload": { "$ref": "#/$defs/executionPriceSelected" } } } + }, + { + "if": { + "properties": { + "event_type": { + "const": "run_started" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/runStarted" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "initial_state" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/initialState" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "market_slice_received" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/marketSlice" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "target_portfolio_requested" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/targetPortfolio" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "enum": [ + "order_accepted", + "order_rejected", + "order_triggered" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/order" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "order_cancelled" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/orderCancelled" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "split_applied" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/splitApplied" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "cash_dividend_applied" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/dividendApplied" + } + } + } + }, + { + "if": { "properties": { "event_type": { "const": "distribution_applied" } } }, + "then": { "properties": { "payload": { "$ref": "#/$defs/distributionApplied" } } } + }, + { + "if": { "properties": { "event_type": { "const": "lifecycle_applied" } } }, + "then": { "properties": { "payload": { "$ref": "#/$defs/lifecycleApplied" } } } + }, + { + "if": { + "properties": { + "event_type": { + "const": "order_adjusted" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/orderAdjusted" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "fill_applied" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/fill" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "enum": [ + "settlement_instruction_created", + "settlement_completed", + "settlement_failed" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/settlementInstruction" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "fill_clipped" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/fillClipped" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "borrow_fee_applied" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/borrowFee" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "borrow_charge_applied" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/borrowCharge" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "borrow_recall_received" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/borrowRecall" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "cash_interest_applied" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/cashInterest" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "enum": [ + "margin_call", + "margin_restored", + "valuation" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/valuation" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "intent_rejected" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/intentRejected" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "metric_emitted" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/metric" + } + } + } + }, + { + "if": { + "properties": { + "event_type": { + "const": "run_completed" + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "#/$defs/runCompleted" + } + } + } + } + ], + "$defs": { + "identifier": { + "type": "string", + "minLength": 1, + "pattern": "^[^\\s\\u0000-\\u001f\\u007f]+$" + }, + "signedDecimal": { + "type": "string", + "pattern": "^(?:0|-?(?:0[.][0-9]{0,5}[1-9]|[1-9][0-9]*(?:[.][0-9]{0,5}[1-9])?))$" + }, + "unsignedDecimal": { + "type": "string", + "pattern": "^(?:0|0[.][0-9]{0,5}[1-9]|[1-9][0-9]*(?:[.][0-9]{0,5}[1-9])?)$" + }, + "positiveDecimal": { + "type": "string", + "pattern": "^(?:0[.][0-9]{0,5}[1-9]|[1-9][0-9]*(?:[.][0-9]{0,5}[1-9])?)$" + }, + "sequence": { + "type": "string", + "pattern": "^[1-9][0-9]*$" + }, + "nonnegativeSequence": { + "type": "string", + "pattern": "^(?:0|[1-9][0-9]*)$" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-5][0-9](?:[.][0-9]{1,6})?(?:[zZ]|[+-][0-9]{2}:[0-9]{2})$" + }, + "runStarted": { + "type": "object", + "additionalProperties": false, + "required": [ + "scenario_sha256", + "execution_model" + ], + "properties": { + "scenario_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "execution_model": { + "enum": ["completed_bar_v1", "completed_bar_next_open_v1", "completed_bar_adverse_touch_v1", "quote_trade_v1", "order_book_v1"] + } + } + }, + "initialState": { + "type": "object", + "additionalProperties": false, + "required": [ + "portfolio", + "valuation" + ], + "properties": { + "portfolio": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/initialPortfolio" + }, + "valuation": { + "$ref": "#/$defs/valuation" + } + } + }, + "bar": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "open", + "high", + "low", + "close", + "volume" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "open": { + "$ref": "#/$defs/positiveDecimal" + }, + "high": { + "$ref": "#/$defs/positiveDecimal" + }, + "low": { + "$ref": "#/$defs/positiveDecimal" + }, + "close": { + "$ref": "#/$defs/positiveDecimal" + }, + "volume": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/unsignedDecimal" + } + ] + } + } + }, + "fxRate": { + "type": "object", + "additionalProperties": false, + "required": [ + "currency", + "rate" + ], + "properties": { + "currency": { + "$ref": "#/$defs/identifier" + }, + "rate": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "corporateAction": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "action_id", + "instrument_id", + "numerator", + "denominator" + ], + "properties": { + "type": { + "const": "split" + }, + "action_id": { + "$ref": "#/$defs/identifier" + }, + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "numerator": { + "$ref": "#/$defs/sequence" + }, + "denominator": { + "$ref": "#/$defs/sequence" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "action_id", + "instrument_id", + "amount_per_unit" + ], + "properties": { + "type": { + "const": "cash_dividend" + }, + "action_id": { + "$ref": "#/$defs/identifier" + }, + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "amount_per_unit": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "action_id", "instrument_id", "destination_instrument_id", "numerator", "denominator", "basis_allocation_bps", "fractional_policy"], + "properties": { + "type": { "enum": ["stock_dividend", "rights", "spin_off"] }, + "action_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "destination_instrument_id": { "$ref": "#/$defs/identifier" }, + "numerator": { "$ref": "#/$defs/sequence" }, + "denominator": { "$ref": "#/$defs/sequence" }, + "basis_allocation_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "fractional_policy": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/fractionalPolicy" } + } + } + ] + }, + "marketSlice": { + "type": "object", + "additionalProperties": false, + "required": [ + "slice_sequence", + "start_at", + "end_at", + "available_at", + "received_at", + "bars", + "fx_rates", + "corporate_actions", + "borrow_observations", + "cash_rate_observations", + "settlement_failures", + "lifecycle_events", + "market_events", + "order_book_events" + ], + "properties": { + "slice_sequence": { + "$ref": "#/$defs/sequence" + }, + "start_at": { + "$ref": "#/$defs/timestamp" + }, + "end_at": { + "$ref": "#/$defs/timestamp" + }, + "available_at": { + "$ref": "#/$defs/timestamp" + }, + "received_at": { + "$ref": "#/$defs/timestamp" + }, + "bars": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/bar" + } + }, + "fx_rates": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/fxRate" + } + }, + "corporate_actions": { + "type": "array", + "items": { + "$ref": "#/$defs/corporateAction" + } + }, + "borrow_observations": { + "type": "array", + "items": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/borrowObservation" + } + }, + "cash_rate_observations": { + "type": "array", + "items": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/cashRateObservation" + } + }, + "settlement_failures": { + "type": "array", + "items": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/settlementFailure" + } + }, + "lifecycle_events": { + "type": "array", + "items": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/lifecycleEvent" + } + }, + "market_events": { + "type": "array", + "items": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/marketEvent" + } + }, + "order_book_events": { + "type": "array", + "items": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/orderBookEvent" + } + } + } + }, + "settlementInstruction": { + "type": "object", + "additionalProperties": false, + "required": ["instruction_id", "fill_id", "instrument_id", "currency", "cash_movement", "position_movement", "trade_date", "due_date", "status", "settled_at", "failed_at", "failure_reason"], + "properties": { + "instruction_id": { "$ref": "#/$defs/identifier" }, + "fill_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "currency": { "$ref": "#/$defs/identifier" }, + "cash_movement": { "$ref": "#/$defs/signedDecimal" }, + "position_movement": { "$ref": "#/$defs/signedDecimal" }, + "trade_date": { "type": "string", "format": "date", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }, + "due_date": { "type": "string", "format": "date", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }, + "status": { "enum": ["pending", "settled", "failed"] }, + "settled_at": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/timestamp" }] }, + "failed_at": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/timestamp" }] }, + "failure_reason": { "oneOf": [{ "type": "null" }, { "type": "string", "minLength": 1, "pattern": "^\\S(?:.*\\S)?$" }] } + } + }, + "targetPortfolio": { + "type": "object", + "additionalProperties": false, + "required": [ + "basis", + "targets" + ], + "properties": { + "basis": { + "enum": [ + "weights", + "quantities" + ] + }, + "targets": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "weight", + "quantity", + "reference_price" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "weight": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/signedDecimal" + } + ] + }, + "quantity": { + "$ref": "#/$defs/signedDecimal" + }, + "reference_price": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/positiveDecimal" + } + ] + } + } + } + } + } + }, + "order": { + "type": "object", + "additionalProperties": false, + "required": [ + "order_id", + "instrument_id", + "side", + "quantity", + "order_kind", + "trigger_price", + "limit_price", + "time_in_force", + "venue_id", + "calendar_id", + "expires_at", + "origin", + "created_event_id", + "updated_event_id", + "created_sequence", + "created_at", + "eligible_after_slice_sequence", + "triggered_at", + "triggered_slice_sequence", + "filled_quantity", + "filled_notional", + "status", + "rejection_reason" + ], + "properties": { + "order_id": { + "$ref": "#/$defs/identifier" + }, + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "side": { + "enum": [ + "buy", + "sell" + ] + }, + "quantity": { + "$ref": "#/$defs/positiveDecimal" + }, + "order_kind": { + "enum": [ + "market", + "limit", + "stop", + "stop_limit" + ] + }, + "trigger_price": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/positiveDecimal" + } + ] + }, + "limit_price": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/positiveDecimal" + } + ] + }, + "time_in_force": { + "enum": [ + "gtc", + "ioc", + "fok", + "day", + "gtd" + ] + }, + "venue_id": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/identifier" + } + ] + }, + "calendar_id": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/identifier" + } + ] + }, + "expires_at": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/timestamp" + } + ] + }, + "origin": { + "enum": [ + "direct", + "target_rebalance", + "margin_liquidation", + "borrow_recall", + "instrument_halt", + "instrument_terminal" + ] + }, + "created_event_id": { + "$ref": "#/$defs/identifier" + }, + "updated_event_id": { + "$ref": "#/$defs/identifier" + }, + "created_sequence": { + "$ref": "#/$defs/sequence" + }, + "created_at": { + "$ref": "#/$defs/timestamp" + }, + "eligible_after_slice_sequence": { + "$ref": "#/$defs/nonnegativeSequence" + }, + "triggered_at": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/timestamp" + } + ] + }, + "triggered_slice_sequence": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/sequence" + } + ] + }, + "filled_quantity": { + "$ref": "#/$defs/unsignedDecimal" + }, + "filled_notional": { + "$ref": "#/$defs/unsignedDecimal" + }, + "status": { + "enum": [ + "working", + "partially_filled", + "filled", + "cancelled", + "rejected" + ] + }, + "rejection_reason": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "minLength": 1 + } + ] + } + } + }, + "orderCancelled": { + "type": "object", + "additionalProperties": false, + "required": [ + "order", + "reason" + ], + "properties": { + "order": { + "$ref": "#/$defs/order" + }, + "reason": { + "enum": [ + "strategy_requested", + "target_replaced", + "market_ioc", + "immediate_or_cancel", + "fill_or_kill", + "day_expired", + "gtd_expired", + "margin_call", + "borrow_recall" + ] + } + } + }, + "splitApplied": { + "type": "object", + "additionalProperties": false, + "required": [ + "action", + "previous_quantity", + "adjusted_quantity" + ], + "properties": { + "action": { + "$ref": "#/$defs/corporateAction" + }, + "previous_quantity": { + "$ref": "#/$defs/signedDecimal" + }, + "adjusted_quantity": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "dividendApplied": { + "type": "object", + "additionalProperties": false, + "required": [ + "action", + "quantity", + "cash_amount" + ], + "properties": { + "action": { + "$ref": "#/$defs/corporateAction" + }, + "quantity": { + "$ref": "#/$defs/signedDecimal" + }, + "cash_amount": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "distributionApplied": { + "type": "object", + "additionalProperties": false, + "required": ["action", "source_quantity", "destination_quantity", "fractional_quantity", "allocated_basis", "fractional_basis", "cash_in_lieu"], + "properties": { + "action": { "$ref": "#/$defs/corporateAction" }, + "source_quantity": { "$ref": "#/$defs/signedDecimal" }, + "destination_quantity": { "$ref": "#/$defs/signedDecimal" }, + "fractional_quantity": { "$ref": "#/$defs/signedDecimal" }, + "allocated_basis": { "$ref": "#/$defs/signedDecimal" }, + "fractional_basis": { "$ref": "#/$defs/signedDecimal" }, + "cash_in_lieu": { "$ref": "#/$defs/signedDecimal" } + } + }, + "lifecycleApplied": { + "type": "object", + "additionalProperties": false, + "required": ["lifecycle_event", "listing", "liquidated_quantity", "cash_amount"], + "properties": { + "lifecycle_event": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/lifecycleEvent" }, + "listing": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "symbol", "status", "provider_mappings"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "symbol": { "$ref": "#/$defs/identifier" }, + "status": { "enum": ["tradable", "halted", "expired", "delisted"] }, + "provider_mappings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["provider", "provider_instrument_id"], + "properties": { + "provider": { "$ref": "#/$defs/identifier" }, + "provider_instrument_id": { "$ref": "#/$defs/identifier" } + } + } + } + } + }, + "liquidated_quantity": { "$ref": "#/$defs/signedDecimal" }, + "cash_amount": { "$ref": "#/$defs/signedDecimal" } + } + }, + "orderAdjusted": { + "type": "object", + "additionalProperties": false, + "required": [ + "order", + "action_id" + ], + "properties": { + "order": { + "$ref": "#/$defs/order" + }, + "action_id": { + "$ref": "#/$defs/identifier" + } + } + }, + "executionPriceSelected": { + "type": "object", + "additionalProperties": false, + "required": ["order_id", "instrument_id", "side", "reference_price", "spread_adjustment", "impact_adjustment", "final_price"], + "properties": { + "order_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "side": { "enum": ["buy", "sell"] }, + "reference_price": { "$ref": "#/$defs/positiveDecimal" }, + "spread_adjustment": { "$ref": "#/$defs/unsignedDecimal" }, + "impact_adjustment": { "$ref": "#/$defs/unsignedDecimal" }, + "final_price": { "$ref": "#/$defs/positiveDecimal" } + } + }, + "fill": { + "type": "object", + "additionalProperties": false, + "required": [ + "fill_id", + "order_id", + "instrument_id", + "quote_currency", + "side", + "quantity", + "price", + "notional", + "fee", + "executed_at", + "slice_sequence", + "fee_components" + ], + "properties": { + "fill_id": { + "$ref": "#/$defs/identifier" + }, + "order_id": { + "$ref": "#/$defs/identifier" + }, + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "quote_currency": { + "$ref": "#/$defs/identifier" + }, + "side": { + "enum": [ + "buy", + "sell" + ] + }, + "quantity": { + "$ref": "#/$defs/positiveDecimal" + }, + "price": { + "$ref": "#/$defs/positiveDecimal" + }, + "notional": { + "$ref": "#/$defs/positiveDecimal" + }, + "fee": { + "$ref": "#/$defs/signedDecimal" + }, + "fee_components": { + "type": "array", + "items": { + "$ref": "#/$defs/calculatedFeeComponent" + } + }, + "executed_at": { + "$ref": "#/$defs/timestamp" + }, + "slice_sequence": { + "$ref": "#/$defs/sequence" + } + } + }, + "calculatedFeeComponent": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "kind", + "currency", + "amount", + "quote_amount" + ], + "properties": { + "name": { + "$ref": "#/$defs/identifier" + }, + "kind": { + "enum": [ + "fixed", + "notional_bps", + "per_unit", + "minimum_adjustment", + "maximum_adjustment" + ] + }, + "currency": { + "$ref": "#/$defs/identifier" + }, + "amount": { + "$ref": "#/$defs/signedDecimal" + }, + "quote_amount": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "feeComponentAttribution": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "kind", + "currency", + "amount", + "quote_currency", + "quote_amount", + "base_amount" + ], + "properties": { + "name": { + "$ref": "#/$defs/identifier" + }, + "kind": { + "enum": [ + "fixed", + "notional_bps", + "per_unit", + "minimum_adjustment", + "maximum_adjustment" + ] + }, + "currency": { + "$ref": "#/$defs/identifier" + }, + "amount": { + "$ref": "#/$defs/signedDecimal" + }, + "quote_currency": { + "$ref": "#/$defs/identifier" + }, + "quote_amount": { + "$ref": "#/$defs/signedDecimal" + }, + "base_amount": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "quantityThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "unit", + "value" + ], + "properties": { + "unit": { + "const": "quantity" + }, + "value": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "moneyThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "unit", + "value" + ], + "properties": { + "unit": { + "const": "money" + }, + "value": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "ratioThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "unit", + "value" + ], + "properties": { + "unit": { + "const": "ratio" + }, + "value": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "basisPointsThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "unit", + "value" + ], + "properties": { + "unit": { + "const": "basis_points" + }, + "value": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + } + } + }, + "instrumentQuantityThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "unit", + "value" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "unit": { + "const": "quantity" + }, + "value": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "instrumentMoneyThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "unit", + "value" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "unit": { + "const": "money" + }, + "value": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "currencyMoneyThreshold": { + "type": "object", + "additionalProperties": false, + "required": ["currency", "unit", "value"], + "properties": { + "currency": { "$ref": "#/$defs/identifier" }, + "unit": { "const": "money" }, + "value": { "$ref": "#/$defs/unsignedDecimal" } + } + }, + "settlementPositionThreshold": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "unit", "value"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "unit": { "const": "quantity" }, + "value": { "$ref": "#/$defs/unsignedDecimal" } + } + }, + "instrumentBasisPointsThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "unit", + "value" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "unit": { + "const": "basis_points" + }, + "value": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + } + } + }, + "instrumentShortingThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "value" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "value": { + "const": false + } + } + }, + "groupMoneyThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "group_id", + "unit", + "value" + ], + "properties": { + "group_id": { + "$ref": "#/$defs/identifier" + }, + "unit": { + "const": "money" + }, + "value": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "groupRatioThreshold": { + "type": "object", + "additionalProperties": false, + "required": [ + "group_id", + "unit", + "value" + ], + "properties": { + "group_id": { + "$ref": "#/$defs/identifier" + }, + "unit": { + "const": "ratio" + }, + "value": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "fillClipReason": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "max_order_quantity" + }, + "threshold": { + "$ref": "#/$defs/quantityThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "max_long_position" + }, + "threshold": { + "$ref": "#/$defs/quantityThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "max_short_position" + }, + "threshold": { + "$ref": "#/$defs/quantityThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "max_gross_exposure" + }, + "threshold": { + "$ref": "#/$defs/moneyThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "max_leverage" + }, + "threshold": { + "$ref": "#/$defs/ratioThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "initial_margin" + }, + "threshold": { + "$ref": "#/$defs/basisPointsThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "instrument_max_long_position" + }, + "threshold": { + "$ref": "#/$defs/instrumentQuantityThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["version", "policy", "threshold"], + "properties": { + "version": { "const": "1" }, + "policy": { "const": "settlement_cash_buying_power" }, + "threshold": { "$ref": "#/$defs/currencyMoneyThreshold" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["version", "policy", "threshold"], + "properties": { + "version": { "const": "1" }, + "policy": { "const": "settlement_position_availability" }, + "threshold": { "$ref": "#/$defs/settlementPositionThreshold" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "instrument_max_short_position" + }, + "threshold": { + "$ref": "#/$defs/instrumentQuantityThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "instrument_max_notional_exposure" + }, + "threshold": { + "$ref": "#/$defs/instrumentMoneyThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "instrument_shorting_disabled" + }, + "threshold": { + "$ref": "#/$defs/instrumentShortingThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "instrument_borrow_availability" + }, + "threshold": { + "$ref": "#/$defs/instrumentQuantityThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "instrument_initial_margin" + }, + "threshold": { + "$ref": "#/$defs/instrumentBasisPointsThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "group_max_gross_exposure" + }, + "threshold": { + "$ref": "#/$defs/groupMoneyThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "group_max_long_exposure" + }, + "threshold": { + "$ref": "#/$defs/groupMoneyThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "group_max_short_exposure" + }, + "threshold": { + "$ref": "#/$defs/groupMoneyThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "group_max_absolute_net_exposure" + }, + "threshold": { + "$ref": "#/$defs/groupMoneyThreshold" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "policy", + "threshold" + ], + "properties": { + "version": { + "const": "1" + }, + "policy": { + "const": "group_max_concentration" + }, + "threshold": { + "$ref": "#/$defs/groupRatioThreshold" + } + } + } + ] + }, + "fillClipped": { + "type": "object", + "additionalProperties": false, + "required": [ + "reason", + "order_id", + "instrument_id", + "proposed_quantity", + "permitted_quantity", + "price" + ], + "properties": { + "reason": { + "$ref": "#/$defs/fillClipReason" + }, + "order_id": { + "$ref": "#/$defs/identifier" + }, + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "proposed_quantity": { + "$ref": "#/$defs/positiveDecimal" + }, + "permitted_quantity": { + "$ref": "#/$defs/unsignedDecimal" + }, + "price": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "borrowFee": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "quote_currency", + "short_quantity", + "reference_price", + "borrow_bps", + "period_start", + "period_end", + "fee" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "quote_currency": { + "$ref": "#/$defs/identifier" + }, + "short_quantity": { + "$ref": "#/$defs/positiveDecimal" + }, + "reference_price": { + "$ref": "#/$defs/positiveDecimal" + }, + "borrow_bps": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "period_start": { + "$ref": "#/$defs/timestamp" + }, + "period_end": { + "$ref": "#/$defs/timestamp" + }, + "fee": { + "$ref": "#/$defs/positiveDecimal" + } + } + }, + "borrowCharge": { + "type": "object", + "additionalProperties": false, + "required": [ + "observation", + "quote_currency", + "short_quantity", + "reference_price", + "day_count", + "compounding", + "period_start", + "period_end", + "amount" + ], + "properties": { + "observation": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/borrowObservation" + }, + "quote_currency": { + "$ref": "#/$defs/identifier" + }, + "short_quantity": { + "$ref": "#/$defs/positiveDecimal" + }, + "reference_price": { + "$ref": "#/$defs/positiveDecimal" + }, + "day_count": { + "enum": [ + "actual_365", + "actual_360" + ] + }, + "compounding": { + "enum": [ + "simple", + "daily" + ] + }, + "period_start": { + "$ref": "#/$defs/timestamp" + }, + "period_end": { + "$ref": "#/$defs/timestamp" + }, + "amount": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "borrowRecall": { + "type": "object", + "additionalProperties": false, + "required": [ + "observation", + "short_quantity", + "close_out_quantity" + ], + "properties": { + "observation": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/borrowObservation" + }, + "short_quantity": { + "$ref": "#/$defs/positiveDecimal" + }, + "close_out_quantity": { + "$ref": "#/$defs/unsignedDecimal" + } + } + }, + "cashInterest": { + "type": "object", + "additionalProperties": false, + "required": [ + "observation", + "opening_balance", + "applied_rate_bps", + "day_count", + "compounding", + "period_start", + "period_end", + "amount", + "closing_balance" + ], + "properties": { + "observation": { + "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/cashRateObservation" + }, + "opening_balance": { + "$ref": "#/$defs/signedDecimal" + }, + "applied_rate_bps": { + "type": "integer", + "minimum": -1000000, + "maximum": 1000000 + }, + "day_count": { + "enum": [ + "actual_365", + "actual_360" + ] + }, + "compounding": { + "enum": [ + "simple", + "daily" + ] + }, + "period_start": { + "$ref": "#/$defs/timestamp" + }, + "period_end": { + "$ref": "#/$defs/timestamp" + }, + "amount": { + "$ref": "#/$defs/signedDecimal" + }, + "closing_balance": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "cashAttribution": { + "type": "object", + "additionalProperties": false, + "required": [ + "currency", + "amount", + "fx_rate", + "base_value", + "interest", + "base_interest", + "settled_amount", + "unsettled_amount", + "base_settled_value", + "base_unsettled_value" + ], + "properties": { + "currency": { + "$ref": "#/$defs/identifier" + }, + "amount": { + "$ref": "#/$defs/signedDecimal" + }, + "fx_rate": { + "$ref": "#/$defs/positiveDecimal" + }, + "base_value": { + "$ref": "#/$defs/signedDecimal" + }, + "interest": { + "$ref": "#/$defs/signedDecimal" + }, + "base_interest": { + "$ref": "#/$defs/signedDecimal" + }, + "settled_amount": { + "$ref": "#/$defs/signedDecimal" + }, + "unsettled_amount": { + "$ref": "#/$defs/signedDecimal" + }, + "base_settled_value": { + "$ref": "#/$defs/signedDecimal" + }, + "base_unsettled_value": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "positionAttribution": { + "type": "object", + "additionalProperties": false, + "required": [ + "instrument_id", + "quote_currency", + "quantity", + "settled_quantity", + "unsettled_quantity", + "mark", + "fx_rate", + "market_value", + "base_market_value", + "cost_basis", + "base_cost_basis", + "realized_pnl", + "base_realized_pnl", + "unrealized_pnl", + "base_unrealized_pnl", + "dividend_pnl", + "base_dividend_pnl", + "execution_fees", + "base_execution_fees", + "borrow_fees", + "base_borrow_fees", + "total_fees", + "base_total_fees", + "execution_fee_components" + ], + "properties": { + "instrument_id": { + "$ref": "#/$defs/identifier" + }, + "quote_currency": { + "$ref": "#/$defs/identifier" + }, + "quantity": { + "$ref": "#/$defs/signedDecimal" + }, + "settled_quantity": { + "$ref": "#/$defs/signedDecimal" + }, + "unsettled_quantity": { + "$ref": "#/$defs/signedDecimal" + }, + "mark": { + "$ref": "#/$defs/positiveDecimal" + }, + "fx_rate": { + "$ref": "#/$defs/positiveDecimal" + }, + "market_value": { + "$ref": "#/$defs/signedDecimal" + }, + "base_market_value": { + "$ref": "#/$defs/signedDecimal" + }, + "cost_basis": { + "$ref": "#/$defs/signedDecimal" + }, + "base_cost_basis": { + "$ref": "#/$defs/signedDecimal" + }, + "realized_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "base_realized_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "unrealized_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "base_unrealized_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "dividend_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "base_dividend_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "execution_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "base_execution_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "borrow_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "base_borrow_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "total_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "base_total_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "execution_fee_components": { + "type": "array", + "items": { + "$ref": "#/$defs/feeComponentAttribution" + } + } + } + }, + "margin": { + "type": "object", + "additionalProperties": false, + "required": [ + "initial_requirement", + "maintenance_requirement", + "initial_excess", + "maintenance_excess", + "margin_call" + ], + "properties": { + "initial_requirement": { + "$ref": "#/$defs/unsignedDecimal" + }, + "maintenance_requirement": { + "$ref": "#/$defs/unsignedDecimal" + }, + "initial_excess": { + "$ref": "#/$defs/signedDecimal" + }, + "maintenance_excess": { + "$ref": "#/$defs/signedDecimal" + }, + "margin_call": { + "type": "boolean" + } + } + }, + "groupExposure": { + "type": "object", + "additionalProperties": false, + "required": [ + "group_id", + "gross_exposure", + "net_exposure", + "long_exposure", + "short_exposure", + "concentration" + ], + "properties": { + "group_id": { + "$ref": "#/$defs/identifier" + }, + "gross_exposure": { + "$ref": "#/$defs/unsignedDecimal" + }, + "net_exposure": { + "$ref": "#/$defs/signedDecimal" + }, + "long_exposure": { + "$ref": "#/$defs/unsignedDecimal" + }, + "short_exposure": { + "$ref": "#/$defs/unsignedDecimal" + }, + "concentration": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/signedDecimal" + } + ] + } + } + }, + "valuation": { + "type": "object", + "additionalProperties": false, + "required": [ + "base_currency", + "cash", + "settled_cash", + "unsettled_cash", + "net_market_value", + "long_market_value", + "short_market_value", + "gross_exposure", + "cost_basis", + "realized_pnl", + "unrealized_pnl", + "equity", + "dividend_pnl", + "execution_fees", + "borrow_fees", + "cash_interest", + "total_fees", + "cash_balances", + "positions", + "margin", + "group_exposures", + "execution_fee_components" + ], + "properties": { + "base_currency": { + "$ref": "#/$defs/identifier" + }, + "cash": { + "$ref": "#/$defs/signedDecimal" + }, + "settled_cash": { + "$ref": "#/$defs/signedDecimal" + }, + "unsettled_cash": { + "$ref": "#/$defs/signedDecimal" + }, + "net_market_value": { + "$ref": "#/$defs/signedDecimal" + }, + "long_market_value": { + "$ref": "#/$defs/unsignedDecimal" + }, + "short_market_value": { + "$ref": "#/$defs/unsignedDecimal" + }, + "gross_exposure": { + "$ref": "#/$defs/unsignedDecimal" + }, + "cost_basis": { + "$ref": "#/$defs/signedDecimal" + }, + "realized_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "unrealized_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "equity": { + "$ref": "#/$defs/signedDecimal" + }, + "dividend_pnl": { + "$ref": "#/$defs/signedDecimal" + }, + "execution_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "borrow_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "total_fees": { + "$ref": "#/$defs/signedDecimal" + }, + "cash_balances": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/cashAttribution" + } + }, + "positions": { + "type": "array", + "items": { + "$ref": "#/$defs/positionAttribution" + } + }, + "margin": { + "$ref": "#/$defs/margin" + }, + "group_exposures": { + "type": "array", + "items": { + "$ref": "#/$defs/groupExposure" + } + }, + "execution_fee_components": { + "type": "array", + "items": { + "$ref": "#/$defs/feeComponentAttribution" + } + }, + "cash_interest": { + "$ref": "#/$defs/signedDecimal" + } + } + }, + "intentRejected": { + "type": "object", + "additionalProperties": false, + "required": [ + "reason" + ], + "properties": { + "reason": { + "type": "string", + "minLength": 1 + } + } + }, + "metric": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "value" + ], + "properties": { + "name": { "type": "string", "minLength": 1, "maxLength": 128, "pattern": "^\\S(?:.*\\S)?$" }, + "value": { "$ref": "#/$defs/metricValue" }, + "unit": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^\\S(?:.*\\S)?$" }, + "dimensions": { + "type": "object", + "maxProperties": 16, + "propertyNames": { "minLength": 1, "maxLength": 64, "pattern": "^\\S(?:.*\\S)?$" }, + "additionalProperties": { "type": "string", "maxLength": 128 } + }, + "aggregation": { "enum": ["last", "sum", "minimum", "maximum", "mean"] } + }, + "allOf": [ + { "if": { "properties": { "value": { "properties": { "type": { "enum": ["string", "boolean"] } } } } }, "then": { "properties": { "aggregation": { "enum": ["last"] } } } } + ] + }, + "metricValue": { + "oneOf": [ + { "type": "object", "additionalProperties": false, "required": ["type", "value"], "properties": { "type": { "const": "numeric" }, "value": { "type": "string", "pattern": "^(0|[1-9][0-9]*|-[1-9][0-9]*)(\\.[0-9]*[1-9])?$|^-0\\.[0-9]*[1-9]$" } } }, + { "type": "object", "additionalProperties": false, "required": ["type", "value"], "properties": { "type": { "const": "string" }, "value": { "type": "string", "maxLength": 1024 } } }, + { "type": "object", "additionalProperties": false, "required": ["type", "value"], "properties": { "type": { "const": "boolean" }, "value": { "type": "boolean" } } } + ] + }, + "runCompleted": { + "type": "object", + "additionalProperties": false, + "required": [ + "scenario_sha256", + "execution_model", + "valuation", + "order_counts" + ], + "properties": { + "scenario_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "execution_model": { + "enum": ["completed_bar_v1", "completed_bar_next_open_v1", "completed_bar_adverse_touch_v1", "quote_trade_v1", "order_book_v1"] + }, + "valuation": { + "$ref": "#/$defs/valuation" + }, + "order_counts": { + "type": "object", + "additionalProperties": false, + "required": [ + "total", + "active", + "filled", + "rejected", + "cancelled" + ], + "properties": { + "total": { + "type": "integer", + "minimum": 0 + }, + "active": { + "type": "integer", + "minimum": 0 + }, + "filled": { + "type": "integer", + "minimum": 0 + }, + "rejected": { + "type": "integer", + "minimum": 0 + }, + "cancelled": { + "type": "integer", + "minimum": 0 + } + } + } + } + } + } +} diff --git a/contracts/v16/scenario-stream.schema.json b/contracts/v16/scenario-stream.schema.json new file mode 100644 index 0000000..e3b1694 --- /dev/null +++ b/contracts/v16/scenario-stream.schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/fallblu/trading-engine/contracts/v16/scenario-stream.schema.json", + "title": "Trading Engine v16 replay scenario stream record", + "description": "Market-slice records are ordered and non-overlapping: each start_at is at or after the prior end_at. Equal boundaries are valid. This cross-record rule is enforced semantically.", + "oneOf": [ + { "$ref": "#/$defs/headerRecord" }, + { "$ref": "#/$defs/sliceRecord" }, + { "$ref": "#/$defs/endRecord" } + ], + "$defs": { + "headerRecord": { + "type": "object", + "additionalProperties": false, + "required": ["contract_version", "scenario_sequence", "record_type", "payload"], + "properties": { + "contract_version": { "const": "16" }, + "scenario_sequence": { "const": "1" }, + "record_type": { "const": "scenario_header" }, + "payload": { "$ref": "#/$defs/headerPayload" } + } + }, + "sliceRecord": { + "type": "object", + "additionalProperties": false, + "required": ["contract_version", "scenario_sequence", "record_type", "payload"], + "properties": { + "contract_version": { "const": "16" }, + "scenario_sequence": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/sequence" }, + "record_type": { "const": "market_slice" }, + "payload": { "$ref": "#/$defs/slicePayload" } + } + }, + "endRecord": { + "type": "object", + "additionalProperties": false, + "required": ["contract_version", "scenario_sequence", "record_type", "payload"], + "properties": { + "contract_version": { "const": "16" }, + "scenario_sequence": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/sequence" }, + "record_type": { "const": "scenario_end" }, + "payload": { + "type": "object", + "additionalProperties": false, + "required": ["slice_count"], + "properties": { "slice_count": { "type": "string", "pattern": "^(?:0|[1-9][0-9]*)$" } } + } + } + }, + "headerPayload": { + "type": "object", + "additionalProperties": false, + "required": ["metadata", "run_id", "base_currency", "initial_portfolio", "instruments", "venue_calendars", "risk", "execution", "financing", "settlement", "max_internal_events"], + "properties": { + "metadata": { "type": "object" }, + "run_id": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/identifier" }, + "base_currency": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/identifier" }, + "initial_portfolio": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/initialPortfolio" }, + "instruments": { "type": "array", "minItems": 1, "maxItems": 4096, "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/instrument" } }, + "venue_calendars": { "type": "array", "minItems": 1, "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/venueCalendar" } }, + "risk": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/risk" }, + "execution": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/execution" }, + "financing": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/financing" }, + "settlement": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/settlement" }, + "max_internal_events": { "type": "integer", "minimum": 1, "maximum": 100000 } + } + }, + "slicePayload": { + "type": "object", + "additionalProperties": false, + "required": ["market_slice", "intents"], + "properties": { + "market_slice": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/marketSlice" }, + "intents": { "type": "array", "maxItems": 4096, "items": { "$ref": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json#/$defs/intent" } } + } + } + } +} diff --git a/contracts/v16/scenario.schema.json b/contracts/v16/scenario.schema.json new file mode 100644 index 0000000..1af81e1 --- /dev/null +++ b/contracts/v16/scenario.schema.json @@ -0,0 +1,888 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/fallblu/trading-engine/contracts/v16/scenario.schema.json", + "title": "Trading Engine v16 replay scenario", + "description": "Strict deterministic scenario contract with explicit venue-local session policies resolved to absolute instants outside the reducer.", + "type": "object", + "additionalProperties": false, + "required": ["contract_version", "metadata", "run_id", "base_currency", "initial_portfolio", "instruments", "venue_calendars", "risk", "execution", "financing", "settlement", "max_internal_events", "schedule", "slices"], + "properties": { + "contract_version": { "const": "16" }, + "metadata": { "type": "object" }, + "run_id": { "$ref": "#/$defs/identifier" }, + "base_currency": { "$ref": "#/$defs/identifier" }, + "initial_portfolio": { "$ref": "#/$defs/initialPortfolio" }, + "instruments": { + "type": "array", + "minItems": 1, + "maxItems": 4096, + "items": { "$ref": "#/$defs/instrument" } + }, + "venue_calendars": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/venueCalendar" } + }, + "risk": { "$ref": "#/$defs/risk" }, + "execution": { "$ref": "#/$defs/execution" }, + "financing": { "$ref": "#/$defs/financing" }, + "settlement": { "$ref": "#/$defs/settlement" }, + "max_internal_events": { "type": "integer", "minimum": 1, "maximum": 100000 }, + "schedule": { "type": "array", "items": { "$ref": "#/$defs/scheduleItem" } }, + "slices": { + "description": "Slices are ordered and non-overlapping: each start_at is at or after the prior end_at. Equal boundaries are valid. This cross-item rule is enforced semantically.", + "type": "array", + "items": { "$ref": "#/$defs/marketSlice" } + } + }, + "$defs": { + "settlement": { + "type": "object", + "additionalProperties": false, + "required": ["cash_buying_power", "position_availability", "calendars", "rules"], + "properties": { + "cash_buying_power": { "enum": ["total_cash", "settled_cash"] }, + "position_availability": { "enum": ["total_positions", "settled_positions"] }, + "calendars": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/settlementCalendar" } }, + "rules": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/settlementRule" } } + } + }, + "settlementCalendar": { + "type": "object", + "additionalProperties": false, + "required": ["calendar_id", "version", "business_dates"], + "properties": { + "calendar_id": { "$ref": "#/$defs/identifier" }, + "version": { "const": "1" }, + "business_dates": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "format": "date", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" } } + } + }, + "settlementRule": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "calendar_id", "lag_business_days"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "calendar_id": { "$ref": "#/$defs/identifier" }, + "lag_business_days": { "type": "integer", "minimum": 0, "maximum": 30 } + } + }, + "settlementFailure": { + "type": "object", + "additionalProperties": false, + "required": ["instruction_id", "reason"], + "properties": { + "instruction_id": { "$ref": "#/$defs/identifier" }, + "reason": { "type": "string", "minLength": 1, "pattern": "^\\S(?:.*\\S)?$" } + } + }, + "financing": { + "type": "object", + "additionalProperties": false, + "required": ["day_count", "compounding", "borrow_missing_data", "cash_missing_data", "locate_policy", "recall_policy"], + "properties": { + "day_count": { "enum": ["actual_365", "actual_360"] }, + "compounding": { "enum": ["simple", "daily"] }, + "borrow_missing_data": { "enum": ["reject", "zero"] }, + "cash_missing_data": { "enum": ["reject", "zero"] }, + "locate_policy": { "enum": ["reject_order", "clip_fill"] }, + "recall_policy": { "enum": ["reject_new_shorts", "close_out"] } + } + }, + "borrowObservation": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "effective_at", "available_quantity", "annual_rate_bps", "recalled"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "effective_at": { "$ref": "#/$defs/timestamp" }, + "available_quantity": { "$ref": "#/$defs/unsignedDecimal" }, + "annual_rate_bps": { "type": "integer", "minimum": -1000000, "maximum": 1000000 }, + "recalled": { "type": "boolean" } + } + }, + "cashRateObservation": { + "type": "object", + "additionalProperties": false, + "required": ["currency", "effective_at", "credit_rate_bps", "debit_rate_bps"], + "properties": { + "currency": { "$ref": "#/$defs/identifier" }, + "effective_at": { "$ref": "#/$defs/timestamp" }, + "credit_rate_bps": { "type": "integer", "minimum": -1000000, "maximum": 1000000 }, + "debit_rate_bps": { "type": "integer", "minimum": -1000000, "maximum": 1000000 } + } + }, + "identifier": { + "type": "string", + "minLength": 1, + "pattern": "^[^\\s\\u0000-\\u001f\\u007f]+$" + }, + "signedDecimal": { + "type": "string", + "pattern": "^(?:0|-?(?:0[.][0-9]{0,5}[1-9]|[1-9][0-9]*(?:[.][0-9]{0,5}[1-9])?))$" + }, + "unsignedDecimal": { + "type": "string", + "pattern": "^(?:0|0[.][0-9]{0,5}[1-9]|[1-9][0-9]*(?:[.][0-9]{0,5}[1-9])?)$" + }, + "positiveDecimal": { + "type": "string", + "pattern": "^(?:0[.][0-9]{0,5}[1-9]|[1-9][0-9]*(?:[.][0-9]{0,5}[1-9])?)$" + }, + "sequence": { "type": "string", "pattern": "^[1-9][0-9]*$" }, + "timestamp": { + "type": "string", + "format": "date-time", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-5][0-9](?:[.][0-9]{1,6})?(?:[zZ]|[+-][0-9]{2}:[0-9]{2})$" + }, + "cashBalance": { + "type": "object", + "additionalProperties": false, + "required": ["currency", "amount"], + "properties": { + "currency": { "$ref": "#/$defs/identifier" }, + "amount": { "$ref": "#/$defs/signedDecimal" } + } + }, + "initialPortfolio": { + "type": "object", + "additionalProperties": false, + "required": ["cash", "positions", "marks", "fx_rates"], + "properties": { + "cash": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/cashBalance" } }, + "positions": { "type": "array", "items": { "$ref": "#/$defs/initialPosition" } }, + "marks": { "type": "array", "items": { "$ref": "#/$defs/initialMark" } }, + "fx_rates": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/fxRate" } } + } + }, + "initialPosition": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "quantity", "cost_basis", "realized_pnl", "dividend_pnl", "execution_fees", "borrow_fees"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "quantity": { "$ref": "#/$defs/signedDecimal" }, + "cost_basis": { "$ref": "#/$defs/signedDecimal" }, + "realized_pnl": { "$ref": "#/$defs/signedDecimal" }, + "dividend_pnl": { "$ref": "#/$defs/signedDecimal" }, + "execution_fees": { "$ref": "#/$defs/unsignedDecimal" }, + "borrow_fees": { "$ref": "#/$defs/unsignedDecimal" } + } + }, + "initialMark": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "price"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "price": { "$ref": "#/$defs/positiveDecimal" } + } + }, + "instrument": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "symbol", "quote_currency", "tick_size", "lot_size"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "symbol": { "$ref": "#/$defs/identifier" }, + "quote_currency": { "$ref": "#/$defs/identifier" }, + "tick_size": { "$ref": "#/$defs/positiveDecimal" }, + "lot_size": { "$ref": "#/$defs/positiveDecimal" } + } + }, + "venueCalendar": { + "type": "object", + "additionalProperties": false, + "required": ["calendar_id", "calendar_version", "venue_id", "instrument_ids", "sessions"], + "properties": { + "calendar_id": { "$ref": "#/$defs/identifier" }, + "calendar_version": { "const": "1" }, + "venue_id": { "$ref": "#/$defs/identifier" }, + "instrument_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/identifier" } + }, + "sessions": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/venueSession" } + } + } + }, + "venueSession": { + "oneOf": [ + { "$ref": "#/$defs/openVenueSession" }, + { "$ref": "#/$defs/holidayVenueSession" } + ] + }, + "openVenueSession": { + "type": "object", + "additionalProperties": false, + "required": ["session_date", "policy", "phases"], + "properties": { + "session_date": { "type": "string", "format": "date", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }, + "policy": { "enum": ["regular", "early_close"] }, + "phases": { + "type": "array", + "minItems": 1, + "maxItems": 5, + "items": { "$ref": "#/$defs/venuePhase" } + } + } + }, + "holidayVenueSession": { + "type": "object", + "additionalProperties": false, + "required": ["session_date", "policy", "phases"], + "properties": { + "session_date": { "type": "string", "format": "date", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }, + "policy": { "const": "holiday" }, + "phases": { "type": "array", "maxItems": 0 } + } + }, + "venuePhase": { + "type": "object", + "additionalProperties": false, + "required": ["phase", "opens_at", "closes_at"], + "properties": { + "phase": { "enum": ["premarket", "opening_auction", "regular", "closing_auction", "postmarket"] }, + "opens_at": { "$ref": "#/$defs/timestamp" }, + "closes_at": { "$ref": "#/$defs/timestamp" } + } + }, + "risk": { + "type": "object", + "additionalProperties": false, + "required": ["max_gross_exposure", "max_leverage", "short_borrow_bps", "instrument_policies", "groups"], + "properties": { + "max_gross_exposure": { "$ref": "#/$defs/positiveDecimal" }, + "max_leverage": { "$ref": "#/$defs/positiveDecimal" }, + "short_borrow_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "instrument_policies": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/instrumentRiskPolicy" } + }, + "groups": { + "type": "array", + "items": { "$ref": "#/$defs/riskGroup" } + } + } + }, + "instrumentRiskPolicy": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "max_order_quantity", "max_long_position", "max_short_position", "max_notional_exposure", "initial_margin_bps", "maintenance_margin_bps", "shorting_allowed"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "max_order_quantity": { "$ref": "#/$defs/positiveDecimal" }, + "max_long_position": { "$ref": "#/$defs/positiveDecimal" }, + "max_short_position": { "$ref": "#/$defs/positiveDecimal" }, + "max_notional_exposure": { "$ref": "#/$defs/positiveDecimal" }, + "initial_margin_bps": { "type": "integer", "minimum": 1, "maximum": 10000 }, + "maintenance_margin_bps": { "type": "integer", "minimum": 1, "maximum": 10000 }, + "shorting_allowed": { "type": "boolean" } + } + }, + "nullablePositiveDecimal": { + "oneOf": [ + { "type": "null" }, + { "$ref": "#/$defs/positiveDecimal" } + ] + }, + "riskGroup": { + "type": "object", + "additionalProperties": false, + "required": ["group_id", "group_version", "group_type", "instrument_ids", "limits"], + "properties": { + "group_id": { "$ref": "#/$defs/identifier" }, + "group_version": { "const": "1" }, + "group_type": { "enum": ["issuer", "sector", "currency", "country", "asset_class", "custom"] }, + "instrument_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/identifier" } + }, + "limits": { "$ref": "#/$defs/riskGroupLimits" } + } + }, + "riskGroupLimits": { + "type": "object", + "additionalProperties": false, + "required": ["max_gross_exposure", "max_long_exposure", "max_short_exposure", "max_absolute_net_exposure", "max_concentration"], + "properties": { + "max_gross_exposure": { "$ref": "#/$defs/nullablePositiveDecimal" }, + "max_long_exposure": { "$ref": "#/$defs/nullablePositiveDecimal" }, + "max_short_exposure": { "$ref": "#/$defs/nullablePositiveDecimal" }, + "max_absolute_net_exposure": { "$ref": "#/$defs/nullablePositiveDecimal" }, + "max_concentration": { + "oneOf": [ + { "type": "null" }, + { "allOf": [{ "$ref": "#/$defs/positiveDecimal" }, { "pattern": "^(?:0[.][0-9]{0,5}[1-9]|1)$" }] } + ] + } + } + }, + "execution": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["model", "configuration"], + "properties": { + "model": { "const": "completed_bar_v1" }, + "configuration": { "$ref": "#/$defs/completedBarV1Configuration" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["model", "configuration"], + "properties": { + "model": { "enum": ["completed_bar_next_open_v1", "completed_bar_adverse_touch_v1"] }, + "configuration": { "$ref": "#/$defs/conservativeBarConfiguration" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["model", "configuration"], + "properties": { + "model": { "const": "quote_trade_v1" }, + "configuration": { "$ref": "#/$defs/quoteTradeConfiguration" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["model", "configuration"], + "properties": { + "model": { "const": "order_book_v1" }, + "configuration": { "$ref": "#/$defs/orderBookConfiguration" } + } + } + ] + }, + "completedBarV1Configuration": { + "type": "object", + "additionalProperties": false, + "required": ["version", "participation_bps", "fee_schedules"], + "properties": { + "version": { "const": "2" }, + "participation_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "fee_schedules": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/feeSchedule" } } + } + }, + "conservativeBarConfiguration": { + "type": "object", + "additionalProperties": false, + "required": ["version", "participation_bps", "fee_schedules", "spread_model", "impact_model"], + "properties": { + "version": { "const": "1" }, + "participation_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "fee_schedules": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/feeSchedule" } }, + "spread_model": { "$ref": "#/$defs/fixedSpreadModel" }, + "impact_model": { "$ref": "#/$defs/linearImpactModel" } + } + }, + "quoteTradeConfiguration": { + "type": "object", + "additionalProperties": false, + "required": ["version", "participation_bps", "fee_schedules"], + "properties": { + "version": { "const": "1" }, + "participation_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "fee_schedules": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/feeSchedule" } } + } + }, + "orderBookConfiguration": { + "type": "object", + "additionalProperties": false, + "required": ["version", "participation_bps", "fee_schedules", "max_depth_levels"], + "properties": { + "version": { "const": "1" }, + "participation_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "fee_schedules": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/feeSchedule" } }, + "max_depth_levels": { "type": "integer", "minimum": 1, "maximum": 1024 } + } + }, + "fixedSpreadModel": { + "type": "object", + "additionalProperties": false, + "required": ["model", "half_spread_bps"], + "properties": { + "model": { "const": "fixed_half_spread_v1" }, + "half_spread_bps": { "type": "integer", "minimum": 0, "maximum": 10000 } + } + }, + "linearImpactModel": { + "type": "object", + "additionalProperties": false, + "required": ["model", "coefficient_bps", "missing_volume_policy"], + "properties": { + "model": { "const": "linear_participation_v1" }, + "coefficient_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "missing_volume_policy": { "enum": ["reject", "zero_impact"] } + } + }, + "feeSchedule": { + "type": "object", + "additionalProperties": false, + "required": ["schedule_id", "instrument_id", "settlement_currency", "minimum", "maximum", "components"], + "properties": { + "schedule_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "settlement_currency": { "$ref": "#/$defs/identifier" }, + "minimum": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/unsignedDecimal" }] }, + "maximum": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/unsignedDecimal" }] }, + "components": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/feeComponent" } } + } + }, + "feeComponent": { + "type": "object", + "additionalProperties": false, + "required": ["name", "currency", "kind", "value", "rounding", "applies_to"], + "properties": { + "name": { "$ref": "#/$defs/identifier" }, + "currency": { "$ref": "#/$defs/identifier" }, + "kind": { "enum": ["fixed", "notional_bps", "per_unit"] }, + "value": { "oneOf": [{ "$ref": "#/$defs/signedDecimal" }, { "type": "integer", "minimum": -10000, "maximum": 10000 }] }, + "rounding": { "enum": ["up", "down", "nearest"] }, + "applies_to": { "enum": ["any", "maker", "taker"] } + }, + "allOf": [ + { "if": { "properties": { "kind": { "const": "notional_bps" } } }, "then": { "properties": { "value": { "type": "integer" } } } }, + { "if": { "properties": { "kind": { "enum": ["fixed", "per_unit"] } } }, "then": { "properties": { "value": { "$ref": "#/$defs/signedDecimal" } } } } + ] + }, + "scheduleItem": { + "type": "object", + "additionalProperties": false, + "required": ["after_slice_sequence", "intents"], + "properties": { + "after_slice_sequence": { "$ref": "#/$defs/sequence" }, + "intents": { "type": "array", "maxItems": 4096, "items": { "$ref": "#/$defs/intent" } } + } + }, + "intent": { + "oneOf": [ + { "$ref": "#/$defs/targetWeights" }, + { "$ref": "#/$defs/targetQuantities" }, + { "$ref": "#/$defs/submitOrder" }, + { "$ref": "#/$defs/cancelOrder" }, + { "$ref": "#/$defs/metric" } + ] + }, + "targetWeights": { + "type": "object", + "additionalProperties": false, + "required": ["type", "targets"], + "properties": { + "type": { "const": "target_weights" }, + "targets": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "weight"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "weight": { "$ref": "#/$defs/signedDecimal" } + } + } + } + } + }, + "targetQuantities": { + "type": "object", + "additionalProperties": false, + "required": ["type", "targets"], + "properties": { + "type": { "const": "target_quantities" }, + "targets": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "quantity"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "quantity": { "$ref": "#/$defs/signedDecimal" } + } + } + } + } + }, + "submitOrder": { + "type": "object", + "additionalProperties": false, + "required": ["type", "instrument_id", "side", "quantity", "order_kind", "trigger_price", "limit_price", "time_in_force", "venue_id", "calendar_id", "expires_at"], + "properties": { + "type": { "const": "submit_order" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "side": { "enum": ["buy", "sell"] }, + "quantity": { "$ref": "#/$defs/positiveDecimal" }, + "order_kind": { "enum": ["market", "limit", "stop", "stop_limit"] }, + "trigger_price": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/positiveDecimal" }] }, + "limit_price": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/positiveDecimal" }] }, + "time_in_force": { "enum": ["gtc", "ioc", "fok", "day", "gtd"] }, + "venue_id": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/identifier" }] }, + "calendar_id": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/identifier" }] }, + "expires_at": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/timestamp" }] } + }, + "allOf": [ + { "if": { "properties": { "order_kind": { "const": "market" } } }, "then": { "properties": { "trigger_price": { "type": "null" }, "limit_price": { "type": "null" } } } }, + { "if": { "properties": { "order_kind": { "const": "limit" } } }, "then": { "properties": { "trigger_price": { "type": "null" }, "limit_price": { "$ref": "#/$defs/positiveDecimal" } } } }, + { "if": { "properties": { "order_kind": { "const": "stop" } } }, "then": { "properties": { "trigger_price": { "$ref": "#/$defs/positiveDecimal" }, "limit_price": { "type": "null" } } } }, + { "if": { "properties": { "order_kind": { "const": "stop_limit" } } }, "then": { "properties": { "trigger_price": { "$ref": "#/$defs/positiveDecimal" }, "limit_price": { "$ref": "#/$defs/positiveDecimal" } } } }, + { "if": { "properties": { "time_in_force": { "const": "day" } } }, "then": { "properties": { "venue_id": { "$ref": "#/$defs/identifier" }, "calendar_id": { "$ref": "#/$defs/identifier" }, "expires_at": { "type": "null" } } } }, + { "if": { "properties": { "time_in_force": { "const": "gtd" } } }, "then": { "properties": { "venue_id": { "type": "null" }, "calendar_id": { "type": "null" }, "expires_at": { "$ref": "#/$defs/timestamp" } } } }, + { "if": { "properties": { "time_in_force": { "enum": ["gtc", "ioc", "fok"] } } }, "then": { "properties": { "venue_id": { "type": "null" }, "calendar_id": { "type": "null" }, "expires_at": { "type": "null" } } } } + ] + }, + "cancelOrder": { + "type": "object", + "additionalProperties": false, + "required": ["type", "order_id"], + "properties": { + "type": { "const": "cancel_order" }, + "order_id": { "$ref": "#/$defs/identifier" } + } + }, + "metric": { + "type": "object", + "additionalProperties": false, + "required": ["type", "name", "value"], + "properties": { + "type": { "const": "emit_metric" }, + "name": { "type": "string", "minLength": 1, "maxLength": 128, "pattern": "^\\S(?:.*\\S)?$" }, + "value": { "$ref": "#/$defs/metricValue" }, + "unit": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^\\S(?:.*\\S)?$" }, + "dimensions": { + "type": "object", + "maxProperties": 16, + "propertyNames": { "minLength": 1, "maxLength": 64, "pattern": "^\\S(?:.*\\S)?$" }, + "additionalProperties": { "type": "string", "maxLength": 128 } + }, + "aggregation": { "enum": ["last", "sum", "minimum", "maximum", "mean"] } + }, + "allOf": [ + { "if": { "properties": { "value": { "properties": { "type": { "enum": ["string", "boolean"] } } } } }, "then": { "properties": { "aggregation": { "enum": ["last"] } } } } + ] + }, + "metricValue": { + "oneOf": [ + { "type": "object", "additionalProperties": false, "required": ["type", "value"], "properties": { "type": { "const": "numeric" }, "value": { "type": "string", "pattern": "^(0|[1-9][0-9]*|-[1-9][0-9]*)(\\.[0-9]*[1-9])?$|^-0\\.[0-9]*[1-9]$" } } }, + { "type": "object", "additionalProperties": false, "required": ["type", "value"], "properties": { "type": { "const": "string" }, "value": { "type": "string", "maxLength": 1024 } } }, + { "type": "object", "additionalProperties": false, "required": ["type", "value"], "properties": { "type": { "const": "boolean" }, "value": { "type": "boolean" } } } + ] + }, + "marketSlice": { + "type": "object", + "additionalProperties": false, + "required": ["slice_sequence", "start_at", "end_at", "available_at", "received_at", "bars", "market_events", "order_book_events", "fx_rates", "corporate_actions", "borrow_observations", "cash_rate_observations", "settlement_failures", "lifecycle_events"], + "properties": { + "slice_sequence": { "$ref": "#/$defs/sequence" }, + "start_at": { "$ref": "#/$defs/timestamp" }, + "end_at": { "$ref": "#/$defs/timestamp" }, + "available_at": { "$ref": "#/$defs/timestamp" }, + "received_at": { "$ref": "#/$defs/timestamp" }, + "bars": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/bar" } }, + "market_events": { "type": "array", "items": { "$ref": "#/$defs/marketEvent" } }, + "order_book_events": { "type": "array", "items": { "$ref": "#/$defs/orderBookEvent" } }, + "fx_rates": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/fxRate" } }, + "corporate_actions": { "type": "array", "items": { "$ref": "#/$defs/corporateAction" } }, + "borrow_observations": { "type": "array", "items": { "$ref": "#/$defs/borrowObservation" } }, + "cash_rate_observations": { "type": "array", "items": { "$ref": "#/$defs/cashRateObservation" } }, + "settlement_failures": { "type": "array", "items": { "$ref": "#/$defs/settlementFailure" } }, + "lifecycle_events": { "type": "array", "items": { "$ref": "#/$defs/lifecycleEvent" } } + } + }, + "bar": { + "type": "object", + "additionalProperties": false, + "required": ["instrument_id", "open", "high", "low", "close", "volume"], + "properties": { + "instrument_id": { "$ref": "#/$defs/identifier" }, + "open": { "$ref": "#/$defs/positiveDecimal" }, + "high": { "$ref": "#/$defs/positiveDecimal" }, + "low": { "$ref": "#/$defs/positiveDecimal" }, + "close": { "$ref": "#/$defs/positiveDecimal" }, + "volume": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/unsignedDecimal" }] } + } + }, + "marketEvent": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["type", "instrument_id", "event_at", "available_at", "received_at", "ingest_sequence", "bid_price", "bid_quantity", "ask_price", "ask_quantity"], + "properties": { + "type": { "const": "quote" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "event_at": { "$ref": "#/$defs/timestamp" }, + "available_at": { "$ref": "#/$defs/timestamp" }, + "received_at": { "$ref": "#/$defs/timestamp" }, + "ingest_sequence": { "$ref": "#/$defs/sequence" }, + "bid_price": { "$ref": "#/$defs/positiveDecimal" }, + "bid_quantity": { "$ref": "#/$defs/positiveDecimal" }, + "ask_price": { "$ref": "#/$defs/positiveDecimal" }, + "ask_quantity": { "$ref": "#/$defs/positiveDecimal" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "instrument_id", "event_at", "available_at", "received_at", "ingest_sequence", "price", "quantity", "aggressor_side"], + "properties": { + "type": { "const": "trade" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "event_at": { "$ref": "#/$defs/timestamp" }, + "available_at": { "$ref": "#/$defs/timestamp" }, + "received_at": { "$ref": "#/$defs/timestamp" }, + "ingest_sequence": { "$ref": "#/$defs/sequence" }, + "price": { "$ref": "#/$defs/positiveDecimal" }, + "quantity": { "$ref": "#/$defs/positiveDecimal" }, + "aggressor_side": { "enum": ["buy", "sell", "unknown"] } + } + } + ] + }, + "orderBookLevel": { + "type": "object", + "additionalProperties": false, + "required": ["price", "quantity"], + "properties": { + "price": { "$ref": "#/$defs/positiveDecimal" }, + "quantity": { "$ref": "#/$defs/positiveDecimal" } + } + }, + "orderBookEvent": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["type", "instrument_id", "event_at", "available_at", "received_at", "ingest_sequence", "book_sequence", "bids", "asks"], + "properties": { + "type": { "const": "snapshot" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "event_at": { "$ref": "#/$defs/timestamp" }, + "available_at": { "$ref": "#/$defs/timestamp" }, + "received_at": { "$ref": "#/$defs/timestamp" }, + "ingest_sequence": { "$ref": "#/$defs/sequence" }, + "book_sequence": { "$ref": "#/$defs/sequence" }, + "bids": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/orderBookLevel" } }, + "asks": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/orderBookLevel" } } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "instrument_id", "event_at", "available_at", "received_at", "ingest_sequence", "book_sequence", "side", "price", "quantity"], + "properties": { + "type": { "const": "set" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "event_at": { "$ref": "#/$defs/timestamp" }, + "available_at": { "$ref": "#/$defs/timestamp" }, + "received_at": { "$ref": "#/$defs/timestamp" }, + "ingest_sequence": { "$ref": "#/$defs/sequence" }, + "book_sequence": { "$ref": "#/$defs/sequence" }, + "side": { "enum": ["bid", "ask"] }, + "price": { "$ref": "#/$defs/positiveDecimal" }, + "quantity": { "$ref": "#/$defs/positiveDecimal" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "instrument_id", "event_at", "available_at", "received_at", "ingest_sequence", "book_sequence", "side", "price"], + "properties": { + "type": { "const": "delete" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "event_at": { "$ref": "#/$defs/timestamp" }, + "available_at": { "$ref": "#/$defs/timestamp" }, + "received_at": { "$ref": "#/$defs/timestamp" }, + "ingest_sequence": { "$ref": "#/$defs/sequence" }, + "book_sequence": { "$ref": "#/$defs/sequence" }, + "side": { "enum": ["bid", "ask"] }, + "price": { "$ref": "#/$defs/positiveDecimal" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "instrument_id", "event_at", "available_at", "received_at", "ingest_sequence", "book_sequence", "price", "quantity", "aggressor_side"], + "properties": { + "type": { "const": "trade" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "event_at": { "$ref": "#/$defs/timestamp" }, + "available_at": { "$ref": "#/$defs/timestamp" }, + "received_at": { "$ref": "#/$defs/timestamp" }, + "ingest_sequence": { "$ref": "#/$defs/sequence" }, + "book_sequence": { "$ref": "#/$defs/sequence" }, + "price": { "$ref": "#/$defs/positiveDecimal" }, + "quantity": { "$ref": "#/$defs/positiveDecimal" }, + "aggressor_side": { "enum": ["buy", "sell", "unknown"] } + } + } + ] + }, + "fxRate": { + "type": "object", + "additionalProperties": false, + "required": ["currency", "rate"], + "properties": { + "currency": { "$ref": "#/$defs/identifier" }, + "rate": { "$ref": "#/$defs/positiveDecimal" } + } + }, + "corporateAction": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["type", "action_id", "instrument_id", "numerator", "denominator"], + "properties": { + "type": { "const": "split" }, + "action_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "numerator": { "$ref": "#/$defs/sequence" }, + "denominator": { "$ref": "#/$defs/sequence" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "action_id", "instrument_id", "amount_per_unit"], + "properties": { + "type": { "const": "cash_dividend" }, + "action_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "amount_per_unit": { "$ref": "#/$defs/positiveDecimal" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "action_id", "instrument_id", "destination_instrument_id", "numerator", "denominator", "basis_allocation_bps", "fractional_policy"], + "properties": { + "type": { "enum": ["stock_dividend", "rights", "spin_off"] }, + "action_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "destination_instrument_id": { "$ref": "#/$defs/identifier" }, + "numerator": { "$ref": "#/$defs/sequence" }, + "denominator": { "$ref": "#/$defs/sequence" }, + "basis_allocation_bps": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "fractional_policy": { "$ref": "#/$defs/fractionalPolicy" } + } + } + ] + }, + "fractionalPolicy": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["policy"], + "properties": { "policy": { "const": "reject" } } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["policy", "price", "currency"], + "properties": { + "policy": { "const": "cash_in_lieu" }, + "price": { "$ref": "#/$defs/positiveDecimal" }, + "currency": { "$ref": "#/$defs/identifier" } + } + } + ] + }, + "terminalPolicy": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["policy"], + "properties": { "policy": { "const": "hold" } } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["policy", "price", "currency"], + "properties": { + "policy": { "const": "cash_out" }, + "price": { "$ref": "#/$defs/positiveDecimal" }, + "currency": { "$ref": "#/$defs/identifier" } + } + } + ] + }, + "lifecycleEvent": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["type", "event_id", "instrument_id", "reason"], + "properties": { + "type": { "const": "halt" }, + "event_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "reason": { "type": "string", "minLength": 1 } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "event_id", "instrument_id"], + "properties": { + "type": { "const": "resume" }, + "event_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "event_id", "instrument_id", "symbol", "provider", "provider_instrument_id"], + "properties": { + "type": { "const": "identifier_change" }, + "event_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "symbol": { "$ref": "#/$defs/identifier" }, + "provider": { "$ref": "#/$defs/identifier" }, + "provider_instrument_id": { "$ref": "#/$defs/identifier" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "event_id", "instrument_id", "terminal_policy"], + "properties": { + "type": { "const": "expiration" }, + "event_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "terminal_policy": { "$ref": "#/$defs/terminalPolicy" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "event_id", "instrument_id", "terminal_policy", "reason"], + "properties": { + "type": { "const": "delisting" }, + "event_id": { "$ref": "#/$defs/identifier" }, + "instrument_id": { "$ref": "#/$defs/identifier" }, + "terminal_policy": { "$ref": "#/$defs/terminalPolicy" }, + "reason": { "type": "string", "minLength": 1 } + } + } + ] + } + } +} diff --git a/docs/api-reference.md b/docs/api-reference.md index a5d0aed..d7b9192 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -7,4 +7,4 @@ that every public interface has a corresponding page. The generated reference describes library types and functions. The versioned JSON and JSON Lines -files under [Contracts](../contracts/v15/README.md) remain authoritative for process boundaries. +files under [Contracts](../contracts/v16/README.md) remain authoritative for process boundaries. diff --git a/docs/continuous-integration.md b/docs/continuous-integration.md index aff839c..dbe9e62 100644 --- a/docs/continuous-integration.md +++ b/docs/continuous-integration.md @@ -21,7 +21,7 @@ Every runtime cell replays the frozen v3 demo, v5 demo, and v5 risk-limited fill canonical fixtures. Standard output and standard error are captured separately because human diagnostics may contain platform-specific paths or process details and are not part of the journal contract. -The full test suite additionally validates and replays the current v15 batch, stream, journal, and +The full test suite additionally validates and replays the current v16 batch, stream, journal, and strategy-v12 fixtures, including quote/trade causality and the reconciled first valuation. Coverage runs once in the exact locked Ubuntu environment. The required Persistra job also runs diff --git a/docs/execution-model.md b/docs/execution-model.md index 53d70d6..157fcaf 100644 --- a/docs/execution-model.md +++ b/docs/execution-model.md @@ -1,12 +1,12 @@ # Execution model The engine selects a compiled execution module by the scenario's stable `execution.model` name. -Contract v15 advertises `completed_bar_v1`, `completed_bar_next_open_v1`, +Contract v16 advertises `completed_bar_v1`, `completed_bar_next_open_v1`, `completed_bar_adverse_touch_v1`, `quote_trade_v1`, and `order_book_v1`; embedders can inject another module through the typed engine configuration without introducing runtime shared-library loading. The selected name is repeated in both terminal audit records. -Each compiled model owns a strict configuration contract. The v15 envelope separates selection from +Each compiled model owns a strict configuration contract. The v16 envelope separates selection from model-specific parameters: ```json diff --git a/docs/persistra.md b/docs/persistra.md index f66b6be..8ebacb5 100644 --- a/docs/persistra.md +++ b/docs/persistra.md @@ -54,12 +54,12 @@ lifecycle belong to Persistra. Persistra currently uses the transitional v3 [scenario](../contracts/v3/scenario.schema.json) and [journal](../contracts/v3/journal.schema.json) schemas and their adjacent conformance fixtures for -structural checks. The engine advertises current contract v15 while retaining v14 through v3 and +structural checks. The engine advertises current contract v16 while retaining v15 through v3 and exact v3 journal output for v3 inputs. The engine parser is authoritative for ordering, catalog coverage, causality, tick, lot, risk, and accounting invariants that JSON Schema cannot express. External strategies use the separate -[strategy protocol v13](../contracts/strategy/v13/README.md). Persistra's host turns protocol +[strategy protocol v14](../contracts/strategy/v14/README.md). Persistra's host turns protocol initialization, marked portfolio contexts, market-slice, fill, order, and rejection events into typed callbacks. Realized weights are available only for positive equity. The retained run manifest binds the strategy identity, executable hash, declared input hashes, transcript hash, @@ -79,7 +79,7 @@ compatibility claim. - **Engine:** `--capabilities` is the authoritative machine-readable surface. The engine must reject unsupported versions and malformed or semantically invalid input before reporting a successful run. -- **Scenario:** Frozen scenario and stream artifacts do not change. The current v15 contract may +- **Scenario:** Frozen scenario and stream artifacts do not change. The current v16 contract may receive additive changes only when old valid inputs retain their meaning; breaking changes need a new version. Transitional v3 support remains explicit in `--capabilities`. - **Journal:** A run emits the journal version paired with its accepted scenario. Record ordering, diff --git a/docs/scenario.md b/docs/scenario.md index 64b50cb..c4c7292 100644 --- a/docs/scenario.md +++ b/docs/scenario.md @@ -4,8 +4,8 @@ A replay scenario uses either one strict JSON object or a strict JSON Lines stre weights, quantities, money, and sequences are canonical JSON strings. Counts and basis points are JSON integers. Unknown, missing, duplicate, noncanonical, and non-finite values fail parsing. -Use [the v15 demo](../contracts/v15/fixtures/demo.scenario.json) as the canonical complete example. -The [scenario JSON Schema](../contracts/v15/scenario.schema.json) provides structural validation. +Use [the v16 demo](../contracts/v16/fixtures/demo.scenario.json) as the canonical complete example. +The [scenario JSON Schema](../contracts/v16/scenario.schema.json) provides structural validation. The engine parser also enforces cross-field and cross-record invariants. Diagnostics identify the failed field or array item. Stream diagnostics additionally retain the record line and sequence. @@ -32,8 +32,8 @@ The batch object and stream header share one domain-construction path and the sa checks. Stream items reuse the batch slice and intent validators directly; no synthetic batch scenario is constructed. -The [stream record JSON Schema](../contracts/v15/scenario-stream.schema.json) validates each line, -and [the v15 stream fixture](../contracts/v15/fixtures/demo.scenario.jsonl) is the canonical example. +The [stream record JSON Schema](../contracts/v16/scenario-stream.schema.json) validates each line, +and [the v16 stream fixture](../contracts/v16/fixtures/demo.scenario.jsonl) is the canonical example. The engine validates the entire stream before creating a journal. It then replays one record at a time without retaining prior slices, scheduled batches, or audit events. Reducer state still retains current account, order, target, and latest-bar state required by execution semantics. @@ -42,7 +42,7 @@ retains current account, order, target, and latest-bar state required by executi | Field | Meaning | |---|---| -| `contract_version` | Required string identifying this file contract; v15 is `"15"` | +| `contract_version` | Required string identifying this file contract; v16 is `"16"` | | `metadata` | Required arbitrary JSON object preserved for provenance and ignored by execution | | `run_id` | Stable identity used in generated IDs | | `base_currency` | Reporting currency used for aggregate risk and valuation | @@ -167,7 +167,11 @@ Supported intents are: - `target_quantities` with a `targets` array of `instrument_id` and `quantity` - `submit_order` with instrument, side, quantity, kind, and nullable limit price - `cancel_order` with a deterministic `order_id` -- `emit_metric` with string `name` and `value` +- `emit_metric` with a bounded string `name` and typed `value`. The value object declares + `numeric` (a canonical decimal string), `string`, or `boolean`. Optional `unit`, `aggregation` + (`last`, `sum`, `minimum`, `maximum`, or `mean`), and up to 16 string dimensions carry + reconciliation metadata. Dimension keys are unique and journal encoding sorts them + lexicographically. Contracts through v15 retain the legacy string-only shape. Both target forms contain every configured instrument exactly once. Weights and quantities are signed. Gross absolute weight must not exceed `max_leverage`; quantity targets align to their @@ -268,7 +272,7 @@ records `event_at`, `available_at`, `received_at`, and a positive `ingest_sequen strictly ordered by availability, receipt, and ingest sequence; economic time cannot follow availability, and no event may escape its containing slice's time or observability boundary. Prices and quantities align to the instrument tick and lot. The -[`quote-trade` fixture](../contracts/v15/fixtures/quote-trade.scenario.json) demonstrates passive +[`quote-trade` fixture](../contracts/v16/fixtures/quote-trade.scenario.json) demonstrates passive fills and has an equivalent bounded JSON Lines replay. Version 15 slices add `order_book_events`. Every configured instrument supplies a fresh full @@ -278,7 +282,7 @@ books are accepted. Runtime validation enforces the configured `max_depth_levels delete, sequence continuity, slice observability, and tick/lot alignment. Marketable orders walk the visible book; passive limits queue behind displayed same-price depth, with reductions moving them forward and additions joining behind. The -[`order-book` fixture](../contracts/v15/fixtures/order-book.scenario.json) demonstrates bounded +[`order-book` fixture](../contracts/v16/fixtures/order-book.scenario.json) demonstrates bounded queue replay and has equivalent JSON Lines and journal artifacts. For causal next-open execution, an order-changing schedule entry's anchor `received_at` is no later @@ -286,7 +290,7 @@ than the next slice `start_at`. ## Audit journal -The [journal JSON Schema](../contracts/v15/journal.schema.json) validates each JSON Lines record. +The [journal JSON Schema](../contracts/v16/journal.schema.json) validates each JSON Lines record. Every record contains `contract_version`, `engine_sequence`, deterministic `event_id`, ordered `causation_ids`, `run_id`, `recorded_at`, `event_type`, and an event-specific `payload`. Causal references are unique prior event IDs from the same run. The version is repeated on every record diff --git a/lib/audit.ml b/lib/audit.ml index 0734f09..6145ce3 100644 --- a/lib/audit.ml +++ b/lib/audit.ml @@ -128,7 +128,7 @@ type event = | Margin_call_triggered of valuation | Margin_restored of valuation | Intent_rejected of string - | Metric_emitted of { name : string; value : string } + | Metric_emitted of Metric.t | Valuation of valuation | Run_completed of { scenario_sha256 : string; diff --git a/lib/audit.mli b/lib/audit.mli index 30328af..07708df 100644 --- a/lib/audit.mli +++ b/lib/audit.mli @@ -130,7 +130,7 @@ type event = | Margin_call_triggered of valuation | Margin_restored of valuation | Intent_rejected of string - | Metric_emitted of { name : string; value : string } + | Metric_emitted of Metric.t | Valuation of valuation | Run_completed of { scenario_sha256 : string; diff --git a/lib/codec.ml b/lib/codec.ml index 238a9d5..fb5f935 100644 --- a/lib/codec.ml +++ b/lib/codec.ml @@ -457,9 +457,10 @@ let versioned_market_slice_to_yojson ~contract_version market_slice = ] |> function | `Assoc fields - when List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] -> + when List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] + -> let settlement = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] then [ ( "settlement_failures", `List @@ -469,7 +470,7 @@ let versioned_market_slice_to_yojson ~contract_version market_slice = else [] in let lifecycle = - if List.mem contract_version [ "15"; "14"; "13"; "12" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12" ] then [ ( "lifecycle_events", `List @@ -479,7 +480,7 @@ let versioned_market_slice_to_yojson ~contract_version market_slice = else [] in let market_events = - if List.mem contract_version [ "15"; "14" ] then + if List.mem contract_version [ "16"; "15"; "14" ] then [ ( "market_events", `List @@ -489,7 +490,7 @@ let versioned_market_slice_to_yojson ~contract_version market_slice = else [] in let order_book_events = - if String.equal contract_version "15" then + if List.mem contract_version [ "16"; "15" ] then [ ( "order_book_events", `List @@ -534,6 +535,9 @@ let market_slice_to_yojson_v14 market_slice = let market_slice_to_yojson_v15 market_slice = versioned_market_slice_to_yojson ~contract_version:"15" market_slice +let market_slice_to_yojson_v16 market_slice = + versioned_market_slice_to_yojson ~contract_version:"16" market_slice + let request_fields request = let kind, limit_price = match request.Order.kind with @@ -637,7 +641,9 @@ let order_to_yojson_v8 order = ]) let versioned_order_to_yojson ~contract_version order = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8" ] + if + List.mem contract_version + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8" ] then order_to_yojson_v8 order else order_to_yojson order @@ -835,8 +841,9 @@ let account_valuation_to_yojson ?(contract_version = "8") valuation = ( "cash_balances", `List (List.map - (if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then - cash_attribution_to_yojson_v11 + (if + List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] + then cash_attribution_to_yojson_v11 else if String.equal contract_version "10" then cash_attribution_to_yojson_v10 else cash_attribution_to_yojson) @@ -844,8 +851,9 @@ let account_valuation_to_yojson ?(contract_version = "8") valuation = ( "positions", `List (List.map - (if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then - position_attribution_to_yojson_v11 + (if + List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] + then position_attribution_to_yojson_v11 else if String.equal contract_version "9" || String.equal contract_version "10" @@ -855,15 +863,16 @@ let account_valuation_to_yojson ?(contract_version = "8") valuation = ] |> function | `Assoc fields - when List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10"; "9" ] - -> + when List.mem contract_version + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9" ] -> let financing = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] then - [ ("cash_interest", money valuation.Account.cash_interest) ] + if + List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] + then [ ("cash_interest", money valuation.Account.cash_interest) ] else [] in let settlement = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] then [ ("settled_cash", money valuation.Account.settled_cash); ("unsettled_cash", money valuation.unsettled_cash); @@ -912,7 +921,7 @@ let valuation_to_yojson ~contract_version valuation = let fields = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8" ] then fields @ [ @@ -970,6 +979,41 @@ let requested_target_to_yojson target = Option.fold ~none:`Null ~some:price target.reference_price ); ] +let metric_to_yojson metric = + let value = + match metric.Metric.value with + | Metric.Numeric value -> + `Assoc + [ + ("type", string "numeric"); + ("value", string (Metric.numeric_to_string value)); + ] + | Metric.String value -> + `Assoc [ ("type", string "string"); ("value", string value) ] + | Metric.Boolean value -> + `Assoc [ ("type", string "boolean"); ("value", `Bool value) ] + in + `Assoc + ([ ("name", string metric.name); ("value", value) ] + @ (match metric.unit_ with + | None -> [] + | Some unit_ -> [ ("unit", string unit_) ]) + @ (if metric.dimensions = [] then [] + else + [ + ( "dimensions", + `Assoc + (List.map + (fun dimension -> + (dimension.Metric.key, string dimension.value)) + metric.dimensions) ); + ]) + @ + match metric.aggregation with + | None -> [] + | Some aggregation -> + [ ("aggregation", string (Metric.aggregation_to_string aggregation)) ]) + let payload_to_yojson ~contract_version = function | Audit.Run_started { scenario_sha256; execution_model } -> `Assoc @@ -1054,7 +1098,9 @@ let payload_to_yojson ~contract_version = function ("final_price", price attribution.final_price); ] | Audit.Fill_applied fill -> - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10"; "9" ] + if + List.mem contract_version + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9" ] then fill_to_yojson_v9 fill else fill_to_yojson fill | Audit.Settlement_instruction_created instruction @@ -1183,8 +1229,16 @@ let payload_to_yojson ~contract_version = function | Audit.Margin_call_triggered valuation | Audit.Margin_restored valuation -> valuation_to_yojson ~contract_version valuation | Audit.Intent_rejected reason -> `Assoc [ ("reason", string reason) ] - | Audit.Metric_emitted { name; value } -> - `Assoc [ ("name", string name); ("value", string value) ] + | Audit.Metric_emitted metric -> + if String.equal contract_version "16" then metric_to_yojson metric + else + let value = + match metric.Metric.value with + | Metric.String value -> value + | Metric.Numeric value -> Metric.numeric_to_string value + | Metric.Boolean value -> string_of_bool value + in + `Assoc [ ("name", string metric.name); ("value", string value) ] | Audit.Valuation valuation -> valuation_to_yojson ~contract_version valuation | Audit.Run_completed { scenario_sha256; execution_model; valuation; order_counts } -> diff --git a/lib/codec.mli b/lib/codec.mli index 12ad70c..f48c608 100644 --- a/lib/codec.mli +++ b/lib/codec.mli @@ -10,6 +10,7 @@ val market_slice_to_yojson_v12 : Market_slice.t -> Yojson.Safe.t val market_slice_to_yojson_v13 : Market_slice.t -> Yojson.Safe.t val market_slice_to_yojson_v14 : Market_slice.t -> Yojson.Safe.t val market_slice_to_yojson_v15 : Market_slice.t -> Yojson.Safe.t +val market_slice_to_yojson_v16 : Market_slice.t -> Yojson.Safe.t val order_to_yojson : Order.t -> Yojson.Safe.t val order_to_yojson_v8 : Order.t -> Yojson.Safe.t val fill_to_yojson : Fill.t -> Yojson.Safe.t diff --git a/lib/contract.ml b/lib/contract.ml index f901c21..d8682a4 100644 --- a/lib/contract.ml +++ b/lib/contract.ml @@ -1,11 +1,12 @@ -let version = "15" -let previous_version = "14" +let version = "16" +let previous_version = "15" let legacy_journal_version = "3" let supported_versions = [ version; previous_version; + "14"; "13"; "12"; "11"; @@ -20,8 +21,8 @@ let supported_versions = ] let is_supported version = List.mem version supported_versions -let strategy_protocol_version = "13" -let previous_strategy_protocol_version = "12" +let strategy_protocol_version = "14" +let previous_strategy_protocol_version = "13" let engine_version = "1.0.0" let strings values = `List (List.map (fun value -> `String value) values) @@ -40,6 +41,7 @@ let capabilities_to_yojson () = [ strategy_protocol_version; previous_strategy_protocol_version; + "12"; "11"; "10"; "9"; diff --git a/lib/engine.ml b/lib/engine.ml index 49af7ba..c88493e 100644 --- a/lib/engine.ml +++ b/lib/engine.ml @@ -75,6 +75,7 @@ let config_v12 = config_v11 let config_v13 = config_v12 let config_v14 = config_v13 let config_v15 = config_v14 +let config_v16 = config_v15 let valid_sha256 value = String.length value = 64 @@ -1499,15 +1500,13 @@ module Interactive = struct | Ok (desired, requested) -> replace_targets reduction Audit.Weights desired requested - let metric reduction name value = - if String.length name = 0 || String.trim name <> name then - reject_intent reduction "metric name must be a nonempty trimmed string" - else emit reduction (Audit.Metric_emitted { name; value }) + let emit_metric reduction observation = + emit reduction (Audit.Metric_emitted observation) let handle_intent reduction = function | intent when reduction.state.liquidation_pending -> ( match intent with - | Strategy.Emit_metric { name; value } -> metric reduction name value + | Strategy.Emit_metric observation -> emit_metric reduction observation | _ -> reject_intent reduction "margin liquidation is in progress") | Strategy.Target_weights targets -> set_weight_targets reduction targets | Strategy.Target_quantities targets -> @@ -1519,7 +1518,7 @@ module Interactive = struct else submit_order reduction request | Strategy.Cancel_order order_id -> cancel_order reduction ~reason:Audit.Strategy_requested order_id - | Strategy.Emit_metric { name; value } -> metric reduction name value + | Strategy.Emit_metric observation -> emit_metric reduction observation type drain_result = | Drained of reduction diff --git a/lib/engine.mli b/lib/engine.mli index ca85780..dc47f1f 100644 --- a/lib/engine.mli +++ b/lib/engine.mli @@ -84,6 +84,17 @@ val config_v15 : max_internal_events:int -> (config, string) result +val config_v16 : + contract_version:string -> + risk:Risk.t -> + venue_calendars:Venue_calendar.t list -> + execution_model:Execution_model.t -> + execution:Execution.t -> + financing:Financing.policy -> + settlement:Settlement.policy -> + max_internal_events:int -> + (config, string) result + module Interactive : sig type t type progress diff --git a/lib/execution_model.ml b/lib/execution_model.ml index eef0033..64c1712 100644 --- a/lib/execution_model.ml +++ b/lib/execution_model.ml @@ -66,7 +66,22 @@ let completed_bar_v1_contract = version = "2"; previous_versions = [ "1" ]; scenario_contract_versions = - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5"; "4"; "3" ]; + [ + "16"; + "15"; + "14"; + "13"; + "12"; + "11"; + "10"; + "9"; + "8"; + "7"; + "6"; + "5"; + "4"; + "3"; + ]; required_fields = [ "version"; "participation_bps"; "fee_schedules" ]; legacy_required_fields = [ "version"; "participation_bps"; "fixed_fee"; "fee_bps" ]; @@ -87,7 +102,7 @@ let conservative_contract = { version = "1"; previous_versions = []; - scenario_contract_versions = [ "15"; "14"; "13" ]; + scenario_contract_versions = [ "16"; "15"; "14"; "13" ]; required_fields = [ "version"; @@ -116,7 +131,7 @@ let quote_trade_contract = { version = "1"; previous_versions = []; - scenario_contract_versions = [ "15"; "14" ]; + scenario_contract_versions = [ "16"; "15"; "14" ]; required_fields = [ "version"; "participation_bps"; "fee_schedules" ]; legacy_required_fields = []; supported_order_types = [ "market"; "limit"; "stop"; "stop_limit" ]; @@ -138,7 +153,7 @@ let order_book_contract = { version = "1"; previous_versions = []; - scenario_contract_versions = [ "15" ]; + scenario_contract_versions = [ "16"; "15" ]; required_fields = [ "version"; "participation_bps"; "fee_schedules"; "max_depth_levels" ]; legacy_required_fields = []; diff --git a/lib/external_replay.ml b/lib/external_replay.ml index 1c95491..1c446ae 100644 --- a/lib/external_replay.ml +++ b/lib/external_replay.ml @@ -105,8 +105,8 @@ let create_runner ~contract_version ~run_id ~scenario_sha256 ~risk Engine.config_v10 ~contract_version ~risk ~venue_calendars ~execution_model ~execution ~financing ~max_internal_events | Some financing, Some settlement -> - if String.equal contract_version "15" then - Engine.config_v15 ~contract_version ~risk ~venue_calendars + if List.mem contract_version [ "16"; "15" ] then + Engine.config_v16 ~contract_version ~risk ~venue_calendars ~execution_model ~execution ~financing ~settlement ~max_internal_events else if String.equal contract_version "14" then diff --git a/lib/market_slice.ml b/lib/market_slice.ml index 130efe0..f3dcd27 100644 --- a/lib/market_slice.ml +++ b/lib/market_slice.ml @@ -186,6 +186,8 @@ let create_v15 ~slice_sequence ~start_at ~end_at ~available_at ~received_at settlement_failures; } +let create_v16 = create_v15 + let create_v14 ~slice_sequence ~start_at ~end_at ~available_at ~received_at ~bars ~fx_rates ~corporate_actions ~borrow_observations ~cash_rate_observations ~settlement_failures ~lifecycle_events diff --git a/lib/market_slice.mli b/lib/market_slice.mli index a1e0ded..7182f96 100644 --- a/lib/market_slice.mli +++ b/lib/market_slice.mli @@ -123,6 +123,23 @@ val create_v15 : order_book_events:Order_book_event.t list -> (t, string) result +val create_v16 : + slice_sequence:int64 -> + start_at:Ptime.t -> + end_at:Ptime.t -> + available_at:Ptime.t -> + received_at:Ptime.t -> + bars:Bar.t list -> + fx_rates:fx_mark list -> + corporate_actions:Corporate_action.t list -> + borrow_observations:Financing.borrow_observation list -> + cash_rate_observations:Financing.cash_rate_observation list -> + settlement_failures:Settlement.failure list -> + lifecycle_events:Instrument_lifecycle.event list -> + market_events:Market_event.t list -> + order_book_events:Order_book_event.t list -> + (t, string) result + val bar : t -> Id.Instrument.t -> Bar.t option val fx_rate : t -> string -> Scalar.Price.t option val compare_replay_order : t -> t -> int diff --git a/lib/metric.ml b/lib/metric.ml new file mode 100644 index 0000000..08e086e --- /dev/null +++ b/lib/metric.ml @@ -0,0 +1,101 @@ +type numeric = string +type value = Numeric of numeric | String of string | Boolean of bool +type aggregation = Last | Sum | Minimum | Maximum | Mean +type dimension = { key : string; value : string } + +type t = { + name : string; + value : value; + unit_ : string option; + dimensions : dimension list; + aggregation : aggregation option; +} + +let max_name_bytes = Resource_limits.metric_name_bytes +let max_string_value_bytes = Resource_limits.metric_string_value_bytes +let max_unit_bytes = Resource_limits.metric_unit_bytes +let max_dimensions = Resource_limits.metric_dimensions +let max_dimension_key_bytes = Resource_limits.metric_dimension_key_bytes +let max_dimension_value_bytes = Resource_limits.metric_dimension_value_bytes + +let valid_trimmed ~maximum value = + String.length value > 0 + && String.length value <= maximum + && String.equal value (String.trim value) + +let numeric_to_string value = value + +let numeric_of_string value = + let length = String.length value in + let start = if length > 0 && value.[0] = '-' then 1 else 0 in + let decimal = ref None in + let valid = ref (start < length) in + for index = start to length - 1 do + match value.[index] with + | '0' .. '9' -> () + | '.' when !decimal = None && index > start && index + 1 < length -> + decimal := Some index + | _ -> valid := false + done; + let integer_end = Option.value !decimal ~default:length in + if integer_end - start > 1 && value.[start] = '0' then valid := false; + if Option.is_some !decimal && value.[length - 1] = '0' then valid := false; + if String.equal value "-0" then valid := false; + if !valid then Ok value + else Error "metric numeric value must be a canonical decimal string" + +let aggregation_to_string = function + | Last -> "last" + | Sum -> "sum" + | Minimum -> "minimum" + | Maximum -> "maximum" + | Mean -> "mean" + +let aggregation_of_string = function + | "last" -> Ok Last + | "sum" -> Ok Sum + | "minimum" -> Ok Minimum + | "maximum" -> Ok Maximum + | "mean" -> Ok Mean + | _ -> Error "metric aggregation must be last, sum, minimum, maximum, or mean" + +let create ~name ~value ?unit_ ?(dimensions = []) ?aggregation () = + if not (valid_trimmed ~maximum:max_name_bytes name) then + Error "metric name must be a nonempty trimmed string of at most 128 bytes" + else if + match value with + | String value -> String.length value > max_string_value_bytes + | Numeric _ | Boolean _ -> false + then Error "metric string value must contain at most 1024 bytes" + else if + match (value, aggregation) with + | (String _ | Boolean _), Some (Sum | Minimum | Maximum | Mean) -> true + | _ -> false + then Error "non-numeric metrics only support last aggregation" + else if + match unit_ with + | Some unit_ -> not (valid_trimmed ~maximum:max_unit_bytes unit_) + | None -> false + then Error "metric unit must be a nonempty trimmed string of at most 64 bytes" + else if List.length dimensions > max_dimensions then + Error "metric dimensions must contain at most 16 entries" + else + let dimensions = + List.sort + (fun (left, _) (right, _) -> String.compare left right) + dimensions + in + let rec validate prior acc = function + | [] -> Ok { name; value; unit_; dimensions = List.rev acc; aggregation } + | (key, value) :: remaining -> + if not (valid_trimmed ~maximum:max_dimension_key_bytes key) then + Error + "metric dimension key must be a nonempty trimmed string of at \ + most 64 bytes" + else if String.length value > max_dimension_value_bytes then + Error "metric dimension value must contain at most 128 bytes" + else if Option.equal String.equal prior (Some key) then + Error "metric dimension keys must be unique" + else validate (Some key) ({ key; value } :: acc) remaining + in + validate None [] dimensions diff --git a/lib/metric.mli b/lib/metric.mli new file mode 100644 index 0000000..a529fae --- /dev/null +++ b/lib/metric.mli @@ -0,0 +1,28 @@ +(** Typed, dimensioned strategy observations. *) + +type numeric +type value = Numeric of numeric | String of string | Boolean of bool +type aggregation = Last | Sum | Minimum | Maximum | Mean +type dimension = { key : string; value : string } + +type t = private { + name : string; + value : value; + unit_ : string option; + dimensions : dimension list; + aggregation : aggregation option; +} + +val create : + name:string -> + value:value -> + ?unit_:string -> + ?dimensions:(string * string) list -> + ?aggregation:aggregation -> + unit -> + (t, string) result + +val aggregation_to_string : aggregation -> string +val aggregation_of_string : string -> (aggregation, string) result +val numeric_of_string : string -> (numeric, string) result +val numeric_to_string : numeric -> string diff --git a/lib/replay.ml b/lib/replay.ml index 3378072..aa9c7bb 100644 --- a/lib/replay.ml +++ b/lib/replay.ml @@ -78,8 +78,8 @@ let engine_config ~contract_version ~risk ~venue_calendars ~execution_model Engine.config_v10 ~contract_version ~risk ~venue_calendars ~execution_model ~execution ~financing ~max_internal_events | Some financing, Some settlement -> - if String.equal contract_version "15" then - Engine.config_v15 ~contract_version ~risk ~venue_calendars + if List.mem contract_version [ "16"; "15" ] then + Engine.config_v16 ~contract_version ~risk ~venue_calendars ~execution_model ~execution ~financing ~settlement ~max_internal_events else if String.equal contract_version "14" then diff --git a/lib/resource_limits.ml b/lib/resource_limits.ml index e873a82..e3df489 100644 --- a/lib/resource_limits.ml +++ b/lib/resource_limits.ml @@ -5,6 +5,12 @@ let internal_events = 100_000 let catalog_instruments = 4_096 let intents_per_batch = 4_096 let artifact_record_bytes = 2_097_152 +let metric_name_bytes = 128 +let metric_string_value_bytes = 1_024 +let metric_unit_bytes = 64 +let metric_dimensions = 16 +let metric_dimension_key_bytes = 64 +let metric_dimension_value_bytes = 128 let to_yojson () = `Assoc @@ -16,4 +22,10 @@ let to_yojson () = ("catalog_instruments", `Int catalog_instruments); ("intents_per_batch", `Int intents_per_batch); ("artifact_record_bytes", `Int artifact_record_bytes); + ("metric_name_bytes", `Int metric_name_bytes); + ("metric_string_value_bytes", `Int metric_string_value_bytes); + ("metric_unit_bytes", `Int metric_unit_bytes); + ("metric_dimensions", `Int metric_dimensions); + ("metric_dimension_key_bytes", `Int metric_dimension_key_bytes); + ("metric_dimension_value_bytes", `Int metric_dimension_value_bytes); ] diff --git a/lib/resource_limits.mli b/lib/resource_limits.mli index e0f4f78..682453c 100644 --- a/lib/resource_limits.mli +++ b/lib/resource_limits.mli @@ -10,4 +10,10 @@ val internal_events : int val catalog_instruments : int val intents_per_batch : int val artifact_record_bytes : int +val metric_name_bytes : int +val metric_string_value_bytes : int +val metric_unit_bytes : int +val metric_dimensions : int +val metric_dimension_key_bytes : int +val metric_dimension_value_bytes : int val to_yojson : unit -> Yojson.Safe.t diff --git a/lib/scenario.ml b/lib/scenario.ml index 81877d0..86a10f8 100644 --- a/lib/scenario.ml +++ b/lib/scenario.ml @@ -556,7 +556,7 @@ let parse_v7_risk base_currency instruments json = let parse_risk ~contract_version base_currency instruments json = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7" ] then parse_v7_risk base_currency instruments json else parse_legacy_risk base_currency instruments json @@ -829,7 +829,7 @@ let parse_versioned_execution ~contract_version ~instruments json = let parse_execution ~contract_version ~instruments json = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] then parse_versioned_execution ~contract_version ~instruments json else parse_legacy_execution ~contract_version json @@ -879,7 +879,8 @@ let parse_portfolio_intent ~name ~parse_target make json = let parse_submit_intent ~contract_version json = let versioned = - List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8" ] + List.mem contract_version + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8" ] in let* fields = object_fields ~name:"submit_order intent" @@ -979,17 +980,86 @@ let parse_cancel_intent json = let* order_id = parse_id Id.Order.of_string ~name:"order_id" order_json in Ok (Strategy.Cancel_order order_id) -let parse_metric_intent json = - let* fields = - object_fields ~name:"emit_metric intent" - ~expected:[ "type"; "name"; "value" ] - json - in - let* name_json = field fields "name" in - let* name = string ~name:"metric name" name_json in - let* value_json = field fields "value" in - let* value = string ~name:"metric value" value_json in - Ok (Strategy.Emit_metric { name; value }) +let parse_metric_intent ~contract_version json = + if not (String.equal contract_version "16") then + let* fields = + object_fields ~name:"emit_metric intent" + ~expected:[ "type"; "name"; "value" ] + json + in + let* name_json = field fields "name" in + let* name = string ~name:"metric name" name_json in + let* value_json = field fields "value" in + let* value = string ~name:"metric value" value_json in + let* metric = Metric.create ~name ~value:(Metric.String value) () in + Ok (Strategy.Emit_metric metric) + else + let* fields = + match json with + | `Assoc fields -> + let names = List.map fst fields in + let unique = List.sort_uniq String.compare names in + let allowed = + [ "aggregation"; "dimensions"; "name"; "type"; "unit"; "value" ] + in + if List.length names <> List.length unique then + Error "emit_metric intent must not contain duplicate fields" + else if + not + (List.for_all (fun name -> List.mem name allowed) unique + && List.for_all + (fun name -> List.mem name unique) + [ "type"; "name"; "value" ]) + then Error "emit_metric intent has unknown or missing fields" + else Ok fields + | _ -> Error "emit_metric intent must be a JSON object" + in + let* name_json = field fields "name" in + let* name = string ~name:"metric name" name_json in + let* value = + let* json = field fields "value" in + let* value_fields = + object_fields ~name:"metric value" ~expected:[ "type"; "value" ] json + in + let* type_json = field value_fields "type" in + let* value_type = string ~name:"metric value type" type_json in + let* value_json = field value_fields "value" in + match (value_type, value_json) with + | "numeric", `String value -> + Metric.numeric_of_string value + |> Result.map (fun value -> Metric.Numeric value) + | "string", `String value -> Ok (Metric.String value) + | "boolean", `Bool value -> Ok (Metric.Boolean value) + | _ -> Error "metric value does not match its declared type" + in + let* unit_ = + match List.assoc_opt "unit" fields with + | None -> Ok None + | Some json -> string ~name:"metric unit" json |> Result.map Option.some + in + let* dimensions = + match List.assoc_opt "dimensions" fields with + | None -> Ok [] + | Some (`Assoc dimensions) -> + List.fold_left + (fun result (key, json) -> + let* values = result in + let* value = string ~name:"metric dimension value" json in + Ok ((key, value) :: values)) + (Ok []) dimensions + | Some _ -> Error "metric dimensions must be an object" + in + let* aggregation = + match List.assoc_opt "aggregation" fields with + | None -> Ok None + | Some json -> + let* value = string ~name:"metric aggregation" json in + Metric.aggregation_of_string value |> Result.map Option.some + in + let* metric = + Metric.create ~name ~value ?unit_ ~dimensions ?aggregation () + in + Ok (Strategy.Emit_metric metric) let parse_intent ~contract_version json = match json with @@ -1008,7 +1078,8 @@ let parse_intent ~contract_version json = | Some (`String "submit_order") -> parse_submit_intent ~contract_version json | Some (`String "cancel_order") -> parse_cancel_intent json - | Some (`String "emit_metric") -> parse_metric_intent json + | Some (`String "emit_metric") -> + parse_metric_intent ~contract_version json | Some _ -> Error "unsupported intent type" | None -> Error "intent is missing type") | _ -> Error "intent must be a JSON object" @@ -1850,25 +1921,27 @@ let parse_order_book_event json = let parse_slice ~contract_version json = let financing_fields = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] then - [ "borrow_observations"; "cash_rate_observations" ] + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] + then [ "borrow_observations"; "cash_rate_observations" ] else [] in let settlement_fields = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] then [ "settlement_failures" ] else [] in let lifecycle_fields = - if List.mem contract_version [ "15"; "14"; "13"; "12" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12" ] then [ "lifecycle_events" ] else [] in let market_event_fields = - if List.mem contract_version [ "15"; "14" ] then [ "market_events" ] else [] + if List.mem contract_version [ "16"; "15"; "14" ] then [ "market_events" ] + else [] in let order_book_event_fields = - if String.equal contract_version "15" then [ "order_book_events" ] else [] + if List.mem contract_version [ "16"; "15" ] then [ "order_book_events" ] + else [] in let* fields = object_fields ~name:"market slice" @@ -1906,7 +1979,7 @@ let parse_slice ~contract_version json = let* actions_json = field fields "corporate_actions" in let* actions_json = list ~name:"corporate_actions" actions_json in let* corporate_actions = map_list parse_corporate_action actions_json in - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] then let* borrow_json = Result.bind (field fields "borrow_observations") @@ -1921,7 +1994,7 @@ let parse_slice ~contract_version json = let* cash_rate_observations = map_list parse_cash_rate_observation cash_json in - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] then let* failures_json = Result.bind (field fields "settlement_failures") @@ -1930,21 +2003,21 @@ let parse_slice ~contract_version json = let* settlement_failures = map_list parse_settlement_failure failures_json in - if List.mem contract_version [ "15"; "14"; "13"; "12" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12" ] then let* lifecycle_json = Result.bind (field fields "lifecycle_events") (list ~name:"lifecycle_events") in let* lifecycle_events = map_list parse_lifecycle_event lifecycle_json in - if List.mem contract_version [ "15"; "14" ] then + if List.mem contract_version [ "16"; "15"; "14" ] then let* events_json = Result.bind (field fields "market_events") (list ~name:"market_events") in let* market_events = map_list parse_market_event events_json in - if String.equal contract_version "15" then + if List.mem contract_version [ "16"; "15" ] then let* book_events_json = Result.bind (field fields "order_book_events") @@ -2015,7 +2088,7 @@ let construct_header ~root ~contract_path ~contract_version let* initial_cash, initial_portfolio = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then let* portfolio = parse_initial_portfolio ~base_currency shape.initial_state @@ -2085,8 +2158,8 @@ let construct_header ~root ~contract_path ~contract_version | _, _ -> Ok Financing.legacy_policy in let financing = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] then - Some financing + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] + then Some financing else None in let* settlement = diff --git a/lib/scenario_shape.ml b/lib/scenario_shape.ml index e324722..71de814 100644 --- a/lib/scenario_shape.ml +++ b/lib/scenario_shape.ml @@ -72,7 +72,7 @@ let common ~root ~contract_version fields = let initial_field = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then "initial_portfolio" else "initial_cash" in @@ -81,19 +81,19 @@ let common ~root ~contract_version fields = let venue_calendars = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] then List.assoc_opt "venue_calendars" fields else None in let* risk = field ~root fields "risk" in let* execution = field ~root fields "execution" in let financing = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] then - List.assoc_opt "financing" fields + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] + then List.assoc_opt "financing" fields else None in let settlement = - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] then List.assoc_opt "settlement" fields else None in @@ -126,14 +126,14 @@ let batch json = let calendar_fields = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] then [ "venue_calendars" ] else [] in let initial_field = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then "initial_portfolio" else "initial_cash" in @@ -154,11 +154,13 @@ let batch json = "slices"; ] @ calendar_fields - @ (if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] + @ (if + List.mem contract_version + [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] then [ "financing" ] else []) @ - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] then [ "settlement" ] else []) json @@ -174,14 +176,14 @@ let stream_header ~contract_version json = let calendar_fields = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] then [ "venue_calendars" ] else [] in let initial_field = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then "initial_portfolio" else "initial_cash" in @@ -199,11 +201,13 @@ let stream_header ~contract_version json = "max_internal_events"; ] @ calendar_fields - @ (if List.mem contract_version [ "15"; "14"; "13"; "12"; "11"; "10" ] + @ (if + List.mem contract_version + [ "16"; "15"; "14"; "13"; "12"; "11"; "10" ] then [ "financing" ] else []) @ - if List.mem contract_version [ "15"; "14"; "13"; "12"; "11" ] then + if List.mem contract_version [ "16"; "15"; "14"; "13"; "12"; "11" ] then [ "settlement" ] else []) json diff --git a/lib/scenario_validation.ml b/lib/scenario_validation.ml index d09f3b7..06bed98 100644 --- a/lib/scenario_validation.ml +++ b/lib/scenario_validation.ml @@ -50,7 +50,7 @@ let header ~root ~contract_version ~base_currency ~initial_cash ~instruments let* () = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then Ok () else Account.create ~base_currency ~initial_cash @@ -71,7 +71,9 @@ let header ~root ~contract_version ~base_currency ~initial_cash ~instruments let* () = if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] + [ + "16"; "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5"; + ] then validate_venue_calendars ~root catalog venue_calendars else Ok () in @@ -91,7 +93,19 @@ let header ~root ~contract_version ~base_currency ~initial_cash ~instruments (child root (if List.mem contract_version - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ + "16"; + "15"; + "14"; + "13"; + "12"; + "11"; + "10"; + "9"; + "8"; + "7"; + "6"; + ] then "initial_portfolio.cash" else "initial_cash")) "initial cash must contain every scenario currency exactly once" diff --git a/lib/strategy.ml b/lib/strategy.ml index 8cdd8b5..85eadf0 100644 --- a/lib/strategy.ml +++ b/lib/strategy.ml @@ -50,7 +50,7 @@ type intent = | Target_quantities of quantity_target list | Submit_order of Order.request | Cancel_order of Id.Order.t - | Emit_metric of { name : string; value : string } + | Emit_metric of Metric.t let ( let* ) result function_ = match result with Ok value -> function_ value | Error _ as error -> error diff --git a/lib/strategy.mli b/lib/strategy.mli index 071e23e..ece4bd7 100644 --- a/lib/strategy.mli +++ b/lib/strategy.mli @@ -47,7 +47,7 @@ type intent = | Target_quantities of quantity_target list | Submit_order of Order.request | Cancel_order of Id.Order.t - | Emit_metric of { name : string; value : string } + | Emit_metric of Metric.t val context : now:Ptime.t -> diff --git a/lib/strategy_protocol.ml b/lib/strategy_protocol.ml index ed1c9c5..8cbc699 100644 --- a/lib/strategy_protocol.ml +++ b/lib/strategy_protocol.ml @@ -103,7 +103,8 @@ let group_kind_to_string = function let nullable render = Option.fold ~none:`Null ~some:render let modern_protocol protocol_version = - List.mem protocol_version [ "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] + List.mem protocol_version + [ "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5" ] let financing_to_yojson policy = `Assoc @@ -253,7 +254,7 @@ let execution_to_yojson ~protocol_version model execution = ] in if - List.mem protocol_version [ "13"; "12"; "11" ] + List.mem protocol_version [ "14"; "13"; "12"; "11" ] && List.mem (Execution_model.name model) [ "completed_bar_next_open_v1"; "completed_bar_adverse_touch_v1" ] @@ -291,7 +292,7 @@ let execution_to_yojson ~protocol_version model execution = ] ); ] else if - List.mem protocol_version [ "13"; "12" ] + List.mem protocol_version [ "14"; "13"; "12" ] && String.equal (Execution_model.name model) "quote_trade_v1" then `Assoc @@ -309,7 +310,7 @@ let execution_to_yojson ~protocol_version model execution = ] ); ] else if - String.equal protocol_version "13" + List.mem protocol_version [ "14"; "13" ] && String.equal (Execution_model.name model) "order_book_v1" then `Assoc @@ -328,7 +329,8 @@ let execution_to_yojson ~protocol_version model execution = `Int (Option.get (Execution.book_depth_limit execution)) ); ] ); ] - else if List.mem protocol_version [ "13"; "12"; "11"; "10"; "9"; "8"; "7" ] + else if + List.mem protocol_version [ "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7" ] then `Assoc [ @@ -368,6 +370,7 @@ let execution_to_yojson ~protocol_version model execution = let protocol_version initialization = match initialization.scenario_contract_version with + | "16" -> "14" | "15" -> "13" | "14" -> "12" | "13" -> "11" @@ -417,7 +420,9 @@ let initialize_message ~sequence:message_sequence initialization = ] in let fields = - if List.mem protocol_version [ "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + if + List.mem protocol_version + [ "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then let initial_portfolio = Option.fold ~none:`Null ~some:Codec.initial_portfolio_to_yojson @@ -431,7 +436,9 @@ let initialize_message ~sequence:message_sequence initialization = ( "venue_calendars", `List (List.map venue_calendar_to_yojson venue_calendars) ); ]; - (if List.mem protocol_version [ "13"; "12"; "11"; "10"; "9"; "8" ] + (if + List.mem protocol_version + [ "14"; "13"; "12"; "11"; "10"; "9"; "8" ] then [ ( "financing", @@ -439,7 +446,8 @@ let initialize_message ~sequence:message_sequence initialization = initialization.financing ); ] else []); - (if List.mem protocol_version [ "13"; "12"; "11"; "10"; "9" ] then + (if List.mem protocol_version [ "14"; "13"; "12"; "11"; "10"; "9" ] + then [ ( "settlement", Option.fold ~none:`Null ~some:settlement_to_yojson @@ -473,14 +481,15 @@ let cash_attribution_to_yojson ~protocol_version ("fx_rate", price balance.fx_rate); ("base_value", money balance.base_value); ] - @ (if List.mem protocol_version [ "13"; "12"; "11"; "10"; "9"; "8" ] then + @ (if List.mem protocol_version [ "14"; "13"; "12"; "11"; "10"; "9"; "8" ] + then [ ("interest", money balance.interest); ("base_interest", money balance.base_interest); ] else []) @ - if List.mem protocol_version [ "13"; "12"; "11"; "10"; "9" ] then + if List.mem protocol_version [ "14"; "13"; "12"; "11"; "10"; "9" ] then [ ("settled_amount", money balance.settled_amount); ("unsettled_amount", money balance.unsettled_amount); @@ -500,7 +509,7 @@ let marked_position_to_yojson ~protocol_version ("weight", Option.fold ~none:`Null ~some:weight position.weight); ] @ - if List.mem protocol_version [ "13"; "12"; "11"; "10"; "9" ] then + if List.mem protocol_version [ "14"; "13"; "12"; "11"; "10"; "9" ] then [ ("settled_quantity", quantity position.settled_quantity); ("unsettled_quantity", quantity position.unsettled_quantity); @@ -585,7 +594,7 @@ let context_to_yojson ~protocol_version context = (List.map (if List.mem protocol_version - [ "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then Codec.order_to_yojson_v8 else Codec.order_to_yojson) working_orders) ); @@ -598,7 +607,9 @@ let event_to_yojson ~protocol_version = function [ ("type", string "market_slice_closed"); ( "market_slice", - if String.equal protocol_version "13" then + if String.equal protocol_version "14" then + Codec.market_slice_to_yojson_v16 market_slice + else if String.equal protocol_version "13" then Codec.market_slice_to_yojson_v15 market_slice else if String.equal protocol_version "12" then Codec.market_slice_to_yojson_v14 market_slice @@ -619,7 +630,7 @@ let event_to_yojson ~protocol_version = function ( "fill", if List.mem protocol_version - [ "13"; "12"; "11"; "10"; "9"; "8"; "7" ] + [ "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7" ] then Codec.fill_to_yojson_v9 fill else Codec.fill_to_yojson fill ); ] @@ -630,7 +641,7 @@ let event_to_yojson ~protocol_version = function ( "order", if List.mem protocol_version - [ "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] + [ "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6" ] then Codec.order_to_yojson_v8 order else Codec.order_to_yojson order ); ] @@ -718,7 +729,8 @@ let parse_intents_payload ~protocol_version json = let* intent = Scenario.intent_of_yojson ~contract_version: - (if String.equal protocol_version "13" then "15" + (if String.equal protocol_version "14" then "16" + else if List.mem protocol_version [ "14"; "13" ] then "15" else if String.equal protocol_version "12" then "14" else if String.equal protocol_version "11" then "13" else if String.equal protocol_version "10" then "12" diff --git a/mkdocs.yml b/mkdocs.yml index 2f04317..0b4fb2f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,14 +29,15 @@ nav: - Diagnostics: - Current v1: contracts/diagnostic/v1/README.md - Scenario and journal: - - Current v15: contracts/v15/README.md + - Current v16: contracts/v16/README.md - Transitional v5: contracts/v5/README.md - Transitional v4: contracts/v4/README.md - Transitional v3: contracts/v3/README.md - Frozen v2: contracts/v2/README.md - Historical v1: contracts/v1/README.md - External strategy: - - Current v13: contracts/strategy/v13/README.md + - Current v14: contracts/strategy/v14/README.md + - Historical v13: contracts/strategy/v13/README.md - Historical v3: contracts/strategy/v3/README.md - Historical v2: contracts/strategy/v2/README.md - Historical v1: contracts/strategy/v1/README.md diff --git a/scripts/check-deterministic-journals b/scripts/check-deterministic-journals index 74a05c0..b05c7df 100755 --- a/scripts/check-deterministic-journals +++ b/scripts/check-deterministic-journals @@ -126,3 +126,23 @@ compare_journal \ v15-order-book \ contracts/v15/fixtures/order-book.scenario.json \ contracts/v15/fixtures/order-book.journal.jsonl + +compare_journal \ + v16-demo \ + contracts/v16/fixtures/demo.scenario.json \ + contracts/v16/fixtures/demo.journal.jsonl + +compare_journal \ + v16-fill-clipped \ + contracts/v16/fixtures/fill-clipped.scenario.json \ + contracts/v16/fixtures/fill-clipped.journal.jsonl + +compare_journal \ + v16-quote-trade \ + contracts/v16/fixtures/quote-trade.scenario.json \ + contracts/v16/fixtures/quote-trade.journal.jsonl + +compare_journal \ + v16-order-book \ + contracts/v16/fixtures/order-book.scenario.json \ + contracts/v16/fixtures/order-book.journal.jsonl diff --git a/scripts/check-documentation.py b/scripts/check-documentation.py index 240086b..39a2038 100644 --- a/scripts/check-documentation.py +++ b/scripts/check-documentation.py @@ -26,13 +26,13 @@ "docs/persistra.md", "SECURITY.md", "contracts/conformance/README.md", - "contracts/v15/README.md", + "contracts/v16/README.md", "contracts/v5/README.md", "contracts/v4/README.md", "contracts/v3/README.md", "contracts/v2/README.md", "contracts/v1/README.md", - "contracts/strategy/v13/README.md", + "contracts/strategy/v14/README.md", "contracts/strategy/v3/README.md", "contracts/strategy/v2/README.md", "contracts/strategy/v1/README.md", diff --git a/scripts/release_artifacts.py b/scripts/release_artifacts.py index baa6858..6da23e8 100644 --- a/scripts/release_artifacts.py +++ b/scripts/release_artifacts.py @@ -376,8 +376,8 @@ def verify_release( ( "bin/trading-engine", "lib/trading_engine/opam", - "share/trading_engine/contracts/v15/scenario.schema.json", - "share/trading_engine/contracts/v15/fixtures/demo.scenario.json", + "share/trading_engine/contracts/v16/scenario.schema.json", + "share/trading_engine/contracts/v16/fixtures/demo.scenario.json", "doc/trading_engine/README.md", ), epoch, @@ -388,7 +388,7 @@ def verify_release( ( "trading_engine.opam", "contracts/v1/scenario.schema.json", - "contracts/v15/fixtures/demo.scenario.json", + "contracts/v16/fixtures/demo.scenario.json", "docs/architecture.md", ".github/workflows/release-candidate.yml", ), @@ -400,8 +400,8 @@ def verify_release( ( "contracts/conformance/manifest.json", "contracts/v1/scenario.schema.json", - "contracts/v15/fixtures/demo.scenario.json", - "contracts/strategy/v13/message.schema.json", + "contracts/v16/fixtures/demo.scenario.json", + "contracts/strategy/v14/message.schema.json", ), epoch, ) @@ -412,7 +412,7 @@ def verify_release( "index.html", "docs/architecture/index.html", "contracts/v1/index.html", - "contracts/v15/scenario.schema.json", + "contracts/v16/scenario.schema.json", "api/trading_engine/Trading_engine/index.html", ), epoch, diff --git a/test/cli.t b/test/cli.t index dff75b2..f127929 100644 --- a/test/cli.t +++ b/test/cli.t @@ -2,7 +2,7 @@ 1.0.0 $ ../bin/main.exe --capabilities - {"engine_version":"1.0.0","scenario_contract_versions":["15","14","13","12","11","10","9","8","7","6","5","4","3"],"journal_contract_versions":["15","14","13","12","11","10","9","8","7","6","5","4","3"],"scenario_formats":["json","jsonl"],"journal_formats":["jsonl"],"execution_models":["completed_bar_v1","completed_bar_next_open_v1","completed_bar_adverse_touch_v1","quote_trade_v1","order_book_v1"],"execution_model_contracts":[{"name":"completed_bar_v1","configuration_versions":["2","1"],"scenario_contract_versions":["15","14","13","12","11","10","9","8","7","6","5","4","3"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"2":["version","participation_bps","fee_schedules"],"1":["version","participation_bps","fixed_fee","fee_bps"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"fee_bps":{"minimum":0,"maximum":10000},"fixed_fee":{"minimum":"0","unit":"money"}}},{"name":"completed_bar_next_open_v1","configuration_versions":["1"],"scenario_contract_versions":["15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_adverse_touch_v1","configuration_versions":["1"],"scenario_contract_versions":["15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"quote_trade_v1","configuration_versions":["1"],"scenario_contract_versions":["15","14"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["causally_ordered_bid_ask_quotes","aggressor_classified_trades_for_passive_fills","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"order_book_v1","configuration_versions":["1"],"scenario_contract_versions":["15"],"required_fields":["version","participation_bps","fee_schedules","max_depth_levels"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","max_depth_levels"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["slice_open_level_two_snapshot","contiguous_absolute_level_updates","aggressor_classified_depth_consuming_trades","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"max_depth_levels":{"minimum":1,"maximum":1024}}}],"strategy_protocol_versions":["13","12","11","10","9","8","7","6","5","4","3"],"resource_limits":{"version":"1","scenario_record_bytes":1048576,"strategy_message_bytes":1048576,"internal_events":100000,"catalog_instruments":4096,"intents_per_batch":4096,"artifact_record_bytes":2097152}} + {"engine_version":"1.0.0","scenario_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"journal_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"scenario_formats":["json","jsonl"],"journal_formats":["jsonl"],"execution_models":["completed_bar_v1","completed_bar_next_open_v1","completed_bar_adverse_touch_v1","quote_trade_v1","order_book_v1"],"execution_model_contracts":[{"name":"completed_bar_v1","configuration_versions":["2","1"],"scenario_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"2":["version","participation_bps","fee_schedules"],"1":["version","participation_bps","fixed_fee","fee_bps"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"fee_bps":{"minimum":0,"maximum":10000},"fixed_fee":{"minimum":"0","unit":"money"}}},{"name":"completed_bar_next_open_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_adverse_touch_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"quote_trade_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["causally_ordered_bid_ask_quotes","aggressor_classified_trades_for_passive_fills","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"order_book_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15"],"required_fields":["version","participation_bps","fee_schedules","max_depth_levels"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","max_depth_levels"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["slice_open_level_two_snapshot","contiguous_absolute_level_updates","aggressor_classified_depth_consuming_trades","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"max_depth_levels":{"minimum":1,"maximum":1024}}}],"strategy_protocol_versions":["14","13","12","11","10","9","8","7","6","5","4","3"],"resource_limits":{"version":"1","scenario_record_bytes":1048576,"strategy_message_bytes":1048576,"internal_events":100000,"catalog_instruments":4096,"intents_per_batch":4096,"artifact_record_bytes":2097152,"metric_name_bytes":128,"metric_string_value_bytes":1024,"metric_unit_bytes":64,"metric_dimensions":16,"metric_dimension_key_bytes":64,"metric_dimension_value_bytes":128}} $ ../bin/main.exe --validate-only --input ../contracts/v8/fixtures/demo.scenario.json valid run=demo instruments=1 schedule=2 slices=4 scenario_sha256=85f7c99e0666159579c79256b3d0dc5f9c328e1275b79465fe1d4c93883e68f1 diff --git a/test/dune b/test/dune index 1d45f1a..e68f723 100644 --- a/test/dune +++ b/test/dune @@ -109,6 +109,21 @@ ../contracts/strategy/v10/fixtures/external.strategy.jsonl ../contracts/strategy/v11/fixtures/external.strategy.jsonl ../contracts/strategy/v13/fixtures/external.strategy.jsonl + ../contracts/strategy/v14/fixtures/external.strategy.jsonl + ../contracts/v16/fixtures/demo.journal.jsonl + ../contracts/v16/fixtures/demo.scenario.json + ../contracts/v16/fixtures/demo.scenario.jsonl + ../contracts/v16/fixtures/fill-clipped.journal.jsonl + ../contracts/v16/fixtures/fill-clipped.scenario.json + ../contracts/v16/fixtures/order-book.journal.jsonl + ../contracts/v16/fixtures/order-book.scenario.json + ../contracts/v16/fixtures/order-book.scenario.jsonl + ../contracts/v16/fixtures/quote-trade.journal.jsonl + ../contracts/v16/fixtures/quote-trade.scenario.json + ../contracts/v16/fixtures/quote-trade.scenario.jsonl + ../contracts/v16/journal.schema.json + ../contracts/v16/scenario-stream.schema.json + ../contracts/v16/scenario.schema.json ../contracts/strategy/v4/fixtures/external.strategy.jsonl fake_strategy.py) (libraries diff --git a/test/test_diagnostic.ml b/test/test_diagnostic.ml index a75dd33..13ebe5b 100644 --- a/test/test_diagnostic.ml +++ b/test/test_diagnostic.ml @@ -137,7 +137,22 @@ let capabilities_describe_execution_contracts () = (strings "configuration_versions"); Alcotest.(check (list string)) "scenario contracts" - [ "15"; "14"; "13"; "12"; "11"; "10"; "9"; "8"; "7"; "6"; "5"; "4"; "3" ] + [ + "16"; + "15"; + "14"; + "13"; + "12"; + "11"; + "10"; + "9"; + "8"; + "7"; + "6"; + "5"; + "4"; + "3"; + ] (strings "scenario_contract_versions"); Alcotest.(check (list string)) "required fields" diff --git a/test/test_domain.ml b/test/test_domain.ml index 8143222..2a607ec 100644 --- a/test/test_domain.ml +++ b/test/test_domain.ml @@ -495,6 +495,51 @@ let risk_limits_cover_lots () = ~initial_margin_bps:5000 ~maintenance_margin_bps:2500 ~short_borrow_bps:100)) +let typed_metric_validation () = + let numeric = T.Metric.numeric_of_string "-12.5" |> ok in + Alcotest.(check string) + "negative fractional numeric" "-0.5" + (T.Metric.numeric_of_string "-0.5" |> ok |> T.Metric.numeric_to_string); + let metric = + T.Metric.create ~name:"strategy.signal" ~value:(T.Metric.Numeric numeric) + ~unit_:"ratio" + ~dimensions:[ ("venue", "XNYS"); ("asset", "ACME") ] + ~aggregation:T.Metric.Mean () + |> ok + in + Alcotest.(check string) + "numeric round trip" "-12.5" + (match metric.value with + | T.Metric.Numeric value -> T.Metric.numeric_to_string value + | _ -> Alcotest.fail "expected numeric metric"); + Alcotest.(check (list string)) + "dimensions sort canonically" [ "asset"; "venue" ] + (List.map (fun dimension -> dimension.T.Metric.key) metric.dimensions); + List.iter + (fun invalid -> + Alcotest.(check bool) + (invalid ^ " rejected") true + (Result.is_error (T.Metric.numeric_of_string invalid))) + [ ""; "01"; "1.0"; "-0"; "+1"; "1e3" ]; + Alcotest.(check bool) + "duplicate dimensions rejected" true + (Result.is_error + (T.Metric.create ~name:"duplicate" ~value:(T.Metric.Boolean true) + ~dimensions:[ ("side", "buy"); ("side", "sell") ] + ())); + Alcotest.(check bool) + "nonnumeric aggregation rejected" true + (Result.is_error + (T.Metric.create ~name:"state" ~value:(T.Metric.String "risk-on") + ~aggregation:T.Metric.Mean ())); + Alcotest.(check bool) + "dimension count bounded" true + (Result.is_error + (T.Metric.create ~name:"bounded" ~value:(T.Metric.String "ok") + ~dimensions: + (List.init 17 (fun index -> (Printf.sprintf "key-%02d" index, "x"))) + ())) + let tests = [ Alcotest.test_case "identifier validation" `Quick identifier_validation; @@ -523,4 +568,5 @@ let tests = Alcotest.test_case "risk accepts multiple currencies" `Quick risk_accepts_multiple_currencies; Alcotest.test_case "risk limits cover lots" `Quick risk_limits_cover_lots; + Alcotest.test_case "typed metric validation" `Quick typed_metric_validation; ] diff --git a/test/test_reducer.ml b/test/test_reducer.ml index 38899d0..46bbd74 100644 --- a/test/test_reducer.ml +++ b/test/test_reducer.ml @@ -796,7 +796,9 @@ let explicit_phase_order_is_stable () = |> ok in let metric = - T.Strategy.Emit_metric { name = "phase.boundary"; value = "reached" } + T.Metric.create ~name:"phase.boundary" ~value:(T.Metric.String "reached") () + |> ok + |> fun metric -> T.Strategy.Emit_metric metric in let state = runner [ (1L, [ target "2" ]); (2L, [ metric; target "0" ]) ] in let state, _ = Runner.process_slice state (market_slice 1L) |> ok in diff --git a/test/test_reducer_properties.ml b/test/test_reducer_properties.ml index 0ca0c95..9135a12 100644 --- a/test/test_reducer_properties.ml +++ b/test/test_reducer_properties.ml @@ -320,8 +320,11 @@ let command_intents context command = ] | Emit_metric value -> [ - T.Strategy.Emit_metric - { name = "generated.reducer.metric"; value = string_of_int value }; + ( T.Metric.create ~name:"generated.reducer.metric" + ~value:(T.Metric.String (string_of_int value)) + () + |> Result.get_ok + |> fun metric -> T.Strategy.Emit_metric metric ); ] module Asset_set = Set.Make (struct diff --git a/test/test_scenario.ml b/test/test_scenario.ml index bf17455..eebadba 100644 --- a/test/test_scenario.ml +++ b/test/test_scenario.ml @@ -2,21 +2,21 @@ open Test_support module T = Trading_engine let demo_document () = - In_channel.with_open_bin "../contracts/v15/fixtures/demo.scenario.json" + In_channel.with_open_bin "../contracts/v16/fixtures/demo.scenario.json" In_channel.input_all let demo () = T.Scenario.of_string (demo_document ()) |> ok let demo_hash () = T.Sha256.digest_string (demo_document ()) -let stream_path = "../contracts/v15/fixtures/demo.scenario.jsonl" -let quote_trade_path = "../contracts/v15/fixtures/quote-trade.scenario.json" +let stream_path = "../contracts/v16/fixtures/demo.scenario.jsonl" +let quote_trade_path = "../contracts/v16/fixtures/quote-trade.scenario.json" let quote_trade_stream_path = - "../contracts/v15/fixtures/quote-trade.scenario.jsonl" + "../contracts/v16/fixtures/quote-trade.scenario.jsonl" -let order_book_path = "../contracts/v15/fixtures/order-book.scenario.json" +let order_book_path = "../contracts/v16/fixtures/order-book.scenario.json" let order_book_stream_path = - "../contracts/v15/fixtures/order-book.scenario.jsonl" + "../contracts/v16/fixtures/order-book.scenario.jsonl" let stream_document () = In_channel.with_open_bin stream_path In_channel.input_all @@ -104,7 +104,7 @@ let write_large_stream path slice_count = let payload = `Assoc [ - ("market_slice", T.Codec.market_slice_to_yojson_v15 market_slice); + ("market_slice", T.Codec.market_slice_to_yojson_v16 market_slice); ("intents", `List []); ] in @@ -149,9 +149,9 @@ let schema_artifacts_parse () = (List.mem_assoc "$defs" fields) | _ -> Alcotest.fail (path ^ " must contain a JSON object") in - check_schema "../contracts/v15/scenario.schema.json"; - check_schema "../contracts/v15/scenario-stream.schema.json"; - check_schema "../contracts/v15/journal.schema.json" + check_schema "../contracts/v16/scenario.schema.json"; + check_schema "../contracts/v16/scenario-stream.schema.json"; + check_schema "../contracts/v16/journal.schema.json" let timestamp_precision_is_bounded () = List.iter @@ -351,7 +351,7 @@ let v12_distributions_and_lifecycle_parse () = | _ -> Alcotest.fail "demo slice must be an object" in `List - (T.Codec.market_slice_to_yojson_v15 market_slice + (T.Codec.market_slice_to_yojson_v16 market_slice :: List.map add_child_bar rest) | _ -> Alcotest.fail "demo slices must be nonempty" in @@ -441,8 +441,8 @@ let contract_version_is_required_and_supported () = let unsupported_diagnostic = T.Scenario.of_yojson unsupported |> error in Alcotest.(check string) "unsupported version diagnosed" - "unsupported scenario contract_version \"2\" (expected one of 15, 14, 13, \ - 12, 11, 10, 9, 8, 7, 6, 5, 4, 3)" + "unsupported scenario contract_version \"2\" (expected one of 16, 15, 14, \ + 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3)" (T.Diagnostic.to_human unsupported_diagnostic); Alcotest.(check string) "unsupported version code" "scenario.unsupported_contract" @@ -607,7 +607,7 @@ let dense_schedule_document slice_count = ~cash_rate_observations:[ cash_rate_observation ] ~settlement_failures:[] ~lifecycle_events:[] ~market_events:[] ~order_book_events:[] - |> ok |> T.Codec.market_slice_to_yojson_v15) + |> ok |> T.Codec.market_slice_to_yojson_v16) in let schedule = List.init slice_count (fun offset -> @@ -622,7 +622,12 @@ let dense_schedule_document slice_count = [ ("type", `String "emit_metric"); ("name", `String "dense_schedule"); - ("value", `String (string_of_int sequence)); + ( "value", + `Assoc + [ + ("type", `String "numeric"); + ("value", `String (string_of_int sequence)); + ] ); ]; ] ); ]) @@ -1191,7 +1196,7 @@ let replay_matches_golden_file () = |> fun value -> value ^ "\n" in let expected = - In_channel.with_open_bin "../contracts/v15/fixtures/demo.journal.jsonl" + In_channel.with_open_bin "../contracts/v16/fixtures/demo.journal.jsonl" In_channel.input_all in Alcotest.(check string) "stable audit contract" expected actual @@ -1219,7 +1224,7 @@ let v3_replay_matches_frozen_golden_file () = let fill_clipping_fixture_reconciles () = let document = In_channel.with_open_bin - "../contracts/v15/fixtures/fill-clipped.scenario.json" + "../contracts/v16/fixtures/fill-clipped.scenario.json" In_channel.input_all in let scenario = T.Scenario.of_string document |> ok in @@ -1233,7 +1238,7 @@ let fill_clipping_fixture_reconciles () = in let expected = In_channel.with_open_bin - "../contracts/v15/fixtures/fill-clipped.journal.jsonl" + "../contracts/v16/fixtures/fill-clipped.journal.jsonl" In_channel.input_all in Alcotest.(check string) "fill clipping audit reconciliation" expected actual @@ -1253,7 +1258,7 @@ let quote_trade_replay_is_causal_and_stream_equivalent () = in let golden = In_channel.with_open_bin - "../contracts/v15/fixtures/quote-trade.journal.jsonl" In_channel.input_all + "../contracts/v16/fixtures/quote-trade.journal.jsonl" In_channel.input_all in Alcotest.(check string) "quote/trade golden journal" golden batch_journal; let fills = @@ -1311,7 +1316,7 @@ let order_book_replay_is_bounded_and_stream_equivalent () = in let golden = In_channel.with_open_bin - "../contracts/v15/fixtures/order-book.journal.jsonl" In_channel.input_all + "../contracts/v16/fixtures/order-book.journal.jsonl" In_channel.input_all in Alcotest.(check string) "order-book golden journal" golden actual; let fills = diff --git a/test/test_strategy_protocol.ml b/test/test_strategy_protocol.ml index f63b960..bec7710 100644 --- a/test/test_strategy_protocol.ml +++ b/test/test_strategy_protocol.ml @@ -45,7 +45,7 @@ let initialize_message_is_complete () = T.Strategy_protocol.initialize_message ~sequence:1L (initialization ()) in Alcotest.(check string) - "protocol version" "13" + "protocol version" "14" (match field "strategy_protocol_version" message with | `String value -> value | _ -> Alcotest.fail "expected version string"); @@ -265,7 +265,7 @@ let nonpositive_equity_omits_weights () = let response message_type payload = `Assoc [ - ("strategy_protocol_version", `String "13"); + ("strategy_protocol_version", `String "14"); ("strategy_sequence", `String "3"); ("message_type", `String message_type); ("payload", payload); @@ -301,7 +301,31 @@ let responses_are_strict_and_typed () = [ ("type", `String "emit_metric"); ("name", `String "signal"); - ("value", `String "0.5"); + ( "value", + `Assoc + [ + ("type", `String "numeric"); ("value", `String "0.5"); + ] ); + ]; + `Assoc + [ + ("type", `String "emit_metric"); + ("name", `String "regime"); + ( "value", + `Assoc + [ + ("type", `String "string"); + ("value", `String "risk-on"); + ] ); + ]; + `Assoc + [ + ("type", `String "emit_metric"); + ("name", `String "healthy"); + ( "value", + `Assoc + [ ("type", `String "boolean"); ("value", `Bool true) ] + ); ]; ] ); ]) @@ -309,9 +333,27 @@ let responses_are_strict_and_typed () = (match T.Strategy_protocol.response_of_yojson ~expected_sequence:3L intents |> ok with - | T.Strategy_protocol.Intents [ T.Strategy.Emit_metric { name; value } ] -> - Alcotest.(check string) "metric name" "signal" name; - Alcotest.(check string) "metric value" "0.5" value + | T.Strategy_protocol.Intents + [ + T.Strategy.Emit_metric metric; + T.Strategy.Emit_metric string_metric; + T.Strategy.Emit_metric boolean_metric; + ] -> ( + Alcotest.(check string) "metric name" "signal" metric.name; + (match metric.value with + | T.Metric.Numeric value -> + Alcotest.(check string) + "metric value" "0.5" + (T.Metric.numeric_to_string value) + | _ -> Alcotest.fail "expected numeric metric value"); + (match string_metric.value with + | T.Metric.String value -> + Alcotest.(check string) "string metric value" "risk-on" value + | _ -> Alcotest.fail "expected string metric value"); + match boolean_metric.value with + | T.Metric.Boolean value -> + Alcotest.(check bool) "boolean metric value" true value + | _ -> Alcotest.fail "expected boolean metric value") | _ -> Alcotest.fail "expected metric intent"); let wrong_sequence = T.Strategy_protocol.response_of_yojson ~expected_sequence:4L ready From 2d8fff959d39f7e99853a2b7f262a44e64e82149 Mon Sep 17 00:00:00 2001 From: James Mallette Date: Sat, 22 Aug 2026 18:01:00 -0400 Subject: [PATCH 2/2] fix: preserve resource capability compatibility --- lib/resource_limits.ml | 6 ------ test/cli.t | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/resource_limits.ml b/lib/resource_limits.ml index e3df489..6ba78ac 100644 --- a/lib/resource_limits.ml +++ b/lib/resource_limits.ml @@ -22,10 +22,4 @@ let to_yojson () = ("catalog_instruments", `Int catalog_instruments); ("intents_per_batch", `Int intents_per_batch); ("artifact_record_bytes", `Int artifact_record_bytes); - ("metric_name_bytes", `Int metric_name_bytes); - ("metric_string_value_bytes", `Int metric_string_value_bytes); - ("metric_unit_bytes", `Int metric_unit_bytes); - ("metric_dimensions", `Int metric_dimensions); - ("metric_dimension_key_bytes", `Int metric_dimension_key_bytes); - ("metric_dimension_value_bytes", `Int metric_dimension_value_bytes); ] diff --git a/test/cli.t b/test/cli.t index f127929..6ce3ee0 100644 --- a/test/cli.t +++ b/test/cli.t @@ -2,7 +2,7 @@ 1.0.0 $ ../bin/main.exe --capabilities - {"engine_version":"1.0.0","scenario_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"journal_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"scenario_formats":["json","jsonl"],"journal_formats":["jsonl"],"execution_models":["completed_bar_v1","completed_bar_next_open_v1","completed_bar_adverse_touch_v1","quote_trade_v1","order_book_v1"],"execution_model_contracts":[{"name":"completed_bar_v1","configuration_versions":["2","1"],"scenario_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"2":["version","participation_bps","fee_schedules"],"1":["version","participation_bps","fixed_fee","fee_bps"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"fee_bps":{"minimum":0,"maximum":10000},"fixed_fee":{"minimum":"0","unit":"money"}}},{"name":"completed_bar_next_open_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_adverse_touch_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"quote_trade_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["causally_ordered_bid_ask_quotes","aggressor_classified_trades_for_passive_fills","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"order_book_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15"],"required_fields":["version","participation_bps","fee_schedules","max_depth_levels"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","max_depth_levels"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["slice_open_level_two_snapshot","contiguous_absolute_level_updates","aggressor_classified_depth_consuming_trades","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"max_depth_levels":{"minimum":1,"maximum":1024}}}],"strategy_protocol_versions":["14","13","12","11","10","9","8","7","6","5","4","3"],"resource_limits":{"version":"1","scenario_record_bytes":1048576,"strategy_message_bytes":1048576,"internal_events":100000,"catalog_instruments":4096,"intents_per_batch":4096,"artifact_record_bytes":2097152,"metric_name_bytes":128,"metric_string_value_bytes":1024,"metric_unit_bytes":64,"metric_dimensions":16,"metric_dimension_key_bytes":64,"metric_dimension_value_bytes":128}} + {"engine_version":"1.0.0","scenario_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"journal_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"scenario_formats":["json","jsonl"],"journal_formats":["jsonl"],"execution_models":["completed_bar_v1","completed_bar_next_open_v1","completed_bar_adverse_touch_v1","quote_trade_v1","order_book_v1"],"execution_model_contracts":[{"name":"completed_bar_v1","configuration_versions":["2","1"],"scenario_contract_versions":["16","15","14","13","12","11","10","9","8","7","6","5","4","3"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"2":["version","participation_bps","fee_schedules"],"1":["version","participation_bps","fixed_fee","fee_bps"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"fee_bps":{"minimum":0,"maximum":10000},"fixed_fee":{"minimum":"0","unit":"money"}}},{"name":"completed_bar_next_open_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_adverse_touch_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14","13"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"quote_trade_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15","14"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["causally_ordered_bid_ask_quotes","aggressor_classified_trades_for_passive_fills","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"order_book_v1","configuration_versions":["1"],"scenario_contract_versions":["16","15"],"required_fields":["version","participation_bps","fee_schedules","max_depth_levels"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","max_depth_levels"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["slice_open_level_two_snapshot","contiguous_absolute_level_updates","aggressor_classified_depth_consuming_trades","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"max_depth_levels":{"minimum":1,"maximum":1024}}}],"strategy_protocol_versions":["14","13","12","11","10","9","8","7","6","5","4","3"],"resource_limits":{"version":"1","scenario_record_bytes":1048576,"strategy_message_bytes":1048576,"internal_events":100000,"catalog_instruments":4096,"intents_per_batch":4096,"artifact_record_bytes":2097152}} $ ../bin/main.exe --validate-only --input ../contracts/v8/fixtures/demo.scenario.json valid run=demo instruments=1 schedule=2 slices=4 scenario_sha256=85f7c99e0666159579c79256b3d0dc5f9c328e1275b79465fe1d4c93883e68f1