Skip to main content

Crypto Backtester

PyPI version PyPI - Downloads Python 3.11+ License: MIT Tests Code Quality codecov Ruff

A backtesting framework for cryptocurrency trading strategies on Binance. Supports spot and perpetual futures markets with risk management, position sizing, and performance analytics.

Features

  • Historical Data Collection: Automated collection and caching of OHLCV, trades, funding rates, and open interest data
  • Strategy Backtesting: Run strategies over historical data with configurable time steps
  • Risk Management: User-defined position manager for custom risk screening and position sizing
  • OMS Simulation: Order management system that tracks positions, balances, and trade history
  • Performance Metrics: Returns, Sharpe ratio, drawdown, and permutation testing
  • Multiple Market Types: Support for both spot and perpetual futures markets

Prerequisites

  • Python 3.11 or higher
  • pip or uv package manager

Installation

pip install crypto-backtester-binance

From Source

git clone https://github.com/LevRoz630/crypto-backtester.git
cd crypto-backtester
pip install -e .

With development dependencies:

pip install -e ".[dev,test,docs]"

Data Directory

The framework will automatically download and cache historical data. You can specify a custom path when initializing the Backtester, or it defaults to ./historical_data.

Project Structure

crypto-backtester-binance/
├── src/crypto_backtester_binance/  # Core library
│   ├── backtester.py               # Main backtest orchestrator
│   ├── oms_simulation.py           # Order management system
│   ├── hist_data.py                # Historical data collector
│   └── utils.py                    # Utility functions
├── examples/                       # Example scripts
│   ├── position_manager.py         # Reference position manager (user-defined)
│   ├── 01_simple_backtest.py       # Buy-and-hold strategy
│   ├── 02_momentum_strategy.py     # SMA crossover strategy
│   └── 03_long_short_strategy.py   # Long BTC / Short Alts
├── tests/                          # Test suite
├── docs/                           # Documentation
└── historical_data/                # Cached data (created on first run)

Quick Start

Running Your First Backtest

from datetime import datetime, timedelta, UTC
from typing import Any

from crypto_backtester_binance.backtester import Backtester
from crypto_backtester_binance.hist_data import HistoricalDataCollector
from crypto_backtester_binance.oms_simulation import OMSClient


# Define a simple strategy
class HoldStrategy:
    def __init__(self, symbols: list[str], lookback_days: int = 0):
        self.symbols = symbols
        self.lookback_days = lookback_days
        self.has_bought = False

    def run_strategy(self, oms_client: OMSClient, data_manager: HistoricalDataCollector):
        orders = []
        if not self.has_bought:
            for symbol in self.symbols:
                orders.append({"symbol": symbol, "instrument_type": "future", "side": "LONG"})
            self.has_bought = True
        return orders


# Define a position manager (user-defined risk management)
class SimplePositionManager:
    def filter_orders(
        self, orders: list[dict[str, Any]], oms_client: Any, data_manager: HistoricalDataCollector
    ) -> list[dict[str, Any]] | None:
        """Add position sizing to orders. Must return orders with 'value' field."""
        if not orders:
            return None
        # Simple equal-weight allocation: 10% of balance per order
        budget = oms_client.balance["USDT"] * 0.1 / len(orders)
        return [{**order, "value": budget} for order in orders]


# Initialize backtester
backtester = Backtester(historical_data_dir="./historical_data")

# Create strategy and position manager
strategy = HoldStrategy(symbols=["BTC-USDT", "ETH-USDT", "SOL-USDT"])
position_manager = SimplePositionManager()

# Set backtest period
start_date = datetime.now(UTC) - timedelta(days=50)
end_date = datetime.now(UTC) - timedelta(days=1)

# Run backtest
results = backtester.run_backtest(
    strategy=strategy,
    position_manager=position_manager,
    start_date=start_date,
    end_date=end_date,
    time_step=timedelta(days=1),
    market_type="futures",
)

# View results
print(f"Total Return: {results['total_return']:.2%}")
print(f"Sharpe Ratio: {results['sharpe_ratio']:.2f}")
print(f"Max Drawdown: {results['max_drawdown']:.2%}")

# Generate plots
backtester.plot_portfolio_value()
backtester.plot_positions(results)

The first run will download historical data automatically. Subsequent runs use cached data.

Running Example Scripts

# Simple buy-and-hold
python examples/01_simple_backtest.py

# Momentum strategy (SMA crossover)
python examples/02_momentum_strategy.py

# Long BTC / Short Alts
python examples/03_long_short_strategy.py

Creating Custom Strategies

A strategy must implement the run_strategy method that returns a list of order dictionaries:

from datetime import timedelta

from crypto_backtester_binance.hist_data import HistoricalDataCollector
from crypto_backtester_binance.oms_simulation import OMSClient


class MyStrategy:
    def __init__(self, symbols: list[str], lookback_days: int):
        self.symbols = symbols
        self.lookback_days = lookback_days

    def run_strategy(
        self,
        oms_client: OMSClient,
        data_manager: HistoricalDataCollector,
    ) -> list[dict]:
        """
        Generate trading orders based on strategy logic.

        Returns:
            List of order dictionaries with keys:
            - symbol: str (e.g., "BTC-USDT")
            - instrument_type: str ("spot" or "future")
            - side: str ("LONG", "SHORT", or "CLOSE")
            - value: float (optional, USDT notional)
        """
        orders = []

        for symbol in self.symbols:
            # Load historical data
            data = data_manager.load_data_period(
                symbol=symbol,
                timeframe="1h",
                data_type="mark_ohlcv_futures",
                start_date=oms_client.current_time - timedelta(days=self.lookback_days),
                end_date=oms_client.current_time,
            )

            # Your strategy logic here...

            orders.append({
                "symbol": symbol,
                "instrument_type": "future",
                "side": "LONG",  # or "SHORT" or "CLOSE"
            })

        return orders

Configuration

Market Types

  • "futures": Uses perpetual futures data with margin-based positions

Time Steps

Supported time deltas map to data timeframes:

  • timedelta(minutes=1) → "1m"
  • timedelta(minutes=5) → "5m"
  • timedelta(minutes=15) → "15m" (default)
  • timedelta(minutes=30) → "30m"
  • timedelta(hours=1) → "1h"

Position Manager

The position manager is user-defined - you create it to match your risk management needs. It must implement a filter_orders method with this signature:

def filter_orders(
    self,
    orders: list[dict],
    oms_client: OMSClient,
    data_manager: HistoricalDataCollector
) -> list[dict] | None:
    """
    Process orders from strategy before execution.

    Args:
        orders: Raw orders from strategy
        oms_client: Access to balance, positions, current time
        data_manager: Access to historical data

    Returns:
        List of orders with 'value' field set, or None to skip
    """

See examples/position_manager.py for a reference implementation with:

  • Volatility-based risk screening
  • Inverse-volatility position sizing
  • Budget enforcement

Documentation

Building Documentation

If you installed with [docs]:

# Build static HTML
mkdocs build

# Serve locally for preview
mkdocs serve

Then open http://127.0.0.1:8000 in your browser.

Documentation Structure

  • Overview: Architecture and data flow (docs/overview.md)
  • API Reference: Auto-generated from docstrings (docs/api/)
  • Examples: Strategy implementations (examples/)

Permutation Testing

Test strategy significance using randomized returns:

results = backtester.run_permutation_backtest(
    strategy=strategy,
    position_manager=position_manager,
    start_date=start_date,
    end_date=end_date,
    time_step=timedelta(days=1),
    market_type="futures",
    permutations=100,  # Number of random permutations
)

print(f"P-value: {results['p_value']:.4f}")
print(f"Observed Sharpe: {results['sharpe_ratio']:.2f}")

Troubleshooting

Data Download Issues

  • Ensure you have internet connectivity for first-time data collection
  • Check that the historical_data directory is writable
  • Data is cached in Parquet format for fast subsequent loads

Import Errors

If you see import errors, ensure the package is installed:

pip install crypto-backtester-binance

Or for development:

pip install -e .

Memory Issues

For large backtests:

  • Reduce lookback_days in your strategy
  • Use longer time_step intervals
  • Process data in chunks

License

See LICENSE file for details.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

Support

For issues and questions, please open an issue on the repository.

Metadata

Release files for crypto-backtester-binance 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for crypto-backtester-binance 1.0.0
File Size Uploaded
crypto_backtester_binance-1.0.0.tar.gz 263.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for crypto-backtester-binance 1.0.0
File Interpreter ABI Platform
crypto_backtester_binance-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 290.7 kB

Release files / crypto_backtester_binance-1.0.0.tar.gz

Download URL crypto_backtester_binance-1.0.0.tar.gz
Size 263.4 kB
Tags Source
SHA-256 checksum
How to use checksums
69440f52d5938d359ac6c50ffc2cd27be60f96029c2afcc80035d7f612b4d9ad
BLAKE2b-256 checksum
How to use checksums
c045e5096c89aceb8f528a4983b55a408940f075dfc1c795357013b2d3201938
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / crypto_backtester_binance-1.0.0-py3-none-any.whl

Download URL crypto_backtester_binance-1.0.0-py3-none-any.whl
Size 27.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c39c0f3952f623671ee342c13eeb3d9dd1d76846eed7c42a438dc439b6a04e5d
BLAKE2b-256 checksum
How to use checksums
0cbe3d7946320600ae626da3b713699968cdb2eb6eacd215c29cb651e4b11984
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page