{"slug":"trade-markouts-explained","qid":"spread_decomposition","label":"Effective spread split into realized spread and adverse selection, by half hour","post_title":"Trade Markouts Explained: Execution Quality","post_url":"/blog/trade-markouts-explained#q-spread_decomposition","columns":["et_time","effective_spread_bps","realized_spread_bps","adverse_selection_bps","fill_count"],"rows":[{"et_time":"09:30","effective_spread_bps":10.651,"realized_spread_bps":2.338,"adverse_selection_bps":8.313,"fill_count":156976},{"et_time":"10:00","effective_spread_bps":8.053,"realized_spread_bps":1.892,"adverse_selection_bps":6.161,"fill_count":93545},{"et_time":"10:30","effective_spread_bps":7.573,"realized_spread_bps":-9.091,"adverse_selection_bps":16.665,"fill_count":95266},{"et_time":"11:00","effective_spread_bps":7.079,"realized_spread_bps":1.666,"adverse_selection_bps":5.414,"fill_count":87234},{"et_time":"11:30","effective_spread_bps":7.704,"realized_spread_bps":-4.864,"adverse_selection_bps":12.568,"fill_count":69850},{"et_time":"12:00","effective_spread_bps":5.624,"realized_spread_bps":3.918,"adverse_selection_bps":1.707,"fill_count":48500},{"et_time":"12:30","effective_spread_bps":5.151,"realized_spread_bps":0.038,"adverse_selection_bps":5.112,"fill_count":45567},{"et_time":"13:00","effective_spread_bps":4.667,"realized_spread_bps":0.932,"adverse_selection_bps":3.735,"fill_count":39236},{"et_time":"13:30","effective_spread_bps":5.598,"realized_spread_bps":-0.034,"adverse_selection_bps":5.632,"fill_count":43249},{"et_time":"14:00","effective_spread_bps":4.59,"realized_spread_bps":-1.134,"adverse_selection_bps":5.723,"fill_count":38018},{"et_time":"14:30","effective_spread_bps":4.504,"realized_spread_bps":1.256,"adverse_selection_bps":3.249,"fill_count":48662},{"et_time":"15:00","effective_spread_bps":5.29,"realized_spread_bps":-1.615,"adverse_selection_bps":6.905,"fill_count":64389},{"et_time":"15:30","effective_spread_bps":6.184,"realized_spread_bps":-1.08,"adverse_selection_bps":7.265,"fill_count":69101}],"shape":"series","sql":"WITH\n    mid_by_second AS\n    (\n        SELECT\n            dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,\n            argMax((toFloat64(bid_price) + toFloat64(ask_price)) / 2, sip_timestamp)    AS mid\n        FROM global_markets.cache_stocks_quotes\n        WHERE ticker = 'INTC'\n          AND sip_timestamp >= toDateTime('2026-06-10 13:30:00', 'UTC')\n          AND sip_timestamp <  toDateTime('2026-06-10 20:00:00', 'UTC')\n          AND bid_price > 0\n          AND ask_price > bid_price\n        GROUP BY sec\n    ),\n    signed_fills AS\n    (\n        SELECT\n            t.sec + 60   AS future_sec,\n            t.et_time    AS et_time,\n            t.fill_price AS fill_price,\n            q.mid        AS ref_mid,\n            if(t.fill_price > q.mid, 1, -1) AS side\n        FROM\n        (\n            SELECT\n                dateDiff('second', toDateTime('2026-06-10 13:30:00', 'UTC'), sip_timestamp) AS sec,\n                sec - 1          AS ref_sec,\n                toFloat64(price) AS fill_price,\n                formatDateTime(toStartOfInterval(toTimeZone(sip_timestamp, 'America/New_York'), toIntervalMinute(30)), '%H:%i') AS et_time\n            FROM global_markets.stocks_trades\n            WHERE ticker = 'INTC'\n              AND sip_timestamp >= toDateTime('2026-06-10 13:30:01', 'UTC')\n              AND sip_timestamp <  toDateTime('2026-06-10 19:55:00', 'UTC')\n              AND price > 0\n              AND size > 0\n        ) AS t\n        INNER JOIN mid_by_second AS q ON q.sec = t.ref_sec\n        WHERE t.fill_price != q.mid\n    )\nSELECT\n    f.et_time                                                                  AS et_time,\n    round(avg(2 * f.side * (f.fill_price - f.ref_mid) / f.ref_mid) * 10000, 3) AS effective_spread_bps,\n    round(avg(2 * f.side * (f.fill_price - fut.mid) / f.ref_mid) * 10000, 3)   AS realized_spread_bps,\n    round(avg(2 * f.side * (fut.mid - f.ref_mid) / f.ref_mid) * 10000, 3)      AS adverse_selection_bps,\n    count()                                                                    AS fill_count\nFROM signed_fills AS f\nINNER JOIN mid_by_second AS fut ON fut.sec = f.future_sec\nGROUP BY f.et_time\nORDER BY f.et_time","computed_at":"2026-08-15T16:21:16.737917+00:00","elapsed":0.005045023}