Skip to content

pm-alf-console — Interactive ALF Trading Terminal

Learning objectives

After reading this page you will understand:

  • Why pm-alf-console is an ALF client, not a gateway, and how that shapes its deployment (local machine only)
  • How to start a session, submit every order type, manage positions, and read responses
  • The full ALF command set: order entry, amendments, cancels, quotes, combos, OCO, monitoring, and session control

Prerequisites: Gateway Concepts — what a gateway is, why real exchanges have several, and why this process is not one. Configuration — you need a valid engine_config.yaml with your gateway ID and role configured before connecting. Order Types — understand what NEW, AMEND, OCO, and COMBO mean before using the commands here. Messages — for the raw two-frame format underlying every response.

pm-alf-console is not a gateway

Despite sitting alongside the gateway chapters in this guide, pm-alf-console is not a gateway by the definition in Gateway Concepts: it does not terminate a TCP port and it cannot accept a connection from another process or another host. Instead, it is an ALF client that connects directly to the matching engine's internal ZeroMQ bus — a PUSH socket to the engine's PULL port and a SUB socket to the engine's PUB port — exactly like pm-viewer, pm-audit, or any other internal observer process.

Local machine only

pm-alf-console connects directly to the engine's ZMQ PUSH/SUB ports (5555 / 5556). It has no TCP listener of its own, so it cannot accept connections from other hosts. It is only suitable for demos, learning, and manual testing on the same machine as the engine.

For programmatic clients or any client running on a different host, use pm-alf-gwy instead. That process is the real ALF gateway: it binds a TCP port (default 5565), accepts multiple ALF connections over the network, and bridges them to the engine's ZMQ bus — without any client needing ZMQ access.

The rest of this page documents pm-alf-console as a trading client: what it does, how to start it, its full command set, and how to read its responses.

What this process does

pm-alf-console is the interactive ALF trading terminal for local, in-session use. It is designed for humans sitting at the same machine as the running engine: it connects directly to the engine's ZMQ sockets, reads commands from stdin, and prints responses to stdout.

Responsibilities of pm-alf-console:

  • authenticates the gateway ID against participants
  • parses ALF command lines into validated engine requests
  • subscribes to gateway-scoped lifecycle events (acks, fills, cancels, rejects)
  • maintains local session caches for ORDERS, POS, QBOOT, and QLEGS
  • provides interactive terminal ergonomics (history, tab completion, live events)

Architecture position

pm-alf-console sits directly on the ZMQ bus. It holds a ZMQ PUSH socket (sends commands to the engine) and a ZMQ SUB socket (receives engine events). There is no TCP layer between the operator and the engine — commands flow directly over ZeroMQ, which is why the process must run on the same host or at least have direct network access to the engine's ZMQ ports.

flowchart TB
  CL["Operator at terminal\n(same machine)"]
  GW["pm-alf-console\n(ZMQ PUSH + SUB)"]
  ENG["pm-engine\nMatching + risk"]
  EVT["Gateway event stream\norder.ack/order.fill/...\ntrade.executed"]

  CL -->|"stdin ALF commands"| GW
  GW -->|"ZMQ PUSH :5555"| ENG
  ENG -->|"ZMQ PUB :5556"| EVT
  EVT --> GW

For clients on another host, pm-alf-gwy — the real ALF gateway — adds a TCP accept loop in front of the same ZMQ bridge, so the client only needs a plain TCP socket:

flowchart LR
  BOT["External bot\n(any host)"]
  GWY["pm-alf-gwy\nTCP :5565"]
  ENG["pm-engine\nZMQ :5555 / :5556"]

  BOT -->|"TCP ALF lines"| GWY
  GWY -->|"ZMQ PUSH :5555"| ENG
  ENG -->|"ZMQ PUB :5556"| GWY
  GWY -->|"TCP ALF lines"| BOT

See ALF TCP Gateway for full documentation of pm-alf-gwy.

When to use ALF — protocol comparison

EduMatcher offers multiple interfaces. Use this quick guide to choose the right one.

Interface Transport Best for Not suitable for
ALF (pm-alf-console) ZMQ direct (local only) Human/manual trading at the same machine — demos, learning, ad-hoc testing Remote clients; any process on a different host
ALF (pm-alf-gwy) TCP text :5565 External bots, remote scripts, any language, any host Interactive terminal workflows (no tab completion, no P&L display)
CALF (pm-md-gwy) TCP text External market data (TOP, TRADE, STATE, INDEX) Order entry
RALF (pm-ralf-gwy) TCP text External post-trade feeds (CLEARING, DROP_COPY, AUDIT) Pre-trade market data, order entry
REST / WebSocket (pm-api-gwy) HTTP/JSON + WS Browser and API-native apps Lowest-latency interactive trading
Internal ZMQ PUB/PULL ZMQ binary Internal processes that import edumatcher External clients

Rule of thumb:

  • Manual command-line order entry, same machine → pm-alf-console
  • Automated bot or remote host → pm-alf-gwy
  • External market data feed → CALF
  • External clearing/audit feed → RALF
  • Browser/web integration → REST/WebSocket

The interactive terminal (pm-alf-console) is your trading console for demos and learning. Each instance represents one user connecting to the trading system. Multiple instances can run simultaneously.

Starting pm-alf-console

poetry run pm-alf-console --id GW01

The --id flag sets your gateway identifier. It appears on all orders and fills. The ID must be preconfigured in engine_config.yaml under participants.

CLI flags:

Flag Default Description
--id GW_ID (required) Unique gateway identifier (e.g. GW01, ALICE). Must be listed under participants in engine_config.yaml
--drop-copy off Enable the drop-copy relay on startup (equivalent to sending DC\|STATE=ON immediately after connecting). Can also be toggled at runtime — see DC — Toggle Drop-Copy Relay
--log-level WARNING Explicit level: CRITICAL, ERROR, WARNING, INFO, DEBUG
-v / --verbose off Increase verbosity (-v → INFO, -vv → DEBUG)
-q / --quiet off Reduce output to warnings/errors
--log-target server (auto-detected pm-log-srv) Where operational logs go: server, stdout, or file
--log-file PATH — Operational log file path; required when --log-target file
--log-failover-timeout SECONDS 30 Grace window before falling back to a local log file once pm-log-srv becomes unreachable
--version — Print the installed version and exit
-h / --help — Print the argument reference and exit

On startup, the terminal:

  1. Connects PUSH socket to the engine PULL port (5555)
  2. Connects SUB socket to the engine PUB port (5556)
  3. Subscribes to: order.ack.{ID}, order.fill.{ID}, order.amended.{ID}, order.cancelled.{ID}, order.expired.{ID}, order.orders.{ID}, combo.ack.{ID}, combo.status.{ID}, oco.ack.{ID}, oco.cancelled.{ID}, quote.ack.{ID}, quote.status.{ID}, risk.kill_switch_ack.{ID}, system.symbols.{ID}, system.quote_bootstrap.{ID}, system.gateway_auth.{ID}, trade.executed
  4. Sends system.gateway_connect and waits up to 3 seconds for the auth response
  5. If accepted: enters the interactive prompt loop
  6. If rejected: prints the reason and exits immediately
  7. If timeout (engine not running): exits with "Gateway authentication timed out"

A second pair of sockets talks to pm-index

Independently of the engine sockets above, pm-alf-console also opens a PUSH socket to the pm-index process's PULL port (EDUMATCHER_INDEX_PULL_PORT, default 5559) and a SUB socket to its PUB port (EDUMATCHER_INDEX_PUB_PORT, default 5558), subscribed to index.update, index.history.{ID}, and index.error.{ID}. These back the INDEX command family below and are independent of gateway authentication — pm-index does not need to be running for the rest of the console to work, but INDEX and INDEX|HISTORY will simply produce no output if it is not.

A third socket talks to the drop-copy feed

pm-alf-console also connects a SUB socket to the engine's drop-copy PUB port (DROP_COPY_PUB_ADDR, default :5557), but subscribes to nothing on it until DC|STATE=ON (or --drop-copy at startup) — see DC — Toggle Drop-Copy Relay below.

sequenceDiagram
    participant GW as pm-alf-console --id TRADER01
    participant ENG as pm-engine

    GW->>ENG: PUSH :5555  system.gateway_connect {gateway_id: "TRADER01"}
    alt ID is in participants
        ENG-->>GW: PUB :5556  system.gateway_auth.TRADER01 {accepted: true}
        Note over GW: Enters interactive command loop
    else ID not configured
        ENG-->>GW: PUB :5556  system.gateway_auth.TRADER01 {accepted: false, reason: "Gateway not configured: TRADER01"}
        Note over GW: Prints reason and exits
    else No reply within 3 seconds
        Note over GW: Prints "Gateway authentication timed out" and exits
    end

The terminal does not subscribe to session.state. Use pm-audit, pm-viewer, pm-orders, or the scheduler output if you need to watch trading phase transitions live.

Allowed gateway IDs are configured in engine_config.yaml under participants.

Example:

participants:
  - id: TRADER01
    description: The first trader
  - id: TRADER02
    description: High frequency

If a session starts with an ID that is not listed there, the engine refuses the connection and the terminal exits.

Session lifecycle

Every session follows this operator lifecycle:

sequenceDiagram
  participant U as Operator
  participant GW as pm-alf-console
  participant ENG as pm-engine

  U->>GW: Start pm-alf-console --id TRADER01
  GW->>ENG: system.gateway_connect
  ENG-->>GW: system.gateway_auth.TRADER01 accepted=true
  U->>GW: NEW / AMEND / CANCEL / QUOTE ...
  GW->>ENG: ALF command translated to engine request
  ENG-->>GW: order.ack / order.fill / order.cancelled / ...
  U->>GW: ORDERS / POS / STATUS / SYMBOLS
  U->>GW: EXIT

Command Format

All commands use the ALF pipe-separated key=value format.

Note

For the precise ALF grammar, parser rules, field semantics, and full command catalog, see Appendix: ALF Protocol Reference.

Command families at a glance

Family Commands Purpose
Order entry NEW, NEW|TYPE=COMBO, NEW|TYPE=OCO, QUOTE Create new exposure
Order updates AMEND, CANCEL, QUOTE_CANCEL Modify or remove exposure
Risk controls KILL Emergency local kill-switch
Drop copy DC Toggle asynchronous relay of this session's own fills from the engine's drop-copy feed
Monitoring STATUS, ORDERS, POS, SYMBOLS, SESSION, QBOOT, QLEGS, INDEX Inspect live/cached state
Session control HELP, EXIT, QUIT Terminal usability

Request tags for amend and cancel

TAG=<order-tag> is an optional client-supplied identifier on NEW. It identifies the order for its whole lifecycle and is echoed as TAG by ALF gateway clients, or as client_tag on engine, REST, and WebSocket payloads.

RTAG=<request-tag> is an optional client-supplied identifier on AMEND and single-order CANCEL. It identifies the request, not the order. The engine echoes it on the resulting AMENDED, CANCELLED, or rejected ACK, so a client can match an asynchronous response back to the exact amend or cancel it sent.

An order tag follows one order for its whole life, while RTAG lasts for one request. Use a different RTAG for each retry or concurrent amend/cancel against the same order. Engine-initiated cancels, such as OCO sibling cancels, combo cascades, kill switches, and session expiry, have no originating client request and therefore do not carry an RTAG.

Role entitlements (what each role can do)

Gateway role Allowed trading behavior Disallowed behavior
TRADER Standard NEW/AMEND/CANCEL, OCO/COMBO, monitoring commands MM quote commands (QUOTE, QUOTE_CANCEL)
MARKET_MAKER All trader behavior plus quote workflows (QUOTE, QBOOT, QLEGS) N/A within configured symbols/limits
ADMIN (if configured) Operational commands per config/policy Business flow outside policy

If a command is not allowed for your configured role, the terminal prints a rejection.

QUOTE — Submit/Replace A Two-Sided MM Quote

Tip

For automated quoting, see Market-Maker Bot (pm-mm-bot).

QUOTE|SYM=<symbol>|BID=<price>|ASK=<price>|BID_QTY=<n>|ASK_QTY=<n>[|TIF=<DAY|GTC>][|QUOTE_ID=<label>]

Example:

[MM01]> QUOTE|SYM=AAPL|BID=209.80|ASK=210.20|BID_QTY=500|ASK_QTY=500|QUOTE_ID=Q1
[09:30:00.101] QUOTE ACK   Q1  bid=7c4a91e2 ask=be2170fd
[09:30:00.102] QUOTE ACTIVE  Q1

Rules:

  • BID must be strictly less than ASK
  • BID_QTY and ASK_QTY must be positive integers
  • Existing quote for the same gateway+symbol is replaced

QUOTE_CANCEL — Cancel Active Quote

QUOTE_CANCEL|SYM=<symbol>

QLEGS — Inspect MM Quote Legs and Fill Flags

QLEGS prints a local quote-leg projection for the current session. It is intended for MARKET_MAKER ALF sessions where operators need a compact, low-cognitive-load view of which quote legs are still active and which have already traded.

pm-alf-console vs. pm-alf-gwy

This section describes pm-alf-console's QLEGS, which renders entirely from its own local, session-scoped cache built by observing this session's own quote/fill/cancel events — it never asks the engine. pm-alf-gwy also supports QLEGS with the same command syntax and column semantics described below, but forwards the request to the engine (system.quote_legs_request) and renders the engine's authoritative reply instead of a local cache — see ALF TCP Gateway → QLEGS. The RECENT/ALL history in both cases is real and current; it is kept in memory only and does not survive an engine restart — see Persistence → What is deliberately not persisted.

QLEGS[|SYM=<symbol>][|SHOW=ACTIVE|RECENT|ALL]
Field Required Default Description
SYM No all symbols Restrict output to one symbol
SHOW No ACTIVE ACTIVE = currently live legs, RECENT = completed legs, ALL = both

Output columns:

  • Symbol, Quote, Leg (BUY/SELL), Order
  • Price — the leg's limit price in display money, - when unknown
  • Qty, Rem, Filled, Filled?
  • Leg status, Quote status, Time

Filled? is YES/NO derived from Filled, not a separate fact; it exists so an operator scanning the table does not have to compare two numbers to answer the only question that usually matters.

Operator examples

  1. Show only currently active quote legs across all symbols:
[MM01]> QLEGS
  1. Show active legs for one symbol while managing a live quote:
[MM01]> QLEGS|SYM=AAPL
  1. Show recent completed/cancelled legs to understand what just happened:
[MM01]> QLEGS|SHOW=RECENT
  1. Full audit-style view (active + recent) for one symbol:
[MM01]> QLEGS|SYM=AAPL|SHOW=ALL

Example workflow (manual MM session)

[MM01]> QUOTE|SYM=AAPL|BID=209.80|ASK=210.20|BID_QTY=500|ASK_QTY=500|QUOTE_ID=Q123
[09:30:00.101] QUOTE ACK   Q123  bid=7c4a91e2 ask=be2170fd
[09:30:00.102] QUOTE ACTIVE  Q123

[MM01]> QLEGS|SYM=AAPL
# shows BUY leg 7c4a91e2 and SELL leg be2170fd as active, Filled?=NO

[09:31:02.417] FILL      7c4a91e2  qty=100 @209.8  remaining=400  [PARTIAL]

[MM01]> QLEGS|SYM=AAPL|SHOW=ALL
# BUY leg now shows Filled=100, Filled?=YES, status=PARTIAL
# SELL leg state reflects remaining quote lifecycle events

QLEGS is read-only. It does not send modify/cancel actions to the engine. Use QUOTE, QUOTE_CANCEL, or KILL for control actions.

QBOOT — Request Quote Bootstrap State

QBOOT asks the engine for the current active quote slot state for this session. It is intended for MM startup/reconnect workflows where quote legs may have been seeded by config before the terminal connected.

QBOOT[|SYM=<symbol>]
Field Required Default Description
SYM No all symbols Restrict bootstrap response to one symbol

Examples:

[MM_AAPL_01]> QBOOT
# prints all active quote bootstrap entries owned by MM_AAPL_01

[MM_AAPL_01]> QBOOT|SYM=AAPL
# prints only AAPL bootstrap quote state for MM_AAPL_01

Sample output:

       Quote bootstrap - MM_AAPL_01
┏━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┓
┃ Symbol ┃ Quote ┃ State  ┃ Bid    ┃ Ask    ┃BidRem ┃AskRem ┃
┡━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━┩
│ AAPL   │ Q123  │ ACTIVE │ 209.80 │ 210.20 │  500  │  500  │
└────────┴───────┴────────┴────────┴────────┴───────┴───────┘

Typical startup use:

  1. Connect terminal / bot as MM_<SYMBOL>_01 (or matching seed owner).
  2. Run QBOOT|SYM=<symbol>.
  3. If exactly one healthy two-leg quote is returned, adopt it.
  4. If state is missing/partial, cancel and re-issue.

INDEX — Show Live Index Level / Structural History

INDEX prints the most recent index level update received from pm-index. INDEX|HISTORY requests the structural/audit history (index creation, corporate actions, constituent adds, delistings) for one index.

INDEX
INDEX|HISTORY[|INDEX=<index_id>][|FROM=<date>][|TO=<date>]
Field Required Default Description
INDEX No the last index ID seen on index.update Index to query history for
FROM No 30 days before now Start of the history window
TO No now End of the history window

Bare INDEX reads from an in-memory cache populated by the index.update feed and does not send a request to the engine or pm-index — if no index.update has been received yet, it prints No index data received yet. Is pm-index running?.

INDEX|HISTORY sends a request to pm-index and prints a structural history table (INIT, CORP_ACTION, ADD_CONSTITUENT, DELIST records) — it is not a level/EOD time series. For level and EOD history, use pm-stats-cli index-daily / index-snapshots instead; see Statistics and Reporting.

If INDEX= is omitted and no index.update has been seen yet, INDEX|HISTORY prints INDEX|HISTORY requires INDEX=<id> or prior index.update. and sends nothing.

Example:

[TRADER01]> INDEX
[09:31:00.512] TECH100  4213.55 [green]+12.30 +0.29%[/green] [dim]O=4201.25 H=4220.10 L=4198.00[/dim] OPEN

[TRADER01]> INDEX|HISTORY|INDEX=TECH100|FROM=2026-06-01|TO=2026-07-01
# prints a "Index structural history" table of corporate-action-style records

KILL — Trigger Kill-Switch

KILL
KILL|SYM=<symbol>

KILL cancels active quote legs and non-quote resting orders for the gateway.

DC — Toggle Drop-Copy Relay

DC|STATE=ON
DC|STATE=OFF

DC subscribes (or unsubscribes) this session to the engine's drop-copy feed (DropCopyPublisher, ZMQ PUB :5557 — see Drop Copy), scoped to this gateway's own fills only. Once DC|STATE=ON is active, every subsequent fill for this gateway arrives asynchronously as a DC_FILL line, in addition to (not instead of) the usual FILL line driven by order.fill.{GW_ID} on the main event bus.

[GW01]> DC|STATE=ON
[dim]DC ON[/dim]
...
[09:31:02.417] DC_FILL   7c4a91e2  AAPL  qty=100 @150.05  [TAKER]  #42  (drop_copy.event.GW01)

Disabled by default; start with --drop-copy to enable automatically on connect instead of sending DC|STATE=ON manually every session:

pm-alf-console --id GW01 --drop-copy

Why both FILL and DC_FILL?

FILL (from order.fill.{GW_ID} on :5556) is the trading session's own fill notification and always arrives regardless of DC state. DC_FILL is a second, independent notification sourced from the engine's dedicated drop-copy feed (:5557) — the same feed a real exchange would deliver to a separate risk/back-office recipient. Both carry LIQUIDITY/liquidity_flag (MAKER/TAKER) — they must always agree, since both derive it from the same trade. Seeing both for the same fill (with different envelopes: DC_FILL carries the drop-copy seq, not the order-lifecycle remaining/status fields FILL carries) is expected and mirrors how a real participant's own trading session and their firm's drop-copy recipient receive independent copies of the same execution. pm-alf-gwy supports the identical DC|STATE=ON/DC|STATE=OFF command for external TCP clients — see ALF TCP Gateway → DC.

NEW — Submit an Order

NEW|SYM=<symbol>|SIDE=<BUY|SELL>|TYPE=<order-type>|QTY=<quantity>[|PRICE=<price>][|STOP=<price>][|TRAIL=<offset>][|TIF=<DAY|GTC>][|VISIBLE=<n>][|SMP=<action>][|TAG=<order-tag>]

SMP (Self Match Prevention) values: NONE, CANCEL_AGGRESSOR, CANCEL_RESTING, CANCEL_BOTH. SMP prevents you from accidentally trading against your own resting orders. If SMP is omitted, the engine falls back to the gateway's configured participants[].smp_action (or NONE if none is configured) rather than a fixed default — see Configuration — Participant Fields for the config field, and Risk Controls — Self-Match Prevention for a full explanation of how SMP is enforced during matching and how the per-order value and the gateway default interact.

ATO / ATC orders

The ATO (At-The-Open) and ATC (At-The-Close) TIF values are accepted by the engine during the appropriate auction phase and are offered by the terminal's tab completion alongside DAY and GTC (see Tab Completion below). These orders are only valid during OPENING_AUCTION and CLOSING_AUCTION phases respectively.

Examples

Order Type Command
Market buy NEW\|SYM=AAPL\|SIDE=BUY\|TYPE=MARKET\|QTY=100
Limit sell NEW\|SYM=AAPL\|SIDE=SELL\|TYPE=LIMIT\|QTY=100\|PRICE=152.00
GTC limit NEW\|SYM=MSFT\|SIDE=BUY\|TYPE=LIMIT\|QTY=200\|PRICE=310.00\|TIF=GTC
Stop-loss NEW\|SYM=AAPL\|SIDE=SELL\|TYPE=STOP\|QTY=100\|STOP=148.00
Stop-limit NEW\|SYM=AAPL\|SIDE=SELL\|TYPE=STOP_LIMIT\|QTY=100\|STOP=148.00\|PRICE=147.50
FOK NEW\|SYM=AAPL\|SIDE=BUY\|TYPE=FOK\|QTY=100\|PRICE=150.00
IOC NEW\|SYM=AAPL\|SIDE=BUY\|TYPE=IOC\|QTY=100\|PRICE=150.00
Iceberg NEW\|SYM=AAPL\|SIDE=BUY\|TYPE=ICEBERG\|QTY=1000\|PRICE=150.00\|VISIBLE=100
Trailing stop NEW\|SYM=AAPL\|SIDE=SELL\|TYPE=TRAILING_STOP\|QTY=100\|TRAIL=1.50
With SMP NEW\|SYM=AAPL\|SIDE=BUY\|TYPE=LIMIT\|QTY=100\|PRICE=150.00\|SMP=CANCEL_RESTING

Required fields by type

Type Required fields Optional
MARKET SYM, SIDE, QTY SMP
LIMIT SYM, SIDE, QTY, PRICE TIF, SMP
STOP SYM, SIDE, QTY, STOP TIF, SMP
STOP_LIMIT SYM, SIDE, QTY, STOP, PRICE TIF, SMP
FOK SYM, SIDE, QTY, PRICE SMP
IOC SYM, SIDE, QTY, PRICE SMP
ICEBERG SYM, SIDE, QTY, PRICE, VISIBLE (must be < QTY) TIF, SMP
TRAILING_STOP SYM, SIDE, QTY, TRAIL STOP (initial stop price), TIF, SMP

NEW (Combo) — Submit a Multi-Leg Order

NEW|TYPE=COMBO|COMBO_ID=<label>|COMBO_TYPE=AON|TIF=<DAY|GTC>|LEG_COUNT=<n>|LEG0.SYM=<sym>|LEG0.SIDE=<BUY|SELL>|LEG0.QTY=<n>|LEG0.PRICE=<p>|LEG1.SYM=...

Combo fields

Field Required Description
TYPE=COMBO Yes Signals multi-leg order
COMBO_ID=<label> Yes Your tracking label (used for cancel)
COMBO_TYPE=AON No All-or-none semantics; defaults to AON, currently the only supported value
TIF=DAY\|GTC No Time-in-force (default DAY), applies to all legs
LEG_COUNT=<n> Yes Number of legs (2–10)
LEG<i>.SYM Yes Symbol for leg i (0-indexed)
LEG<i>.SIDE Yes BUY or SELL
LEG<i>.QTY Yes Quantity
LEG<i>.PRICE Yes* Limit price (*required for LIMIT, FOK, STOP_LIMIT, ICEBERG leg types)
LEG<i>.TYPE No Order type (default LIMIT)
SMP=<action> No Self-match prevention, applied to every leg; same values as NEW's SMP. If omitted, falls back to the gateway's configured participants[].smp_action (else NONE) — see Configuration — Participant Fields

Examples

Strategy Command
Pairs trade NEW\|TYPE=COMBO\|COMBO_ID=PAIR-001\|COMBO_TYPE=AON\|TIF=GTC\|LEG_COUNT=2\|LEG0.SYM=MSFT\|LEG0.SIDE=BUY\|LEG0.QTY=100\|LEG0.PRICE=415.00\|LEG1.SYM=AAPL\|LEG1.SIDE=SELL\|LEG1.QTY=100\|LEG1.PRICE=210.00
3-leg arb NEW\|TYPE=COMBO\|COMBO_ID=ARB-01\|COMBO_TYPE=AON\|TIF=DAY\|LEG_COUNT=3\|LEG0.SYM=AAPL\|LEG0.SIDE=BUY\|LEG0.QTY=200\|LEG0.PRICE=210.00\|LEG1.SYM=MSFT\|LEG1.SIDE=SELL\|LEG1.QTY=100\|LEG1.PRICE=415.00\|LEG2.SYM=GOOG\|LEG2.SIDE=SELL\|LEG2.QTY=50\|LEG2.PRICE=170.00

Constraints

  • 2–10 legs per combo
  • No duplicate symbols (each leg must be a different instrument)
  • All legs validated against engine_config.yaml symbol allowlist

AMEND — Amend a Resting Order

AMEND|ID=<full-order-id>[|PRICE=<new-price>][|QTY=<new-total-qty>][|RTAG=<request-tag>]

At least one of PRICE= or QTY= must be present. Only resting LIMIT and ICEBERG orders can be amended — amending any other order type is rejected with Cannot amend <TYPE> orders.

Field Required Description
ID Yes Full order UUID (visible in the ORDERS table)
PRICE Conditional New limit price; omit to keep current price
QTY Conditional New total quantity; must exceed the already-filled quantity
RTAG No Request tag echoed on AMENDED or rejected ACK

Priority rules:

Change Time priority
Quantity decrease only Preserved — the order keeps its queue position
Price change Lost — the order moves to the back of the queue at the new price
Quantity increase Lost — the order moves to the back of the queue

Reply: AMENDED <id> price=<p> qty=<q> remaining=<r> [(priority reset)] rtag=<request-tag> on success (the (priority reset) suffix appears only when the amend cost the order its time priority; rtag appears only when RTAG was supplied), or a rejection via REJECTED with a reason.

NEW (OCO) — Submit a One-Cancels-Other Pair

An OCO pair links two orders on the same symbol so that when one fills or is cancelled, the engine automatically cancels the other.

NEW|TYPE=OCO|OCO_ID=<label>|SYM=<symbol>|QTY=<qty>[|TIF=<DAY|GTC>]
   |LEG1_SIDE=<BUY|SELL>|LEG1_TYPE=<type>[|LEG1_PRICE=<p>][|LEG1_STOP=<p>][|LEG1_TRAIL=<offset>]
   |LEG2_SIDE=<BUY|SELL>|LEG2_TYPE=<type>[|LEG2_PRICE=<p>][|LEG2_STOP=<p>][|LEG2_TRAIL=<offset>]
Field Required Description
OCO_ID Yes Client label for the pair
SYM Yes Instrument ticker — shared by both legs
QTY Yes Quantity — shared by both legs
TIF No DAY or GTC; defaults to DAY
LEG1_SIDE Yes BUY or SELL
LEG1_TYPE Yes Order type for leg 1
LEG1_PRICE Conditional Required for LIMIT, IOC, FOK legs (a STOP_LIMIT leg only requires LEG1_STOP, not LEG1_PRICE)
LEG1_STOP Conditional Required for STOP, STOP_LIMIT legs
LEG1_TRAIL Conditional Required for TRAILING_STOP legs
LEG2_* Same rules as LEG1 Second leg fields

Example — bracket order

Buy limit below the market + stop-loss above (common bracket structure for a short position):

NEW|TYPE=OCO|OCO_ID=BRACKET-001|SYM=AAPL|QTY=100|TIF=GTC
   |LEG1_SIDE=BUY|LEG1_TYPE=LIMIT|LEG1_PRICE=148.00
   |LEG2_SIDE=BUY|LEG2_TYPE=STOP|LEG2_STOP=155.00

If the LIMIT leg fills at 148.00, the engine automatically cancels the STOP leg. If the STOP triggers at 155.00, the LIMIT leg is cancelled.

sequenceDiagram
    participant GW as pm-alf-console
    participant ENG as pm-engine

    GW->>ENG: NEW|TYPE=OCO|OCO_ID=BRACKET-001|SYM=AAPL|QTY=100|...<br/>LEG1=LIMIT BUY @148.00  LEG2=STOP BUY @155.00
    ENG-->>GW: oco.ack.GW {accepted: true, order_id_1: "abc..", order_id_2: "def.."}
    Note over GW: [OCO ACK]  BRACKET-001  legs=abc.../def...

    Note over ENG: Time passes — AAPL trades at 148.00
    ENG-->>GW: order.fill.GW  {order_id: "abc..", fill_qty: 100, status: "FILLED"}
    ENG-->>GW: oco.cancelled.GW {oco_id: "BRACKET-001", cancelled_order_id: "def..", reason: "OCO sibling filled"}
    Note over GW: [FILL] abc...  qty=100 @148.00 [FILLED]
    Note over GW: [OCO CANCEL]  BRACKET-001  sibling=def...  OCO sibling filled

CANCEL — Cancel a Resting Order, Combo, or OCO

CANCEL|ID=<full-order-id>[|RTAG=<request-tag>]  # single-leg order
CANCEL|COMBO_ID=<combo-label>      # combo and all its resting legs
CANCEL|OCO_ID=<oco-label>          # both legs of an OCO pair

The full order ID is shown in the ORDERS table. Only the first 8 characters appear in inline fill/cancel messages — use ORDERS to copy the full UUID.

For a single-order cancel, RTAG is echoed on the CANCELLED event or rejected ACK. Group cancels are identified by COMBO_ID or OCO_ID and do not use RTAG.

Cancelling a combo or OCO is atomic: all resting child legs are cancelled, but fills that already occurred are not reversed.

STATUS — View Gateway Summary

STATUS prints a quick local summary for the current session.

STATUS

Use it when you want to confirm the gateway identity, known symbols, cached order counts by lifecycle state, cached quote legs, and position symbols without opening the full order table.

Order inspection is via ORDERS

STATUS is a summary command. For detailed order inspection — full order IDs, quantities, remaining quantity, price, TIF, and current status — use ORDERS.

ORDERS — Inspect This Gateway's Orders

ORDERS

Prints a rich table of all single-leg orders submitted in this session with full order ID, current status, remaining quantity, and last update time. This is the primary command for order inspection inside pm-alf-console.

Note

Combo child orders ARE resting orders like any other and do appear in the ORDERS table once accepted — there is nothing in the table to identify them as combo legs versus single-leg orders. Combo-level lifecycle (acceptance, partial/full match, cascade-cancel) is tracked separately via real-time combo.ack and combo.status messages printed as they arrive.

POS — View Current Positions

POS

Displays a position summary table showing net quantity, average entry cost, last trade price, unrealized P&L, and realized P&L for each symbol traded:

┌─────────────────────────────────────────────────────────────────────────┐
│                            Positions                                    │
├──────────┬─────────┬──────────┬──────────┬────────────┬─────────────────┤
│ Symbol   │ Net Qty │ Avg Cost │  Last Px │ Unreal P&L │     Real P&L    │
├──────────┼─────────┼──────────┼──────────┼────────────┼─────────────────┤
│ AAPL     │    +100 │   150.25 │   151.00 │     +75.00 │          +0.00  │
│ MSFT     │     -50 │   415.00 │   414.50 │     +25.00 │        +120.00  │
│ TSLA     │       0 │        — │        — │          — │        +340.00  │
└──────────┴─────────┴──────────┴──────────┴────────────┴─────────────────┘

How it works:

  • Positions are accumulated locally from order.fill events received by this gateway
  • Last price per symbol is tracked from the trade.executed feed (all trades, not just this gateway's)
  • Unrealized P&L = (last price − avg cost) × net quantity
  • Realized P&L is booked when reducing or closing a position (average cost method)
  • Flat positions (net qty = 0) with non-zero realized P&L are shown dimmed
  • Positions reset when the terminal disconnects (they are session-local)

Tip

Use POS after each fill to monitor your exposure without switching to pm-orders.

POS|GW=<gateway_id> — Query Another Gateway's Position

POS|GW=MM_AAPL_01

Unlike bare POS above, this does not use this session's local fill ledger. It asks the engine what the given gateway_id — typically a running pm-mm-bot instance, but any connected gateway works, including your own — is currently holding, via system.position_request / system.position_snapshot.{gateway_id}. The reply is rendered as a simpler table (the engine's ledger has no P&L, only net position and average cost):

┌───────────────────────────────────┐
│      Position — MM_AAPL_01        │
├──────────┬─────────┬──────────────┤
│ Symbol   │ Net Qty │ Avg Cost     │
├──────────┼─────────┼──────────────┤
│ AAPL     │    -300 │       150.05 │
└──────────┴─────────┴──────────────┘

A gateway with no open positions prints <gateway_id>: flat (no open positions). instead of an empty table.

How it works:

  • The console sends system.position_request for the given gateway_id and dynamically subscribes to system.position_snapshot.{gateway_id} just for the duration of this one query, unsubscribing once the reply arrives — a mistyped or offline gateway_id does not leave a stray subscription behind.
  • A second POS|GW= query before the first one's reply arrives replaces it (the earlier subscription is dropped).
  • This works for any gateway_id known to the engine, not just MM bots — it is exactly the same query a pm-mm-bot's own inventory-skewing logic effectively answers itself, just visible to an operator from outside. See Market-Maker Bot → Inventory skewing for the bot side of this.

SYMBOLS — List Active Instruments

SYMBOLS

Requests the list of all symbols that currently have an active order book in the engine. The terminal sends the request and the engine replies with the current instrument list plus any available symbol_meta fields, which are printed as a rich table:

┌─────────────────────────────────────────────────────────┐
│                    Active Instruments                   │
├────┬────────┬──────┬─────────────┬────────────┬─────────┤
│  # │ Symbol │ Tick │ MM Enforced │ Max Spread │ Min Qty │
├────┼────────┼──────┼─────────────┼────────────┼─────────┤
│  1 │ AAPL   │ 0.01 │ YES         │         10 │     100 │
│  2 │ MSFT   │ 0.01 │ NO          │         10 │     100 │
│  3 │ TSLA   │ 0.01 │ —           │          — │       — │
└────┴────────┴──────┴─────────────┴────────────┴─────────┘

When metadata is available, the columns mean:

  • Tick: symbol tick size derived from engine config
  • MM Enforced: whether MM obligation rules are currently enforced for this gateway/symbol
  • Max Spread: effective mm_max_spread_ticks
  • Min Qty: effective mm_min_qty

Note

SYMBOLS returns symbols that have at least one active order book (created when the first order for a symbol arrives or when market-maker orders are injected at startup). It does NOT list all symbols from engine_config.yaml unless those symbols already have orders.

Gateway authorization

SYMBOLS and all trading commands are available only after successful startup authentication. If your ID is not listed under participants, the engine refuses the connection.

SESSION — Query Current Trading Session State

SESSION

Asks the engine for its current trading session state — PRE_OPEN, OPENING_AUCTION, CONTINUOUS, CLOSING_AUCTION, or CLOSED — right now, rather than waiting for the next unsolicited session-phase-change event. This is the fastest way to discover the current phase immediately after connecting or reconnecting.

[SC01]> SESSION
[09:31:02.104] SESSION  CONTINUOUS  (normal continuous matching)

If session-phase enforcement is disabled for this engine, an additional note is printed:

[SC01]> SESSION
[09:31:02.104] SESSION  CONTINUOUS  (normal continuous matching)
  [sessions gating disabled]

Note

Sending the SESSION command asks the engine for its current state on demand. The terminal does not subscribe to unsolicited session phase-change broadcasts — see the "Session phase changes" note under System Events below.

HELP — Command Reference

HELP

EXIT / QUIT — Disconnect

EXIT

Reconnect and state re-sync playbook

After restarting the terminal, rebuild local confidence in state with this sequence.

  1. Run SYMBOLS to confirm active books and symbol metadata.
  2. Run ORDERS to recover your current resting single-leg orders.
  3. If you are a market maker, run QBOOT then QLEGS|SHOW=ALL.
  4. Run POS to rebuild your session-local position view from current stream.
  5. Resume normal trading only after steps 1-4 look consistent.

Why this matters:

  • ORDERS gives authoritative order IDs for follow-up AMEND/CANCEL
  • QBOOT/QLEGS avoids accidental duplicate quoting after reconnect
  • POS helps detect drift between expected and observed exposure

Gateway Responses

All events are printed inline with a [HH:MM:SS.mmm] timestamp prefix. A background thread receives events from the engine while you continue typing.

Order Events

Message Meaning
ACK <id> order accepted Engine received and registered the order
REJECTED <id> code=<reject-code> rtag=<request-tag> <reason> Order was rejected; code is stable for automation, rtag is present when the request supplied one
FILL <id> qty=50 @150.50 remaining=50 [PARTIAL] Partial fill
FILL <id> qty=100 @150.50 remaining=0 [FILLED] Full fill
AMENDED <id> price=151.0 qty=100 remaining=100 Amendment confirmed
CANCELLED <id> Cancel confirmed
EXPIRED <id> (DAY order — trading day ended) Engine shut down or session phase change

Quote Events

Message Meaning
QUOTE ACK <quote_id> bid=<8-char-id> ask=<8-char-id> Both quote legs accepted and posted to the book
QUOTE REJ <quote_id> <reason> Quote rejected (e.g. "Quote requires bid_price < ask_price", missing gateway role)
QUOTE <status> <quote_id> [reason] Quote lifecycle update — status is INACTIVATED, CANCELLED, or FILLED

OCO Events

Message Meaning
OCO ACK <oco_id> legs=<leg1_8char>/<leg2_8char> Both legs linked; IDs are first 8 chars of each order UUID
OCO REJ <oco_id> <reason> OCO rejected (invalid legs, symbol mismatch, etc.)
OCO CANCEL <oco_id> sibling=<order_8char> <reason> Engine auto-cancelled the sibling leg after the other leg filled or was cancelled

Combo Events

Message Meaning
COMBO ACK <combo_id> combo accepted Combo validated, child orders posted to books
COMBO REJ <combo_id> <reason> Combo failed validation (e.g. "Duplicate symbols in combo legs")
COMBO STATUS <combo_id> PARTIALLY_MATCHED At least one leg has filled
COMBO STATUS <combo_id> MATCHED All legs fully filled
COMBO STATUS <combo_id> FAILED <reason> A leg was cancelled/expired; siblings cascade-cancelled
COMBO STATUS <combo_id> CANCELLED User-initiated cancel completed

Risk Events

Message Meaning
KILL ACK orders=<n> quote_legs=<n> Kill-switch applied; counts show what was cancelled
KILL REJ <reason> Kill-switch rejected (not expected in normal operation)

Drop-Copy Events

Only printed while DC is enabled (DC|STATE=ON or --drop-copy) — see DC — Toggle Drop-Copy Relay.

Message Meaning
DC ON / DC OFF Confirms the relay was toggled locally (printed immediately, no round trip to the engine)
DC_FILL <id> <symbol> qty=<n> @<price> [<MAKER\|TAKER>] #<seq> (drop_copy.event.<GW_ID>) One event per fill, sourced from the engine's drop-copy feed (:5557) rather than the main event bus — arrives in addition to the ordinary FILL line

System Events

Message Meaning
Active Instruments table Response to SYMBOLS command
<INDEX_ID> <level> ... Response to INDEX, printed from the cached index.update feed
Index structural history table Response to INDEX\|HISTORY
INDEX ERROR <reason> pm-index rejected an INDEX\|HISTORY request

Session phase changes

The terminal does not subscribe to session.state events. Trading phase transitions are not displayed in the terminal. To monitor session phases, run pm-audit, pm-viewer, or pm-orders in a separate terminal.

End-to-end operator walkthrough

This transcript shows a realistic manual session from connect to disconnect.

# Start terminal
$ poetry run pm-alf-console --id TRADER01

[TRADER01]> SYMBOLS
# confirms AAPL/MSFT active

[TRADER01]> NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.00|TAG=ORDER-001
[09:30:00.100] ACK  7c4a91e2  order accepted

[TRADER01]> ORDERS
# copy full UUID from table if you plan to amend/cancel

[TRADER01]> AMEND|ID=7c4a91e2-...|PRICE=150.10|RTAG=AMD-001
[09:30:01.500] AMENDED  7c4a91e2  price=150.1 qty=100 remaining=100 (priority reset) rtag=AMD-001

[09:30:02.200] FILL  7c4a91e2  qty=40 @150.10  remaining=60  [PARTIAL]

[TRADER01]> POS
# exposure now reflects partial fill

[TRADER01]> CANCEL|ID=7c4a91e2-...
[09:30:03.000] CANCELLED  7c4a91e2

[TRADER01]> EXIT

Recommended habits:

  • Always use ORDERS to copy full IDs before AMEND/CANCEL
  • Check POS after fills, not only at end of session
  • If behavior looks inconsistent, run the re-sync playbook above

Multiple pm-alf-console Sessions

Run as many sessions as needed — each in its own terminal window:

# Terminal A
poetry run pm-alf-console --id TRADER01

# Terminal B  
poetry run pm-alf-console --id TRADER02

# Terminal C
poetry run pm-alf-console --id TRADER03

Before starting a new gateway ID (for example TRADER03), add it to engine_config.yaml in participants and restart the engine.

Each session only receives events for its own orders. Use pm-orders to see all gateways' activity.

Interactive Features

Tab Completion

The terminal provides context-aware tab completion:

Position Completions
First word NEW, QUOTE, QUOTE_CANCEL, QBOOT, QLEGS, KILL, AMEND, CANCEL, STATUS, ORDERS, POS, SYMBOLS, INDEX, HELP, CLEAR, EXIT, QUIT
After NEW\| SYM=, SIDE=, TYPE=, QTY=, PRICE=, STOP=, TRAIL=, TIF=, VISIBLE=, SMP=
After NEW\|TYPE=COMBO\| COMBO_ID=, COMBO_TYPE=, TIF=, LEG_COUNT=, plus LEG0.SYM=, LEG0.SIDE=, etc.
After NEW\|TYPE=OCO\| OCO_ID=, SYM=, QTY=, TIF=, LEG1_SIDE=, LEG1_TYPE=, etc.
After AMEND\| ID=, PRICE=, QTY=, RTAG=
After CANCEL\| ID=, COMBO_ID=, OCO_ID=, RTAG=
After TYPE= All order types: MARKET, LIMIT, STOP, STOP_LIMIT, FOK, IOC, ICEBERG, TRAILING_STOP, COMBO, OCO
After SIDE= BUY, SELL
After TIF= DAY, GTC, ATO, ATC
After SMP= NONE, CANCEL_AGGRESSOR, CANCEL_RESTING, CANCEL_BOTH
After SYM= Known symbols (populated from the last SYMBOLS reply)

Press Tab to cycle through suggestions.

Command History

Press ↑ / ↓ arrow keys to navigate through previously entered commands (in-memory, session-scoped — history is not persisted across restarts).

Background Event Display

A background thread listens on the SUB socket and prints events (fills, cancels, combo status changes, session transitions) in real-time, interleaved cleanly with the command prompt. You can continue typing while events arrive.

Common mistakes and fast triage

Symptom Typical cause Fast check Action
Gateway authentication timed out Engine is not running/reachable Is pm-engine running in another terminal? Start pm-engine first
Gateway not configured: GW01 Gateway ID not in engine_config.yaml Check participants list Add ID under participants and restart engine
REJECTED ... code=UNKNOWN_SYMBOL Symbol unknown to engine config Run SYMBOLS Use listed symbols or add symbol to config
REJECTED ... code=INSTRUMENT_HALTED (or CIRCUIT_BREAKER_ACTIVE) Circuit breaker/operator halt Check audit/viewer output Wait for resume or use admin controls
REJECTED ... code=COLLAR_BREACH Price too far from reference Compare to recent trade prices Reprice closer to market
Order rests but does not fill No crossing liquidity Check opposite side activity Wait or improve price
Quotes are only allowed for MARKET_MAKER participants Role is TRADER Verify gateway role in config Change role to MARKET_MAKER if intended
AMEND/CANCEL fails with unknown ID Short ID used instead of full UUID Run ORDERS and inspect full ID Retry with full order UUID

See also

  • Gateway Concepts — what a gateway is, and why pm-alf-console is not one
  • ALF TCP Gateway — the real ALF gateway, for remote/external clients
  • Configuration — gateway roles, allowlists, disconnect behaviour, and MM obligations
  • Order Types — full semantics for every order type accepted by the terminal
  • ALF Protocol Reference — formal ABNF grammar and field rules for the pipe-delimited syntax
  • Messages — the ZeroMQ messages the terminal publishes and subscribes to
  • Risk Controls — how the engine enforces collars, halts, and kill switches on gateway flow
  • Running the Engine — how to start pm-alf-console and verify the connection
  • Index — pm-index process, index composition, and the index.update/index.history feed behind the INDEX command
  • Statistics and Reporting — pm-stats-cli index-daily/index-snapshots for level/EOD history (vs. INDEX|HISTORY's structural records)