RFC: List expiring futures on EP3 with maturity T+7, not T

Date: 2026-07-30

Status: Draft

Background

When we list a dated future on EP3, we currently set the EP3 maturity_date to the contract's true expiry T — future_attributes_for in rs/admin-cli/src/instruments.rs:44-58 carries the real contract_date into EP3 "so EP3 expires them correctly" (perpetuals keep the far-future 2099-12-31 sentinel).

This is wrong for how we actually retire an instrument. EP3 does not auto-resolve futures: at maturity it flips the instrument to Expired and leaves positions untouched — we must book the final settlement. Our delist orchestrator (admin-cli delist, rs/admin-cli/src/delist.rs) does that by booking one EP3 two-sided block trade per holder against the house settlement account, then setting the EP3 state to Terminated. Those closeout block trades require EP3 to still consider the instrument open. If EP3 has already expired the instrument at T, the delist procedure can't book the closing trades — the exchange-side expiry races the very procedure that settles it.

AX already has its own source of truth for the real expiry and lifecycle:

So the EP3 maturity date doesn't need to be the real expiry at all — clients never see it (the public expiration field is served from our DB row). It only needs to be late enough that EP3 never expires the instrument out from under the delist procedure.

Proposal

  1. List dated EP3 instruments with an EP3 maturity_date of T+7 days (T = the true expiry, i.e. the nominal contract_date). The buffer is a backstop, not a schedule: the normal path is that admin-cli delist runs shortly after T and terminates the EP3 instrument well before T+7. EP3's own expiry should never fire.

  2. At the true T, an automated process moves the instrument to closing state: set is_closing = TRUE on the AX row while the EP3 instrument stays open. From T onward the instrument is exactly in today's pre-delist posture — clients can cancel and be closed out but not open new exposure — until ops runs the delist within the buffer window.

The lifecycle becomes:

Time EP3 state AX state
listing → T Open trading normally, expiration = T
T (automated) Open (maturity T+7 not yet reached) is_closing = TRUE
delist run (T … T+7) closeouts booked, then Terminated delisted_at stamped, is_closing cleared
T+7 never reached in the normal path —

Perpetuals (contract_date = 0) are unaffected — they keep the 2099 sentinel and have no automated closing step.

Changes

Open questions