Processes¶
Learning objectives
After reading this page you will understand:
- Why EduMatcher is built as a set of independent processes connected by a message bus rather than a single monolithic program
- The trade-offs that architecture introduces: scalability and observability
vs. deployment complexity and latency
- The role and responsibilities of each of the core runtime processes plus the optional AI trader, market-maker bot, and admin tools
- Which processes are mandatory and which are optional observers
- How to read the message-flow tables to trace an order from submission to fill
Background — Why Separate Processes?¶
The monolith approach¶
The simplest trading system is a single program: the user types an order, the matching logic runs, and the result is printed — all in the same process. This is easy to build, easy to debug, and fast. It is also a dead end as soon as you need any of the following:
- More than one trader connected simultaneously
- Observers (audit, statistics, P&L) that see every trade without modifying the matching logic
- The ability to restart one component without restarting everything
- A viewer that can run on a different machine from the engine
The message-bus approach¶
EduMatcher distributes work across processes connected by a ZeroMQ message bus. The engine owns three sockets:
flowchart LR
subgraph producers["Producers (PUSH → 5555)"]
GW["pm-alf-console\n(ALF order entry)"]
ALFGWY["pm-alf-gwy\n(ALF TCP gateway)"]
SCH["pm-scheduler\n(session transitions)"]
ADM["pm-admin / pm-admin-cli\n(admin commands)"]
AI["pm-ai-trader / pm-ai-swarm\n(AI bots)"]
MMB["pm-mm-bot\n(MM bot)"]
end
subgraph engine["pm-engine"]
PULL["PULL :5555"]
PUB["PUB :5556"]
DC["PUB :5557\n(drop-copy)"]
end
subgraph subscribers["Subscribers (SUB from 5556)"]
VW["pm-viewer"]
ORD["pm-orders"]
AUD["pm-audit"]
CLR["pm-clearing"]
STS["pm-stats"]
TKR["pm-ticker"]
BRD["pm-board"]
end
GW -->|PUSH| PULL
ALFGWY -->|PUSH| PULL
SCH -->|PUSH| PULL
ADM -->|PUSH| PULL
AI -->|PUSH| PULL
MMB -->|PUSH| PULL
PUB -->|SUB| GW
PUB -->|SUB| ALFGWY
PUB -->|SUB| VW
PUB -->|SUB| ORD
PUB -->|SUB| AUD
PUB -->|SUB| CLR
PUB -->|SUB| STS
PUB -->|SUB| TKR
PUB -->|SUB| BRD
PUB -->|SUB| AI
PUB -->|SUB| MMB
DC -.->|drop-copy SUB| EXT["External risk /\ncompliance systems"]
The engine is the only process that writes to the order book. Every other process is either a producer (sends commands via PUSH) or a subscriber (reads events via SUB), or both. No process shares memory with any other.
| Port | Socket type | Bound by | Purpose |
|---|---|---|---|
| 5555 | PULL | pm-engine | Receives all inbound commands (orders, cancels, admin) |
| 5556 | PUB | pm-engine | Broadcasts all market events (fills, book updates, session changes) |
| 5557 | PUB | pm-engine | Drop-copy feed, fill events tagged per gateway, with sequence numbers and replay support |
This design has concrete advantages:
| Advantage | How it helps |
|---|---|
| Isolation | A crashing viewer cannot corrupt the order book |
| Observability | Any subscriber can see every event without changing the engine |
| Horizontal scale | Multiple gateways connect simultaneously; each is independent |
| Language freedom | A subscriber can be written in any language that speaks ZeroMQ |
| Testability | The engine can be tested with a fake gateway; subscribers can be tested with recorded message streams |
And honest disadvantages:
| Disadvantage | Consequence |
|---|---|
| Latency | Each message crosses a socket; a round-trip (order → engine → ACK) takes microseconds rather than nanoseconds on localhost |
| Startup order | The engine must bind before any other process connects |
| Partial failure | If the engine crashes mid-session, subscribers hold stale state until it restarts |
| No shared clock | Timestamps are set by the sender; two processes on different machines can disagree by milliseconds |
| Message loss | ZeroMQ PUB/SUB drops messages if a subscriber is slow; fast publishers can outrun slow consumers |
For an educational system running on a laptop, none of the disadvantages are serious. For a production exchange, each one would require careful engineering (persistent queues, hardware timestamping, consensus protocols).
The two bus patterns used here¶
| Pattern | Socket pair | Direction | Used for |
|---|---|---|---|
| PUSH/PULL | Gateway PUSH → Engine PULL | One-to-one, reliable delivery | Sending commands (orders, cancel requests) to the engine |
| PUB/SUB | Engine PUB → many SUBs | One-to-many, topic-filtered | Broadcasting events (fills, book updates, session changes) |
PUSH/PULL guarantees delivery to exactly one receiver (the engine). PUB/SUB does not guarantee delivery — a subscriber that is not yet connected will miss messages sent before it subscribes. This is why the engine publishes an initial book snapshot on startup: late-joining viewers can request the current state rather than waiting for the next change.
Process Overview¶
A complete EduMatcher session uses ten core runtime processes across three categories, plus optional AI trader, admin, and setup/config entrypoints.
Protocol landscape
EduMatcher supports multiple external protocol families with different roles: ALF for order entry, BALF for planned binary order entry, CALF for planned market-data dissemination, and RALF for post-trade dissemination. For a single map of purpose, runtime status, and links to detailed chapters and formal specs, see External Protocols Overview.
Why pm-?
All CLI commands share the pm- prefix, short for Process for Matching.
The prefix avoids name collisions with system utilities and makes it easy to
identify EduMatcher processes in a process list (ps aux | grep pm-).
Installation modes
Commands are shown without a prefix. If you are running from a source
checkout (developer mode), prepend poetry run:
If you installed with pipx (end-user mode), the commands are on your PATH and need no prefix:
See Getting Started → Installation
for the full setup guide and the pm-setup bootstrap command.
Environment variables¶
Two variables control where EduMatcher finds and stores files at runtime.
Set them once in your shell profile (~/.zshrc or ~/.bashrc) and every
pm-* command picks them up automatically.
| Variable | Default (installed) | Default (source checkout) | Purpose |
|---|---|---|---|
EDUMATCHER_DATA_DIR |
~/.local/share/edumatcher |
<repo>/src/data/ |
Directory for all persistent data files (gtc_orders.json, stats.db, audit.log, etc.) |
EDUMATCHER_CONFIG |
./engine_config.yaml (CWD) |
<repo>/engine_config.yaml |
Path to the engine configuration YAML |
The --config flag on pm-engine and pm-scheduler overrides both the
environment variable and the default.
# Example: per-session isolation
export EDUMATCHER_DATA_DIR="$HOME/sessions/morning"
export EDUMATCHER_CONFIG="$HOME/sessions/morning/engine_config.yaml"
pm-engine --verbose
Core processes:
| Process | Command | Role | Required? |
|---|---|---|---|
| pm-engine | pm-engine |
Matching engine — the single writer | Yes |
| pm-alf-console | pm-alf-console --id GW01 |
ALF order entry terminal (one per trader) | At least one |
| pm-scheduler | pm-scheduler |
Drives session phase transitions | No |
| pm-viewer | pm-viewer --symbol AAPL |
Live order book display | No |
| pm-orders | pm-orders |
Cross-gateway order status monitor | No |
| pm-board | pm-board |
Full-screen multi-symbol display | No |
| pm-ticker | pm-ticker |
Scrolling market data ticker | No (needs pm-stats) |
| pm-stats | pm-stats |
OHLCV statistics to SQLite | No |
| pm-clearing | pm-clearing |
P&L and trade settlement | No |
| pm-audit | pm-audit |
Full event log to disk | No |
| pm-alf-gwy | pm-alf-gwy |
ALF TCP gateway — order entry for external bots over TCP :5565 | No |
| pm-ralf-gwy | pm-ralf-gwy |
External post-trade dissemination gateway (RALF) | No |
| pm-md-gwy | pm-md-gwy |
External market-data gateway (CALF) over TCP :5570 | No |
| pm-api-gwy | pm-api-gwy |
REST/WebSocket order-entry and market-data API gateway | No |
| pm-index | pm-index |
Real-time cap-weighted index calculation and dissemination | No |
Admin tools:
| Process | Command | Role | Required? |
|---|---|---|---|
| pm-admin | pm-admin |
Interactive admin console | No |
| pm-admin-cli | pm-admin-cli <command> |
One-shot CLI admin commands | No |
| pm-cverifier | pm-cverifier [options] <config> |
Validate engine_config.yaml (YAML, schema, semantic, completeness checks) |
No |
Setup and configuration tools:
| Process | Command | Role | Required? |
|---|---|---|---|
| pm-setup | pm-setup |
Bootstrap working directory and runtime files | Recommended first run |
| pm-config-gen | pm-config-gen [options] |
Generate engine_config.yaml from CLI options |
Optional |
| pm-clearing-cli | pm-clearing-cli <command> [options] |
Read/query interface for clearing.db (includes prune) |
Optional |
| pm-stats-cli | pm-stats-cli <command> [options] |
Read-only query interface for stats.db |
Optional |
| pm-index-cli | pm-index-cli <command> [options] |
Read-only query interface for index history JSONL files | Optional |
Optional AI trader tools:
| Process | Command | Role | Required? |
|---|---|---|---|
| pm-ai-trader | pm-ai-trader |
Single AI trading bot gateway | No |
| pm-ai-swarm | pm-ai-swarm |
Coordinated multi-agent AI trading swarm | No |
| pm-mm-bot | pm-mm-bot --symbol SYM |
Autonomous market-maker bot for a single symbol | No |
Start the engine first
The engine binds the ZeroMQ sockets. All other processes connect to those sockets. If a process starts before the engine is ready, it will either fail immediately or silently lose its first messages.
Recommended startup sequence
For a deterministic first run, use this order:
- Bootstrap session files:
- Generate a config (or reuse/edit the sample config):
- Start the engine first:
- Start one or more gateways:
- Start optional observers/tools as needed (
pm-board,pm-viewer,pm-audit --terminal,pm-stats,pm-clearing,pm-ticker,pm-scheduler).
pm-engine — Matching Engine¶
The heart of the system — receives orders, matches them, publishes events.
Startup options:
| Flag | Description |
|---|---|
--verbose / -v |
Print each order received and trade produced to stdout |
--config / -c |
Path to engine config YAML (default: engine_config.yaml) |
Expected runtime input arguments:
None. pm-engine is a long-running background process after startup.
Startup behaviour:
1. Creates data/ directory if needed
2. Parses engine_config.yaml if present — registers symbol/gateway allowlists and session settings
3. Binds the main ZMQ PULL :5555 and PUB :5556 sockets during engine initialization
4. On run(), restores persisted book stats, GTC orders, and GTC combos
5. Applies config-driven seeds (book stats gaps, market-maker quotes, market-maker combos)
6. Tries to bind the dedicated drop-copy PUB :5557 socket
7. Publishes initial book snapshots for populated books and enters the poll loop
See Configuration for full details on the config file.
Shutdown (Ctrl-C):
1. Publishes order.expired for all resting DAY orders
2. Serializes all resting GTC orders to data/gtc_orders.json
3. Publishes system.eod with the final book snapshot for every active symbol
4. Closes sockets
Messages received (PULL :5555):
| Topic | Source |
|---|---|
order.new |
Gateways |
order.cancel |
Gateways |
order.combo |
Gateways |
order.combo_cancel |
Gateways |
system.gateway_connect |
Gateways |
book.snapshot_request |
Viewers, Stats |
system.symbols_request |
Gateways, Stats |
order.orders_request |
Gateways |
risk.kill_switch |
Gateways, Admin |
risk.circuit_breaker_halt_all |
Admin gateways |
risk.circuit_breaker_resume_all |
Admin gateways |
session.transition |
Scheduler |
Messages published (PUB :5556):
| Topic | Purpose |
|---|---|
order.ack.{GW_ID} |
Order accepted/rejected |
order.fill.{GW_ID} |
Partial or full fill |
order.cancelled.{GW_ID} |
Cancel confirmation |
order.expired.{GW_ID} |
TIF expiry |
order.orders.{GW_ID} |
Order list reply |
combo.ack.{GW_ID} |
Combo accepted/rejected |
combo.status.{GW_ID} |
Combo lifecycle change |
trade.executed |
Every matched trade pair |
book.{SYMBOL} |
Book snapshot after every change |
session.state |
Session phase change |
auction.result.{SYMBOL} |
Auction uncross result |
system.gateway_auth.{GW_ID} |
Gateway authentication reply |
system.symbols.{GW_ID} |
Symbol list reply |
system.eod |
End-of-day broadcast |
Messages published (drop-copy PUB :5557):
| Topic | Purpose |
|---|---|
drop_copy.event.{GW_ID} |
Fill event with sequence number and nanosecond timestamp, filtered per gateway |
The drop-copy socket is lazily bound on startup. See Drop-Copy Feed for subscription protocol and replay support.
Warning
Start the engine first — gateways and subscribers will fail to connect otherwise.
pm-alf-console — User Gateway¶
One instance per user. Accepts ALF commands on stdin. See ALF Protocol Reference.
Startup options:
| Flag | Required | Description |
|---|---|---|
--id |
Yes | Unique gateway identifier (e.g. GW01, ALICE) |
Expected runtime input arguments:
NEW|...order-entry commands (includingLIMIT,MARKET,STOP,STOP_LIMIT,FOK,ICEBERG,IOC,TRAILING_STOP)AMEND|...,CANCEL|...QUOTE|...,QUOTE_CANCEL|...QLEGS|...(market-maker quote-leg monitor with fill flags)ORDERS,SYMBOLS,HELP,EXIT,QUITKILL|...for gateway-scoped kill-switch actions
Connection behaviour:
1. Sends system.gateway_connect to the engine
2. Waits for system.gateway_auth.<GW_ID>
3. Enters command loop only if accepted
If the ID is not listed in engine_config.yaml under gateways.alf, connection
is refused and the gateway exits.
Messages sent (PUSH → :5555):
| Topic | Purpose |
|---|---|
system.gateway_connect |
Authentication request |
order.new |
Submit order |
order.cancel |
Cancel order |
order.combo |
Submit combo |
order.combo_cancel |
Cancel combo |
order.orders_request |
Request order list |
system.symbols_request |
Request symbol list |
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
system.gateway_auth.{own GW_ID} |
Authentication reply |
order.ack.{own GW_ID} |
Order acknowledgements |
order.fill.{own GW_ID} |
Fill notifications |
order.amended.{own GW_ID} |
Successful amend notifications |
order.cancelled.{own GW_ID} |
Cancel confirmations |
order.expired.{own GW_ID} |
Expiry notifications |
order.orders.{own GW_ID} |
Order list replies |
combo.ack.{own GW_ID} |
Combo acknowledgements |
combo.status.{own GW_ID} |
Combo status updates |
oco.ack.{own GW_ID} |
OCO creation acknowledgements |
oco.cancelled.{own GW_ID} |
OCO sibling-cancel notifications |
quote.ack.{own GW_ID} |
Quote acknowledgements |
quote.status.{own GW_ID} |
Quote lifecycle updates |
risk.kill_switch_ack.{own GW_ID} |
Kill-switch acknowledgement |
system.symbols.{own GW_ID} |
Symbol list reply |
trade.executed |
Global trade feed for last-price / P&L display |
See the Gateway Reference for the full command list.
pm-alf-gwy — ALF TCP Gateway¶
Accepts ALF order-entry commands from external bots and remote processes over a
plain TCP connection. Uses the same ALF command vocabulary as pm-alf-console
but is designed for programmatic clients, not interactive terminals. One
connection per gateway ID; all configured gateways.alf IDs may connect.
Startup options:
| Flag | Default | Description |
|---|---|---|
--config / -c |
engine_config.yaml |
Config file with optional alf_gateway: section |
--bind |
from config / 0.0.0.0 |
TCP bind address for external clients |
--port |
from config / 5565 |
TCP listen port for ALF clients |
--engine-host |
from config | Override engine host for ZMQ ports 5555 / 5556 |
Expected runtime input arguments:
No terminal input. External clients connect over TCP and send ALF lines:
HELLO|CLIENT=...|PROTO=ALF1|ID=<gateway-id>(must be first)NEW|...,AMEND|...,CANCEL|...QUOTE|...,QUOTE_CANCEL|...KILL[|SYM=...]SYMBOLS,ORDERS,QBOOT[|SYM=...]PING,EXIT/QUIT
Messages sent (PUSH → :5555):
| Topic | Purpose |
|---|---|
system.gateway_connect |
Authentication request |
system.gateway_disconnect |
Graceful disconnect notice |
order.new |
Submit order |
order.cancel |
Cancel order |
order.amend |
Amend order |
order.combo |
Submit combo |
order.combo_cancel |
Cancel combo |
order.oco |
Submit OCO pair |
order.oco_cancel |
Cancel OCO pair |
order.orders_request |
Request order list |
quote.new |
Submit / replace MM quote |
quote.cancel |
Cancel MM quote |
risk.kill_switch |
Gateway kill-switch |
system.symbols_request |
Request symbol list |
system.quote_bootstrap_request |
Request quote bootstrap state |
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
system.gateway_auth.{GW_ID} |
Engine authentication reply |
order.ack.{GW_ID} |
Order accepted or rejected |
order.fill.{GW_ID} |
Partial or full fill |
order.amended.{GW_ID} |
Amend confirmation |
order.cancelled.{GW_ID} |
Cancel confirmation |
order.expired.{GW_ID} |
TIF expiry |
order.orders.{GW_ID} |
Order list reply |
combo.ack.{GW_ID} |
Combo accepted or rejected |
combo.status.{GW_ID} |
Combo lifecycle change |
oco.ack.{GW_ID} |
OCO accepted or rejected |
oco.cancelled.{GW_ID} |
OCO sibling-cancel notification |
quote.ack.{GW_ID} |
Quote accepted or rejected |
quote.status.{GW_ID} |
Quote lifecycle change |
risk.kill_switch_ack.{GW_ID} |
Kill-switch acknowledgement |
system.symbols.{GW_ID} |
Symbol list reply |
system.quote_bootstrap.{GW_ID} |
Quote bootstrap state reply |
session.state |
Session phase changes (broadcast) |
trade.executed |
Trade events (broadcast) |
circuit_breaker.halt.* |
Symbol halt events (broadcast) |
circuit_breaker.resume.* |
Symbol resume events (broadcast) |
See ALF TCP Gateway for operational usage, command reference, and client examples.
pm-viewer — Order Book Viewer¶
Live terminal view of a single symbol's order book.
Startup options:
| Flag | Default | Description |
|---|---|---|
--symbol / -s |
required | Symbol to watch |
--depth / -d |
10 | Number of price levels to show per side |
Expected runtime input arguments:
None.
Display: - Left panel: top-N bid levels (price, total qty, number of orders) - Middle panel: top-N ask levels - Right panel: 5 most recent trades with timestamps - Header bar: symbol name, last trade price, last trade qty, refresh time
Iceberg orders
Iceberg orders only show their displayed_qty (the visible peak) in the viewer.
The hidden quantity is completely invisible — this is by design and demonstrates the
privacy feature of iceberg orders.
Run multiple viewers simultaneously for different symbols:
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
book.{SYMBOL} |
Book updates for the watched symbol |
session.state |
Session phase changes (displayed in header) |
Messages sent (PUSH → :5555):
| Topic | Purpose |
|---|---|
book.snapshot_request |
Requests initial book state on startup |
pm-orders — Order Status Monitor¶
Live cross-gateway view of all orders in the system.
Startup options:
| Flag | Default | Description |
|---|---|---|
--gateway / -g |
(all) | Filter to a single gateway |
Expected runtime input arguments:
None.
Displays a live table with columns:
ID | Gateway | Symbol | Side | Type | TIF | Qty | Remaining | Price | Status | Updated
Status colours: green=NEW, yellow=PARTIAL, bright green=FILLED, red=REJECTED/CANCELLED, dim=EXPIRED.
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
order. (prefix) |
All order events (ack, fill, cancelled, expired) |
combo. (prefix) |
Combo ack and status events |
session.state |
Session phase changes |
pm-audit — Event Logger¶
Records every message on the bus to a rotating log file.
Startup options:
| Flag | Default | Description |
|---|---|---|
--log-file |
data/audit.log |
Output log file path |
--terminal / -t |
off | Also print each entry to stdout |
Expected runtime input arguments:
None.
Log format (one entry per line):
[2026-04-29T14:32:01.123] [trade.executed] {"id": "...", "symbol": "AAPL", ...}
[2026-04-29T14:32:01.125] [book.AAPL] {"bids": [...], "asks": [...], ...}
Log files rotate at 10 MB with 5 backups kept.
Use --terminal during demos so the class can see every event in real time.
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
| (empty prefix — receives all messages) | Records everything published on the PUB socket |
pm-clearing — Clearing & P&L¶
SQLite-backed clearing writer for P&L and position state.
pm-clearing [--datapath PATH] [--db-name NAME] [--flush-size N] [--flush-interval SEC] [--print-every N]
Startup options:
| Flag | Default | Description |
|---|---|---|
--datapath |
data dir from EDUMATCHER_DATA_DIR |
Data directory or explicit .db path |
--db-name |
clearing.db |
SQLite filename when --datapath is a directory |
--flush-size |
100 |
Flush immediately when buffered trades reaches N |
--flush-interval |
5.0 |
Flush interval in seconds when buffer is non-empty |
--print-every |
100 |
Print in-memory P&L snapshot every N trades (0 disables) |
pm-clearing-cli global options include --raw-output to disable display
normalization and print raw tick-unit values for price-derived fields.
Expected runtime input arguments:
None.
On startup, pm-clearing applies schema migrations and prunes trade_events
older than the retention window (default: 90 days). Writes are batched into
single SQLite transactions to update:
trade_eventsgateway_symbol_positionsgateway_daily_summary
trade.executed.tick_decimals is persisted so downstream output can normalize
price-derived values for table/JSON/CSV rendering.
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
trade.executed |
Every matched trade pair (including tick_decimals) — drives P&L calculations |
See P&L & Clearing for the full accounting model.
pm-clearing-cli — Clearing Query CLI¶
pm-clearing-cli is a command-line interface for querying clearing.db
without writing SQL manually. It supports both read-style reporting commands
and a maintenance command (prune) that deletes old raw trade rows.
pm-clearing-cli [--datapath PATH] [--db-name clearing.db] [--format table|json|csv] [--no-header] [--raw-output] COMMAND [options]
Unlike pm-clearing, this is a one-shot tool: it runs one command, prints
output, and exits.
Global options:
| Flag | Default | Description |
|---|---|---|
--datapath PATH |
resolved from EDUMATCHER_DATA_DIR |
Data directory or explicit .db file path |
--db-name NAME |
clearing.db |
SQLite filename when --datapath is a directory |
--format |
table |
Output format: table, json, or csv |
--no-header |
off | Suppress header row in CSV output |
--raw-output |
off | Disable tick-decimal normalization and emit raw tick-unit values |
Subcommands:
| Subcommand | Default limit | Purpose | Typical filters |
|---|---|---|---|
gateways |
1000 | Gateway-level realized/unrealized/total P&L totals | --gateway |
positions |
10000 | Current open position state by gateway and symbol | --gateway, --symbol |
pnl |
10000 | Realized/unrealized/total P&L rows per gateway and symbol | --gateway, --symbol |
daily |
1000 | Daily rollup summary rows | --gateway, --symbol, --date, --from, --to |
trades |
200 | Raw trade-event rows | --gateway, --symbol, --date, --from, --to |
exposure |
1000 | Net/gross notional exposure and P&L | --gateway, --symbol, --sort |
symbols |
1000 | Symbol-level totals and open exposure snapshot | --date, --from, --to, --sort |
dates |
1000 | Available trade dates (optionally with totals) | --gateway, --symbol, --from, --to, --with-totals |
health |
n/a | DB row counts, flush metadata, and WAL mode | none |
reconcile |
n/a | Compares raw trade_events buy-side aggregates vs daily summary |
--gateway, --symbol, --from, --to |
prune |
n/a | Deletes old trade_events rows by retention window |
--days, --dry-run |
Sort options:
| Command | Allowed --sort values |
|---|---|
exposure |
gross_notional, net_notional, realized_pnl, unrealized_pnl, total_pnl |
symbols |
symbol, traded_qty, traded_notional, realized_pnl, open_net_qty |
Examples:
# Gateway totals (table)
pm-clearing-cli gateways
# One gateway in JSON
pm-clearing-cli --format json gateways --gateway GW_A
# Current positions as CSV (normalized display values)
pm-clearing-cli --format csv positions --gateway MM01
# Emit raw tick-unit values instead of normalized display values
pm-clearing-cli --format json --raw-output positions --gateway MM01
# Daily summary for one date range
pm-clearing-cli daily --from 2026-07-01 --to 2026-07-05
# Raw trades for one symbol
pm-clearing-cli trades --symbol AAPL --limit 50
# Risk view by total P&L
pm-clearing-cli exposure --sort total_pnl
# Date discovery with aggregate totals
pm-clearing-cli dates --with-totals
# Data-consistency check
pm-clearing-cli reconcile --from 2026-07-01 --to 2026-07-05
# Retention maintenance (dry run then execute)
pm-clearing-cli prune --days 90 --dry-run
pm-clearing-cli prune --days 90
No-row behaviour:
table: printsNo rows found.(exceptreconcile, which printsOK — no discrepancies found.)json: prints[]csv: prints only header row unless--no-headeris set
See P&L & Clearing for the accounting model and schema-level details.
pm-stats — Statistics Recorder¶
Records market statistics for every symbol to a SQLite database (data/stats.db).
Startup options:
| Flag | Default | Description |
|---|---|---|
--db |
data/stats.db |
SQLite database file path |
Expected runtime input arguments:
None.
Subscriptions:
| Topic | Purpose |
|---|---|
trade.* |
Updates OHLCV, VWAP, min/max, volume, trade log |
book.* |
Records opening bid/ask prices; drives 15-minute snapshots |
system.eod |
Records end-of-day closing bid/ask prices |
On engine shutdown, the engine broadcasts system.eod before closing sockets.
pm-stats receives it and immediately flushes the closing bid/ask for every active symbol
into daily_stats.
How statistics are computed¶
OHLCV and VWAP accumulate in memory, one SymbolStats object per symbol, and are
flushed to daily_stats after each trade.
| Statistic | Trigger | Rule |
|---|---|---|
open_price |
First trade.* of the day |
Set once; never overwritten |
close_price |
Every trade.* |
Always overwritten with the latest price |
high_price |
Every trade.* |
Running max(current, trade_price) |
low_price |
Every trade.* |
Running min(current, trade_price) |
volume |
Every trade.* |
Cumulative sum of matched quantities |
trade_count |
Every trade.* |
Incremented by 1 |
vwap |
Every trade.* |
\(\sum(price \times qty) / \sum(qty)\); maintained as two running accumulators |
largest_trade_qty/price |
Every trade.* |
Replaced when qty > current_largest |
open_bid/ask |
First book.* update of the day |
Set once from the book snapshot |
close_bid/ask |
system.eod |
Overwritten with the final bid/ask from the book |
15-minute price snapshots are written to price_snapshots when a book.* message
arrives and at least 15 minutes have elapsed since the last snapshot for that symbol
(SNAPSHOT_INTERVAL_SEC = 900). The mid-price is computed as:
If one side of the book is empty the available side is used as the mid-price; if both sides are empty the last known trade price is used instead.
Trade log rows are appended on every trade.* event — no aggregation, one row per
matched trade pair, using the trade UUID from the engine as the primary key.
Startup behaviour — on startup pm-stats sends a book.snapshot_request via PUSH
:5555 for every symbol it discovers from system.symbols.STATS. This primes the
open_bid/open_ask values and the first snapshot interval even before the first trade.
Statistics Database Schema¶
The database lives at data/stats.db (SQLite 3). Three tables are maintained.
daily_stats¶
One row per (date, symbol). Upserted on every trade and again when system.eod arrives.
| Column | Type | Description |
|---|---|---|
date |
TEXT (PK) | Calendar date YYYY-MM-DD |
symbol |
TEXT (PK) | Instrument ticker |
open_price |
REAL | Price of the first trade of the day |
high_price |
REAL | Highest trade price of the day |
low_price |
REAL | Lowest trade price of the day |
close_price |
REAL | Price of the last trade of the day |
open_bid |
REAL | Best bid at the time of the first book update |
open_ask |
REAL | Best ask at the time of the first book update |
close_bid |
REAL | Best bid recorded at engine shutdown (system.eod) |
close_ask |
REAL | Best ask recorded at engine shutdown (system.eod) |
volume |
INTEGER | Total traded quantity for the day |
trade_count |
INTEGER | Number of individual trades |
vwap |
REAL | Volume-weighted average price: \(\sum(price \times qty) / \sum(qty)\) |
largest_trade_qty |
INTEGER | Quantity of the single largest trade |
largest_trade_price |
REAL | Price of the single largest trade |
Example query — end-of-day summary:
SELECT date, symbol,
open_price, high_price, low_price, close_price,
volume, trade_count,
ROUND(vwap, 4) AS vwap
FROM daily_stats
ORDER BY date DESC, symbol;
price_snapshots¶
One row per (ts, symbol) written every 15 minutes when a book update arrives.
| Column | Type | Description |
|---|---|---|
ts |
TEXT (PK) | ISO-8601 timestamp (UTC, second precision) |
symbol |
TEXT (PK) | Instrument ticker |
mid_price |
REAL | (best_bid + best_ask) / 2; falls back to whichever side is present, then last_price |
best_bid |
REAL | Best bid price at snapshot time (null if empty book) |
best_ask |
REAL | Best ask price at snapshot time (null if empty book) |
pct_change |
REAL | Percentage change of mid_price vs. previous snapshot: \(100 \times (mid_{t} - mid_{t-1}) / mid_{t-1}\) |
Example query — intraday price path for MSFT:
SELECT ts, mid_price, best_bid, best_ask,
ROUND(pct_change, 4) || '%' AS change
FROM price_snapshots
WHERE symbol = 'MSFT'
ORDER BY ts;
trade_log¶
Append-only record of every individual matched trade.
| Column | Type | Description |
|---|---|---|
ts |
TEXT | ISO-8601 timestamp (UTC, millisecond precision) |
trade_id |
TEXT (PK) | UUID from the engine |
symbol |
TEXT | Instrument ticker |
price |
REAL | Execution price |
quantity |
INTEGER | Matched quantity |
buy_gateway_id |
TEXT | Gateway that submitted the buy order |
sell_gateway_id |
TEXT | Gateway that submitted the sell order |
Example query — all trades for a symbol sorted by time:
SELECT ts, price, quantity, buy_gateway_id, sell_gateway_id
FROM trade_log
WHERE symbol = 'AAPL'
ORDER BY ts;
Querying the database
You can open data/stats.db with any SQLite client:
pm-stats-cli — Statistics Query CLI¶
pm-stats-cli is a read-only command-line interface for querying
data/stats.db without writing SQL manually.
Unlike pm-stats, this is not a subscriber process. It runs one query,
prints output, and exits.
Global options:
| Flag | Default | Description |
|---|---|---|
--db |
data/stats.db |
SQLite database file path |
--format |
table |
Output format: table, json, or csv |
--no-header |
off | Suppress header row in csv output |
Subcommands:
| Subcommand | Purpose | Typical filters |
|---|---|---|
daily |
Daily OHLCV summary from daily_stats |
--date, --symbol, --limit, --wide |
snapshots |
Intraday snapshots from price_snapshots |
--symbol (required), --date, --from, --to, --limit |
trades |
Trade history from trade_log |
--symbol, --date, --from, --to, --limit |
symbols |
Discover symbols available in stats data | --date |
dates |
Discover trading dates available in daily_stats |
--symbol |
Examples:
# Latest available daily summary date
pm-stats-cli daily
# Daily summary for one date and one symbol
pm-stats-cli daily --date 2026-06-14 --symbol AAPL
# Include bid/ask and largest-trade fields
pm-stats-cli daily --date 2026-06-14 --wide
# Intraday snapshots for one symbol in a time window
pm-stats-cli snapshots --symbol MSFT --from 2026-06-14T09:00:00+00:00 --to 2026-06-14T16:30:00+00:00
# Trades as CSV for scripting/export
pm-stats-cli --format csv trades --symbol AAPL --date 2026-06-14
# Trades as JSON for automation
pm-stats-cli --format json trades --symbol AAPL --limit 50
# Discovery helpers
pm-stats-cli symbols
pm-stats-cli dates --symbol AAPL
No-row behaviour:
table: printsNo rows found.json: prints[]csv: prints only header row unless--no-headeris set
For schema details, see the pm-stats section above and
Statistics and Reporting.
pm-scheduler — Session Scheduler¶
Drives session-phase transitions (PRE_OPEN → OPENING_AUCTION → CONTINUOUS →
CLOSING_AUCTION → CLOSED) by sending session.transition messages to the
engine at configured wall-clock times.
Startup options:
| Flag | Default | Description |
|---|---|---|
--config / -c |
engine_config.yaml |
Config file containing the schedule section |
--now |
off | Skip wall-clock waiting; send all transitions immediately with a delay between each |
--delay |
3 | Seconds between transitions in --now mode |
Expected runtime input arguments:
None.
Messages sent (PUSH → :5555):
| Topic | Purpose |
|---|---|
session.transition |
Requests the engine to move to the next session phase |
The scheduler does not subscribe to any PUB messages — it is fire-and-forget.
See Auctions & Scheduling for the full schedule configuration and session-phase documentation.
pm-ticker — Scrolling Market Ticker¶
Prints a scrolling ticker-tape line at regular intervals — one line per snapshot containing all active symbols with live prices, OHLCV, and bid/ask spreads.
Startup options:
| Flag | Default | Description |
|---|---|---|
--interval |
30 | Seconds between printed ticker lines |
--db |
data/stats.db |
Path to the statistics SQLite database |
--db-interval |
900 | Seconds between daily_stats DB re-queries (15 min) |
Expected runtime input arguments:
None.
Output format (one line per interval, scrolls up naturally):
Each symbol segment shows:
| Field | Description |
|---|---|
| Symbol | Instrument name (bold cyan) |
| Last price | Most recent trade price or closing price from DB |
| Change % | Percentage change vs. today's open price (green if up, red if down) |
| H: / L: | Intraday high (green) and low (red) from the daily_stats table |
| Vol | Cumulative traded volume for the day |
| (nT) | Number of trades today |
| Bid/Ask | Current best bid (green) / best ask (red) from live book |
The ticker combines live ZMQ data (last price, bid/ask from book.* messages)
with historical DB data (OHLCV, trade count from pm-stats's SQLite database).
The DB is re-queried every --db-interval seconds to pick up updated daily statistics.
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
book. (prefix) |
Live last price, best bid/ask per symbol |
Depends on pm-stats
The ticker reads from data/stats.db which is populated by pm-stats.
Without pm-stats running, the ticker still works but only shows live bid/ask
and last price — OHLCV, volume, and trade count will be missing.
pm-board — Market Board¶
Full-screen multi-symbol display designed for large monitors or projection screens. Shows all active symbols in a single paged table with exchange-style colouring.
Startup options:
| Flag | Default | Description |
|---|---|---|
--rows / -r |
8 | Maximum number of symbols (rows) displayed per page |
--interval / -i |
10 | Seconds before auto-rotating to the next page |
Expected runtime input arguments:
- ++enter++ to advance to the next page immediately
- ++ctrl+c++ to exit
Controls:
| Key | Action |
|---|---|
| ++enter++ | Advance to next page immediately |
| ++ctrl+c++ | Exit |
Display columns:
| Column | Description |
|---|---|
| Symbol | Instrument ticker |
| Last | Last traded price (coloured green if up from first trade, red if down) |
| Chg % | Percentage change from the first trade of the session: \(100 \times (last - first) / first\) |
| Bid | Best (highest) bid price currently in the book |
| Ask | Best (lowest) ask price currently in the book |
| Spread | Difference between best ask and best bid: \(ask - bid\) |
| Last Buy | Last trade price where this symbol was bought (green) |
| Last Sell | Last trade price where this symbol was sold (red) |
| Vol | Cumulative traded volume for the session |
| Updated | Timestamp of the most recent book update for this symbol |
The header bar shows: page number, total pages, number of active symbols, the auto-rotate interval, and the current clock time.
Paging behaviour:
- Symbols are sorted alphabetically and divided into pages of
--rowssymbols each. - The display auto-rotates to the next page every
--intervalseconds. - When the last page is reached, rotation wraps back to page 1.
- Pressing ENTER advances immediately and resets the auto-rotate timer.
Colour conventions (matching standard exchange displays):
- Green — price increase, buy trades, positive change %
- Red — price decrease, sell trades, negative change %
- White — unchanged or neutral values
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
book. (prefix) |
Book updates for all symbols — discovers symbols automatically |
trade.executed |
Every matched trade — drives volume and last-price tracking |
Large-screen demo
For classroom or conference demos, maximise the terminal and use large rows:
The board auto-discovers symbols as they become active — no configuration needed.pm-ai-trader — Autonomous Trader Bot¶
Runs one autonomous trading gateway with a selectable behaviour profile.
Startup options:
| Flag | Default | Description |
|---|---|---|
--id |
required | Gateway ID used by the bot (e.g. AI01) |
--profile |
cautious |
Personality profile (available_profiles() set) |
--symbols |
empty | Comma-separated symbol allowlist (e.g. AAPL,MSFT) |
--seed |
1 |
RNG seed for deterministic behaviour |
--duration |
0 |
Runtime in seconds; 0 means run until stopped |
--run-id |
autogenerated | Optional run label for audit/traceability |
--max-position |
1000 |
Absolute per-symbol position limit |
--max-rejects |
25 |
Reject threshold before cooldown breaker trips |
--reject-window |
10.0 |
Rolling reject window in seconds |
--reject-cooldown |
5.0 |
Pause interval after reject breaker trips |
--stale-data |
4.0 |
Max market-data age (seconds) before pausing orders |
Expected runtime input arguments:
None.
Connect / restart handshake:
On every startup (or reconnect) the bot performs the following initialization sequence before it begins submitting orders:
- Send
gateway_connect; wait forsystem.gateway_auth.<ID>. - Send
system.symbols_request; receivesystem.symbols.<ID>to populate the symbol universe and resolve per-symboltick_sizeandprev_close. - Send
system.session_state_request; receivesystem.session_status.<ID>to seed the current trading phase. Session broadcasts are edge-triggered — without this explicit query, a bot that connects mid-session would not know it is inOPENING_AUCTIONorCLOSEDuntil the next phase transition. - Send
system.halt_status_request; receivesystem.halt_status.<ID>to seed the per-symbol halt flags so the bot never submits into a halted symbol. - Send
system.position_request; receivesystem.position_snapshot.<ID>to re-seed per-symbol net position and average cost from the engine's ledger. This ensures risk guards (position cap, drawdown guard) are accurate even when the bot restarts while the engine is still running.
Steps 3–5 are idempotent: if the engine returns an empty list or a
CONTINUOUS state the bot simply starts from a flat / unhalt / continuous
state, which is correct for a fresh session.
Launches and supervises multiple pm-ai-trader bots as a coordinated swarm.
Startup options:
| Flag | Default | Description |
|---|---|---|
--count |
10 |
Number of bot processes to launch |
--prefix |
AI |
Gateway-ID prefix |
--start-index |
1 |
First numeric suffix for generated IDs |
--profiles |
all profiles | Comma-separated profile cycle |
--symbols |
from config | Comma-separated symbol list override |
--config |
engine_config.yaml |
Config used to discover symbols |
--seed-base |
1000 |
Base seed; bot i gets seed-base + i |
--duration |
60.0 |
Per-bot runtime in seconds |
--python |
current interpreter | Python executable used for child processes |
--max-position |
1000 |
Passed through to child bots |
--max-rejects |
25 |
Passed through to child bots |
--reject-window |
10.0 |
Passed through to child bots |
--reject-cooldown |
5.0 |
Passed through to child bots |
--stale-data |
4.0 |
Passed through to child bots |
Expected runtime input arguments:
None.
See AI Bot Traders for strategy and orchestration details.
pm-mm-bot — Autonomous Market-Maker Bot¶
Runs one autonomous market-maker gateway for a single symbol. Connects as a
MARKET_MAKER participant, posts a two-sided quote, and automatically reprices
on fills, mid-price drift, and session transitions.
Each instance handles one symbol. Run multiple instances (with different
--id-suffix values) to cover several symbols or to compete on the same symbol.
Startup options:
| Flag | Default | Description |
|---|---|---|
--symbol |
required | Instrument to make a market in (e.g. AAPL) |
--gap |
0.10 |
Total spread in price units (bid at mid−gap/2, ask at mid+gap/2) |
--qty |
500 |
Quote size on each leg |
--id-suffix |
01 |
Running number for gateway ID (MM_AAPL_01) |
--drift-ticks |
3 |
Reprice when mid moves by this many ticks |
--reissue-delay-ms |
200 |
Milliseconds to wait after fill before re-issuing |
--tif |
DAY |
Time-in-force for quote legs (DAY or GTC) |
--heartbeat-interval-sec |
5.0 |
Periodic live-quote check interval |
--startup-session-timeout-sec |
5.0 |
Max wait for first session.state event |
--bootstrap-timeout-sec |
1.0 |
Max wait for QBOOT reply |
--cancel-timeout-sec |
1.0 |
Max wait for cancel confirmation |
--shutdown-timeout-sec |
2.0 |
Max wait for cancel on SIGINT/SIGTERM |
--qlegs-reconcile-interval-sec |
15.0 |
Periodic QLEGS reconciliation interval |
--initial_min |
unset | Lower bound for random bootstrap price |
--initial_max |
unset | Upper bound for random bootstrap price |
--engine-pull |
tcp://127.0.0.1:5555 |
Engine PUSH/PULL address |
--engine-pub |
tcp://127.0.0.1:5556 |
Engine PUB address |
-v / --verbose |
off | Print debug-level events |
Expected runtime input arguments:
None. The bot is fully autonomous after startup.
Gateway ID convention:
The gateway ID is constructed as MM_<SYMBOL>_<suffix> — for example,
pm-mm-bot --symbol AAPL --id-suffix 02 connects as MM_AAPL_02. This ID
must be pre-registered in engine_config.yaml with role: MARKET_MAKER.
Startup behaviour:
- Opens ZMQ PUSH and SUB sockets
- Sends
system.gateway_connectand waits forsystem.gateway_authACK - Requests the symbol list and verifies the assigned symbol exists
- Sends
QBOOT(quote bootstrap) request — if an active quote already exists for(gateway_id, symbol), adopts it instead of creating a duplicate - Sends
QLEGSrequest to reconcile quote-leg mapping - Waits for
session.state(exits if not received within timeout) - Resolves initial reference price and enters the quoting loop
Reference price resolution (in priority order):
- Active quote from
QBOOT(restart recovery) - Book mid-price (if other participants are already quoting)
- Last trade price from
trade.executedevents - Bootstrap quote prices from
QBOOTsnapshot (inactive) - Random price from
[--initial_min, --initial_max]range
State machine:
| State | Meaning |
|---|---|
CONNECTING |
Opening ZMQ sockets |
AUTHENTICATING |
Waiting for gateway auth reply |
WAITING_FOR_SESSION |
Waiting for session to enter CONTINUOUS |
QUOTING |
Active two-sided quote is resting in the book |
REPRICING |
Mid-price drift detected; preparing new quote |
REISSUING |
Sending replacement quote after fill or drift |
PAUSED |
Session is not CONTINUOUS (auction, halt, closed) |
Shutdown (Ctrl-C or SIGTERM):
- Sends
quote.cancelto remove the resting quote - Waits up to
--shutdown-timeout-secfor cancel confirmation - Closes ZMQ sockets and exits
Messages sent (PUSH → :5555):
| Topic | Purpose |
|---|---|
system.gateway_connect |
Authentication request |
system.symbols_request |
Request symbol list |
system.quote_bootstrap_request |
Request active quote state for adopt-or-create (QBOOT) |
system.quote_legs_request |
Request quote-leg mapping for reconciliation (QLEGS) |
quote.new |
Submit or replace a two-sided quote |
quote.cancel |
Cancel the active quote (shutdown or session change) |
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
system.gateway_auth.{GW_ID} |
Authentication reply |
system.symbols.{GW_ID} |
Symbol list reply |
system.quote_bootstrap.{GW_ID} |
Bootstrap snapshot with active quote state |
system.quote_legs.{GW_ID} |
Quote-leg reconciliation reply |
book.{SYMBOL} |
Book updates — drives mid-price tracking and drift detection |
depth.{SYMBOL} |
Depth / imbalance metrics (microprice, mid_price) for the assigned symbol |
trade.executed |
Trade events — fallback reference price source |
order.fill.{GW_ID} |
Fill notifications on quote legs |
order.cancelled.{GW_ID} |
Cancel confirmations for quote legs |
quote.ack.{GW_ID} |
Quote accepted/rejected acknowledgements |
quote.status.{GW_ID} |
Quote lifecycle changes (inactivated, cancelled) |
session.state |
Session phase transitions (pause/resume quoting) |
circuit_breaker.halt.{SYMBOL} |
Circuit breaker halt for the assigned symbol |
circuit_breaker.resume.{SYMBOL} |
Circuit breaker resume for the assigned symbol |
See Market-Maker Bot for usage examples, configuration guide, bootstrap details, and troubleshooting.
pm-ralf-gwy — Post-Trade Dissemination Gateway¶
Runs the external machine-facing post-trade dissemination gateway that publishes RALF over TCP for clearing, drop-copy, and audit consumers.
pm-ralf-gwy [--config engine_config.yaml] [--bind 0.0.0.0] [--port 5580] [--engine-pub tcp://127.0.0.1:5556]
Startup options:
| Flag | Default | Description |
|---|---|---|
--config / -c |
engine_config.yaml |
Config file; optional post_trade_gateway section |
--bind |
from config / 0.0.0.0 |
TCP bind address for external clients |
--port |
from config / 5580 |
TCP listen port for RALF clients |
--engine-pub |
tcp://127.0.0.1:5556 |
Engine PUB address consumed by the gateway |
Expected runtime input arguments:
No terminal input. External parties connect over TCP and send RALF lines:
HELLO|...SUB|...UNSUB|...PINGEXIT
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
trade.executed |
Source for EXEC post-trade events |
system.eod |
Source for EOD summary markers |
Messages published (TCP RALF feed):
| Message type | Purpose |
|---|---|
WELCOME |
Session acceptance and capabilities |
SNAP |
Subscription/recovery baseline |
EXEC |
Live execution dissemination |
EOD |
End-of-day summary marker |
HB |
Heartbeat |
PONG |
Ping reply |
ERR |
Protocol/entitlement/replay errors |
EXIT |
Session termination reason |
For external usage and examples see Post-Trade Dissemination and RALF Protocol Reference.
pm-index — Index Calculation Process¶
Subscribes to trade events from the engine and maintains one or more configurable
cap-weighted market indices in real time. Publishes live index values on a
dedicated ZMQ PUB socket so pm-md-gwy can forward them to external subscribers
over the CALF INDEX channel.
Startup options:
| Flag | Default | Description |
|---|---|---|
--config / -c |
engine_config.yaml |
Path to engine config YAML containing the indices: section |
--reset |
off | Delete persisted state files and reinitialise all indices from config |
Expected runtime input arguments:
None. pm-index is a long-running background process after startup.
Startup behaviour:
1. Reads all indices: entries from engine_config.yaml
2. For each index: loads divisor and last prices from the state file if present, or initialises fresh from constituent reference prices and base_value
3. Validates that every constituent symbol appears in symbols: with a positive outstanding_shares
4. Writes an INIT history record for each index that starts without prior state
5. Binds ZMQ PULL :5559 for operator commands and history requests
6. Binds ZMQ PUB :5558 for broadcasting index.update messages
7. Subscribes to engine PUB :5556 for trade.executed, session.state, and system.eod
8. Enters the poll loop
Shutdown (Ctrl-C): 1. Flushes all pending JSONL history writes 2. Closes ZMQ sockets
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
trade.executed |
Drives index recalculation for constituent symbols |
session.state |
Tracks intraday OHLC reset points and triggers EOD finalisation on CLOSED |
system.eod |
Alternate EOD trigger — writes final snapshot and OHLC record |
Messages received (PULL :5559):
| Topic | Purpose |
|---|---|
index.history_request |
Returns LEVEL and EOD records from the JSONL history file |
index.corp_action |
Applies a stock split, cash dividend, or shares-issuance adjustment |
index.constituent_change |
Adds or removes a constituent symbol from an index basket |
Messages published (PUB :5558):
| Topic | Purpose |
|---|---|
index.update |
Current index level, OHLC, aggregate cap, divisor, and session state |
index.history.{GW_ID} |
History query response addressed to the requesting gateway |
index.corp_action_ack.{GW_ID} |
Corporate action applied / rejected acknowledgement |
index.constituent_change_ack.{GW_ID} |
Constituent add or delist accepted / rejected acknowledgement |
index.error.{GW_ID} |
Generic error for malformed or rejected requests |
Persistence:
| File | Purpose |
|---|---|
data/indexes/<ID>_state.json |
Divisor, last prices, and intraday OHLC checkpoint — rewritten after each EOD and corporate action |
data/indexes/<ID>_history.jsonl |
Append-only JSONL audit trail with INIT, LEVEL, EOD, CORP_ACTION, ADD_CONSTITUENT, and DELIST records |
See Market Index (pm-index) for configuration details, calculation explanation, and corporate action procedures.
pm-api-gwy — REST/WebSocket API Gateway¶
Exposes EduMatcher order entry, order management, reference data, history, and
market data over REST/JSON and WebSocket. It is not a second matching engine —
it translates HTTP and WebSocket requests into the same ZMQ messages used by the
interactive pm-alf-console terminal. Reads configuration from the api_gateways:
section of engine_config.yaml.
Startup options:
| Flag | Default | Description |
|---|---|---|
--config PATH |
EDUMATCHER_CONFIG resolution |
Path to engine_config.yaml |
--instance NAME |
auto-selected when only one entry exists | Named api_gateways entry to run; required when more than one entry is configured |
--host ADDR |
config value | Override HTTP bind address |
--port PORT |
config value | Override HTTP listen port |
--engine-host HOST |
config value | Override engine host for ZMQ connections (cross-host deployments) |
--stats-db PATH |
config value | Path to data/stats.db for /history/* endpoints |
--log-level LEVEL |
config value | debug, info, warning, or error |
Expected runtime input arguments:
No terminal input. External clients connect via HTTP or WebSocket.
Startup behaviour:
1. Reads the named api_gateways.<NAME> entry from engine_config.yaml
2. Exits immediately if enabled: false
3. Sends system.gateway_connect to the engine and waits for system.gateway_auth ACK
4. Starts the uvicorn HTTP server and enters the request-serving loop
REST endpoints (base path /api/v1):
| Method | Path | Key type | Purpose |
|---|---|---|---|
POST |
/orders |
trading | Submit an order |
DELETE |
/orders/{id} |
trading | Cancel an order |
PATCH |
/orders/{id} |
trading | Amend price and/or quantity |
GET |
/orders |
trading | List live orders |
POST |
/oco |
trading | Submit OCO pair |
POST |
/combos |
trading | Submit combo order |
POST |
/quotes |
trading | Submit two-sided quote |
DELETE |
/quotes/{symbol} |
trading | Cancel active quote |
POST |
/mass-cancel |
trading | Cancel all or symbol-scoped exposure |
GET |
/symbols |
any | Instrument metadata |
GET |
/session |
any | Current engine session state |
GET |
/positions |
trading | Net positions by symbol |
GET |
/history/orders |
trading | Historical order lifecycle events |
GET |
/history/fills |
trading | Historical fills |
GET |
/history/trades |
any | Public trade log from pm-stats |
GET |
/history/daily |
any | Daily OHLCV from pm-stats |
GET |
/market-data |
any | Live order book top-of-book via REST |
WebSocket endpoints:
| Path | Key type | Stream |
|---|---|---|
/events |
trading | Private fills, acks, expiries — filtered to the connected gateway |
/market-data |
any | Public order book snapshots and trade events |
Authentication:
- REST:
Authorization: Bearer <api_key>header - WebSocket: send
{"api_key": "<key>"}as the first JSON message
Trading keys (gateway_id set to a gateway ID) can reach order-entry endpoints
and private event streams. Read-only keys (gateway_id: null) are limited to
market-data and history endpoints.
Tip
When swagger_enabled: true in config, browse http://127.0.0.1:<PORT>/docs
for interactive endpoint documentation and a live try-it UI.
Messages sent (PUSH → :5555):
| Topic | Purpose |
|---|---|
system.gateway_connect |
Engine authentication request (once at startup) |
order.new |
Order submission |
order.cancel |
Order cancellation |
order.combo |
Combo submission |
quote.new |
Quote submission |
quote.cancel |
Quote cancellation |
risk.kill_switch |
Mass cancel triggered by /mass-cancel |
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
system.gateway_auth.{GW_ID} |
Engine authentication reply |
order.ack.{GW_ID} |
Order accepted or rejected |
order.fill.{GW_ID} |
Partial or full fill |
order.cancelled.{GW_ID} |
Cancel confirmation |
order.expired.{GW_ID} |
TIF expiry |
order.amended.{GW_ID} |
Amend confirmation |
combo.ack.{GW_ID} |
Combo accepted or rejected |
combo.status.{GW_ID} |
Combo lifecycle changes |
quote.ack.{GW_ID} |
Quote accepted or rejected |
quote.status.{GW_ID} |
Quote lifecycle changes |
system.symbols.{GW_ID} |
Symbol list reply |
book.{SYMBOL} |
Live book snapshots for the market-data WebSocket stream |
trade.executed |
Trade events for the public market-data stream |
session.state |
Session phase changes |
See API Gateway (REST/WebSocket) for endpoint reference, authentication examples, Swagger access, and client code.
pm-admin — Interactive Admin Console¶
An interactive REPL for sending operational commands to a running engine without needing a full gateway session.
Startup options:
| Flag | Required | Description |
|---|---|---|
--id |
Yes | ADMIN gateway ID configured in engine_config.yaml (e.g. GW_ADMIN) |
Expected runtime input arguments:
HALT,RESUMEHALT_SYM|SYM=<sym>,RESUME_SYM|SYM=<sym>,CANCEL_SYM|SYM=<sym>KILL|GW=<gw>[|SYM=<sym>]KICK|GW=<gw>[|REASON=<text>]QCANCEL|GW=<gw>|SYM=<sym>BOOK|SYM=<sym>ORDERS|GW=<gw>SYMBOLSSESSION|STATE=<PRE_OPEN|OPENING_AUCTION|CONTINUOUS|CLOSING_AUCTION|CLOSED>SESSION_STATUS,SCHEDULE,GATEWAYS,VOLUMEHELP,EXIT,QUIT
Messages sent (PUSH → :5555):
| Topic | Purpose |
|---|---|
risk.kill_switch |
Trigger an exchange-wide order kill |
risk.circuit_breaker_halt_all |
Halt all symbols (requires ADMIN gateway role) |
risk.circuit_breaker_resume_all |
Resume all halted symbols (requires ADMIN gateway role) |
Messages subscribed (SUB from :5556):
| Topic | Purpose |
|---|---|
system.gateway_auth.{GW_ID} |
Authentication reply |
risk.kill_switch_ack.{GW_ID} |
Kill-switch acknowledgement |
session.state |
Session phase changes |
pm-admin-cli — CLI Admin Commands¶
A non-interactive alternative to pm-admin for scripting or single-shot
operational commands.
Each invocation sends one command to the engine, waits for an acknowledgement, prints the result, and exits. Suitable for shell scripts and CI automation.
Startup options:
| Flag | Required | Description |
|---|---|---|
--id <GW_ID> |
Yes | ADMIN gateway ID |
--push <ADDR> |
No | Engine PULL address (default from config) |
--sub <ADDR> |
No | Engine PUB address (default from config) |
--timeout <MS> |
No | Ack timeout in milliseconds (default: 3000) |
Expected runtime input arguments:
Subcommands and required arguments:
| Command | Required arguments | Optional arguments | Purpose |
|---|---|---|---|
halt |
none | none | Exchange-wide halt |
resume |
none | none | Exchange-wide resume |
halt-sym |
--sym <SYMBOL> |
none | Halt one symbol |
resume-sym |
--sym <SYMBOL> |
none | Resume one symbol |
cancel-sym |
--sym <SYMBOL> |
none | Cancel all resting orders on one symbol |
kill |
--gw <GW_ID> |
--sym <SYMBOL> |
Cancel all (or symbol-scoped) orders/quotes for gateway |
kick |
--gw <GW_ID> |
--reason <TEXT> |
Disconnect a gateway |
qcancel |
--gw <GW_ID> --sym <SYMBOL> |
none | Cancel active quote for gateway on symbol |
book |
--sym <SYMBOL> |
none | Fetch book snapshot |
orders |
--gw <GW_ID> |
none | List resting orders for gateway |
symbols |
none | none | List configured instruments |
session |
--state <STATE> |
none | Request session transition |
session-status |
none | none | Read current session state |
schedule |
none | none | Read configured schedule |
gateways |
none | none | List gateway states |
volume |
none | none | Show daily volume summary |
pm-setup — Session Bootstrap Tool¶
Bootstraps a runnable EduMatcher session directory with sensible defaults.
Startup options:
| Flag | Default | Description |
|---|---|---|
--data-dir <PATH> |
$EDUMATCHER_DATA_DIR or ~/.local/share/edumatcher |
Data directory for persistent files |
--config-dest <PATH> |
./engine_config.yaml |
Destination path for copied sample config |
--force |
off | Overwrite existing config destination |
--no-config |
off | Skip sample config copy; create data dir only |
Expected runtime input arguments:
None.
Typical actions include creating/confirming data directories and placing sample configuration files so first-time startup is repeatable.
Use this once per new environment (VM, container volume, or fresh local workspace) before starting runtime processes.
This tool is local bootstrap logic and does not participate in the ZeroMQ runtime message bus.
pm-config-gen — Engine Config Generator¶
Generates an engine_config.yaml from explicit CLI parameters so environments
can be recreated without manual YAML editing.
pm-config-gen --symbols AAPL MSFT --gateways TRADER01 TRADER02 OPS01:ADMIN --sessions-enabled --output engine_config.yaml
Startup options:
| Flag | Required | Default | Description |
|---|---|---|---|
--symbols <SYM ...> |
Yes | — | One or more symbols |
--gateways <GW_SPEC ...> |
Yes | — | One or more gateway specs (ID[:ROLE[:DISCONNECT]]) |
--symbol-opts <SPEC> |
No | repeatable | Per-symbol overrides (SYMBOL:KEY=VALUE,...) |
--sessions-enabled / --no-sessions-enabled |
No | disabled | Enable/disable scheduler-driven sessions |
--snapshot-interval <SECS> |
No | default | Snapshot interval in seconds (>0) |
--no-collars |
No | off | Disable collar enforcement |
--no-circuit-breakers |
No | off | Disable circuit-breaker enforcement |
--static-band <PCT> |
No | unset | Default static band in (0,1) |
--dynamic-band <PCT> |
No | unset | Default dynamic band in (0,1) |
--risk-level <SPEC> |
No | repeatable | NAME:STATIC_PCT[:DYNAMIC_PCT] |
--cb-levels <CB_SPEC ...> |
No | unset | NAME:SHIFT_PCT[:HALT_MINS] entries |
--cb-window-ns <NS> |
No | default | Circuit-breaker reference window |
--mm-spread-ticks <N> |
No | default | Global MM max spread ticks |
--mm-min-qty <N> |
No | default | Global MM minimum quote qty |
--enforce-mm-obligations / --no-enforce-mm-obligations |
No | disabled | Enable/disable MM obligations |
--tick-decimals <N> |
No | default | Default tick decimals (0..8) |
--seed-last-prices |
No | off | Emit null last_buy_price/last_sell_price placeholders |
--schedule / --no-schedule |
No | auto | Force include/suppress schedule section |
--pre-open <HH:MM> |
No | default | Pre-open schedule time |
--opening-auction <HH:MM> |
No | default | Opening auction start time |
--continuous <HH:MM> |
No | default | Continuous trading start time |
--closing-auction <HH:MM> |
No | default | Closing auction start time |
--closing-end <HH:MM> |
No | default | Closing auction end time |
--output <FILE> |
No | stdout mode | Output path |
--force |
No | off | Overwrite existing output file |
--dry-run |
No | off | Print generated YAML only; do not write file |
Expected runtime input arguments:
None.
Use cases:
- Generate consistent configs across multiple machines.
- Create deterministic demo/test environments quickly.
- Avoid hand-editing errors in gateway/symbol/session configuration.
Like pm-setup, this is a local tooling command and does not subscribe/publish
on the runtime ZeroMQ sockets.
For more details on the options see Configuration Chapter
pm-index-cli — Index History Query Tool¶
Reads index history JSONL files written by pm-index directly from disk and
renders them as a table, JSON, or CSV. No running process is required.
Startup options:
| Flag | Default | Description |
|---|---|---|
--config PATH / -c PATH |
unset | Path to engine_config.yaml; auto-discovers history file paths and index IDs |
--data-dir DIR |
data/indexes |
Directory containing history files; used when --config is absent or an index is not in config |
--format table\|json\|csv |
table |
Output format |
--no-header |
off | Suppress header row (CSV only) |
Expected runtime input arguments:
None.
Subcommands:
| Subcommand | Default limit | Purpose |
|---|---|---|
level |
1,000 | Throttled LEVEL records — index value snapshots captured during trading |
eod |
365 | EOD records — one row per trading day with open/high/low/close |
events |
1,000 | Structural events: INIT, CORP_ACTION, ADD_CONSTITUENT, DELIST |
indices |
— | List configured indices from engine_config.yaml |
All history subcommands share --index ID (repeatable), --days N,
--from DATE_OR_TS, --to DATE_OR_TS, and --limit N. The events
subcommand also accepts --type TYPE to filter to a specific event kind.
Like pm-stats-cli, this is a read-only offline tool. It does not connect to
any ZeroMQ socket.
See pm-index-cli in the commands reference for the full option reference, output column descriptions, and examples including CSV export and Python plotting.
Order Lifecycle Message Flow¶
The following diagram traces a single limit order from submission to full fill, showing which process sends each message and on which socket:
sequenceDiagram
participant GW as pm-alf-console
participant ENG as pm-engine
participant VW as pm-viewer
participant CLR as pm-clearing
participant AUD as pm-audit
GW->>ENG: order.new (PUSH :5555)
ENG-->>GW: order.ack.GW_ID (PUB :5556)
Note over ENG: order rests in book
ENG-->>VW: book.SYMBOL (PUB :5556)
ENG-->>AUD: order.ack.GW_ID (PUB :5556)
GW->>ENG: order.new — aggressive order (PUSH :5555)
ENG-->>GW: order.ack.GW_ID (PUB :5556)
ENG-->>GW: order.fill.GW_ID (PUB :5556)
ENG-->>CLR: trade.executed (PUB :5556)
ENG-->>VW: book.SYMBOL (PUB :5556)
ENG-->>AUD: trade.executed (PUB :5556)
All subscribers (pm-viewer, pm-clearing, pm-audit, pm-stats, pm-board,
etc.) receive the same trade.executed and book.{SYMBOL} events concurrently
from the single PUB socket — none of them coordinate with each other.
Planned Processes¶
The following processes are documented as design proposals and are not yet available as runnable scripts.
| Process | Protocol | Purpose | Status |
|---|---|---|---|
| pm-balf-gateway | BALF | Binary order entry gateway for low-latency programmatic clients | Design proposal |
See the BALF Protocol Reference for message specifications planned for future gateway implementations.
pm-alf-gwy — ALF TCP gateway¶
pm-alf-gwy is implemented and provides external ALF order-entry over TCP.
It accepts the same ALF command vocabulary as pm-alf-console and is designed
for programmatic clients and remote bots.
See ALF TCP Gateway for operational usage and ALF Protocol Reference for the wire-level contract.
pm-md-gwy (CALF Market-Data Gateway)¶
pm-md-gwy is implemented and provides external market-data distribution over
CALF/TCP. It consumes engine PUB topics and exposes sequence-aware streams for:
TOP(top-of-book style updates)TRADE(executed trades)STATE(session and halt/resume state)
See Market Data Feed (CALF) for operational usage and CALF Protocol Reference for the wire-level contract.
See also¶
- Running the Engine — startup order, launch scripts, and verification
- Configuration — how each process is configured via
engine_config.yaml - Messages — the full ZeroMQ message catalog all processes share
- Persistence — which process writes which data file
- Drop Copy — the engine's built-in :5557 drop-copy feed
- Post-Trade Dissemination — external RALF gateway usage
- RALF Protocol Reference — official protocol appendix
- Market Data Feed (CALF) — external CALF gateway usage
- ALF TCP Gateway — external ALF order-entry gateway usage
- API Gateway (REST/WebSocket) — REST/WebSocket gateway usage and endpoint reference
- Market Index (pm-index) — index configuration, calculation, and corporate actions
- Market-Maker Bot — autonomous quoting process for a single symbol