Last updated: 2026-04-28 | Reading time: 12 minutes | Technical complexity: Intermediate
Introduction
When I was building a high-frequency arbitrage bot last quarter, I spent three frustrating weeks trying to reconstruct historical order book snapshots from Hyperliquid's websocket streams. The real-time data was abundant, but accessing clean historical order book data for backtesting felt like pulling teeth. That's when I discovered Tardis.dev's comprehensive market data relay—and the integration turned out to be remarkably straightforward once you understand the data structure.
This tutorial walks you through the complete workflow for retrieving Hyperliquid historical order book data using the Tardis API, with production-ready Python code that you can copy-paste and run immediately. Whether you're building a backtesting framework, training a market microstructure model, or analyzing liquidity patterns, by the end of this guide you'll have a fully functional data pipeline fetching order book snapshots with sub-second granularity.
What is Hyperliquid and Why Order Book Data Matters
Hyperliquid is a high-performance decentralized perpetual futures exchange that has gained significant traction among algorithmic traders. Unlike centralized exchanges, Hyperliquid offers on-chain settlement with CEX-level speed, making it attractive for strategies that require both decentralization and low latency.
The order book is the foundation of market microstructure analysis. It contains:
- Bid/Ask levels — All price points where traders have placed limit orders
- Quantity at each level — How much volume sits at each price point
- Order flow imbalance — Whether buy or sell pressure is dominant
- Spread dynamics — The gap between best bid and best ask
For backtesting purposes, historical order book data allows you to replay market conditions with precise liquidity information—something that simple OHLCV candles simply cannot provide.
Tardis.dev: The Data Relay Layer
Tardis.dev (note: HolySheep AI partners with Tardis for crypto market data relay) provides normalized, real-time and historical market data from over 40 exchanges including Binance, Bybit, OKX, Deribit, and Hyperliquid. The service handles the complexity of exchange-specific WebSocket protocols, message normalization, and data storage, delivering clean, consistent data through a unified API.
Key advantages of using Tardis for Hyperliquid data:
- Normalized message format across all exchanges
- Historical data going back to exchange launch
- Sub-second granularity for order book snapshots
- No WebSocket complexity—simple REST and streaming APIs
- Direct S3/Google Cloud storage for bulk historical data
Prerequisites and Setup
Before diving into the code, ensure you have:
- Python 3.9 or higher
- A Tardis.dev API key (free tier available with 30-day data access)
- Basic understanding of REST APIs
- The requests library:
pip install requests pandas
For production AI workloads that consume this market data, consider using HolySheep AI as your inference layer—we offer sub-50ms latency at $1 per dollar (saving 85%+ versus ¥7.3 pricing), with WeChat and Alipay payment support for convenient onboarding.
Python Code: Complete Integration Examples
Example 1: Fetching Historical Order Book Snapshots
#!/usr/bin/env python3
"""
Hyperliquid Order Book Data Fetcher using Tardis.dev API
Handles both REST polling and real-time streaming modes
"""
import requests
import pandas as pd
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional
============================================================
CONFIGURATION
============================================================
TARDIS_API_KEY = "YOUR_TARDIS_API_KEY" # Get from https://tardis.dev
BASE_URL = "https://api.tardis.dev/v1"
Hyperliquid-specific exchange ID on Tardis
EXCHANGE_ID = "hyperliquid"
Trading pair configuration
SYMBOL = "BTC-PERP" # Hyperliquid perpetual contract
class HyperliquidOrderBookFetcher:
"""Fetches and processes Hyperliquid historical order book data."""
def __init__(self, api_key: str):
self.api_key = api_key
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def get_available_symbols(self) -> List[Dict]:
"""Retrieve all available trading symbols for Hyperliquid."""
url = f"{BASE_URL}/exchanges/{EXCHANGE_ID}/symbols"
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json()
def fetch_order_book_snapshot(
self,
symbol: str,
from_ts: int,
to_ts: int,
limit: int = 100
) -> pd.DataFrame:
"""
Fetch historical order book snapshots within a time range.
Args:
symbol: Trading pair (e.g., "BTC-PERP")
from_ts: Start timestamp in milliseconds
to_ts: End timestamp in milliseconds
limit: Maximum number of snapshots to return (max 1000)
Returns:
DataFrame with order book snapshots containing:
- timestamp: Unix timestamp in milliseconds
- asks: List of [price, quantity] pairs
- bids: List of [price, quantity] pairs
- spread: Best ask minus best bid
- mid_price: Average of best bid and ask
"""
url = f"{BASE_URL}/exchanges/{EXCHANGE_ID}/orderbooks"
params = {
"symbol": symbol,
"from": from_ts,
"to": to_ts,
"limit": limit,
"format": "json"
}
response = requests.get(url, params=params, headers=self.headers)
response.raise_for_status()
raw_data = response.json()
if not raw_data or len(raw_data) == 0:
return pd.DataFrame()
# Parse and structure the order book data
snapshots = []
for entry in raw_data:
snapshot = {
"timestamp": entry.get("timestamp"),
"date": pd.to_datetime(entry["timestamp"], unit="ms"),
"asks": entry.get("asks", []),
"bids": entry.get("bids", []),
}
# Calculate derived metrics
if snapshot["asks"] and snapshot["bids"]:
best_ask = float(snapshot["asks"][0][0])
best_bid = float(snapshot["bids"][0][0])
snapshot["best_ask"] = best_ask
snapshot["best_bid"] = best_bid
snapshot["spread"] = best_ask - best_bid
snapshot["mid_price"] = (best_ask + best_bid) / 2
snapshot["spread_bps"] = (snapshot["spread"] / snapshot["mid_price"]) * 10000
# Calculate weighted mid price (order quantity weighted)
total_bid_qty = sum(float(b[1]) for b in snapshot["bids"][:5])
total_ask_qty = sum(float(a[1]) for a in snapshot["asks"][:5])
snapshot["bid_depth_5"] = total_bid_qty
snapshot["ask_depth_5"] = total_ask_qty
snapshot["order_imbalance"] = (total_bid_qty - total_ask_qty) / (total_bid_qty + total_ask_qty)
snapshots.append(snapshot)
return pd.DataFrame(snapshots)
def fetch_and_save_daily_data(
self,
symbol: str,
date: str,
output_path: str = "order_book_data.parquet"
) -> pd.DataFrame:
"""
Convenience method: Fetch full day of order book data at 1-minute intervals.
Args:
symbol: Trading pair
date: Date string in format "YYYY-MM-DD"
output_path: Path to save Parquet file
Returns:
DataFrame with all snapshots for the day
"""
# Parse date and convert to timestamps
target_date = datetime.strptime(date, "%Y-%m-%d")
from_ts = int(target_date.timestamp() * 1000)
to_ts = int((target_date + timedelta(days=1)).timestamp() * 1000)
print(f"Fetching {symbol} order book data for {date}...")
all_snapshots = []
# Fetch in chunks (Tardis returns max 1000 per request)
current_from = from_ts
while current_from < to_ts:
chunk_to = min(current_from + (60 * 60 * 1000), to_ts) # 1 hour chunks
df = self.fetch_order_book_snapshot(
symbol=symbol,
from_ts=current_from,
to_ts=chunk_to,
limit=1000
)
if len(df) > 0:
all_snapshots.append(df)
print(f" Retrieved {len(df)} snapshots from {df['date'].min()} to {df['date'].max()}")
current_from = chunk_to
time.sleep(0.1) # Rate limiting
if all_snapshots:
combined_df = pd.concat(all_snapshots, ignore_index=True)
combined_df.to_parquet(output_path)
print(f"Saved {len(combined_df)} snapshots to {output_path}")
return combined_df
else:
print("No data retrieved for the specified date.")
return pd.DataFrame()
============================================================
USAGE EXAMPLE
============================================================
if __name__ == "__main__":
# Initialize fetcher
fetcher = HyperliquidOrderBookFetcher(api_key=TARDIS_API_KEY)
# Check available symbols
symbols = fetcher.get_available_symbols()
print(f"Available Hyperliquid symbols: {len(symbols)}")
for sym in symbols[:5]:
print(f" - {sym.get('symbol')} ({sym.get('base')}/{sym.get('quote')})")
# Fetch a specific day's data
df = fetcher.fetch_and_save_daily_data(
symbol="BTC-PERP",
date="2026-04-20",
output_path="hyperliquid_btc_perp_2026_04_20.parquet"
)
if len(df) > 0:
print("\n=== Data Summary ===")
print(f"Total snapshots: {len(df)}")
print(f"Time range: {df['date'].min()} to {df['date'].max()}")
print(f"Average spread: {df['spread_bps'].mean():.2f} bps")
print(f"Average order imbalance: {df['order_imbalance'].mean():.4f}")
# Display sample data
print("\n=== Sample Data (first 5 rows) ===")
display_cols = ["date", "best_bid", "best_ask", "spread", "mid_price", "order_imbalance"]
print(df[display_cols].head())
Example 2: Real-Time Order Book Streaming
#!/usr/bin/env python3
"""
Real-time Hyperliquid Order Book Streaming Client
Uses Tardis WebSocket API for live data
"""
import json
import asyncio
import aiohttp
from typing import Callable, Optional
from dataclasses import dataclass
from datetime import datetime
For production use, integrate with your HolySheep AI workflow
Sign up at https://www.holysheep.ai/register for sub-50ms inference
@dataclass
class OrderBookLevel:
"""Represents a single price level in the order book."""
price: float
quantity: float
@classmethod
def from_list(cls, data: list) -> "OrderBookLevel":
return cls(price=float(data[0]), quantity=float(data[1]))
class OrderBookState:
"""Maintains current order book state with efficient updates."""
def __init__(self, symbol: str):
self.symbol = symbol
self.bids: dict[float, float] = {} # price -> quantity
self.asks: dict[float, float] = {}
self.last_update_ts: Optional[int] = None
self.message_count: int = 0
def apply_snapshot(self, bids: list, asks: list, timestamp: int):
"""Apply a full order book snapshot."""
self.bids = {float(b[0]): float(b[1]) for b in bids}
self.asks = {float(a[0]): float(a[1]) for a in asks}
self.last_update_ts = timestamp
self.message_count += 1
def apply_delta(self, updates: dict, timestamp: int):
"""Apply incremental order book update."""
self.last_update_ts = timestamp
self.message_count += 1
# Process bid updates
if "b" in updates: # bids
for level in updates["b"]:
price, qty = float(level[0]), float(level[1])
if qty == 0:
self.bids.pop(price, None)
else:
self.bids[price] = qty
# Process ask updates
if "a" in updates: # asks
for level in updates["a"]:
price, qty = float(level[0]), float(level[1])
if qty == 0:
self.asks.pop(price, None)
else:
self.asks[price] = qty
@property
def best_bid(self) -> Optional[float]:
return max(self.bids.keys()) if self.bids else None
@property
def best_ask(self) -> Optional[float]:
return min(self.asks.keys()) if self.asks else None
@property
def mid_price(self) -> Optional[float]:
if self.best_bid and self.best_ask:
return (self.best_bid + self.best_ask) / 2
return None
@property
def spread_bps(self) -> Optional[float]:
if self.mid_price and self.spread:
return (self.spread / self.mid_price) * 10000
return None
@property
def spread(self) -> Optional[float]:
if self.best_bid and self.best_ask:
return self.best_ask - self.best_bid
return None
def get_top_levels(self, n: int = 10) -> dict:
"""Get top N levels from both sides."""
sorted_bids = sorted(self.bids.items(), reverse=True)[:n]
sorted_asks = sorted(self.asks.items())[:n]
return {
"bids": [(p, q) for p, q in sorted_bids],
"asks": [(p, q) for p, q in sorted_asks]
}
def get_order_imbalance(self, depth: int = 5) -> float:
"""Calculate order imbalance ratio."""
top_bids = sorted(self.bids.keys(), reverse=True)[:depth]
top_asks = sorted(self.asks.keys())[:depth]
bid_volume = sum(self.bids[p] for p in top_bids)
ask_volume = sum(self.asks[p] for p in top_asks)
if bid_volume + ask_volume == 0:
return 0.0
return (bid_volume - ask_volume) / (bid_volume + ask_volume)
class HyperliquidStreamClient:
"""
Real-time streaming client for Hyperliquid order book data via Tardis WebSocket.
"""
WS_BASE_URL = "wss://api.tardis.dev/v1/stream"
def __init__(self, api_key: str):
self.api_key = api_key
self.session: Optional[aiohttp.ClientSession] = None
self.websocket: Optional[aiohttp.ClientWebSocketResponse] = None
self.order_books: dict[str, OrderBookState] = {}
self.is_connected: bool = False
self._run_task: Optional[asyncio.Task] = None
async def connect(self, symbols: list[str]):
"""Establish WebSocket connection and subscribe to symbols."""
self.session = aiohttp.ClientSession()
# Construct subscription message
subscribe_msg = {
"method": "subscribe",
"params": {
"channel": "orderbook",
"exchange": "hyperliquid",
"symbols": symbols
},
"id": 1
}
try:
self.websocket = await self.session.ws_connect(
self.WS_BASE_URL,
headers={"Authorization": f"Bearer {self.api_key}"}
)
await self.websocket.send_json(subscribe_msg)
self.is_connected = True
# Initialize order book state for each symbol
for symbol in symbols:
self.order_books[symbol] = OrderBookState(symbol)
print(f"Connected to Tardis WebSocket, subscribed to: {symbols}")
except aiohttp.ClientError as e:
print(f"Failed to connect: {e}")
self.is_connected = False
raise
async def _message_handler(self, callback: Optional[Callable] = None):
"""Process incoming WebSocket messages."""
async for msg in self.websocket:
if msg.type == aiohttp.WSMsgType.TEXT:
try:
data = json.loads(msg.data)
await self._process_message(data, callback)
except json.JSONDecodeError:
print(f"Invalid JSON: {msg.data}")
elif msg.type == aiohttp.WSMsgType.ERROR:
print(f"WebSocket error: {msg.data}")
break
elif msg.type == aiohttp.WSMsgType.CLOSED:
print("WebSocket connection closed")
break
async def _process_message(self, data: dict, callback: Optional[Callable]):
"""Process and route incoming data messages."""
msg_type = data.get("type") or data.get("channel")
if msg_type == "subscribed":
print(f"Subscription confirmed: {data}")
return
if msg_type == "orderbook":
payload = data.get("data", {})
symbol = payload.get("symbol")
timestamp = data.get("timestamp", 0)
if symbol not in self.order_books:
self.order_books[symbol] = OrderBookState(symbol)
ob = self.order_books[symbol]
# Apply update (Tardis sends both snapshots and deltas)
if "snapshot" in payload:
ob.apply_snapshot(
payload["snapshot"]["bids"],
payload["snapshot"]["asks"],
timestamp
)
elif "update" in payload:
ob.apply_delta(payload["update"], timestamp)
# Invoke callback with current state
if callback:
await callback(symbol, ob, timestamp)
async def start_streaming(
self,
symbols: list[str],
callback: Optional[Callable] = None,
duration_seconds: Optional[int] = None
):
"""
Start streaming order book data.
Args:
symbols: List of trading symbols to subscribe to
callback: Async function called on each update: func(symbol, orderbook, timestamp)
duration_seconds: Optional streaming duration limit
"""
await self.connect(symbols)
async def default_callback(symbol: str, ob: OrderBookState, ts: int):
"""Default logging callback for demonstration."""
if ob.message_count % 100 == 0: # Log every 100 messages
print(f"[{datetime.fromtimestamp(ts/1000):%H:%M:%S.%f}] "
f"{symbol}: bid={ob.best_bid:.2f} ask={ob.best_ask:.2f} "
f"spread={ob.spread_bps:.2f}bps imbalance={ob.get_order_imbalance():.4f}")
handler = callback or default_callback
self._run_task = asyncio.create_task(self._message_handler(handler))
if duration_seconds:
await asyncio.sleep(duration_seconds)
await self.stop()
else:
await self._run_task
async def stop(self):
"""Gracefully close the WebSocket connection."""
self.is_connected = False
if self._run_task:
self._run_task.cancel()
try:
await self._run_task
except asyncio.CancelledError:
pass
if self.websocket:
await self.websocket.close()
if self.session:
await self.session.close()
print("Connection closed")
============================================================
USAGE EXAMPLE: Real-time Order Imbalance Detector
============================================================
async def imbalance_detector(symbol: str, ob: OrderBookState, timestamp: int):
"""Detect significant order imbalances for trading signals."""
imbalance = ob.get_order_imbalance(depth=5)
threshold = 0.3 # 30% imbalance threshold
if abs(imbalance) > threshold:
signal = "BUY" if imbalance > 0 else "SELL"
print(f"\n🚨 SIGNAL: {signal}")
print(f" Symbol: {symbol}")
print(f" Imbalance: {imbalance:.4f} (threshold: ±{threshold})")
print(f" Bid depth: {sum(ob.bids[p] for p in sorted(ob.bids.keys(), reverse=True)[:5]):.4f}")
print(f" Ask depth: {sum(ob.asks[p] for p in sorted(ob.asks.keys())[:5]):.4f}")
print(f" Time: {datetime.fromtimestamp(timestamp/1000)}\n")
async def main():
"""Example: Stream BTC-PERP order book for 60 seconds."""
client = HyperliquidStreamClient(api_key="YOUR_TARDIS_API_KEY")
try:
print("Starting Hyperliquid order book stream...")
print("Press Ctrl+C to stop early\n")
await client.start_streaming(
symbols=["BTC-PERP", "ETH-PERP"],
callback=imbalance_detector,
duration_seconds=60
)
except KeyboardInterrupt:
print("\nInterrupted by user")
finally:
await client.stop()
if __name__ == "__main__":
asyncio.run(main())
Example 3: Backtesting Integration with pandas
#!/usr/bin/env python3
"""
Backtesting Example: Order Book Imbalance Strategy on Hyperliquid Data
Combines Tardis historical data with strategy execution simulation
"""
import pandas as pd
import numpy as np
from pathlib import Path
from dataclasses import dataclass
from typing import List, Tuple
import matplotlib.pyplot as plt
Configuration
TARDIS_API_KEY = "YOUR_TARDIS_API_KEY"
Strategy parameters
IMBALANCE_THRESHOLD = 0.25 # Enter when imbalance exceeds this
SPREAD_THRESHOLD_BPS = 5.0 # Minimum spread to consider trade
POSITION_SIZE = 1.0 # Contracts per trade
CLOSEOUT_THRESHOLD = 0.05 # Exit when imbalance reverts below this
@dataclass
class Trade:
"""Represents a single executed trade."""
entry_time: pd.Timestamp
entry_price: float
direction: str # "LONG" or "SHORT"
size: float
exit_time: Optional[pd.Timestamp] = None
exit_price: Optional[float] = None
pnl: Optional[float] = None
def close(self, exit_time: pd.Timestamp, exit_price: float):
self.exit_time = exit_time
self.exit_price = exit_price
if self.direction == "LONG":
self.pnl = (exit_price - self.entry_price) * self.size
else:
self.pnl = (self.entry_price - exit_price) * self.size
class OrderBookBacktester:
"""
Backtests strategies using historical Hyperliquid order book data.
"""
def __init__(self, data_path: str):
self.df = pd.read_parquet(data_path)
self.df = self.df.sort_values("timestamp").reset_index(drop=True)
self.trades: List[Trade] = []
self.current_position: Optional[Trade] = None
# Precompute features
self._compute_features()
def _compute_features(self):
"""Compute additional features for strategy decisions."""
# Rolling order imbalance
self.df["imbalance_rolling"] = self.df["order_imbalance"].rolling(5).mean()
# Price momentum
self.df["price_change"] = self.df["mid_price"].pct_change()
self.df["price_momentum"] = self.df["price_change"].rolling(10).sum()
# Volume proxy (order book depth)
self.df["total_depth"] = self.df["bid_depth_5"] + self.df["ask_depth_5"]
# Fill NaN values
self.df = self.df.fillna(method="ffill").fillna(0)
def run_backtest(self) -> pd.DataFrame:
"""
Execute the backtest simulation.
Strategy logic:
1. Enter LONG when positive imbalance exceeds threshold AND spread is wide enough
2. Enter SHORT when negative imbalance exceeds threshold AND spread is wide enough
3. Exit when imbalance reverts (approaches zero)
"""
print(f"Running backtest on {len(self.df)} order book snapshots...")
for idx, row in self.df.iterrows():
timestamp = row["date"]
mid_price = row["mid_price"]
imbalance = row["imbalance_rolling"]
spread_bps = row["spread_bps"]
# Check if we should enter a position
if self.current_position is None:
# Check LONG entry
if imbalance > IMBALANCE_THRESHOLD and spread_bps > SPREAD_THRESHOLD_BPS:
self.current_position = Trade(
entry_time=timestamp,
entry_price=mid_price,
direction="LONG",
size=POSITION_SIZE
)
print(f"[{timestamp}] LONG entered @ {mid_price:.4f}")
# Check SHORT entry
elif imbalance < -IMBALANCE_THRESHOLD and spread_bps > SPREAD_THRESHOLD_BPS:
self.current_position = Trade(
entry_time=timestamp,
entry_price=mid_price,
direction="SHORT",
size=POSITION_SIZE
)
print(f"[{timestamp}] SHORT entered @ {mid_price:.4f}")
# Check if we should exit current position
else:
should_exit = False
# Exit LONG when imbalance reverts
if self.current_position.direction == "LONG":
if imbalance < CLOSEOUT_THRESHOLD:
should_exit = True
# Exit SHORT when imbalance reverts
elif self.current_position.direction == "SHORT":
if imbalance > -CLOSEOUT_THRESHOLD:
should_exit = True
if should_exit:
self.current_position.close(timestamp, mid_price)
self.trades.append(self.current_position)
pnl = self.current_position.pnl
direction = self.current_position.direction
print(f"[{timestamp}] {direction} closed @ {mid_price:.4f}, PnL: {pnl:.4f}")
self.current_position = None
# Close any remaining position at end
if self.current_position:
last_row = self.df.iloc[-1]
self.current_position.close(last_row["date"], last_row["mid_price"])
self.trades.append(self.current_position)
print(f"[END] Position closed @ {last_row['mid_price']:.4f}")
return self._generate_results()
def _generate_results(self) -> pd.DataFrame:
"""Generate performance metrics and trade history."""
if not self.trades:
print("No trades executed during backtest period.")
return pd.DataFrame()
trades_df = pd.DataFrame([
{
"entry_time": t.entry_time,
"exit_time": t.exit_time,
"direction": t.direction,
"entry_price": t.entry_price,
"exit_price": t.exit_price,
"pnl": t.pnl,
"duration_minutes": (t.exit_time - t.entry_time).total_seconds() / 60
}
for t in self.trades
])
# Calculate metrics
total_pnl = trades_df["pnl"].sum()
winning_trades = trades_df[trades_df["pnl"] > 0]
losing_trades = trades_df[trades_df["pnl"] <= 0]
win_rate = len(winning_trades) / len(trades_df) * 100
avg_win = winning_trades["pnl"].mean() if len(winning_trades) > 0 else 0
avg_loss = losing_trades["pnl"].mean() if len(losing_trades) > 0 else 0
profit_factor = abs(winning_trades["pnl"].sum() / losing_trades["pnl"].sum()) if len(losing_trades) > 0 and losing_trades["pnl"].sum() != 0 else float('inf')
print("\n" + "=" * 60)
print("BACKTEST RESULTS")
print("=" * 60)
print(f"Total Trades: {len(trades_df)}")
print(f"Win Rate: {win_rate:.2f}%")
print(f"Total PnL: {total_pnl:.4f}")
print(f"Average Win: {avg_win:.4f}")
print(f"Average Loss: {avg_loss:.4f}")
print(f"Profit Factor: {profit_factor:.2f}")
print(f"Max Drawdown: {self._calculate_max_drawdown(trades_df):.4f}")
print("=" * 60)
return trades_df
def _calculate_max_drawdown(self, trades_df: pd.DataFrame) -> float:
"""Calculate maximum drawdown from cumulative PnL."""
if len(trades_df) == 0:
return 0.0
cumulative = trades_df["pnl"].cumsum()
running_max = cumulative.cummax()
drawdown = cumulative - running_max
return drawdown.min()
============================================================
USAGE
============================================================
if __name__ == "__main__":
# Load pre-fetched data (from Example 1)
data_path = "hyperliquid_btc_perp_2026_04_20.parquet"
if Path(data_path).exists():
backtester = OrderBookBacktester(data_path)
results = backtester.run_backtest()
else:
print(f"Data file not found: {data_path}")
print("Run Example 1 first to fetch order book data.")
Data Format Reference
Tardis.dev normalizes Hyperliquid order book data into a consistent format across all exchanges. Here's the structure you'll receive:
| Field | Type | Description | Example |
|---|---|---|---|
| timestamp | integer | Unix timestamp in milliseconds | 1713567600000 |
| symbol | string | Trading pair identifier | BTC-PERP |
| asks | array | List of [price, quantity] for asks | [[64250.5, 2.1], [64251.0, 0.5]] |
| bids | array | List of [price, quantity] for bids | [[64249.5, 1.8], [64248.0, 3.2]] |
| type | string | Message type: "snapshot" or "update" | snapshot |
API Rate Limits and Quotas
Understanding Tardis API limits is crucial for production deployments:
| Plan Tier | Historical Data Access | Requests/Minute | WebSocket Connections | Price (Monthly) |
|---|---|---|---|---|
| Free | Last 30 days | 60 | 1 | $0 |
| Starter | Last 12 months | 300 | 3 | $49 |
| Pro | Full history | 1000 | 10 | $199 |
| Enterprise | Full history + custom | Unlimited | Unlimited | Custom |
Who This Tutorial Is For
This Guide is Perfect For:
- Algorithmic traders building backtesting frameworks for Hyperliquid strategies
- Quantitative researchers analyzing order book dynamics and market microstructure
- Data scientists training ML models on historical liquidity patterns
- DeFi developers building analytics dashboards or monitoring systems
- Academic researchers studying perpetual futures markets
This Guide is NOT For:
- Traders looking for real-time trading signals (use websocket streaming, not historical)
- Those needing data from exchanges other than Hyperliquid (Tardis supports 40+ exchanges)
- Developers without programming experience (basic Python skills required)
- Users needing tick-by-tick data for ultra-low latency strategies (consider direct exchange APIs)
Pricing and ROI Analysis
When calculating the return on investment for order book data access, consider these factors:
| Cost Factor | Tardis.dev (Starter) | Direct Exchange API | HolySheep AI (for downstream processing) |
|---|---|---|---|
| Monthly Cost | $49 | $0 (free tier) | $1 per $1 (¥1 pricing) |
| Data Normalization | Included | Custom implementation | N/A |
| Historical Depth | 12 months | Varies by exchange | N/A |
| Implementation Time | 1-2 days | 2-4 weeks | Hours (for AI inference) |
| Maintenance Overhead | Low | High | Minimal |
ROI Calculation: If your development time is valued at $100/hour, building custom exchange integrations (20+ hours) versus using Tardis (4 hours) saves approximately $1,600 in development costs. The $49 monthly subscription