Core framework data structures. Objects from this module can also be imported from the top-level module directly, e.g.
from backtesting import Backtest, Strategy
class Backtest (data, strategy, *, cash=10000, commission=0.0, margin=1.0, trade_on_close=False, hedging=False, exclusive_orders=False)
Backtest a particular (parameterized) strategy on particular data.
Initialize a backtest. Requires data and a strategy to test.
Close, and (optionally)
Volume. If any columns are missing, set them to what you have available, e.g.
df['Open'] = df['High'] = df['Low'] = df['Close']
The passed data frame can contain additional columns that can be used by the strategy (e.g. sentiment info). DataFrame index can be either a datetime index (timestamps) or a monotonic range index (i.e. a sequence of periods).
Strategysubclass (not an instance).
cashis the initial cash to start with.
commissionis the commission ratio. E.g. if your broker's commission is 1% of trade value, set commission to
0.01. Note, if you wish to account for bid-ask spread, you can approximate doing so by increasing the commission, e.g. set it to
0.0002for commission-less forex trading where the average spread is roughly 0.2‰ of asking price.
marginis the required margin (ratio) of a leveraged account. No difference is made between initial and maintenance margins. To run the backtest using e.g. 50:1 leverge that your broker allows, set margin to
0.02(1 / leverage).
True, market orders will be filled with respect to the current bar's closing price instead of the next bar's open.
True, allow trades in both directions simultaneously. If
False, the opposite-facing orders first close existing trades in a FIFO manner.
True, each new order auto-closes the previous trade/position, making at most a single trade (long or short) in effect at each time.
def optimize(self, *, maximize='SQN', method='grid', max_tries=None, constraint=None, return_heatmap=False, return_optimization=False, random_state=None, **kwargs)
Optimize strategy parameters to an optimal combination. Returns result
pd.Seriesof the best run.
maximizeis a string key from the
Backtest.run()-returned results series, or a function that accepts this series object and returns a number; the higher the better. By default, the method maximizes Van Tharp's System Quality Number.
methodis the optimization method. Currently two methods are supported:
"grid"which does an exhaustive (or randomized) search over the cartesian product of parameter combinations, and
"skopt"which finds close-to-optimal strategy parameters using model-based optimization, making at most
max_triesis the maximal number of strategy runs to perform. If
method="grid", this results in randomized grid search. If
max_triesis a floating value between (0, 1], this sets the number of runs to approximately that fraction of full grid space. Alternatively, if integer, it denotes the absolute maximum number of evaluations. If unspecified (default), grid search is exhaustive, whereas for
max_triesis set to 200.
constraintis a function that accepts a dict-like object of parameters (with values) and returns
Truewhen the combination is admissible to test with. By default, any parameters combination is considered admissible.
True, besides returning the result series, an additional
pd.Seriesis returned with a multiindex of all admissible parameter combinations, which can be further inspected or projected onto 2D to plot a heatmap (see
return_optimizationis True and
method = 'skopt', in addition to result series (and maybe heatmap), return raw
scipy.optimize.OptimizeResultfor further inspection, e.g. with scikit-optimize plotting tools.
If you want reproducible optimization results, set
random_stateto a fixed integer random seed.
Additional keyword arguments represent strategy arguments with list-like collections of possible values. For example, the following code finds and returns the "best" of the 7 admissible (of the 9 possible) parameter combinations:
backtest.optimize(sma1=[5, 10, 15], sma2=[10, 20, 40], constraint=lambda p: p.sma1 < p.sma2)
Improve multiprocessing/parallel execution on Windos with start method 'spawn'.
def plot(self, *, results=None, filename=None, plot_width=None, plot_equity=True, plot_return=False, plot_pl=True, plot_volume=True, plot_drawdown=False, smooth_equity=False, relative_equity=True, superimpose=True, resample=True, reverse_indicators=False, show_legend=True, open_browser=True)
Plot the progression of the last backtest run.
filenameis the path to save the interactive HTML plot to. By default, a strategy/parameter-dependent file is created in the current working directory.
plot_widthis the width of the plot in pixels. If None (default), the plot is made to span 100% of browser width. The height is currently non-adjustable.
True, the resulting plot will contain an equity (initial cash plus assets) graph section. This is the same as
plot_returnplus initial 100%.
True, the resulting plot will contain a cumulative return graph section. This is the same as
plot_equityminus initial 100%.
True, the resulting plot will contain a profit/loss (P/L) indicator section.
True, the resulting plot will contain a trade volume section.
True, the resulting plot will contain a separate drawdown graph section.
True, the equity graph will be interpolated between fixed points at trade closing times, unaffected by any interim asset volatility.
True, scale and label equity graph axis with return percent, not absolute cash-equivalent values.
True, superimpose larger-timeframe candlesticks over the original candlestick chart. Default downsampling rule is: monthly for daily data, daily for hourly data, hourly for minute data, and minute for (sub-)second data.
superimposecan also be a valid Pandas offset string, such as
'5min', in which case this frequency will be used to superimpose. Note, this only works for data with a datetime index.
True, the OHLC data is resampled in a way that makes the upper number of candles for Bokeh to plot limited to 10_000. This may, in situations of overabundant data, improve plot's interactive performance and avoid browser's
resamplecan also be a Pandas offset string, such as
'5min', in which case this frequency will be used to resample, overriding above numeric limitation. Note, all this only works for data with a datetime index.
True, the indicators below the OHLC chart are plotted in reverse order of declaration.
True, the resulting plot graphs will contain labeled legends.
True, the resulting
filenamewill be opened in the default web browser.
def run(self, **kwargs)
Run the backtest. Returns
pd.Serieswith results and statistics.
Keyword arguments are interpreted as strategy parameters.
>>> Backtest(GOOG, SmaCross).run() Start 2004-08-19 00:00:00 End 2013-03-01 00:00:00 Duration 3116 days 00:00:00 Exposure Time [%] 93.9944 Equity Final [$] 51959.9 Equity Peak [$] 75787.4 Return [%] 419.599 Buy & Hold Return [%] 703.458 Return (Ann.) [%] 21.328 Volatility (Ann.) [%] 36.5383 Sharpe Ratio 0.583718 Sortino Ratio 1.09239 Calmar Ratio 0.444518 Max. Drawdown [%] -47.9801 Avg. Drawdown [%] -5.92585 Max. Drawdown Duration 584 days 00:00:00 Avg. Drawdown Duration 41 days 00:00:00 # Trades 65 Win Rate [%] 46.1538 Best Trade [%] 53.596 Worst Trade [%] -18.3989 Avg. Trade [%] 2.35371 Max. Trade Duration 183 days 00:00:00 Avg. Trade Duration 46 days 00:00:00 Profit Factor 2.08802 Expectancy [%] 8.79171 SQN 0.916893 _strategy SmaCross _equity_curve Eq... _trades Size EntryB... dtype: object
If you wish to modify aspects of a placed but not yet filled order, cancel it and place a new one instead.
All placed orders are Good 'Til Canceled.
True if the order is long (order size is positive).
True if the order is short (order size is negative).
Order size (negative for short orders).
If size is a value between 0 and 1, it is interpreted as a fraction of current available liquidity (cash plus
Position.plminus used margin). A value greater than or equal to 1 indicates an absolute number of units.
Order stop price for stop-limit/stop-market order, otherwise None if no stop was set, or the stop price has already been hit.
Cancel the order.
if self.position: ... # we have a position, either long or short
True if the position is long (position size is positive).
True if the position is short (position size is negative).
Profit (positive) or loss (negative) of the current position in cash units.
Profit (positive) or loss (negative) of the current position in percent.
Position size in units of asset. Negative if position is short.
def close(self, portion=1.0)
Close portion of position by closing
portionof each active trade. See
List of settled trades (see
Price data, roughly as passed into
Backtest, but with two significant exceptions:
datais not a DataFrame, but a custom structure that serves customized numpy arrays for reasons of performance and convenience. Besides OHLCV columns,
.indexand length, it offers
.pipproperty, the smallest price unit of change.
dataarrays are available in full length, as passed into
Backtest(for precomputing indicators and such). However, within
dataarrays are only as long as the current iteration, simulating gradual price point revelation. In each call of
Strategy.next()(iteratively called by
Backtestinternally), the last array value (e.g.
data.Close[-1]) is always the most recent value.
- If you need data arrays (e.g.
data.Close) to be indexed Pandas series, you can call their
data.Close.s). If you need the whole of data as a DataFrame, use
Current account equity (cash plus assets).
List of orders (see
Order) waiting for execution.
List of active trades (see
def I(self, func, *args, name=None, plot=True, overlay=None, color=None, scatter=False, **kwargs)
funcis a function that returns the indicator array(s) of same length as
In the plot legend, the indicator is labeled with function name, unless
True, the indicator is plotted on the resulting
True, the indicator is plotted overlaying the price candlestick chart (suitable e.g. for moving averages). If
False, the indicator is plotted standalone below the candlestick chart. By default, a heuristic is used which decides correctly most of the time.
colorcan be string hex RGB triplet or X11 color name. By default, the next available color is assigned.
True, the plotted indicator marker will be a circle instead of a connected line segment (default).
**kwargsare passed to
funcand can be used for parameters.
For example, using simple moving average function from TA-Lib:
def init(): self.sma = self.I(ta.SMA, self.data.Close, self.n_sma)
def buy(self, *, size=.9999, limit=None, stop=None, sl=None, tp=None)
Main strategy runtime method, called as each new
Strategy.datainstance (row; full candlestick bar) becomes available. This is the main method where strategy decisions upon data precomputed in
If you extend composable strategies from
backtesting.lib, make sure to call:
def sell(self, *, size=.9999, limit=None, stop=None, sl=None, tp=None)
Candlestick bar index of when the trade was entered.
Trade entry price.
Datetime of when the trade was entered.
Candlestick bar index of when the trade was exited (or None if the trade is still active).
Trade exit price (or None if the trade is still active).
Datetime of when the trade was exited.
True if the trade is long (trade size is positive).
True if the trade is short (trade size is negative).
Trade profit (positive) or loss (negative) in cash units.
Trade profit (positive) or loss (negative) in percent.
Trade size (volume; negative for short trades).
Stop-loss price at which to close the trade.
This variable is writable. By assigning it a new price value, you create or modify the existing SL order. By assigning it
None, you cancel it.
Take-profit price at which to close the trade.
This property is writable. By assigning it a new price value, you create or modify the existing TP order. By assigning it
None, you cancel it.
Trade total value in cash (volume × price).
def close(self, portion=1.0)
portionof the trade at next market price.