Czym jest markout transakcji i jak mierzyć jakość?
Wyjaśnienie konwencji znaków w analizie markout oraz prezentacja krzywej dla horyzontów od jednej sekundy do pięciu minut. Analiza jakości egzekucji zleceń w ujęciu ilościowym.
Czym jest markout transakcji?
Punktem wyjścia jest cena mid: punkt środkowy między najlepszą ofertą kupna (best bid) a najlepszą ofertą sprzedaży (best offer), czyli środek NBBO. Markout oblicza się, biorąc cenę mid w wybranym horyzoncie czasowym po wykonaniu zlecenia, odejmując od niej cenę odniesienia i nadając wynikowi znak zależny od strony transakcji.
**markout w horyzoncie h = strona * (mid w t+h - cena odniesienia)**, gdzie strona wynosi plus jeden w przypadku kupna i minus jeden w przypadku sprzedaży.
Konwencja znaków jest zazwyczaj źródłem nieporozumień. Kupno, po którym następuje spadek ceny mid, daje ujemny markout: kupiono aktywa, cena spadła, a sprzedający uzyskał lepsze warunki transakcji. Jest to tzw. selekcja negatywna (adverse selection) wyrażona liczbowo. Sprzedaż, po której cena mid spada, daje dodatni markout, ponieważ znak zmienia się wraz ze stroną transakcji. Każde wykonanie zlecenia ma dwie strony, a markout jednej strony jest markoutem drugiej strony ze zmienionym znakiem.
Trzy ceny odniesienia
Cena odniesienia, którą odejmujemy, decyduje o tym, co mierzy markout.
- Cena wykonania. Markout staje się wartością pieniężną: określa, ile warta jest pozycja w momencie t+h w stosunku do zapłaconej kwoty. Zawiera w sobie spread, który został zapłacony lub zarobiony.
- Cena mid w momencie wykonania. Eliminuje to wpływ spreadu i pozwala wyizolować ruch rynku po transakcji, co stanowi najbardziej precyzyjny wskaźnik selekcji negatywnej.
- Kwotowanie po stronie, po której zawarto transakcję. Pomiar względem poziomu, po którym dokonano transakcji lub wystawiono zlecenie, pozwala ocenić wykonanie w odniesieniu do ceny, która była faktycznie dostępna.
Analiza jakości egzekucji opiera się na drugim wariancie w przypadku selekcji negatywnej oraz na pierwszym w przypadku zysków i strat. Oba warianty przedstawiono w pierwszym panelu poniżej dla określonego zbioru transakcji.
Krzywa markoutu i znaczenie jej kształtu
Wykonanie tej samej statystyki dla wielu horyzontów czasowych pozwala wykreślić krzywą. Jej kształt niesie więcej informacji niż pojedyncza liczba.
- Krzywa, która gwałtownie spada w ciągu pierwszej sekundy i pozostaje na niskim poziomie, opisuje przepływ zleceń, które są „wybierane” (picked off): cena zmienia się na niekorzyść wykonania niemal natychmiast.
- Krzywa, która spada przez kolejne minuty, opisuje przepływ niosący istotne informacje o kierunku ruchu cen.
- Płaska krzywa w pobliżu zera opisuje neutralny przepływ. Po pięciu minutach cena mid wygląda tak samo, jak w momencie wykonania.
- Krzywa, która zaczyna się od wartości ujemnej i wrasta w kierunku zera, jest typowa dla pasywnego animatora rynku (market maker) pobierającego rabaty. Początkowy spadek to spread plus natychmiastowy wpływ rynkowy, a powrót do zera to stabilizacja rynku.
Wybór horyzontu jest decyzją o tym, co dokładnie mierzymy. Poniżej sekundy markout odczytuje głównie opóźnienia (latency) i pozycję w kolejce. W horyzoncie minuty odczytuje krótkoterminowe informacje zawarte w przepływie zleceń. Powyżej pięciu minut zwykły dryf rynkowy zaczyna przeważać nad samym wykonaniem zlecenia.
Rzeczywista krzywa markoutu, transakcja po transakcji
Poniższy panel uwzględnia każdy print (wykonaną transakcję) dla INTC z sesji regularnej z dnia 10 czerwca 2026 r., klasyfikuje każdą z nich jako inicjowaną kupnem lub sprzedażą względem ostatniego kwotowania z sekundy poprzedzającej transakcję i uśrednia markout w horyzontach 5. Dwie serie dotyczą tych samych transakcji: jedna mierzona od ceny mid w momencie wykonania, druga od ceny wykonania.
Dokładny kod SQL dla każdej liczby
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_sDla 900593 sklasyfikowanych transakcji seria oparta na mid wynosi 3.068 punktów bazowych w 1 sec oraz 2.977 punktów bazowych w 5 min. Punkt bazowy to jedna setna procenta ceny, więc pojedyncza transakcja przesuwa cenę o błąd zaokrąglenia, a milion akcji przesuwa realny kapitał.
Seria oparta na cenie wykonania otwiera się na poziomie -0.488 punktów bazowych. Odległość między dwiema seriami w najkrótszym horyzoncie to efektywny pół-spread: odległość, w jakiej print znalazł się od ceny mid. Taker płaci to przy wejściu, a maker zarabia. Wszystko powyżej pierwszego horyzontu to ruch rynku.
Wielkość zlecenia a kształt krzywej
Selekcja negatywna nie rozkłada się równomiernie na wszystkie transakcje. Podział tej samej sesji według wielkości printu pozwala zestawić małe zlecenia oraz bloki o wielkości tysiąca akcji lub więcej na tych samych osiach.
Dokładny kod SQL dla każdej liczby
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_sW horyzoncie 5 min printy poniżej tysiąca akcji osiągają średnio 2.983 punktów bazowych, podczas gdy 11588 printów o wielkości tysiąca akcji lub więcej osiąga średnio 2.495. Dwa kwotowania realizujące ten sam łączny wolumen wobec tych dwóch grup nie kończą dnia w tym samym miejscu.
Jak markouty stają się spreadami w raportach z egzekucji
Markout jest brakującym elementem w standardowej dekompozycji spreadu. Efektywny spread to to, co taker faktycznie zapłacił: dwukrotność odległości od ceny mid do ceny printu. Dzielimy to na dwie części. Jedną jest zrealizowany spread, czyli to, co zatrzymuje maker po ruchu rynku. Drugą jest selekcja negatywna, czyli markout oparty na mid pomnożony przez dwa. Tożsamość jest dokładna: efektywny spread = zrealizowany spread + selekcja negatywna.
Dokładny kod SQL dla każdej liczby
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_timeW półgodzinnym bloku rozpoczynającym się o 09:30 czasu wschodniego, efektywny spread wynosi średnio 10.651 punktów bazowych, z czego 8.313 to składnik selekcji negatywnej w horyzoncie sześćdziesięciu sekund, a 2.338 to zysk makera. Panel obejmuje 13 półgodzinnych przedziałów.
Te składniki to wielkości podsumowywane dla każdego miejsca obrotu w raportach z egzekucji Rule 605 i 606 i stanowią one podstawę arytmetyczną dla twierdzeń o poprawie ceny (price improvement), które wiążą się z zasadą sub-penny i poprawą ceny. Rabat jest stałym kredytem na akcję. Selekcja negatywna jest zmiennym kosztem na akcję dla transakcji, które faktycznie otrzymuje kwotowanie. Ważenie jednego względem drugiego na podstawie zmierzonej krzywej, zamiast na podstawie cennika, jest weryfikacją stojącą za opłatami i rabatami typu maker-taker i wiąże się z mechaniką opisaną w dlaczego animatorzy rynku tracą pieniądze.
Jak obliczono te liczby
Każdy panel odczytuje kwotowania i printy INTC z sesji regularnej z dnia 10 czerwca 2026 r., od 09:30 do 16:00 czasu wschodniego. Kwotowania są sprowadzane do jednej ceny mid na sekundę (ostatnia cena wewnątrz tej sekundy). Print jest klasyfikowany jako inicjowany kupnem, gdy znalazł się powyżej ceny mid z poprzedniej sekundy, i jako inicjowany sprzedażą, gdy znalazł się poniżej (zgodnie ze standardową regułą kwotowania). Transakcje zawarte dokładnie po cenie mid są pomijane, więc egzekucje po cenie środkowej znajdują się poza próbką. Użycie ceny mid z poprzedniej sekundy jako odniesienia zapobiega „zaglądaniu w przyszłość”. Horyzonty to pełne sekundy liczone od sekundy printu, a transakcja wchodzi do horyzontu tylko wtedy, gdy w docelowej sekundzie istnieje kwotowanie.
FAQ
Czym jest markout w tradingu?
Markout to podpisana zmiana ceny mid w ustalonym horyzoncie czasowym po wykonaniu transakcji. Wartość dodatnia oznacza, że rynek poruszył się w kierunku transakcji po jej wykonaniu. Wartość ujemna oznacza, że poruszył się w przeciwnym kierunku, co jest definicją selekcji negatywnej.
Czy ujemny markout zawsze oznacza złe wykonanie?
Nie. Ujemny markout w horyzoncie jednej sekundy dla zlecenia rynkowego to w większości spread, który zlecenie to zapłaciło – jest to koszt natychmiastowej realizacji. Ważniejsze jest to, czy krzywa nadal spada w dłuższych horyzontach, czy się wypłaszcza.
Jaka jest różnica między markoutem a slippage?
Slippage porównuje wykonanie z ceną decyzji lub ceną wejścia, oceniając całą ścieżkę zlecenia. Markout porównuje wykonanie z ceną mid w horyzoncie po wykonaniu, oceniając to, co wydarzyło się później. Jedno ocenia zlecenie nadrzędne, drugie ocenia pojedynczą transakcję.
Jakiego horyzontu powinien używać markout?
Zależy to od celu transakcji. Horyzonty poniżej sekundy oceniają egzekucję wrażliwą na opóźnienia, podczas gdy jedna minuta jest standardem dla selekcji negatywnej w przypadku płynnych aktywów. Publikowanie horyzontu obok liczby sprawia, że dwa markouty są w ogóle porównywalne.
Jak markout odnosi się do efektywnego spreadu?
Efektywny spread dzieli się na zrealizowany spread oraz składnik selekcji negatywnej, a ten składnik to markout oparty na mid pomnożony przez dwa. Zmiana horyzontu powoduje zmianę podziału między tymi dwiema wartościami, dlatego opublikowana statystyka spreadu jest niekompletna bez podania horyzontu.
Każdy panel tutaj jest dostarczany wraz z kodem SQL, który go wygenerował. Wystarczy otworzyć panel, zmienić ticker lub datę, a ta sama krzywa markoutu zostanie odtworzona od podstaw na terminalu Strasmore.