Runbook: Delisting a Product

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 delist orchestrator, the final_settlement trade flag, the is_closing order-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.

How positions are settled (what clients see)

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.

Phase 1 — Decision and communication

  1. 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.

  2. 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.

  3. 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.

Phase 2 — Wind-down period

  1. 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.

  2. 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.

Phase 3 — Last trading day

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Phase 4 — Settle and terminate

  1. 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.

  2. 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:

  3. 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.

  4. 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).

  5. 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.)

  6. 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.

Phase 5 — Post-delist verification

  1. Order entry for the symbol is rejected on all gateways.
  2. All accounts show zero position; each affected account's fills for the symbol (including is_final_settlement prints at the final price) sum to zero.
  3. Reconcile is green: three-way positions (EP3 / ClickHouse / imputed from trades) agree, positions_are_zero_sum passes.
  4. No funding or settlement runs execute for the symbol after termination.
  5. Public surfaces are clean: no settlement prints on the tape or candles, 24h volume carries no closeout spike, ticker OI is zero.
  6. GUI: the symbol is absent from the market picker and never chosen as the default market; historical positions, fills, and funding remain queryable with correct formatting.
  7. Cash reconciles: each holder's balance change equals qty × multiplier × (final price − last mark) net of prior funding, and the settlement account nets to zero.

Known interactions