v1.0.0 to v2.0.0

82 added, 26 removed. Audit A to A.

---
name: exchange-self-match-prevention-configuration
description: >-
- Quantitative exchange order routing engine for configuring native Self-Match Prevention (SMP / STP) FIX attributes (Tag 7928 SMP ID, Tag 8000 Instruction), auditing self-collisions, and preventing wash trades.
+ Use when an execution algo, market maker, or SOR must configure native exchange Self-Match Prevention (SMP / STP) — CME iLink tag 7928/2362 with tag 8000, FIX SelfMatchPreventionID(2362)/SelfMatchPreventionInstruction(2964), Coinbase `stp`, Nasdaq STP — and audit its own resting book for self-collisions before routing.
domain: Venue Integration & Protocols
subdomain: Wash Trade Prevention & Order Routing (SMP/STP)
- tags: ["smp", "stp", "self-match-prevention", "wash-trade-prevention", "fix-tag-7928", "cme-ilink", "order-routing"]
- brokers_frameworks: ["CME iLink SMP", "Nasdaq INET STP", "Coinbase SMP", "Python Dataclasses"]
- version: "1.0.0"
+ tags:
+ - smp
+ - stp
+ - self-match-prevention
+ - wash-trade-prevention
+ - fix-tag-7928
+ - fix-tag-2362
+ - fix-tag-2964
+ - cme-ilink
+ - order-routing
+ brokers_frameworks:
+ - CME Globex iLink 2 / iLink 3
+ - FIX 5.0 SP2 (SelfMatchPreventionID 2362 / SelfMatchPreventionInstruction 2964)
+ - Coinbase Exchange STP
+ - Nasdaq INET / OUCH STP
+ - Python Dataclasses
+ version: "2.0.0"
author: algo-trading-skills-contributors
license: Apache-2.0
---
## When to Use
- Use this skill in algorithmic execution algorithms, proprietary market making bots, and Smart Order Routers (SOR). To prevent accidental self-execution and comply with regulatory wash trading bans (CFTC Rule 1.38, EU MAR Article 12), exchanges provide native **Self-Match Prevention (SMP / STP)** features. This module configures SMP Group IDs and handling instructions (**Cancel Resting**, **Cancel Aggressive**, **Cancel Both**, **Decrement and Cancel**) on FIX/ETI order headers and audits pre-trade self-collisions.
+ 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:
+
+ 1. **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.
+ 2. **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 — see `multi-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
- - Registered Firm Self-Match Prevention ID (`smp_id`, e.g. `'SMP_FIRM_8810'`).
- - SMP Instruction Mode (`smp_instruction`: `'CANCEL_RESTING'`, `'CANCEL_AGGRESSIVE'`, `'CANCEL_BOTH'`).
- - Proposed order details (`cl_ord_id`, `symbol`, `side`, `price`, `qty`).
+ - 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 own `SmpVenueProfile`.
+ - 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
- 1. **SMP Header Field Configuration**:
- - Inject FIX Tag 7928 / Tag 2362 (`SelfMatchPreventionID = smp_id`).
- - Inject FIX Tag 8000 (`SelfMatchPreventionInstruction = smp_instruction`).
- 2. **Pre-Trade Self-Collision Audit**:
- - Inspect resting orders on the opposite side of the order book with matching `smp_id`.
- 3. **Execution Behavior Simulation & Action**:
- - If collision detected:
- - `CANCEL_RESTING`: Mark resting order for cancellation; allow aggressive order matching.
- - `CANCEL_AGGRESSIVE`: Block incoming aggressive order; preserve resting order.
- - `CANCEL_BOTH`: Cancel both resting and incoming orders.
- 4. **Audit Report Generation**: Output structured `SmpAuditReport`.
+ 1. **Select the venue profile and confirm what it supports.**
+ - `CME_ILINK2` → tag `7928` (`SelfMatchPreventionID`) + tag `8000` (`SelfMatchPreventionInstruction`, `O` = cancel oldest/resting, `N` = cancel newest/aggressing).
+ - `CME_ILINK3` → the ID moves to tag `2362`; the instruction stays in tag `8000`.
+ - `FIX_LATEST` → tag `2362` + tag `2964`, an **integer** enum: `1` cancel aggressive, `2` cancel passive, `3` cancel 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_RESTING` substitution leaves the aggressing side of the intended block live.
+ 2. **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=False` makes that an explicit choice, because a dropped field silently disables the control on every order that carries it.
+
+ 3. **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.
+
+ 4. **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 Coinbase `dc` (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_dispatched` is 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.
+
+ 5. **Reconcile against the venue's execution reports.** On Globex a SMP cancel arrives as `MsgType=8`, `OrdStatus=4`, with `ExecRestatementReason(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: see `references/standards.md`.
> Printable pre-flight checklist: see `assets/checklist.md`.
## Common Pitfalls
- - **Using Unregistered SMP IDs**: Submitting unregistered SMP IDs in FIX Tag 7928, triggering instant exchange session order rejections.
- - **Configuring Mismatched SMP Modes Across Sub-Strategies**: Mixing `CANCEL_RESTING` and `CANCEL_AGGRESSIVE` across strategies within the same firm SMP group, causing unexpected queue drops.
- - **Relying Solely on Local Self-Collision Checks**: Assuming local strategy checks prevent all self-matches without enabling native exchange SMP header flags.
+ - **Putting the internal instruction name on the wire.** `SelfMatchPreventionInstruction` is `char` `O`/`N` on Globex and `int` `1`/`2`/`3` in FIX 5.0 SP2. Sending the string `CANCEL_RESTING` is 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_AGGRESSIVE` means 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-idempotency` exists 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
- - Instantiate `ExchangeSelfMatchPreventionEngine`. Configure `smp_id = 'SMP_PROP_100'`, `smp_instruction = 'CANCEL_RESTING'`. Populate resting SELL order (100 shares @ $150.00, `smp_id = 'SMP_PROP_100'`). Submit incoming BUY order (100 shares @ $150.00, `smp_id = 'SMP_PROP_100'`). Verify engine detects self-collision, marks resting order for cancellation, allows aggressive order routing, and formats FIX Tags 7928/8000. Switch to `CANCEL_AGGRESSIVE` and verify incoming order is blocked.
- - Run `python scripts/test_exchange_self_match_prevention_configuration.py`.
+ - Encoding: `ExchangeSelfMatchPreventionEngine(venue="CME_ILINK2").encode_smp_fields("8810123", "CANCEL_RESTING")` returns tag `7928`/`8000` with wire value `O`; `CANCEL_AGGRESSIVE` returns `N`; `CANCEL_BOTH` and `DECREMENT_AND_CANCEL` raise `SmpConfigurationError`. On `FIX_LATEST` the same three instructions return `1`, `2`, `3` on tag `2964`.
+ - Sweep: with own offers at 185.00, 185.50 and 190.00 (all `SMP_PROP_100`), a BUY 25 @ 186.00 under `CANCEL_RESTING` must report exactly `("R1", "R2")` cancelled, in that order, and `dispatched_qty == 25`.
+ - Ordering: with own offers listed far-price-first, a crossing BUY must report the *lowest*-priced offer as `colliding_resting_ord_id`, and `entry_seq` must break ties within a price level.
+ - Negative checks — each must raise `SmpConfigurationError`: an unknown instruction, an instruction the venue does not offer, a blank `smp_id` (unless `require_smp_id=False`), a side other than BUY/SELL, `order_qty <= 0` or non-integer, a NaN/infinite/non-positive price, a blank `cl_ord_id`, a resting order with no price, and `DECREMENT_AND_CANCEL` on 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/scripts` and confirm a 100% pass rate.
## Related Skills
+ - `wash-trade-and-spoofing-self-detection`
- `eu-market-abuse-regulation-mar-surveillance`
- - `conditional-order-logic-for-execution-triggers`
- ---
+ - `multi-order-netting-before-routing`
+ - `order-placement-idempotency`
+ - `smart-order-routing-across-venues`
+ - `cme-globex-futures-api-integration`