Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Торговий markout: оцінка якості виконання

Пояснення trade markout простою мовою: як працює знак показника, що таке adverse selection і як виглядає крива від однієї секунди до п’яти хвилин.

Торговий markout — це зміна mid price протягом фіксованого періоду після виконання заявки, виміряна від моменту, коли угода була зафіксована. Позитивне значення означає, що після вашої угоди ринок змістився у сприятливий для вас бік. Негативне — що він змістився проти вас. На торгових desk це називають adverse selection. Обраний горизонт — одна секунда чи п’ять хвилин — визначає, на яке саме запитання відповідає цей показник.

Що таке торговий markout?

Почнімо з mid price — середини між найкращою ціною bid і найкращою ціною offer, тобто midpoint NBBO. Markout бере це значення mid на обраному горизонті після виконання вашої заявки, віднімає reference price і визначає знак результату залежно від сторони угоди.

**markout на горизонті h = side * (mid у момент t+h - reference price)**, де side дорівнює plus one, якщо ви купили, і minus one, якщо продали.

Саме правило визначення знака зазвичай спричиняє плутанину. Якщо після купівлі mid price знижується, markout є негативним: ви купили, ціна впала, а продавець отримав вигідніший результат за цією угодою. Це adverse selection, виражена числом. Якщо після продажу mid price знижується, markout є позитивним, оскільки знак змінюється залежно від сторони угоди. У кожної угоди є дві сторони, і markout однієї сторони дорівнює markout іншої зі зміненим знаком.

Три reference price

Ціна, яку ви віднімаєте, визначає, що саме вимірює markout.

  • Ціна виконання. Markout стає грошовим показником: скільки коштує позиція в момент t+h порівняно з тим, скільки ви заплатили. Спред, який ви сплатили або отримали, входить до цього показника.
  • Mid у момент виконання. Це вилучає спред і показує, як ринок змістився після вашої угоди. Це найчистіший спосіб виміряти adverse selection.
  • Котирування на стороні, якою ви торгували. Порівняння з touch, який ви забрали або виставили, показує, як виконання співвідноситься з ціною, фактично доступною на ринку.

Під час аналізу якості виконання для adverse selection використовують другий варіант, а для прибутку та збитку — перший. Обидва варіанти наведено на першій панелі нижче для одного набору виконаних заявок.

Крива markout і значення її форми

Якщо розрахувати той самий показник на багатьох горизонтах, ви отримаєте криву. Її форма містить більше інформації, ніж будь-яке окреме число.

  • Крива, яка різко знижується протягом першої секунди й залишається на низькому рівні, свідчить, що потік заявок був «підібраний»: ціна майже одразу змістилася проти виконаної угоди.
  • Крива, яка продовжує знижуватися протягом кількох хвилин, свідчить, що потік заявок містив реальну інформацію про майбутній напрям руху ціни.
  • Плоска крива біля нуля означає нейтральний потік заявок. Через п’ять хвилин mid виглядає приблизно так само, як у момент виконання.
  • Крива, яка спочатку опускається нижче нуля, а потім повертається до нього, є бажаною для пасивного market maker, що отримує rebates. Початкове зниження відображає спред і негайний impact, а відновлення — повернення ринку.

Вибір горизонту також визначає, що саме ви вимірюєте. На горизонті до однієї секунди markout переважно відображає затримку та queue position. Якщо збільшити горизонт до однієї хвилини, він відображатиме короткострокову інформацію, що міститься в потоці заявок. Після п’яти хвилин звичайний ринковий drift починає затьмарювати ефект самого виконання.

Реальна крива markout для кожної угоди

На панелі нижче використано всі prints по INTC за регулярну сесію 10 червня 2026 року. Кожен print класифіковано як ініційований покупцем або продавцем порівняно з останнім котируванням за секунду до його появи. Потім розраховано середній signed markout на 5 горизонтах. Дві серії побудовано на однакових виконаних заявках: одну виміряно від mid у момент виконання, іншу — від ціни виконання.

ЗапитКрива markout INTC, 10 червня 2026 року, виміряна від двох базових рівнів
Точний SQL-код для кожного числа
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

Для 900593 класифікованих виконаних заявок серія на базі mid показує 3.068 базисних пунктів на горизонті 1 sec і 2.977 базисних пунктів на горизонті 5 min. Базисний пункт — це одна сота відсотка ціни. Тому рух ціни за однією угодою може бути похибкою округлення, тоді як рух за мільйоном акцій уже має суттєву грошову величину.

Серія на базі ціни виконання починається з -0.488 базисних пунктів. Відстань між двома серіями на найкоротшому горизонті — це ефективна половина спреду: наскільки далеко від mid опинився print. Taker сплачує її під час входу, а maker отримує. Усе, що відбувається після першого горизонту, є рухом ринку.

Розмір угоди та форма кривої

Adverse selection розподіляється між виконаними заявками нерівномірно. Якщо розділити ту саму сесію за розміром print, малі угоди та блоки на тисячу акцій або більше можна порівняти на одних осях.

ЗапитТа сама крива за розміром угоди: малі виконання проти блокових
Точний SQL-код для кожного числа
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

На горизонті 5 min prints обсягом менш як тисяча акцій у середньому показують 2.983 базисних пунктів, тоді як 11588 prints обсягом тисяча акцій або більше — 2.495. Два котирування, за якими було виконано однаковий загальний обсяг, можуть завершити день із різним результатом залежно від того, до якої з цих груп вони належали.

Як markout перетворюється на показники спреду у звітах про виконання

Markout — це відсутній елемент стандартного розкладання спреду. Effective spread — це те, що фактично сплатив taker: подвійна signed distance від mid до print. Її можна розділити навпіл. Одна частина — realized spread, тобто те, що зберігає maker після руху ринку. Інша — adverse selection, тобто mid-basis markout, помножений на два. Рівність є точною: effective spread = realized spread + adverse selection.

ЗапитЕфективний спред у розрізі реалізованого спреду та несприятливого відбору, за півгодини
Точний SQL-код для кожного числа
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

За пів години, що починається о 09:30 за Eastern Time, середній effective spread становить 10.651 базисних пунктів. Із них 8.313 припадає на adverse selection на горизонті шістдесят секунд, а 2.338 — на те, що зберігає maker. Панель охоплює 13 півгодинних інтервалів.

Саме ці компоненти узагальнюються по кожному venue у звітах про виконання за Rule 605 і Rule 606. Вони також лежать в основі заяв про price improvement, пов’язаних із правилом sub-penny і price improvement. Rebate — це фіксований кредит на одну акцію. Adverse selection — змінна вартість на одну акцію за виконаними заявками, які фактично отримало котирування. Порівняння цих величин на виміряній кривій, а не за headline rate card, є перевіркою для maker-taker fees і rebates і доповнює механіку, описану в матеріалі чому market makers втрачають гроші.

Як розраховано ці показники

Кожна панель використовує котирування та prints по INTC за регулярну сесію 10 червня 2026 року з 09:30 до 16:00 за Eastern Time. Котирування зведено до одного mid за секунду — останнього, що з’явився протягом цієї секунди. Print класифікується як ініційований покупцем, якщо він відбувся вище за mid попередньої секунди, і як ініційований продавцем, якщо він відбувся нижче. Це стандартне правило котирувань. Prints, що точно збігаються з цим mid, вилучаються, а не класифікуються припущенням. Тому виконання за midpoint не входять до вибірки. Використання mid попередньої секунди як reference не допускає використання інформації з майбутнього. Горизонти вимірюються цілими секундами від секунди появи print. Виконана заявка входить до розрахунку на певному горизонті лише тоді, коли в цільовій секунді існує котирування.

FAQ

Що таке markout у трейдингу?

Markout — це зміна mid price зі знаком протягом фіксованого періоду після виконання заявки. Позитивне значення означає, що після виконання ринок змістився у напрямку вашої угоди. Негативне означає рух у протилежний бік, що і є визначенням adverse selection.

Чи завжди негативний markout означає невигідне виконання?

Ні. Негативний markout на горизонті однієї секунди для marketable order переважно відображає спред, сплачений за негайне виконання. Важливіше, чи продовжує крива знижуватися на довших горизонтах, чи вирівнюється.

У чому різниця між markout і slippage?

Slippage порівнює ціну виконання з ціною ухвалення рішення або arrival price, тому оцінює весь шлях заявки. Markout порівнює ціну виконання з mid на певному горизонті після виконання, тому оцінює те, що сталося далі. Перший показник оцінює parent order, а другий — окреме виконання.

Який горизонт слід використовувати для markout?

Це залежить від мети виконання заявки. Горизонти до однієї секунди оцінюють execution, чутливе до затримки, тоді як одна хвилина є поширеним стандартом для вимірювання adverse selection у ліквідних акціях. Саме публікація горизонту поруч із показником робить два markout порівнюваними.

Як markout пов’язаний з effective spread?

Effective spread розкладається на realized spread і компонент adverse selection. Цей компонент дорівнює mid-basis markout, помноженому на два. Якщо змінити горизонт, змінюється і співвідношення між цими двома складовими. Тому статистика спреду без зазначення горизонту є неповною.


До кожної панелі додається SQL-запит, за допомогою якого її побудовано. Відкрийте запит, замініть ticker або дату, і та сама крива markout буде знову розрахована з нуля на терміналі Strasmore.