Risk Controls¶
Objective¶
Configure and trigger the exchange's safety mechanisms: price collars, circuit
breakers, symbol halts, and the kill switch. You will use both the interactive
pm-admin console and the one-shot pm-admin-cli tool.
Background reading
Prerequisites¶
- Chapters 01–10 completed.
GW_ADMINconfigured withrole: ADMINand connected.
Background¶
Risk controls prevent erroneous or manipulative orders from distorting the market:
- Price collars — reject orders outside static/dynamic price bands.
- Circuit breakers — auto-halt a symbol after violent price moves.
- Symbol halt — manually halt trading on one instrument.
- Exchange halt — halt all trading.
- Kill switch — cancel all orders for a specific gateway.
Administrative controls can be sent through an admin gateway session, but the preferred operator tools are:
pm-admin --id GW_ADMIN— interactive admin console with tab completion.pm-admin-cli --id GW_ADMIN <command>— one command, one response, useful for scripts, demos, and operational runbooks.
Both require a gateway configured with role: ADMIN, such as the GW_ADMIN
gateway from chapter 01.
Exercise 1: Configure Price Collars¶
Collars are configured per symbol under symbols.<SYM>.collar (a direct
override) or inherited from a named risk_controls.levels profile via
symbols.<SYM>.level. Add a direct collar override to engine_config.yaml:
symbols:
AAPL:
collar:
static_band_pct: 0.10 # ±10% from reference price
dynamic_band_pct: 0.05 # ±5% from last traded price
MSFT:
collar:
static_band_pct: 0.10
dynamic_band_pct: 0.05
Both fields are fractions in (0, 1), not whole percentages — 0.10 means
10%. Those built-in defaults (static_band_pct: 0.20, dynamic_band_pct:
0.02) fill in a field that is missing from a collar: block that is
present. Leave the whole collar: section out and the symbol gets no
collar at all — not a default one. enforce_collars (top-level, defaults to
true) must also not be set to false.
A collar needs a reference price to compare against
The engine only registers a collar for a symbol once a reference price
resolves — from last_buy_price/last_sell_price in the configuration, or
from persisted book_stats.json. If those are unset, the collar is
silently never enforced and the rejection below will not fire. Chapter 01's
configuration seeds them; if you generated yours with pm-config-gen, pass
--seed-last-prices-from-mm or set the two fields by hand.
Deploy the change, then restart the engine — editing the YAML alone changes nothing, because every process reads the compiled artifact:
Then restart pm-engine.
Checkpoint: engine loads risk control configuration.
Exercise 2: Trigger a Static Collar Rejection¶
Place an order far outside the allowed range:
If the reference price is 150.00 and the static collar is ±10%, anything above 165.00 or below 135.00 is rejected.
Expected: rejection — price outside static collar.
Checkpoint: out-of-range order rejected with collar error.
Exercise 3: Configure Circuit Breakers¶
Circuit-breaker trigger percentages and halt durations come from a global
ladder, circuit_breaker_defaults, applied to every symbol unless a symbol
defines its own circuit_breaker.levels override (see the TSLA pattern in
The Configuration Workflow).
Add a two-level ladder:
circuit_breaker_defaults:
reference_window_ns: 300000000000 # 5 minutes — window the move is measured over
levels:
L1:
price_shift_pct: 0.05 # halt if price moves ±5% within the window
halt_duration_ns: 30000000000 # 30 second halt
enforce_circuit_breakers (top-level, defaults to true) must also not be
set to false.
Deploy the change, then restart the engine — editing the YAML alone changes nothing, because every process reads the compiled artifact:
Then restart pm-engine.
Checkpoint: circuit breaker config loaded.
Exercise 4: Trigger a Circuit Breaker¶
A circuit breaker does not compare a trade with the previous one. It compares
it with the average price of the trades in the last reference_window_ns
(5 minutes here) — on a fresh start, the opening reference price of 150.00.
A gradual climb therefore has to go further than 5% before it trips, while
the 5% dynamic collar from Exercise 1 stops any single jump of more than 5%.
Walk the price up in steps.
First give the two traders the book to themselves: stop any pm-mm-bot and
clear AAPL so no market-maker quote trades in between:
Then, for each price in turn — 154.00, 158.00, 161.00, 164.50 — let TRADER02 offer 100 shares and TRADER01 buy them:
[TRADER02]> NEW|SYM=AAPL|SIDE=SELL|TYPE=LIMIT|QTY=100|PRICE=154.00|TIF=DAY
[TRADER01]> NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=154.00|TIF=DAY
| Trade at | Reference (average of earlier trades in the window) | Move | Result |
|---|---|---|---|
| 154.00 | 150.00 (opening reference) | 2.7% | trades |
| 158.00 | (150 + 154) / 2 = 152.00 | 3.9% | trades |
| 161.00 | (150 + 154 + 158) / 3 = 154.00 | 4.5% | trades |
| 164.50 | (150 + 154 + 158 + 161) / 4 = 155.75 | 5.6% | trades, then halts AAPL |
The trade at 164.50 still happens; the halt applies from that moment on. The
engine log records it (in pm-engine --verbose, or the Log Operator
Console):
CIRCUIT BREAKER HALT AAPL: level=L1 trigger=16450, ref=15575 ticks, corridor=[14017, 17132] ticks (+/-10.0%)
During the halt, market-maker quote legs are cancelled, no matching takes
place, MARKET, FOK and IOC orders are rejected, and LIMIT orders are
accepted and rest: a halt is the collection phase of a reopening auction,
which uncrosses when the 30-second halt ends.
Verification drill, while the halt lasts:
-
Send a market order — it is rejected with
code=CIRCUIT_BREAKER_ACTIVE: -
Send a limit order — it is accepted and rests, waiting for the reopening:
Checkpoint: AAPL halted by the circuit breaker after the fourth step; a market order is rejected and a limit order rests until the halt ends.
Exercise 5: Open the Admin Console¶
In a new terminal, start the interactive admin console:
At the prompt, inspect the exchange:
[GW_ADMIN|ADMIN]> HELP
[GW_ADMIN|ADMIN]> SYMBOLS
[GW_ADMIN|ADMIN]> SESSION_STATUS
[GW_ADMIN|ADMIN]> GATEWAYS
Use BOOK|SYM=AAPL to confirm the console can query market state:
Checkpoint: pm-admin authenticates as GW_ADMIN and can show symbols, session status, gateways, and book state.
Exercise 6: Manual Symbol Halt and Resume¶
From the pm-admin console:
Try trading MSFT from TRADER01:
Expected: rejection — code=INSTRUMENT_HALTED. (A limit order would be
accepted and rest for the reopening, as in Exercise 4.)
Resume:
Checkpoint: halt prevents trading; resume restores it.
Exercise 7: Exchange-Wide Halt with pm-admin-cli¶
Use the one-shot CLI form when you want an operation to run from a script or checklist without opening an interactive console:
All symbols stop matching. Confirm status:
Then resume:
Checkpoint: pm-admin-cli halts and resumes the exchange without entering a REPL.
Exercise 8: Query and Manage with pm-admin-cli¶
Try the read-only commands first:
pm-admin-cli --id GW_ADMIN book --sym AAPL
pm-admin-cli --id GW_ADMIN orders --gw TRADER01
pm-admin-cli --id GW_ADMIN gateways
pm-admin-cli --id GW_ADMIN volume
pm-admin-cli --id GW_ADMIN schedule
Now halt and resume one symbol through the CLI:
Checkpoint: you can choose pm-admin for interactive operation and pm-admin-cli for repeatable one-shot commands.
Exercise 9: Exchange-Wide Halt from the Admin Console¶
All symbols stop matching. Then:
Checkpoint: full halt and resume works across all symbols.
Exercise 10: Kill Switch¶
Cancel all orders for a misbehaving gateway:
Expected: all of TRADER02's resting orders cancelled.
Or scope to a single symbol:
The same operation as a one-shot CLI command is:
Checkpoint: kill switch cancels targeted orders.
Risk Control Summary¶
| Control | Scope | Trigger | Effect |
|---|---|---|---|
| Static collar | Per symbol | Order price vs reference | Order rejected |
| Dynamic collar | Per symbol | Order price vs last trade | Order rejected |
| Circuit breaker | Per symbol | Price move vs. average of recent trades | Symbol halted, then reopened by auction |
| Symbol halt | Per symbol | pm-admin / pm-admin-cli |
Trading paused |
| Exchange halt | All symbols | pm-admin / pm-admin-cli |
All trading paused |
| Kill switch | Per gateway | pm-admin / pm-admin-cli |
All orders cancelled |
Reflection¶
Why does a circuit breaker halt the whole symbol rather than just rejecting new orders like the collars do? What failure mode (think: a runaway algorithmic trader or a bad data feed) is a circuit breaker specifically designed to stop that per-order collars cannot?
Further Reading¶
- Risk Controls
- Controlling the Exchange
- Processes, Environment and Ports
- Drop Copy
- A Full Trading Day
Next: 12 — P&L & Clearing