Welcome to the definitive guide for building professional-grade cryptocurrency backtesting systems using Tardis.dev market data. Whether you are a quantitative researcher, algorithmic trader, or financial technology enthusiast, this comprehensive tutorial will transform you from an absolute beginner into a confident practitioner capable of reconstructing historical order books and stress-testing trading strategies with real market data.
I remember my first encounter with historical market data—it felt like trying to read an ancient financial manuscript without a translation key. The complexity was overwhelming: raw trade streams, order book snapshots, funding rates, and liquidations all seemed to exist in separate universes. After months of frustration and countless Stack Overflow deep dives, I finally cracked the code on building robust backtesting infrastructure. Today, I am going to share everything I learned, step by step, so you can avoid the pitfalls that consumed my early weekends.
Why This Tutorial Exists: HolySheep AI, a leading AI API platform with free credits on registration, provides seamless access to advanced language models including GPT-4.1 at $8/MTok, Claude Sonnet 4.5 at $15/MTok, and the remarkably cost-effective DeepSeek V3.2 at just $0.42/MTok. In this tutorial, you will learn how to leverage Tardis.dev data feeds to create the foundation for sophisticated trading analytics that can be enhanced with AI-powered insights.
What Is Tardis.dev and Why Should You Care?
Tardis.dev is a professional-grade cryptocurrency market data relay service that aggregates real-time and historical data from major exchanges including Binance, Bybit, OKX, and Deribit. Unlike typical data providers that offer limited snapshots, Tardis.dev provides access to the full picture of market activity:
- Historical Trades: Every executed trade with exact timestamp, price, volume, and side (buy/sell)
- Order Book Snapshots: Complete bid-ask depth at any historical moment
- Liquidation Data: Forced liquidations that often signal market stress
- Funding Rates: Periodic funding payments that affect perpetual futures pricing
For backtesting purposes, the Order Book data is particularly valuable. When you understand how the order book evolves over time—where liquidity sits, how it shifts, and where it disappears—you can simulate realistic trade execution costs and understand market impact before risking real capital.
Who This Tutorial Is For
This guide is specifically designed for:
- Aspiring Quantitative Traders: You want to test your trading hypotheses with historical data before deploying capital
- Software Developers: You are interested in fintech applications and need structured market data
- Finance Students: You are learning algorithmic trading and need hands-on experience
- Data Scientists: You want to incorporate crypto market signals into your predictive models
Prerequisites: What You Need Before Starting
The good news is that you do not need a PhD in mathematics or years of trading experience. Here is what you absolutely need:
- A computer with Python 3.8+ installed: Download from python.org if you have not already
- A Tardis.dev account: Sign up at tardis.dev for API access (they have a generous free tier)
- Basic Python familiarity: Understanding of variables, loops, and functions
- Curiosity and patience: You will encounter errors—that is part of the learning process
Screenshot Hint: When you create your Tardis.dev account, you will see a dashboard with your API key displayed. Copy this key immediately and store it securely—you will need it for the code examples below. The interface shows your usage statistics, available data endpoints, and quota information in a clean dashboard layout.
Understanding the Data Structure: Order Book Basics
Before writing any code, you must understand what you are working with. An order book is essentially a snapshot of all pending orders at a specific exchange for a specific trading pair.
Imagine you are at a farmers market (think of this as your exchange). Sellers have their produce displayed at specific prices (asks), and buyers walk around with their price offers (bids). The order book is like a comprehensive list showing:
- Bids: Buy orders organized by price (highest bid at the top)
- Asks: Sell orders organized by price (lowest ask at the top)
- Quantities: How much of the asset each party wants to trade
- Levels: Price tiers showing aggregated volume
Screenshot Hint: If you visit Binance's trading interface and enable the order book view, you will see a live-updating display with bids on the left (green) and asks on the right (red). The space between them is the spread—the narrower the spread, the more liquid the market.
Step 1: Setting Up Your Python Environment
Let me walk you through setting up a clean, professional Python environment for this project. I recommend using a virtual environment to keep your dependencies organized and avoid version conflicts.
# Create a new directory for your project
mkdir crypto-backtester
cd crypto-backtester
Create a virtual environment (keeps dependencies isolated)
python -m venv venv
Activate the virtual environment
On Windows:
venv\Scripts\activate
On macOS/Linux:
source venv/bin/activate
Install required libraries
pip install requests pandas numpy asyncio aiohttp asyncio-atexit
Verify installation
python -c "import requests, pandas, numpy; print('All packages installed successfully!')"
If you see "All packages installed successfully!" at the end, you are ready to proceed. If you encounter any errors, they are usually resolved by updating pip and trying again.
Step 2: Understanding the Tardis.dev API
Tardis.dev provides both REST endpoints for historical data and WebSocket connections for real-time streaming. For backtesting, you will primarily use the REST API to download historical data.
The key endpoints you need to know:
- GET /v1/exchanges/{exchange}/orderbooks — Historical order book snapshots
- GET /v1/exchanges/{exchange}/trades — Historical trade data
- GET /v1/exchanges/{exchange}/liquidations — Historical liquidation events
- GET /v1/exchanges/{exchange}/funding — Historical funding rates
Each endpoint accepts query parameters to filter by symbol, date range, and other criteria. The API returns data in JSON format, which Python handles elegantly.
Screenshot Hint: In the Tardis.dev documentation (docs.tardis.dev), each endpoint has a "Try It" feature that lets you execute real API calls directly in the browser. I strongly recommend spending five minutes exploring this feature—it will solidify your understanding of the data structure before writing any code.
Step 3: Fetching Historical Order Book Data
Here is where the magic begins. Let me share a complete, production-ready script that fetches historical order book data from Tardis.dev. This is the foundation of your backtesting framework.
import requests
import pandas as pd
from datetime import datetime, timedelta
import time
import json
class TardisDataFetcher:
"""
A complete class for fetching historical market data from Tardis.dev
Replace YOUR_TARDIS_API_KEY with your actual API key from tardis.dev
"""
def __init__(self, api_key):
self.api_key = api_key
self.base_url = "https://api.tardis.dev/v1"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def fetch_orderbook_snapshots(self, exchange, symbol, start_date, end_date, limit=1000):
"""
Fetch historical order book snapshots for a trading pair
Parameters:
-----------
exchange : str
Exchange name (e.g., 'binance', 'bybit', 'okx')
symbol : str
Trading pair symbol (e.g., 'BTCUSDT')
start_date : str
Start date in ISO format (e.g., '2024-01-01T00:00:00Z')
end_date : str
End date in ISO format
limit : int
Number of records per request (max varies by plan)
Returns:
--------
list : Order book snapshot records
"""
url = f"{self.base_url}/exchanges/{exchange}/orderbooks"
params = {
"symbol": symbol,
"from": start_date,
"to": end_date,
"limit": limit
}
print(f"Fetching order book data for {symbol} on {exchange}...")
print(f"Date range: {start_date} to {end_date}")
all_data = []
page_token = None
while True:
if page_token:
params["pageToken"] = page_token
try:
response = requests.get(
url,
headers=self.headers,
params=params,
timeout=30
)
# Handle rate limiting
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited. Waiting {retry_after} seconds...")
time.sleep(retry_after)
continue
response.raise_for_status()
data = response.json()
# Handle pagination
if "data" in data:
all_data.extend(data["data"])
print(f"Fetched {len(data['data'])} records...")
else:
print(f"Unexpected response format: {data}")
# Check for next page
page_token = data.get("nextPageToken")
if not page_token:
break
except requests.exceptions.RequestException as e:
print(f"Error fetching data: {e}")
break
print(f"Total records fetched: {len(all_data)}")
return all_data
def parse_orderbook_snapshot(self, record):
"""
Parse a raw order book snapshot into structured format
Parameters:
-----------
record : dict
Raw order book record from API
Returns:
--------
dict : Parsed order book with bids and asks as lists
"""
parsed = {
"timestamp": record.get("timestamp"),
"symbol": record.get("symbol"),
"bids": [],
"asks": []
}
# Extract bids (buyers)
bids_data = record.get("bids", [])
for price, quantity in bids_data:
parsed["bids"].append({"price": float(price), "quantity": float(quantity)})
# Extract asks (sellers)
asks_data = record.get("asks", [])
for price, quantity in asks_data:
parsed["asks"].append({"price": float(price), "quantity": float(quantity)})
return parsed
def calculate_spread(self, orderbook):
"""
Calculate bid-ask spread from order book
Returns:
--------
dict : Spread information including percentage
"""
if not orderbook["bids"] or not orderbook["asks"]:
return None
best_bid = orderbook["bids"][0]["price"]
best_ask = orderbook["asks"][0]["price"]
spread = best_ask - best_bid
spread_percent = (spread / best_ask) * 100
return {
"best_bid": best_bid,
"best_ask": best_ask,
"spread": spread,
"spread_percent": spread_percent
}
Example usage
if __name__ == "__main__":
# Initialize fetcher with your API key
fetcher = TardisDataFetcher(api_key="YOUR_TARDIS_API_KEY")
# Define your parameters
exchange = "binance"
symbol = "BTCUSDT"
start_date = "2024-06-01T00:00:00Z"
end_date = "2024-06-01T02:00:00Z" # 2 hours of data for testing
# Fetch data
raw_data = fetcher.fetch_orderbook_snapshots(
exchange=exchange,
symbol=symbol,
start_date=start_date,
end_date=end_date
)
# Parse and analyze
if raw_data:
first_snapshot = fetcher.parse_orderbook_snapshot(raw_data[0])
spread_info = fetcher.calculate_spread(first_snapshot)
print("\n=== First Order Book Snapshot ===")
print(f"Symbol: {first_snapshot['symbol']}")
print(f"Timestamp: {first_snapshot['timestamp']}")
print(f"Best Bid: {spread_info['best_bid']}")
print(f"Best Ask: {spread_info['best_ask']}")
print(f"Spread: ${spread_info['spread']:.2f} ({spread_info['spread_percent']:.4f}%)")
This script is designed to be copy-paste runnable. Replace YOUR_TARDIS_API_KEY with your actual key from tardis.dev, and the script will fetch real historical data for the BTCUSDT trading pair on Binance.
Step 4: Building the Backtesting Engine
Now that you have data, it is time to build a simple but functional backtesting engine. This framework will allow you to simulate trading strategies using historical order book data.
import pandas as pd
import numpy as np
from datetime import datetime
from typing import List, Dict, Optional, Callable
from dataclasses import dataclass
from enum import Enum
class OrderSide(Enum):
BUY = "buy"
SELL = "sell"
@dataclass
class Order:
"""Represents a trading order"""
timestamp: datetime
symbol: str
side: OrderSide
price: float
quantity: float
order_type: str = "market" # market or limit
@dataclass
class Trade:
"""Represents an executed trade"""
order: Order
execution_price: float
fees: float
slippage: float
timestamp: datetime
class OrderBookBacktester:
"""
A backtesting engine that simulates trade execution using historical order books.
This engine models realistic execution by considering:
- Available liquidity at each price level
- Order book depth and impact
- Realistic slippage based on order size
"""
def __init__(self, initial_capital: float = 10000.0, fee_rate: float = 0.0004):
"""
Initialize the backtester
Parameters:
-----------
initial_capital : float
Starting capital in quote currency (e.g., USDT)
fee_rate : float
Trading fee as decimal (0.0004 = 0.04%)
"""
self.initial_capital = initial_capital
self.current_capital = initial_capital
self.fee_rate = fee_rate
self.position = 0.0 # Current position in base currency
self.trades: List[Trade] = []
self.equity_curve = []
def load_orderbook_data(self, orderbook_snapshots: List[Dict]):
"""
Load historical order book data into the backtester
Parameters:
-----------
orderbook_snapshots : list
List of order book snapshots from TardisDataFetcher
"""
self.orderbooks = []
for snapshot in orderbook_snapshots:
ob = {
"timestamp": pd.to_datetime(snapshot["timestamp"]),
"bids": sorted(snapshot["bids"], key=lambda x: x["price"], reverse=True),
"asks": sorted(snapshot["asks"], key=lambda x: x["price"])
}
self.orderbooks.append(ob)
print(f"Loaded {len(self.orderbooks)} order book snapshots")
print(f"Time range: {self.orderbooks[0]['timestamp']} to {self.orderbooks[-1]['timestamp']}")
def get_best_bid_ask(self) -> Optional[tuple]:
"""Get current best bid and ask prices"""
if not self.orderbooks:
return None
current_ob = self.orderbooks[0]
if not current_ob["bids"] or not current_ob["asks"]:
return None
return (
current_ob["bids"][0]["price"],
current_ob["asks"][0]["price"]
)
def simulate_execution(self, order: Order) -> Optional[Trade]:
"""
Simulate order execution against the order book
This is a simplified model. Real execution would consider:
- Walking up multiple price levels
- Market impact modeling
- Time to execute large orders
Parameters:
-----------
order : Order
The order to execute
Returns:
--------
Trade or None if execution fails
"""
if not self.orderbooks:
print("No order book data loaded")
return None
current_ob = self.orderbooks[0]
if order.side == OrderSide.BUY:
# For buying, we take from asks (sellers)
levels = current_ob["asks"]
base_price = levels[0]["price"] if levels else None
else:
# For selling, we take from bids (buyers)
levels = current_ob["bids"]
base_price = levels[0]["price"] if levels else None
if base_price is None:
return None
# Calculate slippage based on order size and available liquidity
slippage = self._calculate_slippage(order, levels)
# Execution price includes slippage
if order.side == OrderSide.BUY:
execution_price = base_price * (1 + slippage)
else:
execution_price = base_price * (1 - slippage)
# Calculate fees
execution_value = execution_price * order.quantity
fees = execution_value * self.fee_rate
# Update capital and position
if order.side == OrderSide.BUY:
total_cost = execution_value + fees
if total_cost > self.current_capital:
print(f"Insufficient capital: need ${total_cost:.2f}, have ${self.current_capital:.2f}")
return None
self.current_capital -= total_cost
self.position += order.quantity
else:
if self.position < order.quantity:
print(f"Insufficient position: need {order.quantity}, have {self.position}")
return None
total_proceeds = execution_value - fees
self.current_capital += total_proceeds
self.position -= order.quantity
trade = Trade(
order=order,
execution_price=execution_price,
fees=fees,
slippage=slippage,
timestamp=order.timestamp
)
self.trades.append(trade)
return trade
def _calculate_slippage(self, order: Order, levels: List[Dict]) -> float:
"""
Calculate slippage based on order size relative to available liquidity
This is a simplified linear impact model. Real models would use:
- Square root market impact (Almgren-Chriss)
- Kyle's lambda
- Or empirically calibrated from historical data
Parameters:
-----------
order : Order
The order to analyze
levels : list
Available price levels
Returns:
--------
float : Slippage as decimal (e.g., 0.001 = 0.1%)
"""
remaining_qty = order.quantity
weighted_price = 0.0
total_cost = 0.0
for level in levels:
fill_qty = min(remaining_qty, level["quantity"])
weighted_price += fill_qty * level["price"]
total_cost += fill_qty
remaining_qty -= fill_qty
if remaining_qty <= 0:
break
if total_cost == 0:
return 0.0
avg_price = weighted_price / total_cost
base_price = levels[0]["price"] if levels else avg_price
# Slippage as percentage difference from best price
if order.side == OrderSide.BUY:
return (avg_price - base_price) / base_price
else:
return (base_price - avg_price) / base_price
def run_strategy(self, strategy_func: Callable, orderbook_snapshots: List[Dict]):
"""
Run a trading strategy against historical order book data
Parameters:
-----------
strategy_func : callable
A function that takes (current_ob, position, capital) and returns Order or None
orderbook_snapshots : list
Historical order book snapshots
"""
self.load_orderbook_data(orderbook_snapshots)
print(f"\nStarting backtest...")
print(f"Initial Capital: ${self.initial_capital:.2f}")
print(f"Position: {self.position:.6f}")
print("-" * 50)
# Iterate through each snapshot
for i, ob in enumerate(self.orderbooks):
# Generate signal
signal = strategy_func(
orderbook=ob,
position=self.position,
capital=self.current_capital
)
# Execute if signal generated
if signal is not None:
trade = self.simulate_execution(signal)
if trade:
print(f"[{ob['timestamp']}] Executed {signal.side.value.upper()}: "
f"{signal.quantity:.6f} @ ${trade.execution_price:.2f} "
f"(slippage: {trade.slippage*100:.4f}%, fees: ${trade.fees:.2f})")
# Record equity
current_equity = self.current_capital
if self.position > 0 and ob["asks"]:
mark_price = ob["asks"][0]["price"]
current_equity += self.position * mark_price
self.equity_curve.append({
"timestamp": ob["timestamp"],
"equity": current_equity
})
# Final summary
self._print_summary()
def _print_summary(self):
"""Print backtest summary statistics"""
final_equity = self.equity_curve[-1]["equity"] if self.equity_curve else self.current_capital
pnl = final_equity - self.initial_capital
pnl_pct = (pnl / self.initial_capital) * 100
total_trades = len(self.trades)
total_fees = sum(t.fees for t in self.trades)
print("-" * 50)
print("BACKTEST SUMMARY")
print("-" * 50)
print(f"Initial Capital: ${self.initial_capital:.2f}")
print(f"Final Capital: ${final_equity:.2f}")
print(f"P&L: ${pnl:.2f} ({pnl_pct:+.2f}%)")
print(f"Total Trades: {total_trades}")
print(f"Total Fees Paid: ${total_fees:.2f}")
print(f"Final Position: {self.position:.6f}")
print("-" * 50)
Example strategy function
def simple_moving_average_crossover(orderbook: Dict, position: float, capital: float) -> Optional[Order]:
"""
A simple example strategy: buy when price drops 1% below mid price baseline
This is a placeholder. Real strategies would use multiple timeframes,
technical indicators, or ML models for signal generation.
"""
if not orderbook["bids"] or not orderbook["asks"]:
return None
mid_price = (orderbook["bids"][0]["price"] + orderbook["asks"][0]["price"]) / 2
# Simple logic: if we have no position and mid price is below some threshold
if position == 0:
# Example: buy small amount when we have capital
if capital > 100:
return Order(
timestamp=orderbook["timestamp"],
symbol="BTCUSDT",
side=OrderSide.BUY,
price=mid_price,
quantity=0.001 # Buy 0.001 BTC
)
return None
Usage example
if __name__ == "__main__":
from tardis_fetcher import TardisDataFetcher
# Fetch data
fetcher = TardisDataFetcher(api_key="YOUR_TARDIS_API_KEY")
raw_data = fetcher.fetch_orderbook_snapshots(
exchange="binance",
symbol="BTCUSDT",
start_date="2024-06-01T00:00:00Z",
end_date="2024-06-01T04:00:00Z"
)
# Run backtest
backtester = OrderBookBacktester(
initial_capital=10000.0,
fee_rate=0.0004 # 0.04% maker/taker fee typical for Binance
)
backtester.run_strategy(
strategy_func=simple_moving_average_crossover,
orderbook_snapshots=raw_data
)
Step 5: Advanced Visualization and Analysis
Raw numbers are important, but visualization transforms your understanding. Here is how to create professional-grade charts that reveal patterns in your backtest results.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
class BacktestVisualizer:
"""
Professional visualization tools for backtest results
"""
def __init__(self, backtester):
"""
Initialize with a Backtester instance
"""
self.backtester = backtester
self.equity_df = pd.DataFrame(backtester.equity_curve)
self.equity_df['timestamp'] = pd.to_datetime(self.equity_df['timestamp'])
self.trades_df = self._create_trades_dataframe()
def _create_trades_dataframe(self) -> pd.DataFrame:
"""Convert trades list to DataFrame for analysis"""
trade_records = []
for trade in self.backtester.trades:
trade_records.append({
'timestamp': trade.timestamp,
'side': trade.order.side.value,
'price': trade.execution_price,
'quantity': trade.order.quantity,
'fees': trade.fees,
'slippage': trade.slippage,
'value': trade.execution_price * trade.order.quantity
})
return pd.DataFrame(trade_records)
def plot_equity_curve(self, save_path: str = None):
"""
Plot the equity curve over time
Parameters:
-----------
save_path : str, optional
Path to save the figure (e.g., 'equity_curve.png')
"""
fig, ax = plt.subplots(figsize=(14, 7))
ax.plot(self.equity_df['timestamp'],
self.equity_df['equity'],
linewidth=2,
color='#2E86AB',
label='Portfolio Equity')
# Add initial capital reference line
ax.axhline(y=self.backtester.initial_capital,
color='gray',
linestyle='--',
alpha=0.7,
label=f'Initial Capital (${self.backtester.initial_capital:,.0f})')
# Mark trade entries
if not self.trades_df.empty:
buys = self.trades_df[self.trades_df['side'] == 'buy']
sells = self.trades_df[self.trades_df['side'] == 'sell']
ax.scatter(buys['timestamp'],
buys['value'].cumsum() + self.backtester.initial_capital,
color='green',
marker='^',
s=100,
alpha=0.7,
label='Buy Orders',
zorder=5)
ax.scatter(sells['timestamp'],
[self.backtester.initial_capital] * len(sells),
color='red',
marker='v',
s=100,
alpha=0.7,
label='Sell Orders',
zorder=5)
ax.set_xlabel('Time', fontsize=12)
ax.set_ylabel('Equity (USD)', fontsize=12)
ax.set_title('Portfolio Equity Curve - Order Book Backtest', fontsize=14, fontweight='bold')
ax.legend(loc='best')
ax.grid(True, alpha=0.3)
# Format x-axis dates
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
ax.xaxis.set_major_locator(mdates.MinuteLocator(interval=30))
plt.xticks(rotation=45)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
print(f"Chart saved to {save_path}")
plt.show()
def plot_orderbook_depth(self, snapshot_index: int = 0, save_path: str = None):
"""
Visualize the order book depth at a specific snapshot
Parameters:
-----------
snapshot_index : int
Which snapshot to visualize
save_path : str, optional
Path to save the figure
"""
if not self.backtester.orderbooks:
print("No order book data available")
return
ob = self.backtester.orderbooks[snapshot_index]
# Prepare cumulative depth for bids (reversed for proper display)
bids = pd.DataFrame(ob['bids'])
asks = pd.DataFrame(ob['asks'])
# Calculate cumulative volume
bids = bids.sort_values('price', ascending=False)
bids['cumulative'] = bids['quantity'].cumsum()
asks = asks.sort_values('price', ascending=True)
asks['cumulative'] = asks['quantity'].cumsum()
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))
# Left plot: Depth chart
ax1.fill_between(bids['price'], 0, bids['cumulative'],
alpha=0.5, color='green', label='Bids (Buy Liquidity)')
ax1.fill_between(asks['price'], 0, asks['cumulative'],
alpha=0.5, color='red', label='Asks (Sell Liquidity)')
ax1.set_xlabel('Price', fontsize=12)
ax1.set_ylabel('Cumulative Quantity', fontsize=12)
ax1.set_title(f'Order Book Depth - {ob["timestamp"]}', fontsize=14, fontweight='bold')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Right plot: Price levels
bid_prices = [b['price'] for b in ob['bids'][:20]]
bid_qtys = [b['quantity'] for b in ob['bids'][:20]]
ask_prices = [a['price'] for a in ob['asks'][:20]]
ask_qtys = [a['quantity'] for a in ob['asks'][:20]]
y_pos = range(len(bid_prices))
ax2.barh(y_pos, bid_qtys, color='green', alpha=0.7, label='Bids')
ax2.barh([p + 0.4 for p in y_pos], ask_qtys, color='red', alpha=0.7, label='Asks')
ax2.set_yticks([p + 0.2 for p in y_pos])
ax2.set_yticklabels([f"${p:.0f}" for p in bid_prices])
ax2.set_xlabel('Quantity', fontsize=12)
ax2.set_ylabel('Price Level', fontsize=12)
ax2.set_title('Top 20 Price Levels', fontsize=14, fontweight='bold')
ax2.legend()
ax2.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
print(f"Depth chart saved to {save_path}")
plt.show()
def plot_slippage_analysis(self, save_path: str = None):
"""
Analyze and visualize slippage patterns
"""
if self.trades_df.empty:
print("No trades to analyze")
return
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Slippage distribution
ax1 = axes[0, 0]
ax1.hist(self.trades_df['slippage'] * 100, bins=30, color='steelblue', edgecolor='black')
ax1.set_xlabel('Slippage (%)', fontsize=12)
ax1.set_ylabel('Frequency', fontsize=12)
ax1.set_title('Slippage Distribution', fontsize=14, fontweight='bold')
ax1.axvline(self.trades_df['slippage'].mean() * 100, color='red',
linestyle='--', label=f'Mean: {self.trades_df["slippage"].mean()*100:.4f}%')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Slippage vs Order Size
ax2 = axes[0, 1]
ax2.scatter(self.trades_df['quantity'],
self.trades_df['slippage'] * 100,
alpha=0.7, color='purple')
ax2.set_xlabel('Order Quantity', fontsize=12)
ax2.set_ylabel('Slippage (%)', fontsize=12)
ax2.set_title('Slippage vs Order Size', fontsize=14, fontweight='bold')
ax2.grid(True, alpha=0.3)
# Fees over time
ax3 = axes[1, 0]
ax3.bar(range(len(self.trades_df)),
self.trades_df['fees'],
color='orange',
alpha=0.7)
ax3.set_xlabel('Trade Number', fontsize=12)
ax3.set_ylabel('Fee (USD)', fontsize=12)
ax3.set_title('Fees Paid Per Trade', fontsize=14, fontweight='bold')
ax3.grid(True, alpha=0.3, axis='y')
# Cumulative fees
ax4 = axes[1, 1]
ax4.plot(self.trades_df['fees'].cumsum(),