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:

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:

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:

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:

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:

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(),