Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Trade Markout 解釋:如何衡量執行品質

了解 trade markout 的定義與正負號約定,並看懂從1秒到5分鐘的實際 markout 曲線,以及它如何衡量 adverse selection 與執行品質。

交易 markout 是成交後固定時間區間內,中間價的變化,從該筆成交被打印的時刻開始計算。正值表示交易後市場朝有利於你的方向移動。負值表示市場走勢對你不利,交易台稱之為 adverse selection。你選擇的時間區間是一秒或五分鐘,會決定這個數值實際回答哪個問題。

什麼是 trade markout?

先從中間價開始:它是最佳買價與最佳賣價之間的中點,也就是 NBBO 的中間價。markout 會取成交後特定時間區間的中間價,扣除參考價格,再依交易方向標記結果正負。

**h 時點的 markout = side *(t+h 時的中間價 − 參考價格)**;其中,若買進,side 為 plus one;若賣出,side 為 minus one。

通常混淆出在正負號的約定。買進後中間價下跌,markout 就是負值:你買進後價格下跌,而賣給你的人在這筆交易中取得較有利的價格。這就是以數值表示的 adverse selection。賣出後中間價下跌,markout 則是正值,因為正負號會隨交易方向反轉。每筆成交都有買賣雙方,而一方的 markout,就是另一方 markout 的相反數。

三種參考價格

扣除哪個參考價格,決定了 markout 衡量的是什麼。

  • 成交價。 此時 markout 會轉化為金額:比較 t+h 時部位的價值與你的支付價格。你支付或賺取的價差包含在其中。
  • 成交當下的中間價。 這會排除價差,單獨衡量交易後市場的走勢,是觀察 adverse selection 最直接的方法。
  • 你交易方向一側的報價。 以你吃進的 touch,或你掛出的 touch 作為基準,可以回答成交價相對於當時實際可得價格的表現。

執行品質分析通常使用第二種方法衡量 adverse selection,使用第一種方法衡量損益。下方第一個圖表會在同一批成交上同時呈現這兩種結果。

markout 曲線及其形狀代表的意義

在多個時間區間計算相同統計值,就會得到一條曲線。曲線形狀提供的資訊,不只是一個單一數值。

  • 曲線在第一秒內急跌並持續維持低位,表示交易流被市場搶先利用:成交後價格幾乎立即朝不利方向移動。
  • 曲線持續下跌數分鐘,表示交易流包含關於價格未來走勢的真實資訊。
  • 曲線在零附近持平,表示交易流沒有明顯資訊。五分鐘後,中間價看起來與成交時相近。
  • 曲線先為負值,之後回升至接近零,這是收取回饋金的被動造市商希望看到的形狀。早期下跌反映價差與即時衝擊,之後回升則表示市場價格回復。

時間區間的選擇,本身就是對衡量內容的決定。不到一秒的 markout 主要反映延遲與 佇列位置。拉長至一分鐘,則會反映交易流中的短期資訊。超過五分鐘後,一般市場漂移開始壓過成交本身的影響。

逐筆成交的實際 markout 曲線

下方圖表納入2026年6月10日正常交易時段的每一筆 INTC 成交。每筆成交都根據成交打印前一秒的最後一筆報價,分類為買方主動或賣方主動,並在 5 個時間區間計算有方向性的 markout 平均值。兩條序列使用完全相同的成交:一條以成交時的中間價為基準,另一條以成交價為基準。

查詢INTC價差曲線,2026年6月10日,以兩個參考基準計算
每個數據背後的精確 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 筆分類成交中,以中間價為基準的序列在 1 sec 時為 3.068 個基點,在 5 min 時為 2.977 個基點。一個基點是價格的百分之一個百分點,因此單筆成交的影響可能只是四捨五入誤差,但一百萬股就會形成實際金額。

以成交價為基準的序列起始值為 -0.488 個基點。最短時間區間內兩條序列的距離,就是有效半價差:成交打印距離中間價有多遠。吃單者在進場時支付這筆成本,造市商則賺取這筆收益。第一個時間區間之後的所有變化,都是市場價格的移動。

成交量與曲線形狀

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        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 的時間區間,低於一千股的成交平均為 2.983 個基點;而 11588 筆一千股以上的成交平均為 2.495 個基點。針對這兩組成交,若以相同總成交量完成兩筆報價成交,最終的日內結果並不相同。

markout 如何成為執行報告中的價差數據

markout 是標準價差分解中缺少的一項。有效價差是吃單者實際支付的價差,也就是中間價至成交打印之間有方向距離的兩倍。將其拆成兩部分:第一部分是實現價差,即市場移動後造市商保留的金額;第二部分是 adverse selection,也就是以中間價為基準的 markout 乘以二。兩者的關係完全成立:有效價差 = 實現價差 + 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 開始的半小時內,有效價差平均為 10.651 個基點。其中,8.313 個基點是在六十秒時間區間下的 adverse selection,2.338 個基點是造市商保留的金額。圖表涵蓋 13 個半小時區間。

這些組成項目,就是 Rule 605 與 Rule 606 執行報告按交易場所彙總的數據,也是建立在 次美分規則與價格改善之上的價格改善主張所依據的算式。回饋金是每股固定的抵免。Adverse selection 則是報價實際成交後,每股會產生變動的成本。將兩者放在已測量的曲線上比較,而不是只看公開費率表,是檢驗 maker taker 費用與回饋金的依據;這也與 為何造市商會虧損中的機制相互補充。

這些數值的計算方式

每個圖表都使用2026年6月10日正常交易時段、 美東時間09:30至16:00的 INTC 報價與成交資料。報價資料按秒整合,每秒採用該秒內最後一筆報價,並計算一個中間價。若成交價高於前一秒的中間價,便分類為買方主動;若低於前一秒的中間價,便分類為賣方主動,這是標準的報價分類規則。成交價恰好等於該中間價的成交,不進行推測,而是予以剔除,因此中間價成交不在樣本內。使用前一秒的中間價作為參考,可避免參考值提前看到未來資訊。時間區間以成交所在秒數起算的完整秒數表示;只有在目標秒數存在報價時,該筆成交才會納入該時間區間。

常見問題

交易中的 markout 是什麼?

markout 是成交後固定時間區間內,中間價變化的有方向數值。正值表示成交後市場朝有利於交易方向的走勢移動。負值表示市場朝相反方向移動,這就是 adverse selection 的定義。

負 markout 一定代表成交不佳嗎?

不一定。可成交訂單在一秒時間區間內出現負 markout,主要反映該訂單為立即成交而支付的價差。更重要的是觀察曲線在較長時間區間是否持續下跌,或是趨於平坦。

markout 與滑價有何不同?

滑價比較成交價與決策價格或到達價格,因此評估的是整筆訂單的執行過程。markout 則比較成交價與成交後特定時間區間的中間價,因此評估的是成交後發生的情況。前者評估母單,後者評估單筆成交。

markout 應使用哪個時間區間?

這取決於該筆成交的目的。不到一秒的時間區間可評估對延遲敏感的執行;對流動性高的標的而言,一分鐘是衡量 adverse selection 的常見預設值。將時間區間與數值一併公布,才能讓不同 markout 具備可比性。

markout 與有效價差有何關係?

有效價差可分解為實現價差與 adverse selection,而 adverse selection 就是以中間價為基準的 markout 乘以二。改變時間區間,兩者的分配也會隨之改變。因此,未標明時間區間的公開價差統計並不完整。


本文每個圖表都附有產生該圖表的 SQL。開啟其中一個查詢,替換 ticker 或日期,即可在 Strasmore terminal 上重新建立相同的 markout 曲線。