Market Microstructure·8 min read·Dec 15, 2025

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 i

Here'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 mid

Conclusion

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.