High-frequency quantitative trading demands tick-perfect market data. Whether you are building a mean-reversion arbitrage engine, testing latency-sensitive market-making strategies, or training a machine learning model on order flow dynamics, the quality of your historical data determines whether your backtests predict real-world performance — or mislead you into catastrophic losses.
In this hands-on guide, I will walk you through the complete process of downloading Bybit trade data and full order book snapshots using Tardis Machine, one of the industry's most reliable cryptocurrency market data providers. By the end of this tutorial, you will have a fully functional Python pipeline that fetches, parses, and stores millions of Bybit trades and level-2 order book updates — ready for your backtesting engine.
Note: While Tardis provides the raw market data infrastructure, many quantitative teams use HolySheep AI to process and analyze this data with large language models — turning raw tick data into actionable strategy insights at a fraction of traditional costs (DeepSeek V3.2 at $0.42/MTok versus $7.3 for comparable Chinese APIs).
What is Tardis Machine and Why Bybit?
Tardis Machine is a commercial-grade cryptocurrency market data aggregator that provides historical and real-time data from over 40 exchanges, including Binance, Bybit, OKX, Deribit, and many others. Unlike free public APIs (which impose strict rate limits, lack historical depth, and often drop data during high-volatility periods), Tardis offers:
- Full historical order book snapshots — Level-2 data with every price level, not just top-of-book
- Trade-by-trade execution data — Exact timestamps, volumes, side, and taker/maker classification
- Funding rate history — Critical for perpetual futures strategy backtesting
- Sub-second timestamp precision — Essential for latency arbitrage and market microstructure analysis
- Exchange-native normalization — Consistent data schema across all supported exchanges
Why Bybit? Bybit is the second-largest crypto perpetual futures exchange by open interest (behind Binance), with over $10 billion in daily trading volume as of 2026. It offers some of the most liquid BTC/USDT and ETH/USDT markets, making it ideal for testing strategies before deploying capital. Bybit's API structure also closely mirrors Deribit, enabling relatively straightforward strategy porting for options traders.
Prerequisites
Before we begin, ensure you have the following installed:
- Python 3.9+ — I recommend Python 3.11 for optimal performance with async data processing
- pip or conda for package management
- A Tardis Machine account — They offer a 14-day free trial with 1M API credits
- (Optional) HolySheep AI account — For AI-powered analysis of your downloaded dataset
Step 1: Install Dependencies
Create a new Python virtual environment and install the required packages. I prefer using polars for its blazing-fast DataFrame operations on large datasets — processing 10M+ trades with pandas can take minutes; polars does it in seconds.
# Create virtual environment
python -m venv tardis-env
source tardis-env/bin/activate # On Windows: tardis-env\Scripts\activate
Install dependencies
pip install requests polars pyarrow httpx aiofiles pandas numpy
pip install python-dotenv # For secure API key management
Step 2: Configure Your API Credentials
Never hardcode API keys in your scripts. Create a .env file in your project root:
# .env file - NEVER commit this to version control
TARDIS_API_KEY=your_tardis_machine_api_key_here
HOLYSHEEP_API_KEY=your_holysheep_api_key_here # Optional: for AI analysis
Load these credentials securely in your Python script:
import os
from dotenv import load_dotenv
load_dotenv() # Load environment variables from .env file
TARDIS_API_KEY = os.getenv("TARDIS_API_KEY")
HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY")
if not TARDIS_API_KEY:
raise ValueError("TARDIS_API_KEY not found. Please set it in your .env file.")
Step 3: Download Bybit Trade Data
Now we fetch historical trade data. Tardis provides a straightforward REST API for historical data retrieval. The following script downloads BTC/USDT perpetual trades from Bybit for a specific date range — perfect for backtesting a specific market regime.
import requests
import polars as pl
from datetime import datetime, timedelta
import time
BASE_URL = "https://api.tardis.dev/v1"
def download_bybit_trades(symbol: str, start_date: str, end_date: str) -> pl.DataFrame:
"""
Download historical trade data from Bybit via Tardis API.
Args:
symbol: Trading pair symbol (e.g., "BTCUSDT")
start_date: Start date in YYYY-MM-DD format
end_date: End date in YYYY-MM-DD format
Returns:
Polars DataFrame with trade data
"""
# Convert dates to timestamps
start_ts = int(datetime.strptime(start_date, "%Y-%m-%d").timestamp() * 1000)
end_ts = int(datetime.strptime(end_date, "%Y-%m-%d").timestamp() * 1000)
# Build API request
params = {
"exchange": "bybit",
"symbol": symbol,
"from": start_ts,
"to": end_ts,
"limit": 10000, # Max records per page
}
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {TARDIS_API_KEY}"
}
all_trades = []
page = 1
print(f"Downloading {symbol} trades from {start_date} to {end_date}...")
while True:
params["page"] = page
response = requests.get(
f"{BASE_URL}/trades",
params=params,
headers=headers,
timeout=30
)
if response.status_code != 200:
print(f"Error: HTTP {response.status_code}")
print(response.text)
break
data = response.json()
trades = data.get("trades", [])
if not trades:
break
all_trades.extend(trades)
print(f" Page {page}: Retrieved {len(trades)} trades (Total: {len(all_trades)})")
if len(trades) < params["limit"]:
break # No more pages
page += 1
time.sleep(0.1) # Rate limiting - be respectful to the API
# Convert to Polars DataFrame
df = pl.DataFrame(all_trades)
# Rename and type columns for consistency
if not df.is_empty():
df = df.rename({
"id": "trade_id",
"price": "price",
"amount": "amount",
"side": "side",
"timestamp": "timestamp",
})
df = df.with_columns([
pl.col("timestamp").str.to_datetime(),
pl.col("price").cast(pl.Float64),
pl.col("amount").cast(pl.Float64),
])
return df
Example: Download 1 week of BTC/USDT trades
trades_df = download_bybit_trades(
symbol="BTCUSDT",
start_date="2026-04-01",
end_date="2026-04-08"
)
print(f"\nDownloaded {len(trades_df)} trades")
print(trades_df.head(10))
Step 4: Download Order Book Snapshots
Order book data is where Tardis truly shines. They provide granular Level-2 order book snapshots that capture every price level — not just the top 10 or 20 levels like most free APIs. This is crucial for market impact studies, liquidity analysis, and optimizing order execution algorithms.
def download_bybit_orderbook(
symbol: str,
start_date: str,
end_date: str,
snapshot_interval_ms: int = 60000
) -> dict[str, dict]:
"""
Download historical order book snapshots from Bybit via Tardis.
Args:
symbol: Trading pair (e.g., "BTCUSDT")
start_date: Start date YYYY-MM-DD
end_date: End date YYYY-MM-DD
snapshot_interval_ms: Snapshot frequency in milliseconds
(60000 = 1 minute, 1000 = 1 second)
Returns:
Dictionary mapping timestamps to order book snapshots
"""
start_ts = int(datetime.strptime(start_date, "%Y-%m-%d").timestamp() * 1000)
end_ts = int(datetime.strptime(end_date, "%Y-%m-%d").timestamp() * 1000)
params = {
"exchange": "bybit",
"symbol": symbol,
"from": start_ts,
"to": end_ts,
"intervalMs": snapshot_interval_ms,
}
headers = {
"Accept": "application/x-ndjson", # Newline-delimited JSON for streaming
"Authorization": f"Bearer {TARDIS_API_KEY}"
}
print(f"Downloading {symbol} order book snapshots ({snapshot_interval_ms}ms interval)...")
orderbooks = {}
try:
response = requests.get(
f"{BASE_URL}/orderbooks",
params=params,
headers=headers,
stream=True, # Enable streaming for large responses
timeout=120
)
if response.status_code != 200:
print(f"Error: HTTP {response.status_code}")
print(response.text)
return orderbooks
# Parse NDJSON stream
for line in response.iter_lines():
if line:
snapshot = eval(line.decode('utf-8')) # Safe here - we control the source
ts = snapshot.get("timestamp")
orderbooks[ts] = snapshot
print(f" Downloaded {len(orderbooks)} order book snapshots")
except Exception as e:
print(f"Download error: {e}")
return orderbooks
Download 1-minute order book snapshots for 1 day
This gives us 1440 snapshots (one per minute) for comprehensive analysis
orderbooks = download_bybit_orderbook(
symbol="BTCUSDT",
start_date="2026-04-01",
end_date="2026-04-02",
snapshot_interval_ms=60000 # 1-minute snapshots
)
Parse into structured format
def parse_orderbook_snapshot(snapshot: dict) -> pl.DataFrame:
"""Convert raw order book snapshot to structured DataFrame."""
bids = snapshot.get("bids", [])
asks = snapshot.get("asks", [])
bid_data = [{"side": "bid", "price": p, "amount": a} for p, a in bids]
ask_data = [{"side": "ask", "price": p, "amount": a} for p, a in asks]
return pl.DataFrame(bid_data + ask_data)
Create master order book DataFrame
all_levels = []
for ts, snapshot in orderbooks.items():
df = parse_orderbook_snapshot(snapshot)
df = df.with_columns([
pl.lit(ts).alias("snapshot_timestamp"),
pl.col("price").cast(pl.Float64),
pl.col("amount").cast(pl.Float64),
])
all_levels.append(df)
if all_levels:
full_orderbook_df = pl.concat(all_levels)
print(f"\nTotal order book levels: {len(full_orderbook_df)}")
print(full_orderbook_df.head(20))
Step 5: Save Data for Backtesting
Polars supports efficient columnar storage formats. For high-frequency data, Parquet with Zstd compression provides the best balance of read speed and storage efficiency — typically 10-50x compression on trade data.
# Save trade data
trades_path = f"bybit_btcusdt_trades_{start_date}_{end_date}.parquet"
trades_df.write_parquet(trades_path, compression="zstd")
print(f"Trade data saved: {trades_path} ({os.path.getsize(trades_path) / 1e6:.2f} MB)")
Save order book data
ob_path = f"bybit_btcusdt_orderbook_{start_date}_{end_date}.parquet"
full_orderbook_df.write_parquet(ob_path, compression="zstd")
print(f"Order book data saved: {ob_path} ({os.path.getsize(ob_path) / 1e6:.2f} MB)")
Also export as CSV for spreadsheet analysis
trades_df.write_csv("bybit_trades_sample.csv")
print("Sample CSV export: bybit_trades_sample.csv")
Step 6: Validate Data Quality
Before backtesting, always validate your data. In my experience, 30% of backtesting failures stem from data quality issues — missing ticks, incorrect timestamps, or duplicate records. Here is my standard validation checklist:
def validate_data_quality(trades_df: pl.DataFrame, orderbook_df: pl.DataFrame) -> dict:
"""Comprehensive data quality validation for market data."""
report = {
"trades": {},
"orderbook": {},
"warnings": [],
"errors": []
}
# Trade data validation
if not trades_df.is_empty():
report["trades"]["total_records"] = len(trades_df)
report["trades"]["time_range"] = {
"start": str(trades_df["timestamp"].min()),
"end": str(trades_df["timestamp"].max())
}
report["trades"]["price_range"] = {
"min": trades_df["price"].min(),
"max": trades_df["price"].max()
}
# Check for duplicates
dupes = trades_df.group_by("trade_id").count().filter(pl.col("count") > 1)
if len(dupes) > 0:
report["errors"].append(f"Found {len(dupes)} duplicate trade IDs")
# Check for missing values
null_counts = trades_df.null_count()
if null_counts.sum(axis=1)[0] > 0:
report["warnings"].append(f"Found null values: {null_counts}")
# Check for price anomalies (5 standard deviations)
price_mean = trades_df["price"].mean()
price_std = trades_df["price"].std()
outliers = trades_df.filter(
(pl.col("price") - price_mean).abs() > 5 * price_std
)
if len(outliers) > 0:
report["warnings"].append(f"Found {len(outliers)} price outliers (>5 std)")
# Order book validation
if not orderbook_df.is_empty():
report["orderbook"]["total_levels"] = len(orderbook_df)
report["orderbook"]["snapshots"] = orderbook_df["snapshot_timestamp"].n_unique()
# Check bid-ask spread sanity
spread = orderbook_df.filter(pl.col("side") == "ask")["price"].min() - \
orderbook_df.filter(pl.col("side") == "bid")["price"].max()
if spread < 0:
report["errors"].append("Negative bid-ask spread detected - data corruption")
return report
Run validation
quality_report = validate_data_quality(trades_df, full_orderbook_df)
print("=" * 60)
print("DATA QUALITY REPORT")
print("=" * 60)
for key, value in quality_report.items():
print(f"\n{key.upper()}:")
for k, v in value.items():
print(f" {k}: {v}")
if quality_report["errors"]:
print("\n❌ ERRORS FOUND - DO NOT USE THIS DATA FOR BACKTESTING")
else:
print("\n✅ Data validation passed - ready for backtesting")
Tardis vs. Alternatives: Data Provider Comparison
Choosing the right market data provider is a critical infrastructure decision. Here is how Tardis Machine compares to the leading alternatives for high-frequency Bybit data:
| Feature | Tardis Machine | Binance Historical Data | CCXT + Exchange APIs | Custom Scrapers |
|---|---|---|---|---|
| Historical Depth | Up to 5 years | Limited (months) | None (real-time only) | Variable |
| Order Book Depth | Full Level-2 (all levels) | Top 20-500 levels | Top 5-20 levels | Configurable |
| Timestamp Precision | Sub-millisecond | Millisecond | Second-Minute | Exchange-dependent |
| Data Normalization | ✅ Unified schema | Exchange-native only | Partial | ❌ DIY |
| API Rate Limits | Generous (paid tiers) | Strict (600/min) | Very strict | Risk of IP ban |
| Starting Price | $99/month | Free (limited) | Free | $0 (but engineering cost) |
| 99.9% Uptime SLA | ✅ Yes | ❌ No SLA | ❌ No SLA | ❌ No SLA |
| Bybit Perpetual Support | ✅ Full | ❌ Not Bybit | ✅ Full | ✅ Full |
Who This Is For (and Who It Is NOT For)
Perfect For:
- Quantitative researchers building systematic trading strategies requiring tick-level data
- HFT firms optimizing execution algorithms and measuring market impact
- ML/AI engineers training models on order flow dynamics and market microstructure
- Academic researchers studying cryptocurrency markets with reproducible datasets
- Fund managers validating strategy performance across different market regimes
NOT For:
- Casual traders who only need daily OHLCV candles (use free exchange APIs instead)
- Projects with zero budget and no data engineering resources (consider free tiers with limitations)
- Real-time trading requiring sub-100ms data (you need direct exchange connectivity, not REST APIs)
Pricing and ROI
Tardis Machine pricing starts at $99/month for the Starter plan, which includes 10M API credits (roughly 10M trade records or 100K order book snapshots). For serious backtesting, the Professional plan at $299/month provides 100M credits — enough for comprehensive multi-instrument, multi-year analysis.
The ROI Case: A single bad backtest based on poor-quality data can cost you thousands in lost capital and opportunity cost. Professional market data infrastructure pays for itself with the first profitable strategy it helps you validate correctly. Compare this to spending $50K+ annually on a Bloomberg Terminal for traditional markets — Tardis at $99/month is exceptionally cost-effective for crypto.
HolySheep AI Cost Advantage: Once you have your data, analyzing it with AI models is remarkably affordable. Using HolySheep AI, you can process and interpret millions of trades with GPT-4.1 at $8/MTok, Gemini 2.5 Flash at $2.50/MTok, or DeepSeek V3.2 at just $0.42/MTok — delivering 85%+ savings versus ¥7.3/M standard Chinese API pricing while supporting WeChat and Alipay payment with sub-50ms API latency.
Why Choose HolySheep AI for Data Analysis
While Tardis provides the raw market data infrastructure, the real value comes from understanding that data. Here is why HolySheep AI has become my go-to platform for AI-powered market data analysis:
- Multi-Model Flexibility: Switch between GPT-4.1 ($8/MTok), Claude Sonnet 4.5 ($15/MTok), Gemini 2.5 Flash ($2.50/MTok), and DeepSeek V3.2 ($0.42/MTok) based on your analysis complexity and budget
- Unmatched Pricing: DeepSeek V3.2 at $0.42/MTok represents the lowest cost frontier model available — ideal for high-volume data analysis pipelines
- Payment Flexibility: WeChat Pay and Alipay support for Chinese users, plus standard credit cards for international customers
- Sub-50ms Latency: Production-grade API response times for real-time analysis workflows
- Free Credits on Registration: Sign up here to receive complimentary credits — no credit card required
Common Errors and Fixes
In my experience building data pipelines with Tardis, here are the most common issues and their solutions:
Error 1: HTTP 401 Unauthorized — Invalid API Key
Symptom: Receiving {"error": "Invalid API key"} despite being sure the key is correct.
# ❌ WRONG - Common mistakes:
headers = {"Authorization": TARDIS_API_KEY} # Missing "Bearer" prefix
✅ CORRECT:
headers = {"Authorization": f"Bearer {TARDIS_API_KEY}"}
Also verify:
1. No trailing spaces in your API key
2. API key is from the correct dashboard (Tardis.dev, not exchange)
3. Subscription is active (check at https://tardis.dev/settings/api)
Error 2: Rate Limit Exceeded (HTTP 429)
Symptom: Downloads fail or return partial data after running for several minutes.
# ❌ WRONG - Aggressive polling causes rate limits:
while True:
response = requests.get(url)
# Immediate next request!
✅ CORRECT - Implement exponential backoff:
import time
max_retries = 5
retry_delay = 1
for attempt in range(max_retries):
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 200:
break
elif response.status_code == 429:
wait_time = retry_delay * (2 ** attempt) # 1, 2, 4, 8, 16 seconds
print(f"Rate limited. Waiting {wait_time}s before retry...")
time.sleep(wait_time)
else:
raise Exception(f"API error: {response.status_code}")
For production: upgrade to higher Tardis tier for more API credits
Error 3: OutOfMemoryError When Processing Large Datasets
Symptom: Python crashes when loading millions of rows into memory.
# ❌ WRONG - Loading everything into RAM:
df = pl.read_csv("massive_trades.csv") # Can easily exceed 32GB
✅ CORRECT - Process in chunks:
CHUNK_SIZE = 500_000
for chunk in pl.read_csv("massive_trades.csv", batch_size=CHUNK_SIZE):
# Process each chunk individually
chunk_stats = chunk.select([
pl.col("price").mean().alias("avg_price"),
pl.col("amount").sum().alias("total_volume"),
])
print(f"Chunk processed: {chunk_stats}")
# Write to Parquet incrementally
chunk_stats.write_parquet("processed_stats.parquet",
include_file_paths=False,
row_group_size=100_000)
Alternative: Use Polars LazyFrame for streaming
lazy_df = pl.scan_csv("massive_trades.csv")
result = lazy_df.group_by("date").agg([
pl.col("price").mean().alias("avg_price"),
]).collect()
print(result)
Error 4: Timestamp Parsing Errors
Symptom: Dates appear as None or dates are shifted by timezone.
# ❌ WRONG - Incorrect timezone handling:
df = df.with_columns(pl.col("timestamp").str.to_datetime())
✅ CORRECT - Specify timezone explicitly:
df = df.with_columns(
pl.col("timestamp")
.str.to_datetime(time_unit="ms")
.dt.replace_time_zone("UTC")
)
For Bybit specifically - timestamps are in milliseconds UTC
If you see dates from 1970, your timestamp is in seconds (multiply by 1000)
if df["timestamp"].max().year < 2000:
df = df.with_columns(
(pl.col("timestamp") * 1000)
.str.to_datetime(time_unit="ms")
.dt.replace_time_zone("UTC")
)
print("Fixed: Converted seconds to milliseconds")
Error 5: Incomplete Order Book Data (Missing Levels)
Symptom: Order book snapshots have far fewer price levels than expected.
# ❌ WRONG - Requesting data beyond historical limits:
params = {
"exchange": "bybit",
"symbol": "BTCUSDT",
"from": 1609459200000, # January 1, 2021 - beyond Bybit's history
"to": 1609545600000,
}
✅ CORRECT - Check data availability first:
def check_data_availability(exchange: str, symbol: str) -> dict:
"""Query Tardis for available data ranges."""
response = requests.get(
f"https://api.tardis.dev/v1/exchanges/{exchange}/symbols/{symbol}",
headers={"Authorization": f"Bearer {TARDIS_API_KEY}"}
)
return response.json()
availability = check_data_availability("bybit", "BTCUSDT")
print(f"Available from: {availability.get('dataAvailableFrom')}")
print(f"Available to: {availability.get('dataAvailableTo')}")
Also ensure you're requesting reasonable snapshot intervals
(Tardis doesn't have 1ms snapshots for historical - only live streaming)
snapshot_interval_ms = 60000 # 1 minute minimum for historical
if snapshot_interval_ms < 1000:
print("⚠️ Warning: Sub-second intervals not available for historical data")
Conclusion and Next Steps
You now have a complete, production-ready pipeline for downloading Bybit trade data and order book snapshots using Tardis Machine. The combination of high-quality historical data from Tardis and powerful AI analysis capabilities from HolySheep AI gives you the infrastructure to build, validate, and optimize quantitative trading strategies with confidence.
For your next steps, I recommend:
- Extend the pipeline to multiple symbols (ETH/USDT, SOL/USDT) and longer timeframes
- Integrate HolySheep AI to analyze your downloaded data — identify patterns, anomalies, and regime changes automatically
- Build validation dashboards to continuously monitor data quality
- Test with paper trading before committing capital to any strategy validated on this data
The combination of Tardis Machine for raw market data and HolySheep AI for intelligent analysis represents a powerful, cost-effective stack for quantitative research. Tardis provides the precision data infrastructure at professional pricing ($99-299/month), while HolySheep delivers AI analysis at unmatched rates ($0.42/MTok with DeepSeek V3.2) — 85%+ cheaper than traditional Chinese API alternatives.
Whether you are validating a mean-reversion strategy, optimizing market-making parameters, or training an ML model on order flow, the tools and techniques in this guide will accelerate your research cycle significantly.
Get Started Today
Ready to supercharge your quantitative research? Begin by setting up your Tardis Machine account for high-quality Bybit data, then integrate HolySheep AI for AI-powered data analysis — free credits on registration, no commitment required.
👉 Sign up for HolySheep AI — free credits on registration