Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Wat is een trade markout en hoe meet u execution quality

Begrijp de werking van een trade markout en de bijbehorende tekenconventie. Wij analyseren de markout curve bij horizons van één seconde tot vijf minuten voor uw execution quality.

Wat is een trade markout?

Begin bij de mid price: het middelpunt tussen de best bid en de best offer, het midden van de NBBO. Een markout neemt die mid op een gekozen horizon na uw fill, trekt daar een referentieprijs vanaf en geeft het resultaat een teken op basis van de zijde waaraan u handelde.

**markout op horizon h = zijde * (mid op t+h - referentieprijs)**, waarbij zijde plus één is als u kocht en min één als u verkocht.

De tekenconventie is waar de verwarring meestal ontstaat. Een aankoop gevolgd door een dalende mid heeft een negatieve markout: u kocht, de prijs daalde en degene die aan u verkocht, trok aan het langste eind. Dat is adverse selection uitgedrukt in een getal. Een verkoop gevolgd door een dalende mid heeft een positieve markout, omdat het teken meebeweegt met de zijde. Elke fill heeft twee zijden, en de markout van de ene zijde is die van de andere zijde met het teken omgedraaid.

De drie referentieprijzen

De referentieprijs die u aftrekt, bepaalt wat de markout meet.

  • De fill price. De markout wordt geld: wat de positie op t+h waard is ten opzichte van wat u betaalde. De spread die u betaalde of verdiende, zit hierin verwerkt.
  • De mid op het moment van de fill. Dit filtert de spread eruit en isoleert hoe de markt bewoog nadat u handelde; dit is de zuiverste lezing van adverse selection.
  • De quote aan de zijde waaraan u handelde. Meten ten opzichte van de touch die u nam, of de touch die u plaatste, geeft antwoord op de vraag hoe de fill zich verhoudt tot de prijs die daadwerkelijk beschikbaar was.

Bij analyses van execution quality leunt men op de tweede voor adverse selection en op de eerste voor winst en verlies. Beide verschijnen in het eerste paneel hieronder, over één set fills.

De markout-curve en de betekenis van het verloop

Voer dezelfde statistiek uit op vele horizons en u krijgt een curve. De vorm daarvan zegt meer dan enig enkel getal.

  • Een curve die in de eerste seconde steil daalt en laag blijft, beschrijft flow die wordt 'geplukt': de prijs beweegt vrijwel direct tegen de fill in.
  • Een curve die minutenlang blijft dalen, beschrijft flow die daadwerkelijke informatie bevatte over de richting van de prijs.
  • Een vlakke curve nabij nul beschrijft goedaardige flow. Na vijf minuten ziet de mid eruit zoals op het moment van de fill.
  • Een curve die negatief begint en terugklimt naar nul is het verloop waar een passieve maker die rebates incasseert naar op zoek is. De vroege dip is de spread plus de directe impact, en het herstel is de markt die terugkeert.

De keuze voor een horizon is op zichzelf een beslissing over wat u meet. Onder een seconde leest een markout vooral latency en queue position. Rek hem uit naar een minuut en hij leest de kortetermijninformatie in de flow. Na vijf minuten begint de gebruikelijke marktbeweging de fill zelf te overschaduwen.

Een echte markout-curve, fill voor fill

Het onderstaande paneel neemt elke INTC-print van de reguliere sessie op 10 juni 2026, classificeert elke print als buy-initiated of sell-initiated ten opzichte van de laatste quote van de seconde vóór de print, en middelt de gesigneerde markout op 5 horizons. Twee reeksen lopen over identieke fills: één gemeten vanaf de mid op het moment van de fill, één vanaf de fill price.

QueryINTC markout curve, 10 juni 2026, gemeten vanaf twee referentiebasispunten
De exacte SQL achter elk getal
WITH
    mid_by_second AS
    (
        SELECT
            dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,
            argMax((toFloat64(bid_price) + toFloat64(ask_price)) / 2, sip_timestamp)    AS mid
        FROM global_markets.cache_stocks_quotes
        WHERE ticker = 'INTC'
          AND sip_timestamp >= toDateTime('2026-06-10 13:30:00', 'UTC')
          AND sip_timestamp <  toDateTime('2026-06-10 20:00:00', 'UTC')
          AND bid_price > 0
          AND ask_price > bid_price
        GROUP BY sec
    ),
    signed_fills AS
    (
        SELECT
            t.sec        AS sec,
            t.fill_price AS fill_price,
            q.mid        AS ref_mid,
            if(t.fill_price > q.mid, 1, -1) AS side
        FROM
        (
            SELECT
                dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,
                sec - 1          AS ref_sec,
                toFloat64(price) AS fill_price
            FROM global_markets.stocks_trades
            WHERE ticker = 'INTC'
              AND sip_timestamp >= toDateTime('2026-06-10 13:30:01', 'UTC')
              AND sip_timestamp <  toDateTime('2026-06-10 19:55:00', 'UTC')
              AND price > 0
              AND size > 0
        ) AS t
        INNER JOIN mid_by_second AS q ON q.sec = t.ref_sec
        WHERE t.fill_price != q.mid
    )
SELECT
    multiIf(f.horizon_s < 60,
            concat(toString(f.horizon_s), ' sec'),
            concat(toString(intDiv(f.horizon_s, 60)), ' min'))              AS horizon,
    round(avg(f.side * (fut.mid - f.ref_mid) / f.ref_mid) * 10000, 3)       AS mid_basis_bps,
    round(avg(f.side * (fut.mid - f.fill_price) / f.fill_price) * 10000, 3) AS fill_basis_bps,
    count()                                                                 AS fill_count
FROM
(
    SELECT
        sec,
        fill_price,
        ref_mid,
        side,
        horizon_s,
        sec + horizon_s AS future_sec
    FROM signed_fills
    ARRAY JOIN [1, 5, 15, 60, 300] AS horizon_s
) AS f
INNER JOIN mid_by_second AS fut ON fut.sec = f.future_sec
GROUP BY f.horizon_s
ORDER BY f.horizon_s
Run this yourself

Over 900593 geclassificeerde fills leest de mid basis-reeks 3.068 basis points op 1 sec en 2.977 basis points op 5 min. Een basis point is een honderdste van één procent van de prijs; één fill beweegt dus een afrondingsfout, terwijl een miljoen aandelen echt geld beweegt.

De fill basis-reeks opent op -0.488 basis points. De afstand tussen de twee reeksen op de kortste horizon is de effective half spread: hoe ver de print van de mid af lag. Een taker betaalt dit bij instap en een maker verdient het. Alles na de eerste horizon is de markt die beweegt.

Fill-grootte en de vorm van de curve

Adverse selection verdeelt zich niet gelijkmatig over fills. Het splitsen van dezelfde sessie op printgrootte zet kleine fills en blokken van duizend aandelen of meer op dezelfde assen.

QueryDezelfde curve, uitgesplitst naar print size: small fills versus blocks
De exacte SQL achter elk getal
WITH
    mid_by_second AS
    (
        SELECT
            dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,
            argMax((toFloat64(bid_price) + toFloat64(ask_price)) / 2, sip_timestamp)    AS mid
        FROM global_markets.cache_stocks_quotes
        WHERE ticker = 'INTC'
          AND sip_timestamp >= toDateTime('2026-06-10 13:30:00', 'UTC')
          AND sip_timestamp <  toDateTime('2026-06-10 20:00:00', 'UTC')
          AND bid_price > 0
          AND ask_price > bid_price
        GROUP BY sec
    ),
    signed_fills AS
    (
        SELECT
            t.sec        AS sec,
            t.fill_size  AS fill_size,
            q.mid        AS ref_mid,
            if(t.fill_price > q.mid, 1, -1) AS side
        FROM
        (
            SELECT
                dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,
                sec - 1          AS ref_sec,
                toFloat64(price) AS fill_price,
                size             AS fill_size
            FROM global_markets.stocks_trades
            WHERE ticker = 'INTC'
              AND sip_timestamp >= toDateTime('2026-06-10 13:30:01', 'UTC')
              AND sip_timestamp <  toDateTime('2026-06-10 19:55:00', 'UTC')
              AND price > 0
              AND size > 0
        ) AS t
        INNER JOIN mid_by_second AS q ON q.sec = t.ref_sec
        WHERE t.fill_price != q.mid
    )
SELECT
    multiIf(f.horizon_s < 60,
            concat(toString(f.horizon_s), ' sec'),
            concat(toString(intDiv(f.horizon_s, 60)), ' min'))                                   AS horizon,
    round(avgIf(f.side * (fut.mid - f.ref_mid) / f.ref_mid, f.fill_size < 1000) * 10000, 3)       AS small_fill_bps,
    round(avgIf(f.side * (fut.mid - f.ref_mid) / f.ref_mid, f.fill_size >= 1000) * 10000, 3)      AS block_fill_bps,
    countIf(f.fill_size >= 1000)                                                                 AS block_fill_count
FROM
(
    SELECT
        sec,
        fill_size,
        ref_mid,
        side,
        horizon_s,
        sec + horizon_s AS future_sec
    FROM signed_fills
    ARRAY JOIN [1, 5, 15, 60, 300] AS horizon_s
) AS f
INNER JOIN mid_by_second AS fut ON fut.sec = f.future_sec
GROUP BY f.horizon_s
HAVING countIf(f.fill_size < 1000) > 0
   AND countIf(f.fill_size >= 1000) > 0
ORDER BY f.horizon_s
Run this yourself

Op de 5 min horizon bedragen prints onder de duizend aandelen gemiddeld 2.983 basis points, terwijl de 11588 prints van duizend aandelen of meer gemiddeld 2.495 bedragen. Twee quotes die hetzelfde totale volume vullen tegen die twee groepen, eindigen de dag niet op hetzelfde punt.

Hoe markouts de spread-getallen in execution reports worden

De markout is de ontbrekende term in de standaard spread-decompositie. De effective spread is wat een taker daadwerkelijk betaalde: tweemaal de gesigneerde afstand van de mid tot de print. Splits dat in tweeën. Het ene deel is de realized spread, wat een maker overhoudt zodra de markt is bewogen. Het andere deel is de adverse selection, oftewel de mid basis-markout vermenigvuldigd met twee. De identiteit is exact: effective spread = realized spread + adverse selection.

QueryEffective spread uitgesplitst in realized spread en adverse selection, per half uur
De exacte SQL achter elk getal
WITH
    mid_by_second AS
    (
        SELECT
            dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,
            argMax((toFloat64(bid_price) + toFloat64(ask_price)) / 2, sip_timestamp)    AS mid
        FROM global_markets.cache_stocks_quotes
        WHERE ticker = 'INTC'
          AND sip_timestamp >= toDateTime('2026-06-10 13:30:00', 'UTC')
          AND sip_timestamp <  toDateTime('2026-06-10 20:00:00', 'UTC')
          AND bid_price > 0
          AND ask_price > bid_price
        GROUP BY sec
    ),
    signed_fills AS
    (
        SELECT
            t.sec + 60   AS future_sec,
            t.et_time    AS et_time,
            t.fill_price AS fill_price,
            q.mid        AS ref_mid,
            if(t.fill_price > q.mid, 1, -1) AS side
        FROM
        (
            SELECT
                dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,
                sec - 1          AS ref_sec,
                toFloat64(price) AS fill_price,
                formatDateTime(toStartOfInterval(toTimeZone(sip_timestamp, 'America/New_York'), toIntervalMinute(30)), '%H:%i') AS et_time
            FROM global_markets.stocks_trades
            WHERE ticker = 'INTC'
              AND sip_timestamp >= toDateTime('2026-06-10 13:30:01', 'UTC')
              AND sip_timestamp <  toDateTime('2026-06-10 19:55:00', 'UTC')
              AND price > 0
              AND size > 0
        ) AS t
        INNER JOIN mid_by_second AS q ON q.sec = t.ref_sec
        WHERE t.fill_price != q.mid
    )
SELECT
    f.et_time                                                                  AS et_time,
    round(avg(2 * f.side * (f.fill_price - f.ref_mid) / f.ref_mid) * 10000, 3) AS effective_spread_bps,
    round(avg(2 * f.side * (f.fill_price - fut.mid) / f.ref_mid) * 10000, 3)   AS realized_spread_bps,
    round(avg(2 * f.side * (fut.mid - f.ref_mid) / f.ref_mid) * 10000, 3)      AS adverse_selection_bps,
    count()                                                                    AS fill_count
FROM signed_fills AS f
INNER JOIN mid_by_second AS fut ON fut.sec = f.future_sec
GROUP BY f.et_time
ORDER BY f.et_time
Run this yourself

In het half uur beginnend om 09:30 Eastern bedraagt de effective spread gemiddeld 10.651 basis points, waarvan 8.313 de adverse selection-term is op een horizon van zestig seconden en 2.338 hetgeen een maker behoudt. Het paneel beslaat 13 halfuur-buckets.

Die componenten zijn de grootheden die per venue worden samengevat in Rule 605 and 606 execution reports, en zij vormen de rekenkunde achter de claims over price improvement die rusten op the sub penny rule and price improvement. Een rebate is een vaste vergoeding per aandeel. Adverse selection is een variabele kostenpost per aandeel op de fills die een quote daadwerkelijk ontvangt. Het afwegen van de een tegen de ander over een gemeten curve, in plaats van over een algemene tariefkaart, is de controle achter maker taker fees and rebates, en het staat naast de mechanismen in why market makers lose money.

Hoe deze getallen werden berekend

Elk paneel leest INTC-quotes en -prints van de reguliere sessie op 10 juni 2026, 09:30 tot 16:00 Eastern. Quotes worden samengevoegd tot één mid per seconde, de laatste die binnen die seconde werd geprint. Een print wordt geclassificeerd als buy-initiated wanneer deze boven de mid van de voorgaande seconde landde en als sell-initiated wanneer deze eronder landde, wat de standaard quote-regel is. Prints die precies op die mid liggen, worden weggelaten in plaats van geschat, waardoor midpoint-executies buiten de steekproef vallen. Door de mid van de voorgaande seconde als referentie te gebruiken, wordt voorkomen dat de referentie vooruitkijkt. Horizons zijn hele seconden geteld vanaf de seconde van de print, en een fill komt alleen in een horizon terecht wanneer er in de doel-seconde een quote bestaat.

Veelgestelde vragen

Wat is een markout in de handel?

Een markout is de gesigneerde verandering in de mid price over een vaste horizon na een fill. Positief betekent dat de markt na uw fill in de richting van uw trade bewoog. Negatief betekent dat deze de andere kant op bewoog, wat de definitie is van adverse selection.

Is een negatieve markout altijd een slechte fill?

Nee. Een negatieve markout op één seconde bij een marketable order is grotendeels de spread die die order betaalde; dat is de prijs voor onmiddellijke uitvoering. Wat belangrijker is, is of de curve op langere horizons blijft dalen of afvlakt.

Wat is het verschil tussen een markout en slippage?

Slippage vergelijkt een fill met een beslissingsprijs of een aankomstprijs, waardoor het het gehele traject van een order beoordeelt. Een markout vergelijkt een fill met de mid op een horizon na de fill, waardoor het beoordeelt wat er daarna gebeurde. Het een scoort de parent order, het ander scoort de individuele fill.

Welke horizon moet een markout gebruiken?

Dat hangt af van waar de fill voor was. Horizons onder een seconde beoordelen latency-gevoelige executie, terwijl één minuut de gebruikelijke standaard is voor adverse selection bij liquide namen. Het publiceren van de horizon naast het getal maakt twee markouts überhaupt vergelijkbaar.

Hoe verhoudt een markout zich tot de effective spread?

De effective spread splitst zich in de realized spread plus een adverse selection-component, en die component is de mid basis-markout vermenigvuldigd met twee. Verander de horizon en de verdeling tussen de twee verschuift mee; daarom is een gepubliceerde spread-statistiek onvolledig zonder de bijbehorende horizon.


Elk paneel hier wordt geleverd met de SQL die het heeft geproduceerd. Open er een, wissel de ticker of de datum, en dezelfde markout-curve wordt vanaf nul opnieuw opgebouwd op de Strasmore-terminal.