Gateway Reference¶
Learning objectives
After reading this page you will understand:
- What a gateway is and what role it plays in an exchange architecture
- Why real exchanges offer multiple gateway protocols and what the major industry-standard formats are
- How EduMatcher simplifies the real-world concepts of users, participants, and members — and what those concepts mean in production
- Why arrival order at the matching engine matters and how real exchanges are legally obligated to handle it fairly
- How to start a gateway, submit every order type, manage positions, and read responses
Prerequisites: 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
gateway response.
Background — Gateways in Real Exchange Architecture¶
What is a gateway?¶
An exchange gateway is the entry point through which external participants send orders and receive market data and execution reports. It translates the external message format (FIX, binary, proprietary) into the internal format the matching engine understands, authenticates the sender, applies pre-trade risk checks, and routes messages to the right destination.
The gateway is deliberately kept outside the matching engine. The engine's only job is to match orders; it must not be slowed down by format parsing, session management, or rate limiting. Separating these concerns also allows the exchange to offer multiple gateway protocols simultaneously — an HFT firm and a retail broker can both connect to the same engine while speaking completely different wire formats.
Industry-standard gateway protocols¶
Real exchanges offer a range of gateway types. Each targets a different client population:
| Protocol | Type | Used by | Notes |
|---|---|---|---|
| FIX 4.2 / 4.4 / 5.0 | Text (tag=value) | Brokers, buy-side OMS | The lingua franca of institutional order routing; every major venue supports it; verbose but universally understood |
| OUCH (Nasdaq) | Binary | HFT, proprietary traders | Ultra-low latency; fixed-length binary fields; single-digit microsecond round trips |
| ITCH (Nasdaq) | Binary (market data only) | Market data consumers | One-way feed; used for direct order book reconstruction at co-location |
| FAST / SBE | Binary | Market data consumers | Simple Binary Encoding; used by CME, Eurex for market data |
| BOE (CBOE/BATS) | Binary | HFT | Binary Order Entry; competes with OUCH |
| ETI (Eurex) | Binary | European derivatives traders | Enhanced Transaction Interface; supports complex derivatives workflows |
| Proprietary REST/WebSocket | Text / JSON | Retail, algorithmic | Used by crypto exchanges and some retail venues; easy to integrate |
EduMatcher's gateway speaks a FIX-inspired pipe-delimited text format that
we call ALF (ALmost Fix):
NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.00.
It borrows FIX's field=value concept but uses a simplified subset - no session
layer, no checksums, no sequence numbers, and no standard FIX message set.
A production FIX gateway would add all of these.
Formal protocol reference
This page explains ALF from the gateway user's point of view. The formal syntax and semantics of the ALF protocol are defined in Appendix: ALF Protocol Reference.
One user per gateway — a learning simplification¶
EduMatcher maps one gateway process to one user. In a real exchange, the relationship between gateways, users, and legal entities has several layers:
Exchange
└─ Member firm (legal entity; signed exchange rules; financial responsibility)
├─ Participant (trading desk or system within the firm)
│ ├─ User (individual trader or algorithm)
│ └─ User
└─ Participant
└─ User
A single FIX session (one TCP connection to the exchange) can carry orders for
many users in the same firm, tagged with a SenderSubID or Account field to
identify the individual. Risk limits may be set at the firm level, the desk
level, or the individual user level. Pre-trade checks (position limits, fat-finger
checks, credit checks) can be applied independently at each layer.
EduMatcher collapses all of this:
- There is no concept of a member firm or legal entity.
- There is no concept of a "user" separate from the gateway.
- The gateway ID (
--id GW01) is the only identity the engine knows. - All orders from
GW01are treated as one account for position tracking and self-match prevention purposes.
This makes the system much easier to learn and operate, at the cost of the access-control and risk-management structures that real venues require.
Multiple gateways and arrival order¶
When two gateways submit orders at almost the same moment, the engine processes them in the order the messages arrive at its PULL socket. On localhost with ZeroMQ, this is effectively FIFO — but only at the network level, not at the wall-clock level of the original submission.
In production this is a critical fairness issue:
- Two orders submitted at the same microsecond by two different participants on opposite sides of a co-location facility do not arrive at the engine at the same time.
- The order that traverses fewer network hops, or whose gateway server sits closer to the matching engine, will arrive first.
- This is the economics behind co-location services: participants pay to place their servers in the same data centre as the exchange, minimising the physical distance their messages travel.
Legal fairness obligations
Regulated exchanges are legally required to treat all participants fairly and without discrimination. In practice this means:
- Deterministic FIFO processing: the engine must process messages in the exact sequence they are received; it cannot re-order them for any reason.
- No preferential access: the exchange must offer the same co-location facilities and network connections to any participant willing to pay the published fee.
- Timestamping: many regulators (MiFID II in Europe, FINRA/SEC in the US) require the exchange to log a nanosecond-precision hardware timestamp ("gateway receipt timestamp") on every inbound message and include it in execution reports. This creates an auditable record of arrival order that can be reviewed by regulators after any suspicious trading pattern.
- Speed bumps: some venues (IEX, Cboe EDGA) deliberately introduce a short delay (350 microseconds for IEX's "Magic Shoebox") on certain order types to level the playing field between speed-optimised HFT and slower participants.
EduMatcher has none of these mechanisms. The engine processes messages in ZeroMQ arrival order with no timestamps beyond the wall clock of the machine running the test. For a learning system on localhost this is irrelevant; for a regulated venue it would be a compliance failure.
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.
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 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.
Responsibilities of pm-alf-console:
- authenticates the gateway ID against
gateways.alf - 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, andQLEGS - 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 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 gateway (pm-alf-console) is your trading console for demos and
learning. Each gateway instance represents one user connecting to the trading
system. Multiple gateways can run simultaneously.
Starting a Gateway¶
The --id flag sets your gateway identifier. It appears on all orders and fills.
The ID must be preconfigured in engine_config.yaml under gateways.alf.
On startup, the gateway:
- Connects PUSH socket to the engine PULL port (5555)
- Connects SUB socket to the engine PUB port (5556)
- 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 - Sends
system.gateway_connectand waits up to 3 seconds for the auth response - If accepted: enters the interactive prompt loop
- If rejected: prints the reason and exits immediately
- If timeout (engine not running): exits with "Gateway authentication timed out"
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 gateways.alf
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 gateway 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 gateways.alf.
Example:
gateways:
alf:
- id: TRADER01
description: The first trader
- id: TRADER02
description: High frequency
If a gateway starts with an ID that is not listed there, the engine refuses the connection and the gateway exits.
Session lifecycle¶
Every gateway 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 gateway kill-switch |
| Monitoring | STATUS, ORDERS, POS, SYMBOLS, QBOOT, QLEGS |
Inspect live/cached state |
| Session control | HELP, EXIT, QUIT |
Terminal usability |
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 gateway 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:
BIDmust be strictly less thanASKBID_QTYandASK_QTYmust be positive integers- Existing quote for the same gateway+symbol is replaced
QUOTE_CANCEL — Cancel Active Quote¶
QLEGS — Inspect MM Quote Legs and Fill Flags¶
QLEGS prints a local quote-leg projection for the current gateway 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.
| 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),OrderQty,Rem,Filled,Filled?Leg status,Quote status,Time
Operator examples¶
- Show only currently active quote legs across all symbols:
- Show active legs for one symbol while managing a live quote:
- Show recent completed/cancelled legs to understand what just happened:
- Full audit-style view (active + recent) for one symbol:
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
gateway. It is intended for MM startup/reconnect workflows where quote legs may
have been seeded by config before the gateway connected.
| 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:
- Connect gateway / bot as
MM_<SYMBOL>_01(or matching seed owner). - Run
QBOOT|SYM=<symbol>. - If exactly one healthy two-leg quote is returned, adopt it.
- If state is missing/partial, cancel and re-issue.
KILL — Trigger Kill-Switch¶
KILL cancels active quote legs and non-quote resting orders for the gateway.
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>]
SMP (Self Match Prevention) values: NONE (default), CANCEL_AGGRESSOR, CANCEL_RESTING, CANCEL_BOTH.
SMP prevents you from accidentally trading against your own resting orders.
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 but are not exposed in the
gateway’s tab completion. To submit an ATO/ATC order, type the TIF value
manually: TIF=ATO or TIF=ATC. 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 |
Yes | All-or-none semantics |
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 type) |
LEG<i>.TYPE |
No | Order type (default LIMIT) |
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.yamlsymbol allowlist
AMEND — Amend a Resting Order¶
At least one of PRICE= or QTY= must be present.
| 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 be ≥ filled quantity |
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> on success, 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, STOP_LIMIT, FOK legs |
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> # 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.
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 gateway session.
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¶
Prints a rich table of all single-leg orders submitted in this gateway 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 orders are not shown in the ORDERS table. Their lifecycle is tracked
via real-time combo.ack and combo.status messages printed as they arrive.
To see all order activity (including combo children), use pm-orders.
POS — View Current Positions¶
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.fillevents received by this gateway - Last price per symbol is tracked from the
trade.executedfeed (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 gateway disconnects (they are session-local)
Tip
Use POS after each fill to monitor your exposure without switching to pm-orders.
SYMBOLS — List Active Instruments¶
Requests the list of all symbols that currently have an active order book in the engine.
The gateway 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 configMM Enforced: whether MM obligation rules are currently enforced for this gateway/symbolMax Spread: effectivemm_max_spread_ticksMin Qty: effectivemm_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 gateways.alf,
the engine refuses the connection.
HELP — Command Reference¶
EXIT / QUIT — Disconnect¶
Reconnect and state re-sync playbook¶
After restarting the gateway, rebuild local confidence in state with this sequence.
- Run
SYMBOLSto confirm active books and symbol metadata. - Run
ORDERSto recover your current resting single-leg orders. - If you are a market maker, run
QBOOTthenQLEGS|SHOW=ALL. - Run
POSto rebuild your session-local position view from current stream. - Resume normal trading only after steps 1-4 look consistent.
Why this matters:
ORDERSgives authoritative order IDs for follow-upAMEND/CANCELQBOOT/QLEGSavoids accidental duplicate quoting after reconnectPOShelps 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> <reason> |
Order was rejected (e.g. FOK: insufficient liquidity) |
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. BID >= ASK, 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> accepted |
Combo validated, child orders posted to books |
COMBO REJECTED <combo_id> <reason> |
Combo failed validation (e.g. "Duplicate symbols") |
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) |
System Events¶
| Message | Meaning |
|---|---|
Active Instruments table |
Response to SYMBOLS command |
Session phase changes
The gateway does not subscribe to session.state events. Trading phase transitions are not displayed in the gateway 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 gateway
$ 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
[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
[09:30:01.500] AMENDED 7c4a91e2 price=150.1 qty=100 remaining=100
[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
ORDERSto copy full IDs beforeAMEND/CANCEL - Check
POSafter fills, not only at end of session - If behavior looks inconsistent, run the re-sync playbook above
Multiple Gateways¶
Run as many gateways 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 gateways.alf and restart the engine.
Each gateway only receives events for its own orders. Use pm-orders to see all
gateways' activity.
Interactive Features¶
Tab Completion¶
The gateway provides context-aware tab completion:
| Position | Completions |
|---|---|
| First word | NEW, AMEND, CANCEL, QUOTE, QUOTE_CANCEL, QBOOT, QLEGS, KILL, STATUS, ORDERS, POS, SYMBOLS, HELP, 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= |
After CANCEL\| |
ID=, COMBO_ID=, OCO_ID= |
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 gateway 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 gateways.alf list |
Add ID under gateways.alf and restart engine |
REJECTED: SYMBOL_NOT_CONFIGURED |
Symbol unknown to engine config | Run SYMBOLS |
Use listed symbols or add symbol to config |
REJECTED: SYMBOL_HALTED |
Circuit breaker/operator halt | Check audit/viewer output | Wait for resume or use admin controls |
REJECTED: STATIC_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¶
- Configuration — gateway roles, allowlists, disconnect behaviour, and MM obligations
- Order Types — full semantics for every order type accepted by the gateway
- ALF Protocol Reference — formal ABNF grammar and field rules for the pipe-delimited syntax
- Messages — the ZeroMQ messages the gateway 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-consoleand verify the connection