Last updated: 2026-07-22
Delisting is a coordinated wind-down: communicate, discourage new
risk, halt trading, settle open positions at the final settlement price,
and terminate the instrument in EP3. The instrument's historical data
(trades, transactions, funding) is preserved; the Postgres
instruments row is never deleted — it is
stamped delisted_at.
Tooling: this runbook assumes the delisting SoW (docs/sow/product-delisting.md) has landed: the
admin-cli delistorchestrator, thefinal_settlementtrade flag, theis_closingorder-entry freeze,instruments.delisted_at, and md-pub print suppression. Do not run the flatten steps manually — the orchestrator is idempotent and audited; manual booking is not.Do not confuse delisting with the admin-cli relist command (A-4115): relist is a symbol rename that preserves positions and the ledger. Delisting extinguishes positions and is terminal.
Residual positions are closed by final-settlement
prints: one two-sided block trade per holder against the house
settlement account, at the final settlement price, fee-free. These are
real trades in every ledger — EP3, the ClickHouse mirror, and
GET /fills (flagged is_final_settlement) — so
a client's fills for the symbol sum to exactly zero after delisting. The
prints do not appear on the public tape, candles, or
volume; published open interest drops to zero automatically. Communicate
this mechanism in the delisting notice.
Set the timeline. The business owner sets, at minimum: the announcement date, the last trading day/time, and the final settlement time. Customers with open positions need enough notice to wind down — for anything with real open interest, two weeks is a reasonable floor.
Notify customers and MMs. Announce via the
public changelog and direct outreach to every account holding an open
position or active quoting obligation in the symbol. State explicitly
what happens to positions still open at final settlement (cash
settlement via final-settlement prints at the final settlement price,
visible in fill history). Note that clients receive no real-time WS fill
event for the print — it appears in GET /fills and
position/balance queries.
Send the vendor-dependent checks. If this is the
first delist since the SoW landed, confirm the Connamara answers
recorded there (block trades book only while Open — outside
it the RPC is accepted but both legs are asynchronously
REJECTED, a definitive no-trade;
block_trade_threshold handling for small residuals) still
hold for this instrument's configuration.
Discourage new risk. There is still no
per-instrument close-only mode. The available lever is margin: raise
initial_margin_pct on the instrument (leave
maintenance_margin_pct so existing positions aren't
force-liquidated) to make new position-opening expensive while leaving
closing unaffected in practice.
Monitor open interest. Track open interest daily through the wind-down window. Chase down the long tail of position holders directly — the goal is zero open interest before final settlement, so forced settlement is the exception, not the mechanism.
Freeze trading (is_closing). At the
announced last trading time, set the freeze via the api-gateway admin
route (POST /admin/instruments/{symbol}/closing). This is
an AX-side-only freeze: the route drives EP3 to Open so the
closeout block trades will be accepted, while the order gateway rejects
all new client orders and modifications as ExchangeClosed
("only cancels are accepted") and still allows cancels. Do
not use set-instrument-state
Closed/Halted — EP3 accepts block trades only
in Open, so those states would block the closeout. Verify
new order entry for the symbol fails cleanly and cancels still
succeed.
Sweep resting orders. Halting does
not auto-cancel. Run the admin per-symbol cancel-all
(find_and_cancel_orders with the symbol filter) and confirm
zero open orders for the symbol on the order gateway.
Determine and publish the final settlement
price. Record the final settlement price per the announced
methodology and push it to EP3 via POST /settlement-price
on the api-gateway.
Confirm no funding run is pending. The
orchestrator checks the settlement-pending:{symbol} guard,
but do not start the closeout while a scheduled funding cycle for the
symbol is mid-flight.
Dry-run the closeout.
admin-cli delist --symbol <SYM> --price <FINAL_PRICE> --dry-run
Review the booking plan: every holder, quantity, side, and notional at the settlement price. Long and short quantities must net to zero.
The counterparty defaults to the canonical AX settlement account
(AX_SETTLEMENT_ACCOUNT_ID under the environment's EP3
firms/prefix); it is provisioned automatically on the first real run.
--settlement-account overrides it, but the override must
already exist in EP3 and its user path must carry a ULID account id —
the command rejects anything else, because trade-engine2 derives account
ids from drop-copy participant paths and a non-conforming party would
stall trade ingestion exchange-wide.
Run the closeout.
admin-cli delist --symbol <SYM> --price <FINAL_PRICE> --actually
The command prints the exact procedure it will follow before doing
anything. With the instrument in Closing (EP3 still
Open), the orchestrator takes the
settlement-pending:{symbol} guard, sweeps resting orders,
books one final-settlement block trade per holder (cross_id
prefix F-, journaled in Postgres
final_settlements), waits for the drop-copy mirror to catch
up, verifies EP3 positions, ClickHouse current_positions,
and sum-of-trades positions are all zero, then terminates the instrument
in EP3 (Terminated) and finally stamps
instruments.delisted_at and clears is_closing
in one atomic update. A set delisted_at therefore always
means the delist fully completed.
If the run fails partway, rerun the same command —
every partial state resumes: holders are enumerated from live EP3
positions, so already-settled holders are skipped; in-flight journal
rows are resolved against EP3 by cross-id; and a run interrupted between
the EP3 terminate and the final stamp is recognized (EP3
Terminated, is_closing still set) and finishes
the stamp. Two cases stop a rerun for an operator:
NEEDS_MANUAL_RECONCILIATION (ambiguous
EP3 outcome) is never retried automatically: confirm against EP3 that
nothing booked, set the row's status to FAILED, then
rerun.BOOKED but who still
shows a nonzero position (e.g. the closeout legs were rejected
asynchronously, or the position moved after booking) is never re-booked
automatically, on any rerun: review the account's fills against the
journal row, book or correct the residual manually, then rerun.Remove from the instrument spec. Remove (or
comment out) the product from instruments.yml in each
environment's config repository so a future
instruments sync doesn't attempt to recreate it. Do
not delete the Postgres instruments
row.
Funding stops automatically.
delisted_at gates the settlement engine's symbol selection.
Confirm on the next funding cycle that the symbol is skipped, and stop
extending its cme_future_roll_schedule rows if applicable
(existing rows remain).
Restart catalog-bound services if required. The
trade and risk engines build their instrument catalogs at startup and
are not on the instruments publication; if any behavior
keys off delisted_at in those services, schedule their
restart. (The order gateway and api-gateway pick up the terminal state
from EP3.)
Update documentation. Remove the contract from
the public contract-specs page, note the delisting in the public
changelog, and keep the instrument ID reserved in
ax_sdk_internal::constants::INSTRUMENT_IDS — IDs are never
reused.
is_final_settlement prints at the
final price) sum to zero.positions_are_zero_sum passes.qty × multiplier × (final price − last mark) net of prior
funding, and the settlement account nets to zero.query_account_volumes must
exclude final_settlement trades, or a closeout promotes
large holders' fee tiers for 30 days. Check this dependency before any
delist after the fee engine ships.trades should exclude final_settlement rows;
flag the delisting to whoever owns the N.A.T.E. dashboards.