← Back to Blog
TUTORIALS

Backtesting Crypto Strategies with vectorbt: A Complete Guide

June 17, 2026 · 11 min read · LMEX.AI

Writing your own backtest in pandas works but takes a week. vectorbt does the same thing in 30 lines and runs orders of magnitude faster. For anyone backtesting strategies on crypto data, it's the right tool.


This article walks through why vectorbt is worth learning, how to set up a strategy, how to run parameter sweeps, and the limitations that matter.


Why vectorbt over hand-rolled loops


A standard pandas backtest loops over candles, simulating fills one at a time. For 10,000 candles and 100 parameter combinations, you're running 1 million iterations. Pure Python: 30+ minutes. Numpy-optimised: a few minutes. Still slow.


vectorbt vectorises the entire simulation. Same task in vectorbt: 5-10 seconds. The speedup comes from running all parameter combinations as parallel numpy operations rather than sequential loops.


For a single strategy with one set of parameters, both approaches work. For walk-forward analysis, parameter sweeps, or comparing multiple strategies, vectorbt is essentially mandatory. The 100x speedup means you can actually iterate.


Other reasons to prefer vectorbt:

  • Built-in handling of commission, slippage, leverage
  • Realistic stop loss and take profit execution
  • Portfolio-level analytics across multiple instruments
  • Easy parameter optimization with proper out-of-sample handling

  • Setup and data


    Installation:


    pip install vectorbt ccxt pandas

    Fetching crypto data via CCXT:


    import ccxt
    import pandas as pd
    import vectorbt as vbt
    
    exchange = ccxt.lmex()
    
    def fetch_history(symbol, timeframe='1h', since_days=365):
        since = exchange.milliseconds() - since_days * 86400 * 1000
        all_candles = []
        while True:
            candles = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=1000)
            if not candles:
                break
            all_candles.extend(candles)
            since = candles[-1][0] + 1
            if len(candles) < 1000:
                break
        
        df = pd.DataFrame(all_candles, columns=['ts', 'open', 'high', 'low', 'close', 'volume'])
        df['ts'] = pd.to_datetime(df['ts'], unit='ms')
        df = df.set_index('ts')
        return df
    
    df = fetch_history('BTC-PERP', '1h', since_days=365)
    print(f"Loaded {len(df)} candles from {df.index[0]} to {df.index[-1]}")

    For longer histories, you'll need to paginate further. Most crypto exchanges return at most 1000 candles per call.


    Building a strategy


    A simple EMA crossover strategy in vectorbt:


    import vectorbt as vbt
    import pandas as pd
    
    # Calculate EMAs
    fast_ema = df['close'].ewm(span=12).mean()
    slow_ema = df['close'].ewm(span=26).mean()
    
    # Generate signals
    entries = (fast_ema > slow_ema) & (fast_ema.shift() <= slow_ema.shift())
    exits = (fast_ema < slow_ema) & (fast_ema.shift() >= slow_ema.shift())
    
    # Run the backtest
    pf = vbt.Portfolio.from_signals(
        df['close'],
        entries=entries,
        exits=exits,
        init_cash=10000,
        fees=0.0006,        # 6 bps per side
        slippage=0.0005,    # 5 bps slippage
        freq='1H',
    )
    
    # Print results
    print(pf.stats())

    This produces a complete backtest with realistic transaction costs. The output includes total return, Sharpe ratio, max drawdown, win rate, and dozens of other metrics.


    The key advantage: changing parameters or strategies requires modifying signal calculations, not loop logic. The simulation engine handles execution mechanics.


    Parameter sweeps


    The real power of vectorbt shows up when sweeping parameters. Test all combinations of fast/slow EMA periods:


    import numpy as np
    
    fast_periods = np.arange(5, 21, 2)    # 5, 7, 9, ..., 19
    slow_periods = np.arange(20, 51, 5)   # 20, 25, 30, ..., 50
    
    # Create cartesian product of all parameter combinations
    results = []
    for fast in fast_periods:
        for slow in slow_periods:
            if fast >= slow:
                continue  # skip nonsensical combos
            
            fast_ema = df['close'].ewm(span=int(fast)).mean()
            slow_ema = df['close'].ewm(span=int(slow)).mean()
            
            entries = (fast_ema > slow_ema) & (fast_ema.shift() <= slow_ema.shift())
            exits = (fast_ema < slow_ema) & (fast_ema.shift() >= slow_ema.shift())
            
            pf = vbt.Portfolio.from_signals(
                df['close'], entries=entries, exits=exits,
                init_cash=10000, fees=0.0006, slippage=0.0005, freq='1H'
            )
            
            results.append({
                'fast': fast, 'slow': slow,
                'total_return': pf.total_return(),
                'sharpe': pf.sharpe_ratio(),
                'max_drawdown': pf.max_drawdown(),
                'num_trades': pf.trades.count(),
            })
    
    results_df = pd.DataFrame(results)
    print(results_df.sort_values('sharpe', ascending=False).head(10))

    This runs ~80 backtests in under a minute. The top-10 Sharpe ratios show which parameter combinations actually worked.


    But ranking by Sharpe alone is dangerous — those top results may be overfit. Better evaluation:


    # Filter for parameters that work robustly
    robust = results_df[
        (results_df['sharpe'] > 1.0) &
        (results_df['max_drawdown'] > -0.3) &
        (results_df['num_trades'] > 50)
    ]
    print(f"Robust parameter combinations: {len(robust)}")
    print(robust.head(10))

    If only 2-3 parameter combinations pass these filters, the strategy is overfit. A robust strategy has 10-20+ parameter combinations that produce reasonable Sharpe and drawdown profiles.


    Reading the results


    vectorbt's `pf.stats()` returns a comprehensive dictionary:


    Start                         2025-06-17 00:00:00
    End                           2026-06-17 00:00:00
    Period                        365 days 00:00:00
    Total Return [%]              42.3
    Benchmark Return [%]          28.5
    Max Drawdown [%]              18.7
    Sharpe Ratio                  1.42
    Sortino Ratio                 2.05
    Calmar Ratio                  2.26
    Win Rate [%]                  52.3
    Profit Factor                 1.84
    Avg Trade [%]                 1.2
    Total Trades                  87

    Useful metrics for crypto strategies:

  • **Sharpe > 1.0** is acceptable, > 1.5 is good, > 2.0 is suspicious (probably overfit)
  • **Max drawdown < 30%** is usually tolerable; > 50% is too risky for most
  • **Win rate**: 35-65% is normal; outside this range suggests overfit
  • **Profit factor > 1.5** indicates the strategy is structurally sound

  • If a strategy shows Sharpe 3.0 with 80% win rate, it's almost certainly overfit. Real strategies aren't that good.


    What vectorbt doesn't do well


    A few limitations worth knowing:


    **Multi-leg strategies** — vectorbt is designed for single-instrument strategies. Pairs trading or basket strategies require manual portfolio construction outside vectorbt.


    **Order book microstructure** — vectorbt simulates fills based on close prices. It doesn't model order book depth, queue position, or adverse selection. For market-making strategies, you need a different tool.


    **Funding rates** — vectorbt doesn't natively handle perpetual funding rates. You can approximate by adjusting commissions, but proper funding modelling requires custom code.


    **Real-time switching to live trading** — vectorbt is research-focused. Going from a backtested strategy to a live bot requires rewriting the strategy logic in your bot framework. The signals generated in vectorbt aren't directly executable.


    For these cases, you might need to combine vectorbt for research with a separate execution framework (CCXT, native exchange APIs).


    Frequently Asked Questions


    Q: How much historical data do I need?

    Minimum 1 year for hourly data, 3 years for daily. Less data means less reliable parameters. Crypto's regime changes are dramatic; longer histories help identify what works across regimes.


    Q: Should I trust vectorbt's results?

    The mechanics are accurate. Your trust should depend on whether you've avoided lookahead bias, used realistic costs, and tested out-of-sample. vectorbt makes the simulation easy; it doesn't fix bad methodology.


    Q: How do I do walk-forward analysis in vectorbt?

    Split data into windows, optimize on each window's first half, test on the second half. There's no built-in helper — you write a loop over time windows manually. Tedious but essential for honest backtesting.


    Q: Is vectorbt free?

    There's a free open-source version with most features. A paid Pro version adds some optimizations and convenience functions. For most retail use, free is sufficient.


    Related Articles


    → Backtesting Your LMEX Trading Bot in Python: A Practical Guide
    → Walk-Forward Optimization: The Only Backtest Method That Survives Reality
    → Building a Crypto Perpetuals Trading Bot in Python: Complete Guide
    ← All ArticlesBuild a Bot →