impact_curve
Answered against 22 years of US equities and 12 years of US options data and published with the query that produced it. This result is stored as of 2026-10-01, from backtesting-in-illiquid-markets.
| volume_quintile | thin_abs_move_bps | liquid_abs_move_bps | thin_over_liquid |
|---|---|---|---|
| Q1 | 1.2 | 1.1 | 1.18 |
| Q2 | 2.8 | 1.3 | 2.09 |
| Q3 | 3.9 | 1.7 | 2.33 |
| Q4 | 5.3 | 2 | 2.61 |
| Q5 | 7.9 | 2.7 | 2.95 |
- Rows × columns
- 5 × 4
- Computed
- Completeness
- No missing values
- Source
- US exchange, SIP and OPRA market data
- Licence
- Strasmore terms · free, no signup
What each column holds
| Column | Type | Range | Notes |
|---|---|---|---|
volume_quintile |
text | 5 distinct values (Q1, Q2, Q3…) | |
thin_abs_move_bps |
number | 1.2 to 7.9 | |
liquid_abs_move_bps |
number | 1.1 to 2.7 | |
thin_over_liquid |
number | 1.18 to 2.95 |
Computed from Strasmore's warehouse of US exchange, SIP and OPRA market data. Equity prices are delayed; options greeks and implied volatility are end-of-day. This result is stored, not recomputed on load — it is exactly the numbers that were returned on , and the query below is what returned them.
Run it yourself
This is the exact query behind the result above. Change a ticker, a date or a column and run it against the warehouse — no account, no key. The no-signup tier is smaller than the one this page was computed on; a query that reaches past it comes back saying which plan runs it.
WITH bars AS
(
SELECT
ticker,
toStartOfMinute(toTimeZone(window_start, 'America/New_York')) AS et_minute,
toFloat64(open) AS open_px,
toFloat64(close) AS close_px,
toFloat64(volume) AS vol
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY', 'AAPL', 'KO', 'SJM', 'LANC')
AND window_start >= '2026-07-01 00:00:00'
AND window_start < '2026-09-26 00:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
AND volume > 0
AND open > 0
),
ranked AS
(
SELECT
ticker,
avg(vol) AS avg_minute_vol
FROM bars
GROUP BY ticker
HAVING count() >= 500
),
picks AS
(
SELECT
argMax(ticker, avg_minute_vol) AS liquid_ticker,
argMin(ticker, avg_minute_vol) AS thin_ticker
FROM ranked
),
sided AS
(
SELECT
if(b.ticker = p.thin_ticker, 'thin', 'liquid') AS side,
b.et_minute AS et_minute,
b.open_px AS open_px,
b.close_px AS close_px,
b.vol AS vol
FROM bars AS b
CROSS JOIN picks AS p
WHERE b.ticker = p.thin_ticker
OR b.ticker = p.liquid_ticker
),
cuts AS
(
SELECT
side,
quantilesDeterministic(0.2, 0.4, 0.6, 0.8)(vol, toUInt64(toUnixTimestamp(et_minute))) AS edges
FROM sided
GROUP BY side
),
tagged AS
(
SELECT
s.side AS side,
1 + length(arrayFilter(x -> s.vol >= x, c.edges)) AS quintile,
abs(s.close_px / s.open_px - 1) * 10000 AS move_bps
FROM sided AS s
INNER JOIN cuts AS c ON c.side = s.side
)
SELECT
concat('Q', toString(quintile)) AS volume_quintile,
round(avgIf(move_bps, side = 'thin'), 1) AS thin_abs_move_bps,
round(avgIf(move_bps, side = 'liquid'), 1) AS liquid_abs_move_bps,
round(avgIf(move_bps, side = 'thin')
/ avgIf(move_bps, side = 'liquid'), 2) AS thin_over_liquid
FROM tagged
GROUP BY quintile
HAVING countIf(side = 'thin') > 0
AND countIf(side = 'liquid') > 0
ORDER BY quintile
Work with this data in your AI assistant
Opens ready to query, with this page's data. Free, no account.