Skip to content

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\n(direct ZMQ)"]
    DC -.->|drop-copy SUB| DCGWY["pm-dc-gwy\n(TCP :5590)"]
    DCGWY -->|TCP| EXT2["External risk /\ncompliance systems\n(no ZeroMQ)"]

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 binary order entry, CALF for 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:

poetry run pm-engine --verbose

If you installed with pipx (end-user mode), the commands are on your PATH and need no prefix:

pm-engine --verbose

See Getting Started → Installation for the full setup guide and the pm-setup bootstrap command.

Environment variables

One variable controls where EduMatcher finds and stores everything at runtime. Set it once in your shell profile (~/.zshrc or ~/.bashrc) and every pm-* command picks it 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.) and the deployed engine configuration

The engine configuration is not separately configurable. Every process reads <EDUMATCHER_DATA_DIR>/ref_data/engine_config.json — there is no --config flag and no EDUMATCHER_CONFIG variable. This is deliberate: while each process took its own path, starting one of them against the wrong file produced an exchange whose parts silently disagreed, and the symptom (a gateway reporting no instruments) pointed nowhere near the cause.

One data directory is therefore one exchange instance. Getting it wrong now detaches a process from its statistics and logs as well, so the mistake announces itself immediately.

# Example: per-session isolation
export EDUMATCHER_DATA_DIR="$HOME/sessions/morning"
pm-config-deploy ~/configs/morning.yaml
pm-engine --verbose

See pm-config-deploy for installing a configuration.

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, trade, and index-level statistics to SQLite No — but recommended immediately after pm-engine (see below)
pm-clearing pm-clearing P&L and trade settlement No
pm-audit pm-audit Full event log to disk No — but recommended immediately after pm-engine (see below)
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-dc-gwy pm-dc-gwy External drop-copy gateway (DC1) — relays :5557 fills over TCP :5590 for non-ZeroMQ clients 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
pm-log-srv pm-log-srv Centralized LALF log collector — logging over TCP :5600 to log.db No

Monitoring & 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
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-audit-cli pm-audit-cli <command> [options] Read-only query interface for audit JSONL log files Optional
pm-index-cli pm-index-cli <command> [options] Read-only query interface for index history JSONL files Optional
pm-index-admin-cli pm-index-admin-cli --id <GW_ID> <command> [options] One-shot CLI for index corporate actions and constituent changes Optional
pm-log-cli pm-log-cli <command> [options] Read-only query/troubleshooting interface for log.db (includes diagnose, prune) Optional
pm-calf-spy pm-calf-spy [--channels CH] [--symbols SYM] [--ping-interval SEC] [--format human\|json] Read-only CALF protocol spy — connects to pm-md-gwy and prints every line it sends Optional
pm-ralf-spy pm-ralf-spy --role ROLE [--channels CH] [--symbols SYM] [--ping-interval SEC] [--format human\|json] Read-only RALF protocol spy — connects to pm-ralf-gwy and prints every line it sends Optional
pm-dc-spy pm-dc-spy [--gateway GW_ID] [--replay-of ID] [--format human\|json] Read-only drop-copy spy — connects to the engine's drop-copy PUB socket (:5557) and prints every fill event Optional

Port Reference

Port numbers are scattered across the protocol, gateway, and configuration chapters (for example Protocols Overview and Examples). The table below is the single place that collects every default port for every long-running process in one view, split into the internal ZeroMQ bus (bound only on 127.0.0.1 by default, spoken only between EduMatcher's own processes) and the external TCP gateways (bound on 0.0.0.0 by default, spoken by outside clients). All ports are configurable in engine_config.yaml; the values below are the shipped defaults, cross-checked against src/edumatcher/config.py and each process's own config.py.

Core processes:

Process Internal ZMQ port(s) External port(s) Protocol Purpose
pm-engine 5555 PULL (bind) · 5556 PUB (bind) · 5557 PUB (bind) ZeroMQ (PULL/PUB) Matching engine. 5555 receives all inbound commands; 5556 broadcasts all market/session events; 5557 is the dedicated drop-copy fill feed.
pm-log-srv 5601 PUB (bind) · 5602 PULL (bind) 5600 LALF over TCP (collection) · LALF-PS over ZeroMQ (distribution) Centralized log collector. 5600 accepts LALF log lines from every other pm-* process; 5601/5602 are the LALF-PS PUB/PULL pair that pushes live rows to log viewers/subscribers without polling log.db.
pm-audit connects out to 5556 ZeroMQ (SUB) Subscribes to every engine event and writes the full audit trail to disk. Binds no port of its own.
pm-clearing connects out to 5556 ZeroMQ (SUB) Subscribes to trade events for P&L and settlement. Binds no port of its own.
pm-scheduler connects out to 5555 ZeroMQ (PUSH) Drives session-phase transitions by pushing commands to the engine. Binds no port of its own.
pm-md-gwy connects out to 5556, 5558 5570 CALF over TCP External market-data gateway. Consumes engine and index PUB feeds internally and republishes top/book/trade/state channels to external CALF clients on 5570.
pm-ralf-gwy connects out to 5556 5580 RALF over TCP External post-trade dissemination gateway for clearing, drop-copy, and audit consumers (role-gated).
pm-dc-gwy connects out to 5557 5590 DC1 over TCP Relays the engine's 5557 drop-copy feed as DC1 text lines to plain-TCP clients that don't speak ZeroMQ.
pm-api-gwy connects out to 5555, 5556 8080 (desk instance) · 8081 (dashboards instance) HTTP/REST + WebSocket REST/WebSocket gateway; translates HTTP/WS requests into the same ZMQ order flow used by pm-alf-console. Each named api_gateways instance in config gets its own port.
pm-alf-gwy connects out to 5555, 5556, 5557 5565 ALF over TCP External ALF order-entry gateway for bots/remote clients (same protocol as pm-alf-console, over TCP instead of stdin/stdout).
pm-balf-gwy connects out to 5555, 5556 5560 BALF (binary) over TCP External binary order-entry gateway for low-latency programmatic clients.
pm-index 5558 PUB (bind) · 5559 PULL (bind); connects out to 5556 ZeroMQ (PUB/PULL) Real-time cap-weighted index calculation. 5558 broadcasts index.update; 5559 receives operator commands and history requests. No external protocol of its own — external consumers reach index data via pm-md-gwy/CALF.

Internal-only utilities (ZeroMQ clients; no listening port of their own):

Process Internal ZMQ port(s) used Protocol Purpose
pm-alf-console connects out to 5555, 5556 ZeroMQ (PUSH/SUB) Interactive ALF order-entry terminal for a human trader; the same PUSH/SUB pattern as a gateway, without a TCP front end.
pm-viewer connects out to 5556 ZeroMQ (SUB) Live single/multi-symbol order book display.
pm-board connects out to 5555, 5556 ZeroMQ (PUSH/SUB) Full-screen multi-symbol market board.
pm-ticker connects out to 5556 ZeroMQ (SUB) Scrolling market-data ticker (reads recent trades from pm-stats's stats.db as well).

Reading the table

"Internal ZMQ port(s)" are ZeroMQ endpoints intended for EduMatcher's own processes on the same host (or a trusted internal network) — by default bound to 127.0.0.1. "External port(s)" are TCP/HTTP listeners meant for outside clients — by default bound to 0.0.0.0. Every port shown is a default; production or multi-host deployments should bind internal ports to a private interface and firewall external ports as appropriate. See Configuration for how to override any of these.

The same table, for the config you actually deployed

This table lists the shipped defaults. To see the ports one particular configuration resolves to — including which were set explicitly and which fell back to a default — run pm-config-show. It derives its port map from edumatcher/gateway_ports.py, the same table pm-cverifier uses for its M018 collision check.

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-cverifier pm-cverifier CONFIG_FILE Validate a config across four layers Recommended
pm-config-deploy pm-config-deploy SOURCE Compile and install the config every process reads Required to run
pm-config-show pm-config-show [options] Display the deployed config, or render it as a PDF 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:

  1. Bootstrap session files:
    pm-setup
    
  2. Generate a config (or reuse/edit the sample config):
    pm-config-gen --symbols AAPL MSFT --gateways TRADER01 TRADER02 OPS01:ADMIN --sessions-enabled --output engine_config.yaml
    
  3. Use the operational control pm-opctl-cli to start the system in the correct order with:
    pm-opctl-cli start
    
    Then use pm-opctl-cli lisgt to lisgt status of each process
  4. Start remaining optional observers/tools as needed (pm-board,pm-viewer, pm-ticker).

pm-engine — Matching Engine

The heart of the system — receives orders, matches them, publishes events.

pm-engine [-v|-vv] [--log-level LEVEL] [-q]

Startup options:

Flag Default Description
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase log verbosity (-vINFO: startup/lifecycle messages; -vvDEBUG: per-order/per-trade detail)
-q / --quiet off Explicit WARNING (same as the default)

See Running the Engine → Logging levels for the full flag reference and example output.

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 (connect)
system.gateway_bye.{GW_ID} Gateway disconnect broadcast
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.

pm-alf-console --id <GW_ID>

Startup options:

Flag Required Description
--id Yes Unique gateway identifier (e.g. GW01, ALICE)
--drop-copy No Enable the drop-copy relay on startup (equivalent to sending DC\|STATE=ON immediately after connecting); default off — see ALF Console → DC
--log-level No Explicit level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose No Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet No Reduce output to warnings/errors

Expected runtime input arguments:

  • NEW|... order-entry commands (including LIMIT, 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, QUIT
  • KILL|... 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 ALF Console 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.

pm-alf-gwy [--bind 0.0.0.0] [--port 5565] [--engine-host HOST] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--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
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

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. Draws a full-screen bordered box on the alternate terminal buffer that repaints cleanly on resize.

pm-viewer --symbol AAPL [--depth N] [--db data/stats.db]

Startup options:

Flag Default Description
--symbol / -s required Symbol to watch
--depth / -d fit to terminal Max price levels to display per side (default: fills available height)
--db data/stats.db Stats SQLite DB — seeds session OHLC and previous-close at startup
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

Expected runtime input arguments:

None.

Display: - Two-line header: last price with directional colour/arrow, change / %, session OHLC seeded from pm-stats DB, previous-close, bid/ask, spread, volume, and a live clock - Three equal-height columns filling the terminal: Bids (with proportional depth bars), Asks, and Trades (newest first, uptick/downtick coloured) - Bids and asks show proportional depth micro-bars per price level - The frame border colour reflects the day's direction (green/red) - Repaints the full frame on every terminal resize — no leftover garbage

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:

pm-viewer --symbol AAPL &
pm-viewer --symbol MSFT &
pm-viewer --symbol TSLA

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.

pm-orders [--gateway GW01]

Startup options:

Flag Default Description
--gateway / -g (all) Filter to a single gateway

No --log-level, -v, or -q flags — pm-orders has no configurable logging.

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.

pm-audit [--audit-log-file data/audit.log] [--terminal] [--buffer-size 100] [--flush-interval 10] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--audit-log-file data/audit.log Audit-trail output log file path
--terminal / -t off Also print each entry to stdout
--buffer-size 100 Number of messages to buffer in memory before writing to disk
--flush-interval 10.0 Maximum seconds to wait before flushing buffer to disk
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors
--log-target server Where operational log records go: server (auto-detected), stdout, or file
--log-file none Operational log file path — required when --log-target file
--log-failover-timeout 30 (from config) Seconds to wait before falling back to a local log file if pm-log-srv becomes unreachable

Expected runtime input arguments:

None.

Buffering behaviour:

To reduce disk wear, pm-audit buffers messages in memory before writing them to disk in batches. The buffer is flushed to disk when either:

  • The buffer reaches --buffer-size messages (e.g., 100 messages), or
  • --flush-interval seconds have elapsed since the last flush (e.g., 10 seconds)

On shutdown (Ctrl-C, SIGINT, or SIGTERM), any remaining buffered messages are automatically flushed to disk before the process exits, ensuring no data is lost.

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. Use -vv or --log-level DEBUG when you want aggregated operational summaries about buffering, flushes, decode errors, and broad topic-family mix without changing the raw event log format.

Examples:

# Default: buffer 100 messages, flush every 10 seconds
pm-audit

# High-traffic scenario: larger buffer, longer flush interval
pm-audit --buffer-size 500 --flush-interval 30

# Low-latency: smaller buffer, aggressive flushing
pm-audit --buffer-size 10 --flush-interval 1

# Disable buffering entirely: flush every message immediately
pm-audit --buffer-size 1 --flush-interval 0.1

Messages subscribed (SUB from :5556):

Topic Purpose
(empty prefix — receives all messages) Records everything published on the PUB socket

pm-audit-cli — Audit Log Query CLI

pm-audit-cli is a read-only offline tool: it reads JSONL files directly from disk, runs one query, prints output, and exits. No ZeroMQ connection required.

pm-audit-cli [--log-file data/audit.log] [--log-dir PATH] [--format table|json|csv] [--no-header] [--use-index PATH] COMMAND [options]

Global options:

Flag Default Description
--log-file PATH data/audit.log Primary audit log file
--log-dir PATH directory of --log-file Directory containing rotated log backups
--format table Output format: table, json, or csv
--no-header off Suppress header row in CSV output
--use-index PATH auto-detected Path to SQLite index file (auto-detected from --log-dir when not specified; ignored for commands that don't support it)

Subcommands:

Subcommand Default limit Purpose Key filters
events 100 Search log entries by topic, gateway, symbol, and time range --topic, --gateway, --symbol, --date, --from, --to, --reverse
orders 100 Order lifecycle events for specific order IDs or filters --id (repeatable), --gateway, --symbol, --date, --from, --to
trades 100 Trade executions --symbol, --gateway, --buy-gateway, --sell-gateway, --min-price, --max-price, --min-qty, --date, --from, --to, --reverse
topics List topics present in logs with event counts --date, --from, --to, --prefix, --sort (count|alpha)
gateways Gateway activity summary --date, --from, --to, --min-events
timeline 500 Raw chronological event stream for session replay --from, --to, --topic, --gateway, --symbol
stats Summary statistics about audit log files --verbose
index Build or update the optional SQLite index for faster queries --output PATH

No-row behaviour:

  • table: prints No rows found.
  • json: prints []
  • csv: prints only header row unless --no-header is set

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] [--retention-days N] [--timezone TZ] [--sql-trace] [--log-level LEVEL] [-v|-vv] [-q]

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)
--retention-days 90 Prune trade_events rows older than N days on startup (0 disables pruning)
--timezone UTC Exchange session timezone (IANA name) used to bucket trades into a trading day; keeps a single wall-clock session in one trade_date
--sql-trace off Log executed SQLite SQL statements from the clearing writer connection
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

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_events
  • gateway_symbol_positions
  • gateway_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
system.eod End-of-day marks and the session-close session_events row
system.gateway_auth. Gateway connect (accepted) — opens a gateway_sessions row
system.gateway_bye. Gateway disconnect — closes the matching gateway_sessions row

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 vs daily summary (both buy and sell sides; also reports summary-only keys) --gateway, --symbol, --from, --to, --retention-days
sessions Gateway connection and disconnection history --gateway, --from, --to, --connected-only, --limit (default 500)
eod 100 End-of-day sentinel events written by pm-clearing on system.eod --from, --to, --limit
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: prints No rows found. (except reconcile, which prints OK — no discrepancies found.)
  • json: prints []
  • csv: prints only header row unless --no-header is set

See P&L & Clearing for the accounting model and schema-level details.

pm-stats — Statistics Recorder

Records market statistics for every symbol, plus index level history, to a SQLite database (data/stats.db). This is the queryable time-series home for both trading statistics and index levels — pm-index's own JSONL file only retains structural/corporate-action audit records, not level ticks.

pm-stats [--db data/stats.db] [--snapshot-interval SEC] [--sql-trace] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--db data/stats.db SQLite database file path
--snapshot-interval 900 (15 min) Seconds between price_snapshots rows per symbol. Use a smaller value for finer intraday resolution, e.g. 60 for one-minute snapshots.
--sql-trace off Log executed SQLite SQL statements from the stats writer connection
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

Expected runtime input arguments:

None.

Subscriptions:

pm-stats opens two independent PUB/SUB connections: one to pm-engine (ENGINE_PUB_ADDR, port 5556) and one to pm-index (INDEX_PUB_CONNECT_ADDR, port 5558).

Source Topic Purpose
pm-engine trade.* Updates OHLCV, VWAP, min/max, volume, trade log
pm-engine book.* Records opening bid/ask prices; drives 15-minute snapshots
pm-engine system.eod Records end-of-day closing bid/ask prices
pm-index index.update Records every index level tick to index_level_snapshots and rolls up daily OHLC into index_daily_stats

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. The index.update subscription is independent of the engine socket and keeps working even if pm-index starts or stops at a different time than pm-engine.

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 --snapshot-interval seconds have elapsed since the last snapshot for that symbol (default: SNAPSHOT_INTERVAL_SEC = 900, i.e. 15 minutes). The mid-price is computed as:

\[mid = \frac{best\_bid + best\_ask}{2}\]

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 --snapshot-interval seconds (default: 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:

sqlite3 data/stats.db
.headers on
.mode column
SELECT * FROM daily_stats;

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.

pm-stats-cli [--db data/stats.db] [--format table|json|csv] COMMAND [options]

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
order-events Private order lifecycle events from the order_events table --gateway (required), --symbol, --event-type, --date, --from, --to, --limit (default 500)
order-lifecycle All events for a single order ID --gateway (required), --order-id (required)
symbols Discover symbols available in stats data --date
dates Discover trading dates available in daily_stats --symbol
index-daily Daily index OHLC rollup from index_daily_stats --date, --index-id, --limit, --wide
index-snapshots Every recorded index level tick from index_level_snapshots --index-id (required), --date, --from, --to, --limit
index-ids Discover index IDs with recorded data --date

index-daily, index-snapshots, and index-ids query the level/EOD history that pm-index's own JSONL file no longer stores — see Statistics and Reporting for the full reference and Market Index for why this data moved here.

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: prints No rows found.
  • json: prints []
  • csv: prints only header row unless --no-header is 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.

pm-scheduler [--now] [--delay 3] [--daily] [--no-confirm] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--now off Skip wall-clock waiting; send all transitions immediately with a delay between each
--delay None (effective default 3.0, only used with --now) Seconds between transitions in --now mode (ignored, with a warning, outside --now)
--daily off Run continuously, repeating the schedule every calendar day
--no-confirm off Do not query/confirm session state via the engine before sending a transition
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
--verbose / -v off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

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

Draws a bordered ticker box across the top rows of the terminal. The box has a header (the EduMatcher brand, today's total trade volume, and the current date/time) and a single ticker line listing all active symbols with live prices, OHLCV, and bid/ask spreads. The symbol line scrolls leftward like a classic ticker tape.

pm-ticker [--db data/stats.db] [--db-interval 900]

Startup options:

Flag Default Description
--db data/stats.db Path to the statistics SQLite database
--db-interval 900 Seconds between daily_stats DB re-queries (15 min)
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

Expected runtime input arguments:

None.

Display behaviour:

  • The box redraws to the terminal width on resize.
  • If every symbol fits within the current width the line stays static; scrolling only starts once the symbols are wider than the box.
  • Below a minimum width the ticker line is truncated with an ellipsis ().

Ticker line format:

AAPL  415.00  +0.48%  H:418.00  L:412.00  Vol:52,400 (8T)  414.50/415.50  ◆  MSFT …

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.

pm-board [--rows 8] [--interval 10]

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
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

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 --rows symbols each.
  • The display auto-rotates to the next page every --interval seconds.
  • 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:

pm-board --rows 15 --interval 8
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.

pm-ai-trader --id AI01 [options]

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
--log-level WARNING Explicit level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-v enables bot debug prints, -vv sets DEBUG)
-q / --quiet off Reduce output to warnings/errors

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:

  1. Send gateway_connect; wait for system.gateway_auth.<ID>.
  2. Send system.symbols_request; receive system.symbols.<ID> to populate the symbol universe and resolve per-symbol tick_size and prev_close.
  3. Send system.session_state_request; receive system.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 in OPENING_AUCTION or CLOSED until the next phase transition.
  4. Send system.halt_status_request; receive system.halt_status.<ID> to seed the per-symbol halt flags so the bot never submits into a halted symbol.
  5. Send system.position_request; receive system.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.

pm-ai-swarm [options]

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
--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
--log-level WARNING Logging level for swarm launcher; also forwarded to child bots
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG); forwarded to child bots
-q / --quiet off Reduce output to warnings/errors; forwarded 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.

pm-mm-bot --symbol AAPL [options]

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
--log-level WARNING Explicit level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-v enables bot debug prints, -vv sets DEBUG)
-q / --quiet off Reduce output to warnings/errors

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:

  1. Opens ZMQ PUSH and SUB sockets
  2. Sends system.gateway_connect and waits for system.gateway_auth ACK
  3. Requests the symbol list and verifies the assigned symbol exists
  4. Sends QBOOT (quote bootstrap) request — if an active quote already exists for (gateway_id, symbol), adopts it instead of creating a duplicate
  5. Sends QLEGS request to reconcile quote-leg mapping
  6. Waits for session.state (exits if not received within timeout)
  7. Resolves initial reference price and enters the quoting loop

Reference price resolution (in priority order):

  1. Active quote from QBOOT (restart recovery)
  2. Book mid-price (if other participants are already quoting)
  3. Last trade price from trade.executed events
  4. Bootstrap quote prices from QBOOT snapshot (inactive)
  5. 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):

  1. Sends quote.cancel to remove the resting quote
  2. Waits up to --shutdown-timeout-sec for cancel confirmation
  3. 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 [--bind 0.0.0.0] [--port 5580] [--engine-pub tcp://127.0.0.1:5556] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--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
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

Expected runtime input arguments:

No terminal input. External parties connect over TCP and send RALF lines:

  • HELLO|...
  • SUB|...
  • UNSUB|...
  • PING
  • EXIT

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-ralf-spy (RALF Protocol Spy)

pm-ralf-spy is a read-only CLI client for pm-ralf-gwy: it opens a RALF TCP session under a chosen --role (CLEARING, DROP_COPY, or AUDIT), subscribes to whatever channels/symbols you ask for, and prints every line it receives — human-readable or as JSON — so you can see exactly what the protocol sends without writing a client. It never mutates exchange state, and any number of instances can run at once (e.g. one per terminal, each with a different --role/--channels/--symbols filter). Since it never sends anything on its own after the initial subscription, it sends a periodic PING (--ping-interval, default 60s) to keep the gateway from dropping it as an idle client.

Startup options:

Flag Default Description
--host 127.0.0.1 pm-ralf-gwy TCP host
--port 5580 pm-ralf-gwy TCP port
--client-name ralf-spy-<pid> HELLO\|CLIENT= identifier reported in gateway logs
--role AUDIT HELLO\|ROLE= to authenticate as: CLEARING, DROP_COPY, or AUDIT
--channels * Comma-separated channels; * = every channel --role is entitled to
--symbols * Comma-separated symbols; * = every symbol
--lastseq 0 Requests replay on connect via HELLO\|LASTSEQ=N (0 = no replay)
--ping-interval 60 Seconds between PING keepalives sent to the gateway; 0 disables
--format human human or json output
--count 0 Exit after N data-carrying lines (0 = run until Ctrl-C)
--raw off Print raw protocol bytes instead of decoded fields
--no-color off Disable ANSI colour in output
--show-heartbeats off Include HB heartbeat lines in output (hidden by default)
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG

See RALF Protocol Spy (pm-ralf-spy) for full usage.

pm-dc-gwy — Drop-Copy TCP Gateway

Runs a small external-facing TCP gateway that relays the engine's drop-copy feed (DropCopyPublisher, PUB :5557) to plain-TCP clients using the DC1 text protocol — no ZeroMQ dependency required on the client side. It is the TCP counterpart to pm-dc-spy: both subscribe internally to drop_copy.event.<gateway_id>, but pm-dc-gwy fans events out to any number of concurrently connected external clients instead of printing to a terminal. There is no authentication, entitlement model, or replay-by-sequence — any client may HELLO with any gateway ID and receive that gateway's live fills for as long as it stays connected.

pm-dc-gwy [--bind 0.0.0.0] [--port 5590] [--engine-dc-pub tcp://127.0.0.1:5557] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--bind from config / 0.0.0.0 TCP bind address for external clients
--port from config / 5590 TCP listen port for DC1 clients
--engine-dc-pub tcp://127.0.0.1:5557 Engine drop-copy PUB address consumed by the gateway
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

Expected runtime input arguments:

No terminal input. External parties connect over TCP and send DC1 lines:

  • HELLO|CLIENT=..|PROTO=DC1|ID=..
  • PING
  • EXIT

Messages subscribed (internal SUB from :5557, refcounted per gateway ID requested by connected clients):

Topic Purpose
drop_copy.event.<gateway_id> Source for DC_FILL relay lines

Messages published (TCP DC1 feed):

Message type Purpose
WELCOME Session acceptance
DC_FILL Unsolicited fill relay
HB Heartbeat
PONG Ping reply
ERR Protocol errors

For external usage and examples see Drop-Copy TCP Gateway.

pm-dc-spy (Drop-Copy Spy)

pm-dc-spy is a read-only CLI client for the engine's drop-copy feed (DropCopyPublisher, PUB :5557): it opens a plain zmq.SUB connection — no handshake, no heartbeat, unlike CALF/RALF — subscribes to fill events for one gateway or all gateways, and prints every message it receives, human-readable or as JSON. It never mutates exchange state, and any number of instances can run at once (e.g. one per terminal, each with a different --gateway filter).

Startup options:

Flag Default Description
--host 127.0.0.1 Drop-copy PUB socket host
--port 5557 Drop-copy PUB socket port
--gateway (none) Only show fills for this gateway; default subscribes to all gateways
--replay-of (none) Also subscribe to drop_copy.replay.<RECIPIENT_ID> to observe DropCopyPublisher.replay() calls
--format human human or json output
--count 0 Exit after N messages (0 = run until Ctrl-C)
--raw off Print raw bytes instead of decoded fields
--no-color off Disable ANSI colour in output
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG

See Drop-Copy Spy (pm-dc-spy) for full usage.

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.

pm-index [--reset] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--reset off Delete persisted state files and reinitialise all indices from config
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

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 structural/audit 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 structural/audit 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 — publishes the final index.update for the day

Messages received (PULL :5559):

Topic Purpose
index.history_request Returns structural/audit records (INIT, CORP_ACTION, ADD_CONSTITUENT, DELIST) from the JSONL history file — not level/EOD data, which lives in pm-stats
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 — also consumed by pm-stats for level/EOD history
index.history.{GW_ID} Structural/audit 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 structural/audit trail with INIT, CORP_ACTION, ADD_CONSTITUENT, and DELIST records only — level and EOD history is recorded by pm-stats in data/stats.db instead (see the pm-stats section)

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.

pm-api-gwy [--instance NAME] [options]

Startup options:

Flag Default Description
--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.

pm-admin --id <ADMIN_GW_ID>

Startup options:

Flag Required Description
--id Yes ADMIN gateway ID configured in engine_config.yaml (e.g. GW_ADMIN)

Expected runtime input arguments:

  • HALT, RESUME
  • HALT_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>
  • SYMBOLS
  • SESSION|STATE=<PRE_OPEN|OPENING_AUCTION|CONTINUOUS|CLOSING_AUCTION|CLOSED>
  • SESSION_STATUS, SCHEDULE, GATEWAYS, VOLUME
  • HELP, 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.

pm-admin-cli <command> [options]

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-cverifier — Config Verifier

Validates engine_config.yaml across four layers: YAML syntax, JSON schema, semantic checks, and completeness checks. Produces a human-readable or JSON report and exits with a non-zero code if any findings meet or exceed the minimum severity threshold.

pm-cverifier [--format text|json] [--level info|warn|error] [--no-color] [--strict] CONFIG_FILE

Startup options:

Flag Default Description
CONFIG_FILE required Path to engine_config.yaml
--format text Output format: text or json
--level info Minimum severity to show: info, warn, or error
--no-color off Disable ANSI colour in text output
--strict off Treat warnings as errors for CI exit-code purposes

Expected runtime input arguments:

None. pm-cverifier reads the config file, prints its report, and exits.

Exit codes:

  • 0 — no findings at or above the threshold (or --strict and no warnings/errors)
  • 1 — one or more findings at or above the threshold

pm-config-show — Config Viewer

Prints the effective configuration as a terminal dashboard, or renders it as a multi-page A4 PDF, and exits. Where pm-cverifier answers is this file correct, pm-config-show answers what does this file say — including the port map, which cannot be read off the YAML at all because the engine and index sockets live in config.py and a gateway section without a port: key still binds on its runtime default.

pm-config-show
pm-config-show -m 2
pm-config-show --all
pm-config-show -f my_config.yaml
pm-config-show --format pdf -o exchange.pdf

Startup options:

Flag Default Description
-f, --file <YAML> <DATA_DIR>/ref_data/engine_config.yaml Config file to read
-m, --density [1\|2] 0 (bare -m means 1) Pack more information in; 1 adds risk and gateway detail, 2 adds every knob
-a, --all off Show everything: implies -m 2, unmasks API keys, lists unrecognised keys
--format <FMT> terminal Output format: terminal or pdf
-o, --output <FILE> engine-config-<stem>.pdf Destination file for --format pdf
--no-color off Disable ANSI colour; also implied when stdout is not a TTY or NO_COLOR is set
--ascii off ASCII box drawing; auto-enabled on non-UTF-8 terminals
--width <N> terminal width (100 when piped) Force render width — for scripted capture and piping
--height <N> terminal height Force render height — affects default-density height trimming only
--version off Print version and exit

Expected runtime input arguments:

None. pm-config-show reads the config file, prints its dashboard (or writes the PDF), and exits.

Exit codes:

  • 0 — the configuration was displayed, or the PDF was written
  • 2 — the config file does not exist or cannot be read
  • 3 — the file is not valid YAML (the message points at pm-cverifier)

With no --file it reads the deployed configuration from <DATA_DIR>/ref_data/engine_config.yaml, so what it shows is what the exchange was configured from. It is strictly read-only: it never writes to the configuration or the data directory, and --output is the only path it creates.

The layout is computed from the terminal actually in use rather than a fixed column count — panels pack side by side where they fit, short panels stack beside tall ones, and tables shed optional columns as they narrow. Below 72 columns or 18 rows it falls back to a plain summary of counts, flags, and the port map. At the default density the output is also trimmed to the window height, dropping optional panels and then shortening the symbol list; both trims say so and name the flag that restores the content. Passing -m or -a disables height trimming.

Two details matter operationally:

  • Ports. Every listener appears in one table with its process, function, bind address, and where the value came from — fixed (a config.py constant), env (an environment override), set (explicit in the file), or default (the section is present but omits port:). A port claimed twice is flagged in red, the same condition pm-cverifier reports as M018.
  • API keys. Masked by default and revealed with --all. A key is never wrapped or truncated at any width, and no styling is applied inside the token, so a terminal double-click selects the whole thing. Masked and revealed keys are the same length, so --all never moves the layout.

Density is a layout control and --all is a disclosure control, which is why they are separate flags: -m 2 shows every setting while keeping keys masked, so it stays safe to run on a projector. Note that a PDF produced with --all contains live credentials — masking is per-run, not per-file.

For the panel-by-panel walkthrough and a worked example, see Inspect Configs with pm-config-show.

This tool is local inspection logic and does not participate in the ZeroMQ runtime message bus.

pm-setup — Session Bootstrap Tool

Bootstraps a runnable EduMatcher session directory with sensible defaults.

pm-setup

Startup options:

Flag Default Description
--data-dir <PATH> $EDUMATCHER_DATA_DIR or ~/.local/share/edumatcher Data directory for persistent files
--force off Replace an already-deployed config
--no-config off Create the data dir only; deploy nothing

Expected runtime input arguments:

None.

It creates the data directory and deploys the bundled sample configuration to <DATA_DIR>/ref_data/engine_config.json, so a fresh environment can start the exchange immediately.

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-deploy — Compile and Install a Configuration

Validates an authored engine_config.yaml, compiles it, and installs the result as the one file every process reads.

pm-config-deploy my_config.yaml
pm-config-deploy --check my_config.yaml
pm-config-deploy --show

Startup options:

Flag Default Description
SOURCE Authored engine_config.yaml to validate, compile and install
--check off Validate and compile, but install nothing — for CI
--show off Print the deployed paths and exit

Expected runtime input arguments:

None. It reads SOURCE, writes <DATA_DIR>/ref_data/engine_config.json plus a copy of the source beside it, and exits.

Validation runs all four pm-cverifier layers. That is stricter than starting used to be: a configuration with, say, a MARKET_MAKER gateway and no market_maker_quotes (M001), or an API credential naming a gateway_id absent from gateways.alf (M022), will now refuse to deploy where it previously ran. Warnings do not block — a command that refused on advice would push people back towards editing the deployed copy by hand.

Compiling also resolves every default exactly once. That is what allows the runtime loaders to deserialise rather than each keep its own copy of the defaults for its section.

A configuration that fails is not installed and the previous artifact is left untouched — the write is staged and renamed, so a process starting during a deploy sees either the old file or the new one, never half of either.

The command prints the symbol and gateway counts it installed. Those numbers are worth reading: an unexpected zero is the difference between an exchange and a gateway that accepts connections but has no instruments to offer.

Deploying does not disturb running processes. Restart them to pick up the new configuration; until you do, each will warn at startup that its source has moved on.

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:

Required

Flag Description
--symbols SYM [SYM ...] One or more symbols
--gateways GW_SPEC [...] One or more gateway specs (ID[:ROLE[:DISCONNECT]])

Gateway / symbol overrides

Flag Description
--gateway-smp GW_ID:SMP_ACTION Per-gateway self-match-prevention default (NONE, CANCEL_AGGRESSOR, CANCEL_RESTING, CANCEL_BOTH); repeatable
--symbol-opts SYMBOL:KEY=VALUE[,...] Per-symbol overrides; repeatable
--symbol-static-band SYM:PCT Per-symbol collar static band in (0,1); repeatable
--symbol-dynamic-band SYM:PCT Per-symbol collar dynamic band in (0,1); repeatable
--symbol-risk-level SYM:LEVEL Per-symbol risk level key; repeatable
--outstanding-shares SYM:N Per-symbol outstanding shares; repeatable

Session & engine tuning

Flag Default Description
--sessions-enabled / --no-sessions-enabled disabled Enable/disable scheduler-driven sessions
--snapshot-interval SECS 0.5 Snapshot interval in seconds
--quote-history-maxlen N 30 Quote history max length
--drop-copy-buffer-size N 10000 Drop-copy buffer size
--recent-trades-maxlen N 20 Recent trades max length
--depth-snapshot-tolerance-ticks N 100 Depth snapshot tolerance in ticks

Collars & circuit breakers

Flag Default Description
--no-collars off Set enforce_collars: false
--no-circuit-breakers off Set enforce_circuit_breakers: false
--static-band PCT unset Default static collar band
--dynamic-band PCT unset Default dynamic collar band
--risk-level NAME:STATIC_PCT[:DYNAMIC_PCT] Risk level definition; repeatable
--cb-levels NAME:SHIFT_PCT[:HALT_MINS[:RESUMPTION_MODE]] Circuit-breaker level definition; repeatable
--cb-window-ns NS 300000000000 Circuit-breaker reference window in nanoseconds

Market-maker obligations

Flag Default Description
--mm-spread-ticks N 20 Global MM max spread ticks
--mm-min-qty N 100 Global MM minimum quote quantity
--enforce-mm-obligations / --no-enforce-mm-obligations disabled Enable/disable MM obligation enforcement

Symbols / seeding

Flag Default Description
--tick-decimals N 2 Default tick decimals (0..8)
--seed-last-prices off Emit null last-price placeholders
--seed N RNG seed for generated training values
--seed-mm-mid-range MIN:MAX Seed MM quotes from a random midpoint
--seed-last-prices-from-mm off Set last prices from the seeded MM midpoint

Gateway sections

Each flag below enables a top-level YAML section for the corresponding gateway process.

Flag Emits Key sub-flags
--post-trade-gateway post_trade_gateway: for pm-ralf-gwy --post-trade-name, --post-trade-bind-address, --post-trade-port, --post-trade-replay-retention-sec, --post-trade-heartbeat-interval-sec, --post-trade-idle-timeout-sec, --post-trade-max-client-queue, --post-trade-allowed-roles
--market-data-gateway market_data_gateway: for pm-md-gwy --market-data-name, --market-data-bind-address, --market-data-port, --market-data-heartbeat-interval-sec, --market-data-idle-timeout-sec, --market-data-replay-window-sec, --market-data-max-symbols-per-client, --market-data-max-client-queue, --market-data-depth-levels, --market-data-enabled / --market-data-disabled
--balf-gateway balf_gateway: for pm-balf-gwy --balf-name, --balf-bind-address, --balf-port, --balf-heartbeat-interval-sec, --balf-heartbeat-timeout-sec, --balf-idle-timeout-sec, --balf-auth-timeout-sec, --balf-max-connections, --balf-max-client-queue, --balf-max-messages-per-second, --balf-max-errors-before-disconnect, --balf-error-window-sec, --balf-duplicate-session-policy
--api-gateway api_gateways: for pm-api-gwy --api-gateway-name, --api-gateway-instance NAME:GATEWAY[,GATEWAY...][:PORT] (repeatable), --api-gateway-host, --api-gateway-port, --api-gateway-log-level, --api-gateway-stats-db, --api-gateway-swagger-enabled / --api-gateway-swagger-disabled, --api-gateway-enabled / --api-gateway-disabled, --api-gateway-rate-limit-writes-per-second, --api-gateway-rate-limit-burst, --api-gateway-engine-auth-sec, --api-gateway-engine-reply-sec, --api-gateway-wait-ack-sec, --api-gateway-order-retention-sec; credential flags: --api-key KEY:GATEWAY_ID[:DESCRIPTION] (repeatable), --api-gateway-generate-keys / --no-api-gateway-generate-keys, --api-gateway-readonly-key

Index sections

Flag Description
--index INDEX_ID Add an index to the config; repeatable
--index-constituents SYM [SYM ...] Constituents for the last --index
--index-base-value N Base value for the last --index
--index-interval SEC Publish interval for the last --index
--index-history-file PATH History file path for the last --index
--index-state-file PATH State file path for the last --index

Schedule

Flag Default Description
--schedule / --no-schedule auto Force include/suppress schedule section
--pre-open HH:MM 09:00 Pre-open schedule time
--opening-auction HH:MM 09:25 Opening auction start time
--continuous HH:MM 09:30 Continuous trading start time
--closing-auction HH:MM 16:00 Closing auction start time
--closing-end HH:MM 16:05 Closing auction end time
--country CODE Holiday calendar country code (ISO 3166-1 alpha-2)

Combos

Flag Description
--combo SPEC Add a pre-configured combo definition; repeatable

Output

Flag Default Description
--output FILE stdout Output YAML file path
--force off Overwrite existing output file
--dry-run off Print generated YAML; do not write file
--comment-default-config-fields off Annotate YAML output with comment markers for default-valued fields

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 Structural/Audit History Query Tool

Reads the structural/corporate-action audit JSONL files written by pm-index directly from disk and renders them as a table, JSON, or CSV. No running process is required. It does not expose level or EOD history — use pm-stats-cli index-daily / index-snapshots for that instead (see the pm-stats-cli section above).

pm-index-cli [--config engine_config.yaml] [--format table|json|csv] COMMAND [options]

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
events 1,000 Structural events: INIT, CORP_ACTION, ADD_CONSTITUENT, DELIST
indices List configured indices from engine_config.yaml

The events subcommand accepts --index ID (repeatable), --days N, --from DATE_OR_TS, --to DATE_OR_TS, --limit N, and --type TYPE to filter to a specific structural 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.

pm-index-admin-cli — Index Corporate Action / Constituent Change CLI

One-shot CLI for applying corporate actions (splits, cash dividends, share issuances and buy-backs) and constituent changes (add/delist) to a running pm-index process. Unlike pm-index-cli above, this tool is a write tool and talks directly to pm-index's live PUSH/PULL socket pair — it does not read JSONL files and does not require --config.

pm-index-admin-cli --id <GW_ID> [--push ADDR] [--sub ADDR] [--timeout MS] [--dry-run] [-y] [--format table|json] COMMAND [options]

Startup options:

Flag Default Description
--id required Ack-routing label — not authenticated by pm-index (its PULL socket has no auth of any kind)
--push tcp://127.0.0.1:5559 pm-index PULL socket address
--sub tcp://127.0.0.1:5558 pm-index PUB socket address
--timeout 3000 Ack timeout in milliseconds
--dry-run off Print the outbound payload instead of sending it
-y / --yes off Skip the confirmation prompt
--format table Output format: table or json

Expected runtime input arguments:

None (one-shot; runs a single subcommand and exits).

Subcommands:

Subcommand Purpose
split Apply a stock split or reverse split
dividend Apply a cash dividend adjustment
shares Set shares outstanding — covers both issuances and buy-backs
add Add a new constituent
delist Remove a constituent
history Show recent structural/corp-action history for an index

Every mutating subcommand (split, dividend, shares, add, delist) prints the action and prompts Continue? [y/N] before sending, unless -y/--yes is given. pm-index has no dedicated buy-back action type — the shares subcommand's --delta flag models a buy-back as a negative delta against the constituent's last known share count, resolved via history before sending an absolute SHARES_ISSUANCE value.

This tool has no connect()/authentication step, unlike pm-admin-cli: pm-index's PULL socket accepts any non-empty gateway_id, using it only as an ack-routing key.

See Index Admin CLI for the full subcommand reference, confirmation-prompt behaviour, and worked examples.

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.

pm-balf-gwy — BALF TCP Gateway

pm-balf-gwy is implemented and provides external binary order-entry over TCP. It accepts BALF binary frames from low-latency programmatic clients and translates them into the same engine order flow as the ALF gateways.

See BALF TCP Gateway for operational usage and BALF Protocol Reference for the wire-level contract.

pm-balf-gwy [--bind 0.0.0.0] [--port 5560] [--engine-host HOST] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--bind from config / 0.0.0.0 TCP bind address for BALF clients
--port from config / 5560 TCP listen port for BALF clients
--engine-host from config Override engine host for ZMQ ports 5555 / 5556
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

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)
pm-md-gwy [--bind 0.0.0.0] [--port 5570] [--engine-pub tcp://127.0.0.1:5556] [--index-pub tcp://127.0.0.1:5558] [--log-level LEVEL] [-v|-vv] [-q]

Startup options:

Flag Default Description
--bind from config / 0.0.0.0 TCP bind address for external clients
--port from config / 5570 TCP listen port for CALF clients
--engine-pub tcp://127.0.0.1:5556 Engine PUB address consumed by the gateway
--index-pub tcp://127.0.0.1:5558 Index PUB socket address (overrides config)
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-vINFO, -vvDEBUG)
-q / --quiet off Reduce output to warnings/errors

See Market Data Feed (CALF) for operational usage and CALF Protocol Reference for the wire-level contract.

pm-calf-spy (CALF Protocol Spy)

pm-calf-spy is a read-only CLI client for pm-md-gwy: it opens a CALF TCP session, subscribes to whatever channels/symbols you ask for, and prints every line it receives — human-readable or as JSON — so you can see exactly what the protocol sends without writing a client. It never mutates exchange state, and any number of instances can run at once (e.g. one per terminal, each with a different --channels/--symbols filter) since pm-md-gwy accepts unlimited concurrent connections up to max_connections. Since it never sends anything on its own after the initial subscription, it sends a periodic PING (--ping-interval, default 60s) to keep the gateway from dropping it as an idle client.

Startup options:

Flag Default Description
--host 127.0.0.1 pm-md-gwy TCP host
--port 5570 pm-md-gwy TCP port
--client-name calf-spy-<pid> HELLO\|CLIENT= identifier reported in gateway logs
--channels * Comma-separated channels; * = every channel in WELCOME\|CH_SUPPORTED=
--symbols * Comma-separated symbols; * = wildcard for channels that allow it
--resume (none) One-shot CH:SYM:LASTSEQ — requests single-stream replay on connect
--ping-interval 60 Seconds between PING keepalives sent to the gateway; 0 disables
--format human human or json output
--count 0 Exit after N data-carrying lines (0 = run until Ctrl-C)
--raw off Print raw protocol bytes instead of decoded fields
--no-color off Disable ANSI colour in output
--show-heartbeats off Include HB heartbeat lines in output (hidden by default)
--log-level WARNING Explicit log level: CRITICAL, ERROR, WARNING, INFO, DEBUG

See CALF Protocol Spy (pm-calf-spy) for full usage.

pm-log-srv — Centralized Log Server

pm-log-srv is a dedicated collector process, unrelated to the ZeroMQ bus: it accepts LALF ("Logging ALF") TCP connections from any number of pm-* processes and appends every received log record to a queryable SQLite database (log.db), the same "dedicated collector + SQLite + read-only query CLI" shape pm-stats/pm-audit already use for market data and trading events, applied here to operational logging-module output.

Phased rollout

pm-log-srv/pm-log-cli are fully implemented and usable today. Wiring every existing pm-* process's own logging into a LALF client that auto-detects a running pm-log-srv at startup is a follow-up phase and has not yet been rolled out — until then, --log-level/-v/-q still control each process's own stdout logging as before.

pm-log-srv [--host ADDR] [--port PORT] [--db PATH]
           [--retention-days N] [--max-message-bytes N]
           [--log-level LEVEL] [-v|-vv] [-q]
Flag Default Description
--host 0.0.0.0 (from config) TCP bind address
--port 5600 TCP listen port for LALF clients
--db data/log.db SQLite database path
--retention-days 30 Prune log_events rows older than N days; 0 = unbounded
--max-message-bytes 65536 Truncation ceiling per LOG payload (never dropped, only truncated)

pm-log-cli — Log Server Query/Troubleshooting CLI

pm-log-cli is a read-only offline tool: it queries log.db directly, never over the network, so a busy or even-stopped pm-log-srv never blocks troubleshooting with data already collected.

pm-log-cli [--db PATH] [--format human|json] COMMAND [options]

Subcommands: tail, query, processes, stats, diagnose (rule-based troubleshooting heuristics with concrete recommendations), and prune (manual retention maintenance).

See Centralized Log Server for the full operational guide — starting the server, every pm-log-cli subcommand, and a workflow cookbook — and LALF Protocol Reference for the normative wire specification.

See also