When to Use
Use this skill when a firm runs more than one order-generating process against the same instrument on the same venue under the same beneficial owner — multiple strategies, a market maker quoting both sides, a SOR fanning a parent across child algos, or a hedger unwinding into a book its own maker is still quoting. In that setup the firm's own buy and sell orders will eventually meet in the matching engine, and the resulting print is a self-match.
The venue, not the client, prevents this. Exchanges expose a Self-Match Prevention (SMP / STP) identifier plus an instruction saying which side to pull. This module does two things:
- Encodes the SMP identifier and instruction onto the field and value the target venue actually accepts — the tag numbers and enum values differ per venue and are not interchangeable.
- Audits the order against a snapshot of the firm's own resting orders and predicts which of them the venue would cancel, so a self-collision is visible pre-trade rather than discovered in a surveillance report.
When NOT to Use
- As the wash-trade control itself. SMP is a mechanical block on matching. A wash trade under CEA §4c(a) / CME Rule 534 turns on intent — whether the party knew or should have known the orders would negate market risk. Orders that never match can still evidence intent. Pair SMP with
wash-trade-and-spoofing-self-detection. - During the pre-open or opening match on CME Globex. CME's SMP functionality does not prevent self-matches on the opening for orders entered during the pre-open state (MRAN RA1614-5, FAQ 17). Orders resting into the open must be de-conflicted upstream; this module's audit is a same-continuous-session model.
- As a substitute for the venue flags. The audit here runs on a local snapshot that is already stale at the matching engine. Never act on it by cancelling locally — see the pitfalls.
- Across venues. SMP scope is one venue, one firm (
SelfMatchPreventionID(2362): "the same SelfMatchPreventionID … submitted by the same firm"). Two orders on two exchanges never trigger it. Cross-venue self-crossing is a netting problem — seemulti-order-netting-before-routing. - Where the exposure is a real position, not a print. SMP cancels orders; it does not net positions or reduce fees.
- As a strategy-level construct. The wash-trade exposure attaches to the beneficial owner, so the SMP group must be scoped to the legal entity / commonly-owned accounts. Grouping per strategy leaves inter-strategy self-matches unprevented.
Prerequisites
- A venue-issued SMP identifier registered with the exchange (CME issues a 7-digit SMP ID through firm administration; Nasdaq scopes STP by MPID with an optional port-level Group ID; Coinbase scopes it to the account/profile).
- The target venue profile —
CME_ILINK2,CME_ILINK3,FIX_LATEST,COINBASE_EXCHANGE,NASDAQ_INET, or your ownSmpVenueProfile. - An SMP instruction the venue actually offers. Globex offers cancel-resting and cancel-aggressing only; base FIX adds cancel-both; crypto venues add decrement.
- A snapshot of the firm's own working orders on the instrument, ideally with venue time-priority sequence numbers.
Workflow
-
Select the venue profile and confirm what it supports.
CME_ILINK2→ tag7928(SelfMatchPreventionID) + tag8000(SelfMatchPreventionInstruction,O= cancel oldest/resting,N= cancel newest/aggressing).CME_ILINK3→ the ID moves to tag2362; the instruction stays in tag8000.FIX_LATEST→ tag2362+ tag2964, an integer enum:1cancel aggressive,2cancel passive,3cancel aggressive and passive.COINBASE_EXCHANGE→stp=dc/co/cn/cb.- Decision point — an instruction the venue does not list is not "close enough" to one it does. The engine raises rather than downgrading, because a silent
CANCEL_BOTH→CANCEL_RESTINGsubstitution leaves the aggressing side of the intended block live.
-
Encode the fields.
encode_smp_fields()returns the tag and the wire value. Never put the internal name ("CANCEL_RESTING") on the wire — tag 8000 is a char and tag 2964 is an int, and either will earn a session-level reject.- Decision point — a blank SMP ID means SMP is off. The engine refuses a blank ID unless
require_smp_id=Falsemakes that an explicit choice, because a dropped field silently disables the control on every order that carries it.
- Decision point — a blank SMP ID means SMP is off. The engine refuses a blank ID unless
-
Audit against the firm's own book.
audit_and_apply_smp()finds every own resting order on the opposite side of the same instrument, carrying the same non-blank SMP ID, at a price the incoming order reaches.- Unpriced (market) orders cross every level and therefore collide with all of them.
- Collisions come back in match order: best price for the aggressor first, then venue time priority (
entry_seq), then input order. - Decision point — two blank SMP IDs are not a group. Orders without an ID are outside SMP entirely and must never be reported as a self-match.
-
Read the predicted outcome, do not enact it.
CANCEL_RESTING: every reachable own resting order is pulled; the aggressor survives in full and may still trade third-party liquidity.CANCEL_AGGRESSIVE: the aggressor is pulled at its first own-book contact; deeper own orders are never reached, so only the trigger is reported.CANCEL_BOTH: aggressor and the first resting order both go.DECREMENT_AND_CANCEL: model-dependent, and the models disagree — symmetric (Nasdaq:min(agg, resting)leaves both sides) versus Coinbasedc(the taker always cancels in full and the maker decrements). The engine refuses to simulate decrement for a profile that declares no model.- Decision point —
is_order_dispatchedis a prediction, not an instruction. The venue performs the cancels. Issuing them locally races the exchange's own SMP cancel and can cancel an order the engine already replaced.
-
Reconcile against the venue's execution reports. On Globex a SMP cancel arrives as
MsgType=8,OrdStatus=4, withExecRestatementReason(378)=103("Cancel Oldest (Resting) due to Self-Match Prevention") for the resting side. Treat a divergence between the pre-trade prediction and the venue's report as a book-staleness defect to investigate, not as noise.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Putting the internal instruction name on the wire.
SelfMatchPreventionInstructionischarO/Non Globex andint1/2/3in FIX 5.0 SP2. Sending the stringCANCEL_RESTINGis a session-level reject, and the reject arrives after the order was supposed to be working. - Silently defaulting an unrecognised instruction. A typo'd instruction that falls back to "cancel resting" hands the firm the least protective outcome for the aggressor while looking configured. Reject the order instead.
- Treating a missing SMP ID as a group. Two orders with a blank identifier are two orders with SMP disabled, not two members of the same group. Matching blank-to-blank produces false collisions on unrelated flow; the real risk is the opposite — those orders will happily self-match at the venue.
- Auditing only the first collision. Globex cancels the resting order(s) at every executable price level. An aggressor sweeping three of the firm's own offers triggers three cancels; reporting one understates the pulled liquidity and the surveillance footprint.
- Letting input list order decide which order "matched first." Without price-then-time ordering the reported trigger changes when the book snapshot is rebuilt, and the audit trail stops being reproducible.
- Assuming SMP covers the open. CME SMP does not prevent self-matches on the opening for orders entered during the pre-open (RA1614-5). Entering orders into a pre-open that the firm knew or should have known would match is itself the violation.
- Assuming
CANCEL_AGGRESSIVEmeans nothing traded. SMP acts at the point of match. An aggressor sweeping the book can fill against third-party liquidity at better prices before it reaches its own resting order and is pulled. The cancel is of the remainder. - Cancelling locally on a predicted collision. The snapshot is stale by construction, the venue is the enforcer, and a client-side cancel racing the exchange's SMP cancel produces an ambiguous order state — the exact condition
order-placement-idempotencyexists to avoid. - Reusing one SMP ID for the whole firm without thinking. Every order sharing the ID is mutually exclusive. A hedging desk and a market maker on the same ID will pull each other's genuine liquidity all day; too narrow an ID leaves inter-strategy self-matches unprevented. Scope it to the beneficial owner and accept the queue cost.
- Comparing untick-aligned floats. A limit price one ULP below the resting price reports "no collision" and routes. Feed venue-tick-aligned prices from the same source that built the book.
Verification
- Encoding:
ExchangeSelfMatchPreventionEngine(venue="CME_ILINK2").encode_smp_fields("8810123", "CANCEL_RESTING")returns tag7928/8000with wire valueO;CANCEL_AGGRESSIVEreturnsN;CANCEL_BOTHandDECREMENT_AND_CANCELraiseSmpConfigurationError. OnFIX_LATESTthe same three instructions return1,2,3on tag2964. - Sweep: with own offers at 185.00, 185.50 and 190.00 (all
SMP_PROP_100), a BUY 25 @ 186.00 underCANCEL_RESTINGmust report exactly("R1", "R2")cancelled, in that order, anddispatched_qty == 25. - Ordering: with own offers listed far-price-first, a crossing BUY must report the lowest-priced offer as
colliding_resting_ord_id, andentry_seqmust break ties within a price level. - Negative checks — each must raise
SmpConfigurationError: an unknown instruction, an instruction the venue does not offer, a blanksmp_id(unlessrequire_smp_id=False), a side other than BUY/SELL,order_qty <= 0or non-integer, a NaN/infinite/non-positive price, a blankcl_ord_id, a resting order with no price, andDECREMENT_AND_CANCELon a profile with no decrement model. - Scoping: two orders both carrying a blank SMP ID must report
has_self_collision == False. - Market order: an unpriced BUY must collide with every own offer rather than raising.
- Run
python -m unittest discover -s skills/exchange-self-match-prevention-configuration/scriptsand confirm a 100% pass rate.