Strasmore Research
Deep Dives Matt ConnorBy Matt Connor

Self-Match Prevention and Wash Trades

Self-match prevention stops your own two orders from trading with each other. How SMP works inside the matching engine, and where wash trade law begins.

Self-match prevention is a matching engine feature that stops two orders from the same firm from trading with each other. Orders carry an identifier on entry, and when two orders sharing that identifier would cross, the engine cancels one of them, or both, before any trade prints. Self-match prevention is venue plumbing you switch on, while the prohibition on wash trades is law, and the two do not cover the same ground.

The moment a second strategy quotes an instrument your first strategy already quotes, this rule is live for you. A two-ladder grid bot on one symbol meets it on the same terms a bank desk does.

How self-match prevention works at the matching engine

Every order a venue receives carries fields the engine reads before matching: side, price, size, time in force. Self-match prevention adds two more. The first is an identifier, a number or string saying which firm, account, or strategy group the order belongs to. The second is an instruction telling the engine what to do when two live orders carrying that identifier are about to trade with each other.

The check runs at the instant an aggressing order reaches the price of a resting order it would match. Four outcomes are in common use:

  • Cancel the resting order. The incoming order carries on into the book and can match whatever sits behind it at that price.
  • Cancel the incoming order. The resting order keeps its queue position and the aggressor disappears.
  • Cancel both orders, the strictest setting.
  • Decrement and cancel. The larger order is reduced by the size of the smaller, the smaller is cancelled, and the remainder stays live.

No trade happens in any of the four. Nothing prints to the tape, nothing lands in a fill log, and an unsolicited cancel arrives on one leg or on two instead.

Queue position is the quiet cost. A resting order cancelled under a cancel-oldest instruction gives up everything it earned by waiting. Under price-time priority that loss is total: re-entering puts the order behind everyone who joined the queue while it sat there. Our guide to estimating queue position covers what that place in line is worth.

One instrument, many books

The check is per venue. An engine compares only the orders it holds itself. Two of your orders resting in two different books are invisible to each other, and no US equities mechanism reaches across them. A single US stock quotes in many books at once. The panel below counts distinct quoting and printing venues for six household names over one fifteen minute window on the morning of June 10, 2026.

QueryVenues quoting and printing the same stock in one 15-minute window
The exact SQL behind every number
SELECT
    q.ticker          AS ticker,
    q.quoting_venues  AS quoting_venues,
    t.printing_venues AS printing_venues
FROM
(
    SELECT
        ticker,
        countDistinct(bid_exchange) AS quoting_venues
    FROM global_markets.cache_stocks_quotes
    WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'SPY', 'KO', 'JNJ')
      AND sip_timestamp >= toDateTime('2026-06-10 14:00:00', 'UTC')
      AND sip_timestamp <  toDateTime('2026-06-10 14:15:00', 'UTC')
      AND bid_price > 0
    GROUP BY ticker
) AS q
INNER JOIN
(
    SELECT
        ticker,
        countDistinct(exchange) AS printing_venues
    FROM global_markets.stocks_trades
    WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'SPY', 'KO', 'JNJ')
      AND sip_timestamp >= toDateTime('2026-06-10 14:00:00', 'UTC')
      AND sip_timestamp <  toDateTime('2026-06-10 14:15:00', 'UTC')
    GROUP BY ticker
) AS t ON t.ticker = q.ticker
ORDER BY quoting_venues DESC, ticker
Run this yourself

AAPL drew bids from 16 separate venues in that quarter hour, and prints landed on 17 of them. The thinnest name in the panel still drew 11 quoting venues. A smart order router that splits child orders by design will put your two strategies in different books much of the time, where no engine-level check applies. Firms close that gap upstream, in the order management layer, before anything leaves the building. Your own orders resting on opposite sides at the same price in two books also produce a locked or crossed market, which carries its own set of rules.

How many matches one book makes in a session

The self-match check sits in the path of every match the engine makes, and on a liquid name that path stays busy all day. The panel below buckets one full session of one stock into fifteen minute slices and counts the prints in each, keeping the slices that carried at least 200.

QueryPrints per 15-minute bucket, one full session
The exact SQL behind every number
SELECT
    formatDateTime(toStartOfInterval(toTimeZone(sip_timestamp, 'America/New_York'), INTERVAL 15 MINUTE), '%H:%i') AS et_time,
    count() AS trade_count
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
  AND sip_timestamp >= toDateTime('2026-06-10 08:00:00', 'UTC')
  AND sip_timestamp <  toDateTime('2026-06-11 04:00:00', 'UTC')
GROUP BY et_time
HAVING count() >= 200
ORDER BY et_time
Run this yourself

At 04:00 ET that one name printed 9402 trades in fifteen minutes, and at 19:45 ET it printed 1345, across 64 buckets that cleared the 200 print floor. Each print is one pair of orders the engine brought together, and the check reads the identifiers on both sides before it lets them meet. Every order that comes to rest in the book is a candidate for meeting another order from the same account later in the day.

What the tape shows when a self-trade prints

A prevented self-match leaves nothing behind. The tape carries only what executed, and every print arrives with condition flags: codes the reporting venue attaches to describe how the trade happened. The panel below breaks one full session of one stock into those flags.

QueryTrade condition flags on one full session
The exact SQL behind every number
SELECT
    condition_name,
    print_count,
    round(100 * print_count / sum(print_count) OVER (), 2) AS share_pct
FROM
(
    SELECT
        cc.id        AS condition_id,
        any(cc.name) AS condition_name,
        count()      AS print_count
    FROM
    (
        SELECT toInt32(arrayJoin(conditions)) AS condition_id
        FROM global_markets.stocks_trades
        WHERE ticker = 'AAPL'
          AND sip_timestamp >= toDateTime('2026-06-10 08:00:00', 'UTC')
          AND sip_timestamp <  toDateTime('2026-06-11 04:00:00', 'UTC')
    ) AS f
    INNER JOIN
    (
        SELECT
            toInt32(id)  AS id,
            any(name)    AS name
        FROM global_markets.stocks_condition_codes
        WHERE asset_class = 'stocks'
          AND has(data_types, 'trade')
        GROUP BY id
    ) AS cc ON cc.id = f.condition_id
    GROUP BY condition_id
)
ORDER BY print_count DESC
LIMIT 10
Run this yourself

Odd Lot Trade covers 48.34% of the flagged prints in that session, and the panel lists the 10 most common flags of the day. Read the list and notice what is absent. No code means that two orders came from one firm. A self-trade that does print looks like any other trade at that price, so surveillance for it runs off the tape, on the participant identifiers the venue holds and the account numbers riding on the orders.

Where self-match prevention ends and wash trade law begins

Self-match prevention is a venue service. You opt into it, you configure it, and the engine will match your two orders happily if you never set it up. The wash trade prohibition is not optional and does not depend on any setting.

Section 9(a)(1) of the Securities Exchange Act of 1934 reaches transactions in securities involving no change in beneficial ownership, entered to create a misleading appearance of active trading. The Commodity Exchange Act carries the futures equivalent, and CME Rule 534 restates it in the exchange rulebook. FINRA Rule 5210 sits under both for broker-dealers, and its supplementary material addresses self-trades directly: trades between two unrelated algorithms at one firm are not by themselves violative, and the firm is expected to keep policies that review and reduce them.

Two consequences follow for anyone running more than one strategy. A self-cross can breach the prohibition on a venue where you set no self-match instruction at all, since the rule attaches to the transaction and the intent behind it. A self-cross that self-match prevention stopped breaches nothing: stopping it is the whole point.

How operators configure it: CME, ICE, LME, and MiFID II

On CME Globex, self-match prevention runs off an identifier submitted on order entry. Firms register the identifiers they will use, each order carries one, and a paired instruction states which side the engine cancels when two orders bearing that identifier meet. Orders sent without an identifier match normally, which is the trap for a new operator: the default is off.

ICE runs Self-Trade Prevention Functionality, configured against a trading firm identifier rather than order by order, with the same family of cancel outcomes. The London Metal Exchange offers Self-Execution Prevention on LMEselect for member trading identifiers. In Europe, MiFID II Article 17 puts the systems and controls duty on any investment firm engaged in algorithmic trading, covering testing, kill functionality, and the prevention of disorderly trading. Article 48 puts a parallel duty on the venue itself, and venue-side prevention tools became standard equipment alongside it.

Configuration lives at the account or firm level rather than symbol by symbol. The size of a single underlying's option surface shows why.

QueryOption contracts with volume on one underlying, June 2026
The exact SQL behind every number
SELECT
    toString(date)                 AS session_date,
    countDistinct(ticker)          AS contracts_traded,
    countDistinct(strike_price)    AS strikes_traded
FROM global_markets.options_greeks
WHERE underlying_symbol = 'AAPL'
  AND date >= '2026-06-01'
  AND date <= '2026-06-30'
  AND volume > 0
  AND iv_converged = 1
GROUP BY date
ORDER BY date
Run this yourself

On 2026-06-01, one underlying carried 1579 separately tradable contracts with volume, spread across 112 strikes, and the panel covers 21 sessions of similar shape. Setting a rule contract by contract is unmanageable at that count. The identifier attaches to the account instead, and travels with every order that account sends.

The failure mode your fill logs will not explain

Here is how this lands in practice. A cancel-newest instruction kills the order you just sent, on arrival, before it can match. From inside the bot the sequence reads as a new order followed by a cancel nobody requested. There is no fill, no reject, and no error string naming a reason, so operators meeting it for the first time tend to hunt for a bug in their own cancel path.

Two habits make it legible. Log the venue's order status messages verbatim rather than a normalized summary, since the prevention reason usually rides as a field on that message. Alert on any cancel your own code did not originate. That alert belongs with your automated trading circuit breakers, since the failure is the same class: the venue changed your order state and your process carried on as though nothing happened.

FAQ

What is self-match prevention?

A matching engine check that stops two orders carrying the same firm or account identifier from trading with each other. When they would match, the engine cancels the resting order, the incoming order, or both, following the instruction attached to the order.

Is a self-trade the same thing as a wash trade?

No. A self-trade is any execution between two orders from the same beneficial owner. A wash trade is a self-trade entered without genuine market risk and with the intent of creating a misleading appearance of activity. Unintentional self-trades between unrelated algorithms are treated differently from arranged ones, and firms are still expected to monitor them.

Does self-match prevention work across different exchanges?

No. Each matching engine applies the check only to orders in its own book. Two orders from one firm resting on two venues can trade with each other, which leaves that job to the firm's own pre-trade checks above the venues.

Why was my order cancelled with no fill and no reason?

A cancel-newest self-match instruction is one candidate: the venue cancelled the order on arrival, before it could match a resting order carrying your identifier. The reason usually appears as a field on the venue's cancel message rather than as a rejection.

Which venues require a self-match prevention identifier?

Requirements vary by venue and product. CME asks for an identifier on order entry, registered in advance, while ICE and the London Metal Exchange offer their own configurations at the firm level and European venues carry systems and controls duties under MiFID II. The venue rulebook for the product you trade is the authority.


Every panel here carries the SQL that produced it, so expand any one and read it. To count the venues quoting a name you trade, or to break a session into condition flags, ask the question in plain English on the Strasmore terminal.