A flexible backtesting framework for Python designed for quantitative trading strategy development and testing.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Core backtesting functionality for combining strategies with data, executing backtests, and analyzing results. The engine provides comprehensive statistics, visualization capabilities, and specialized result analysis tools.
The primary class for combining strategies with historical data to produce backtest results.
class Backtest:
"""
Main backtesting class that combines strategy with data.
Args:
strategy: Strategy instance to backtest
data: Historical price data (DataFrame)
name (str, optional): Backtest name
initial_capital (float): Starting capital (default 1,000,000)
commissions (callable, optional): Commission calculation function
integer_positions (bool): Whether to use integer position sizes
progress_bar (bool): Show progress during execution
additional_data (dict, optional): Additional data for strategy use
"""
def __init__(self, strategy, data, name=None, initial_capital=1000000.0,
commissions=None, integer_positions=True, progress_bar=False,
additional_data=None): ...
def run(self):
"""Execute the backtest."""
...
# Properties
@property
def strategy(self): ...
@property
def data(self): ...
@property
def dates(self): ...
@property
def initial_capital(self) -> float: ...
@property
def name(self) -> str: ...
@property
def stats(self): ...
@property
def has_run(self) -> bool: ...
@property
def weights(self): ...
@property
def positions(self): ...
@property
def security_weights(self): ...
@property
def herfindahl_index(self): ...
@property
def turnover(self): ...
@property
def additional_data(self): ...Primary function for running multiple backtests and comparing results.
def run(*backtests):
"""
Run multiple backtests and return Result object.
Args:
*backtests: Variable number of Backtest instances
Returns:
Result: Combined results object for analysis
"""
...Comprehensive results container with analysis and visualization capabilities.
class Result:
"""
Results analysis class inheriting from ffn.GroupStats.
Container for backtest results with analysis capabilities.
Args:
*backtests: Variable number of completed Backtest instances
"""
def __init__(self, *backtests): ...
def display_monthly_returns(self, backtest=0):
"""
Display monthly return table.
Args:
backtest (int): Index of backtest to display
"""
...
def get_weights(self, backtest=0, filter=None):
"""
Get strategy weights over time.
Args:
backtest (int): Index of backtest
filter (callable, optional): Filter function for weights
Returns:
DataFrame: Weight data over time
"""
...
def plot_weights(self, backtest=0, filter=None, figsize=(15, 5), **kwds):
"""
Plot strategy weights over time.
Args:
backtest (int): Index of backtest
filter (callable, optional): Filter function for weights
figsize (tuple): Figure size
**kwds: Additional plotting arguments
"""
...
def get_security_weights(self, backtest=0, filter=None):
"""
Get security-level weights over time.
Args:
backtest (int): Index of backtest
filter (callable, optional): Filter function for weights
Returns:
DataFrame: Security weight data over time
"""
...
def plot_security_weights(self, backtest=0, filter=None, figsize=(15, 5), **kwds):
"""
Plot security-level weights over time.
Args:
backtest (int): Index of backtest
filter (callable, optional): Filter function for weights
figsize (tuple): Figure size
**kwds: Additional plotting arguments
"""
...
def plot_histogram(self, backtest=0, **kwds):
"""
Plot return histogram.
Args:
backtest (int): Index of backtest
**kwds: Additional plotting arguments
"""
...
def get_transactions(self, strategy_name=None):
"""
Get transaction data for analysis.
Args:
strategy_name (str, optional): Specific strategy name to filter
Returns:
DataFrame: Transaction history
"""
...
# Properties
@property
def backtest_list(self) -> list: ...
@property
def backtests(self) -> dict: ...Utilities for comparing strategies against random benchmarks.
def benchmark_random(backtest, random_strategy, nsim=100):
"""
Benchmark strategy against random portfolios.
Args:
backtest: Backtest instance to benchmark
random_strategy: Random strategy for comparison
nsim (int): Number of random simulations
Returns:
RandomBenchmarkResult: Benchmarking results
"""
...
class RandomBenchmarkResult(Result):
"""
Results for random strategy benchmarking.
Args:
*backtests: Backtest instances including base and random strategies
"""
def __init__(self, *backtests): ...
def plot_histogram(self, statistic="monthly_sharpe", figsize=(15, 5),
title=None, bins=20, **kwargs):
"""
Plot benchmark histogram comparing strategy to random portfolios.
Args:
statistic (str): Statistic to compare
figsize (tuple): Figure size
title (str, optional): Plot title
bins (int): Number of histogram bins
**kwargs: Additional plotting arguments
"""
...
# Properties
@property
def base_name(self) -> str: ...
@property
def r_stats(self): ... # Random strategy statistics
@property
def b_stats(self): ... # Base strategy statisticsSpecialized result class for fixed income strategy analysis with renormalization.
class RenormalizedFixedIncomeResult(Result):
"""
Results for comparing FixedIncomeStrategy with different normalizations.
Args:
normalizing_value: Value used for renormalization
*backtests: Backtest instances to compare
"""
def __init__(self, normalizing_value, *backtests): ...
def _price(self, s, v):
"""Internal price calculation with renormalization."""
...import bt
import pandas as pd
# Get data
data = bt.get('SPY,TLT', start='2010-01-01', end='2020-01-01')
# Create strategy
strategy = bt.Strategy('EqualWeight', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
# Run backtest
backtest = bt.Backtest(strategy, data, initial_capital=100000)
result = bt.run(backtest)
# Analyze results
print(result.stats)
result.plot()
result.display_monthly_returns()# Create multiple strategies
equal_weight = bt.Strategy('EqualWeight', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
risk_parity = bt.Strategy('RiskParity', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighInvVol(),
bt.algos.Rebalance()
])
# Run multiple backtests
bt1 = bt.Backtest(equal_weight, data)
bt2 = bt.Backtest(risk_parity, data)
result = bt.run(bt1, bt2)
# Compare results
result.plot()
result.display()def custom_commission(quantity, price):
"""Custom commission: $0.01 per share, min $1."""
return max(abs(quantity) * 0.01, 1.0)
# Use in backtest
backtest = bt.Backtest(strategy, data, commissions=custom_commission)
result = bt.run(backtest)# Analyze strategy weights over time
result = bt.run(backtest)
# Get weights data
weights = result.get_weights(0)
print(weights.head())
# Plot weights
result.plot_weights(0, figsize=(12, 6))
# Get security-level weights
sec_weights = result.get_security_weights(0)
result.plot_security_weights(0)# Get transaction history
transactions = result.get_transactions()
print(transactions.head())
# Analyze turnover
print(f"Average monthly turnover: {backtest.turnover.mean():.2%}")
# Plot turnover over time
backtest.turnover.plot(title='Monthly Turnover')# Create random strategy for benchmarking
random_strat = bt.Strategy('Random', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighRandomly(),
bt.algos.Rebalance()
])
# Benchmark against random portfolios
benchmark_result = bt.benchmark_random(backtest, random_strat, nsim=1000)
benchmark_result.plot_histogram('monthly_sharpe', bins=50)The Result class inherits from ffn.GroupStats, providing access to comprehensive performance statistics:
# Access built-in statistics
stats = result.stats
print(f"Total Return: {stats.loc['total_return'].iloc[0]:.2%}")
print(f"Sharpe Ratio: {stats.loc['monthly_sharpe'].iloc[0]:.2f}")
print(f"Max Drawdown: {stats.loc['max_drawdown'].iloc[0]:.2%}")
print(f"Volatility: {stats.loc['monthly_vol'].iloc[0]:.2%}")
# Plot equity curves
result.plot()
# Plot drawdown
result.plot_histogram()Install with Tessl CLI
npx tessl i tessl/pypi-bt