Why OHLCV Models Fail: Estimating Slippage with DEX L2 Data
By Imbalance Labs Research
The Illusion of Liquidity
Every quantitative researcher starts with the same dataset: OHLCV candles. Open, High, Low, Close, Volume — the five pillars of traditional market analysis. These features are ubiquitous, free, and easy to source. They are also dangerously misleading.
The fundamental problem with OHLCV data is that it compresses an entire trading period into a single price range. A 5-minute BTC candle might show a high of $67,500 and a low of $67,200, suggesting a $300 spread. But what it doesn't tell you is whether there was enough liquidity at $67,500 to actually fill a meaningful order. In most cases, there wasn't.
This is the illusion of liquidity. A model trained on OHLCV data learns that prices “were” at certain levels. But it has no concept of how much capital was available at those levels. The result? Backtests that show beautiful equity curves, but fail catastrophically in live trading due to slippage, partial fills, and market impact.
What Level 2 Data Reveals
Level 2 orderbook data solves this problem by revealing the actual depth of the market. Instead of a single price point, you see the cumulative volume sitting at multiple price levels on both the bid and ask sides.
Our BTC historical orderbook dataset captures 10 levels of depth with precise metrics: bid_volume_level_1 through bid_volume_level_10, along with the basis-point distance from mid-price (bid_distance_level_1).
Critically, our data comes from Hyperliquid — a fully on-chain decentralized exchange. Unlike centralized exchanges (CEX) where market makers routinely engage in spoofing (placing and canceling large orders to manipulate price), DEX orderbooks represent genuine committed liquidity. Every order you see in our dataset was a real, on-chain commitment of capital.
Estimating Real Slippage
With L2 depth data, you can estimate real execution costs before placing a trade. For a buy order of size Q (in USD notional), sweep the ask side level by level and weight each level's distance by the notional filled there. Critically, the resting volume at each level must be converted to USD (volume × price) before comparing it against the remaining order — mixing token volume with a USD order size is the single most common bug in naive slippage code:
Slippage(Q) = Σᵢ ( fillᵢ × distanceᵢ ) / Q
where fillᵢ = min(remaining_usd, volumeᵢ × priceᵢ)
and remaining_usd is reduced by fillᵢ after each level iHere's how to compute this with our dataset:
import pandas as pd
# Load BTC Level 2 depth data
df = pd.read_parquet('btc_l2_depth_5m.parquet')
def estimate_slippage(row, order_size_usd=50_000):
"""Average slippage (bps) for a market buy of a given USD size."""
remaining = order_size_usd # USD notional left to fill
weighted_distance = 0.0
for level in range(1, 11):
vol = row[f'ask_volume_level_{level}'] # base-asset volume
dist = row[f'ask_distance_level_{level}'] # bps above mid-price
# Convert resting volume to USD notional before comparing to the order
level_price = row['close_price'] * (1 + dist / 10_000)
level_notional = vol * level_price # USD available at this level
filled = min(remaining, level_notional) # both sides in USD now
weighted_distance += filled * dist
remaining -= filled
if remaining <= 0:
break
filled_usd = order_size_usd - max(remaining, 0.0) # book may be too thin
return weighted_distance / filled_usd if filled_usd > 0 else 0.0 # avg bps
df['slippage_50k'] = df.apply(
lambda r: estimate_slippage(r, 50_000), axis=1
)
print(f"Median slippage for $50K order: {df['slippage_50k'].median():.2f} bps")
print(f"95th percentile: {df['slippage_50k'].quantile(0.95):.2f} bps")Building Robust Backtests
Armed with slippage estimates, you can build backtesting engines that model realistic execution. Instead of assuming fills at the close price (as most OHLCV backtests do), you can:
- Simulate order fills level-by-level through the orderbook
- Apply dynamic transaction costs based on actual depth
- Detect low-liquidity regimes where your strategy should reduce position size
- Model market impact for larger orders using depth decay functions
# Realistic volume-weighted fill price using top-5 levels of depth
def realistic_fill_price(row, side='buy', order_usd=10_000):
mid = row['close_price']
remaining = order_usd # USD notional left to fill
total_cost = 0.0 # USD spent
total_base = 0.0 # base-asset units acquired
levels = 'ask' if side == 'buy' else 'bid'
sign = 1 if side == 'buy' else -1 # ask is above mid, bid below
for i in range(1, 6):
vol = row[f'{levels}_volume_level_{i}'] # base-asset volume
dist_bps = row[f'{levels}_distance_level_{i}']
level_price = mid * (1 + sign * dist_bps / 10_000)
level_notional = vol * level_price # USD available here
filled = min(remaining, level_notional) # USD filled at level i
total_cost += filled
total_base += filled / level_price # base units filled
remaining -= filled
if remaining <= 0:
break
# Volume-weighted average fill price (VWAP); falls back to mid if unfilled
return total_cost / total_base if total_base > 0 else midConclusion
OHLCV data is a useful starting point, but it is fundamentally insufficient for serious quantitative research. The gap between backtested performance and live performance — the so-called “backtest-to-live decay” — is largely driven by unrealistic assumptions about liquidity and execution costs. Level 2 orderbook depth data, especially from transparent on-chain venues like Hyperliquid, provides the foundation for models that actually work in production.
Get the Data
Start building slippage-aware models with institutional-grade orderbook depth data across 24 crypto instruments.
Full 47-column schema documentation available.