Backtesting.py
Backtesting.py is a Python framework for backtesting trading strategies on historical candlestick data. It provides a fast, lightweight, and user-friendly API to define strategies, run simulations, inspect detailed statistics, and explore interactive charts. It is currently at version 0.6.5 and is actively maintained with regular releases.
Warnings
- breaking The `Backtest(commission=)` parameter in versions 0.6.0 and later now applies commission *twice* per trade (at both entry and exit). If you intend for costs to be applied only once at entry (e.g., for spread/slippage), use the new `Backtest(spread=)` parameter instead.
- breaking Version 0.2.0 introduced a completely new `Order / Trade / Position API`. Strategies written for versions prior to 0.2.0 are incompatible and require significant updates to use the new API.
- gotcha Bokeh versions 3.0.x and 3.2.x are known to have incompatibilities with `backtesting.py`'s interactive plotting feature, potentially leading to errors.
- gotcha Trading decisions made in the `next()` method are typically executed on the *next* bar's open price (or current bar's close if `trade_on_close=True`). This simulation characteristic means immediate execution at the exact candle price that triggered a signal is not guaranteed, which can impact profitability, especially for high-frequency or tight stop-loss/take-profit strategies.
- gotcha When defining indicators within `Strategy.init()`, it's crucial to wrap indicator functions with `self.I()`. This ensures that indicator values are revealed gradually, bar-by-bar, simulating real-time data availability and preventing look-ahead bias. Failing to do so (e.g., directly computing indicators on `self.data.Close` for the full dataset outside `self.I()`) will lead to unrealistic, overly optimistic backtest results.
- gotcha If a trading position is opened with `self.buy()` or `self.sell()` but never explicitly closed (e.g., with `self.position.close()`, `self.sell()`, or `self.buy()`), it will remain open until the end of the backtest. This can lead to misleading or `NaN`/`0` values in performance statistics because trades are not fully realized within the backtesting period.
Install
-
pip install backtesting
Imports
- Backtest
from backtesting import Backtest
- Strategy
from backtesting import Strategy
- crossover
from backtesting.lib import crossover
- SMA
from backtesting.test import SMA
- GOOG
from backtesting.test import GOOG
Quickstart
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA, GOOG
class SmaCross(Strategy):
def init(self):
price = self.data.Close
self.ma1 = self.I(SMA, price, 10)
self.ma2 = self.I(SMA, price, 20)
def next(self):
if crossover(self.ma1, self.ma2):
self.buy()
elif crossover(self.ma2, self.ma1):
self.sell()
# Prepare data (using built-in test data for quickstart)
# In a real scenario, you'd load your own pandas.DataFrame
# with columns 'Open', 'High', 'Low', 'Close', 'Volume' (optional)
# and a DatetimeIndex.
# For example: from pandas_datareader import data as yf
# data = yf.DataReader('SPY', start='2000', end='2020')
bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002, exclusive_orders=True)
stats = bt.run()
print(stats)
bt.plot(open_browser=False)