{"slug":"backtesting-in-illiquid-markets","qid":"markout","label":"markout","post_title":"backtesting-in-illiquid-markets","post_url":"/blog/backtesting-in-illiquid-markets#q-markout","columns":["horizon_minutes","thin_markout_bps","liquid_markout_bps","observations"],"rows":[{"horizon_minutes":1,"thin_markout_bps":-0.9,"liquid_markout_bps":0,"observations":1909},{"horizon_minutes":2,"thin_markout_bps":-0.2,"liquid_markout_bps":0.2,"observations":1786},{"horizon_minutes":5,"thin_markout_bps":-2.1,"liquid_markout_bps":0.3,"observations":1492},{"horizon_minutes":15,"thin_markout_bps":0.1,"liquid_markout_bps":-0.6,"observations":1102},{"horizon_minutes":30,"thin_markout_bps":-2.7,"liquid_markout_bps":-0.4,"observations":974},{"horizon_minutes":60,"thin_markout_bps":-1.7,"liquid_markout_bps":-0.4,"observations":869}],"shape":"ranking","sql":"WITH bars AS\n(\n    SELECT\n        ticker,\n        toStartOfMinute(toTimeZone(window_start, 'America/New_York')) AS et_minute,\n        toFloat64(open)   AS open_px,\n        toFloat64(close)  AS close_px,\n        toFloat64(volume) AS vol\n    FROM global_markets.delayed_stocks_minute_aggs\n    WHERE ticker IN ('SPY', 'AAPL', 'KO', 'SJM', 'LANC')\n      AND window_start >= '2026-07-01 00:00:00'\n      AND window_start <  '2026-09-26 00:00:00'\n      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60\n           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570\n      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60\n           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960\n      AND volume > 0\n      AND open > 0\n),\nranked AS\n(\n    SELECT\n        ticker,\n        avg(vol) AS avg_minute_vol\n    FROM bars\n    GROUP BY ticker\n    HAVING count() >= 500\n),\npicks AS\n(\n    SELECT\n        argMax(ticker, avg_minute_vol) AS liquid_ticker,\n        argMin(ticker, avg_minute_vol) AS thin_ticker\n    FROM ranked\n),\nsided AS\n(\n    SELECT\n        if(b.ticker = p.thin_ticker, 'thin', 'liquid') AS side,\n        b.et_minute                                    AS et_minute,\n        b.open_px                                      AS open_px,\n        b.close_px                                     AS close_px,\n        b.vol                                          AS vol\n    FROM bars AS b\n    CROSS JOIN picks AS p\n    WHERE b.ticker = p.thin_ticker\n       OR b.ticker = p.liquid_ticker\n),\nmean_vol AS\n(\n    SELECT side, avg(vol) AS avg_vol\n    FROM sided\n    GROUP BY side\n),\nevents AS\n(\n    SELECT\n        s.side                             AS side,\n        s.et_minute                        AS et_minute,\n        s.close_px                         AS fill_px,\n        if(s.close_px >= s.open_px, 1, -1) AS direction\n    FROM sided AS s\n    INNER JOIN mean_vol AS m ON m.side = s.side\n    WHERE s.vol >= 3 * m.avg_vol\n),\nhorizons AS\n(\n    SELECT\n        side,\n        fill_px,\n        direction,\n        hz,\n        et_minute + toIntervalMinute(hz) AS target_minute\n    FROM\n    (\n        SELECT side, et_minute, fill_px, direction, arrayJoin([1, 2, 5, 15, 30, 60]) AS hz\n        FROM events\n    )\n)\nSELECT\n    h.hz AS horizon_minutes,\n    round(avgIf(h.direction * (f.close_px / h.fill_px - 1) * 10000, h.side = 'thin'), 1)   AS thin_markout_bps,\n    round(avgIf(h.direction * (f.close_px / h.fill_px - 1) * 10000, h.side = 'liquid'), 1) AS liquid_markout_bps,\n    count() AS observations\nFROM horizons AS h\nINNER JOIN sided AS f ON f.side = h.side AND f.et_minute = h.target_minute\nGROUP BY h.hz\nHAVING countIf(h.side = 'thin') > 0\n   AND countIf(h.side = 'liquid') > 0\nORDER BY h.hz","computed_at":"2026-10-01T16:17:45.466622+00:00","elapsed":11.268230753}