Module backtesting.backtesting
+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
+
+Classes
+-
+
+class Backtest +(data,
strategy,
*,
cash=10000,
spread=0.0,
commission=0.0,
margin=1.0,
trade_on_close=False,
hedging=False,
exclusive_orders=False,
finalize_trades=False) +
+-
++
Backtest a particular (parameterized) strategy +on particular data.
+Initialize a backtest. Requires data and a strategy to test. +After initialization, you can call method +
+Backtest.run()to run a backtest +instance, orBacktest.optimize()to +optimize it.
+datais apd.DataFramewith columns: +Open,High,Low,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).
+
+strategyis aStrategy+subclass (not an instance).
+cashis the initial cash to start with.
+spreadis the the constant bid-ask spread rate (relative to the price). +E.g. set it to0.0002for commission-less forex +trading where the average spread is roughly 0.2‰ of the asking price.
+commissionis the commission rate. E.g. if your broker's commission +is 1% of order value, set commission to0.01. +The commission is applied twice: at trade entry and at trade exit. +Besides one single floating value,commissioncan also be a tuple of floating +values(fixed, relative). E.g. set it to(100, .01)+if your broker charges minimum $100 + 1%. +Additionally,commissioncan be a callable +func(order_size: int, price: float) -> float+(note, order size is negative for short orders), +which can be used to model more complex commission structures. +Negative commission values are interpreted as market-maker's rebates.++Note
+Before v0.4.0, the commission was only applied once, like
+spreadis now. +If you want to keep the old behavior, simply setspreadinstead.++Note
+With nonzero
+commission, long and short orders will be placed +at an adjusted price that is slightly higher or lower (respectively) +than the current price. See e.g. +#153, +#538, +#633.
+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 to0.02(1 / leverage).If
+trade_on_closeisTrue, market orders will be filled +with respect to the current bar's closing price instead of the +next bar's open.If
+hedgingisTrue, allow trades in both directions simultaneously. +IfFalse, the opposite-facing orders first close existing trades in +a FIFO manner.If
+exclusive_ordersisTrue, each new order auto-closes the previous +trade/position, making at most a single trade (long or short) in effect +at each time.If
+finalize_tradesisTrue, the trades that are still +active and ongoing at the end of the backtest will be closed on +the last bar and will contribute to the computed backtest statistics.+Tip: Fractional trading
+See also
+FractionalBacktestif you want to trade +fractional units (of e.g. bitcoin).Subclasses
+ +Methods
+-
+
+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
+"sambo"which finds close-to-optimal strategy parameters using +model-based optimization, making at mostmax_triesevaluations.
+
+max_triesis the maximal number of strategy runs to perform. +Ifmethod="grid", this results in randomized grid search. +Ifmax_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 formethod="sambo",max_triesis set to 200.
+constraintis a function that accepts a dict-like object of +parameters (with values) and returnsTruewhen the combination +is admissible to test with. By default, any parameters combination +is considered admissible.If
+return_heatmapisTrue, besides returning the result +series, an additionalpd.Seriesis returned with a multiindex +of all admissible parameter combinations, which can be further +inspected or projected onto 2D to plot a heatmap +(seeplot_heatmaps()).If
+return_optimizationis True andmethod = 'sambo', +in addition to result series (and maybe heatmap), return raw +scipy.optimize.OptimizeResultfor further +inspection, e.g. with SAMBO's plotting tools.If you want reproducible optimization results, set
+random_state+to 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:
+best_stats = backtest.optimize(sma1=[5, 10, 15], sma2=[10, 20, 40], + constraint=lambda p: p.sma1 < p.sma2) +
+ +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,
plot_trades=True,
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.
+If
+resultsis provided, it should be a particular result +pd.Seriessuch as returned by +Backtest.run()or +Backtest.optimize(), otherwise the last +run's results are used.
+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.If
+plot_equityisTrue, the resulting plot will contain +an equity (initial cash plus assets) graph section. This is the same +asplot_returnplus initial 100%.If
+plot_returnisTrue, the resulting plot will contain +a cumulative return graph section. This is the same +asplot_equityminus initial 100%.If
+plot_plisTrue, the resulting plot will contain +a profit/loss (P/L) indicator section.If
+plot_volumeisTrue, the resulting plot will contain +a trade volume section.If
+plot_drawdownisTrue, the resulting plot will contain +a separate drawdown graph section.If
+plot_tradesisTrue, the stretches between trade entries +and trade exits are marked by hash-marked tractor beams.If
+smooth_equityisTrue, the equity graph will be +interpolated between fixed points at trade closing times, +unaffected by any interim asset volatility.If
+relative_equityisTrue, scale and label equity graph axis +with return percent, not absolute cash-equivalent values.If
+superimposeisTrue, 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'5T'or'5min', in which case this frequency will be +used to superimpose. +Note, this only works for data with a datetime index.If
+resampleisTrue, 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 +Javascript Error: Maximum call stack size exceededor similar. +Equity & dropdown curves and individual trades data is, +likewise, reasonably aggregated. +resamplecan also be a Pandas offset string, +such as'5T'or'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.If
+reverse_indicatorsisTrue, the indicators below the OHLC chart +are plotted in reverse order of declaration.If
+show_legendisTrue, the resulting plot graphs will contain +labeled legends.If
open_browserisTrue, the resultingfilenamewill 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 [%] 96.74115 +Equity Final [$] 51422.99 +Equity Peak [$] 75787.44 +Return [%] 414.2299 +Buy & Hold Return [%] 703.45824 +Return (Ann.) [%] 21.18026 +Volatility (Ann.) [%] 36.49391 +CAGR [%] 14.15984 +Sharpe Ratio 0.58038 +Sortino Ratio 1.08479 +Calmar Ratio 0.44144 +Alpha [%] 394.37391 +Beta 0.03803 +Max. Drawdown [%] -47.98013 +Avg. Drawdown [%] -5.92585 +Max. Drawdown Duration 584 days 00:00:00 +Avg. Drawdown Duration 41 days 00:00:00 +# Trades 66 +Win Rate [%] 46.9697 +Best Trade [%] 53.59595 +Worst Trade [%] -18.39887 +Avg. Trade [%] 2.53172 +Max. Trade Duration 183 days 00:00:00 +Avg. Trade Duration 46 days 00:00:00 +Profit Factor 2.16795 +Expectancy [%] 3.27481 +SQN 1.07662 +Kelly Criterion 0.15187 +_strategy SmaCross +_equity_curve Eq... +_trades Size EntryB... +dtype: object ++Warning
+You may obtain different results for different strategy parameters. +E.g. if you use 50- and 200-bar SMA, the trading simulation will +begin on bar 201. The actual length of delay is equal to the lookback +period of the
+Strategy.I()indicator which lags the most. +Obviously, this can affect results.
+
+ +class Order +
+-
++
Place new orders through
+Strategy.buy()andStrategy.sell(). +Query existing orders throughStrategy.orders.When an order is executed or filled, it results in a
+Trade.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.
Instance variables
+-
+
prop is_contingent
+-
++
True for contingent orders, i.e. OCO stop-loss and take-profit bracket orders +placed upon an active trade. Remaining contingent orders are canceled when +their parent
+Tradeis closed.You can modify contingent orders through
Trade.slandTrade.tp.
+ prop is_long
+-
++
True if the order is long (order size is positive).
+ prop is_short
+-
++
True if the order is short (order size is negative).
+ prop limit
+-
++
Order limit price for limit orders, or None for market orders, +which are filled at next available price.
+ prop size
+-
++
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.
+ prop sl
+- + + +
prop stop
+-
++
Order stop price for stop-limit/stop-market order, +otherwise None if no stop was set, or the stop price has already been hit.
+ prop tag
+- + + +
prop tp
+- + + +
Methods
+-
+
+def cancel(self) +
+-
++
Cancel the order.
+
+ +class Position +
+-
++
Currently held asset position, available as +
+Strategy.positionwithin +Strategy.next(). +Can be used in boolean contexts, e.g.if self.position: + ... # we have a position, either long or short +Instance variables
+-
+
prop is_long
+-
++
True if the position is long (position size is positive).
+ prop is_short
+-
++
True if the position is short (position size is negative).
+ prop pl
+-
++
Profit (positive) or loss (negative) of the current position in cash units.
+ prop pl_pct
+-
++
Profit (positive) or loss (negative) of the current position in percent.
+ prop size
+-
++
Position size in units of asset. Negative if position is short.
+
Methods
+-
+
+def close(self, portion=1.0) +
+-
++
Close portion of position by closing
portionof each active trade. SeeTrade.close().
+
+ +class Strategy +
+-
++
A trading strategy base class. Extend this class and +override methods +
Strategy.init()and +Strategy.next()to define +your own strategy.Subclasses
+ +Instance variables
+-
+
prop closed_trades
+-
++
List of settled trades (see
Trade).
+ prop data
+-
++
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.
+- Within
Strategy.init(),dataarrays +are available in full length, as passed into +Backtest+(for precomputing indicators and such). However, within +Strategy.next(),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.saccessor +(e.g.data.Close.s). If you need the whole of data +as a DataFrame, use.dfaccessor (i.e.data.df).
+
+ prop equity
+-
++
Current account equity (cash plus assets).
+ prop orders
+-
++
List of orders (see
Order) waiting for execution.
+ prop position
+-
++
Instance of
Position.
+ prop trades
+-
++
List of active trades (see
Trade).
+
Methods
+-
+
+def I(self, func, *args, name=None, plot=True, overlay=None, color=None, scatter=False, **kwargs) +
+-
++
Declare an indicator. An indicator is just an array of values +(or a tuple of such arrays in case of, e.g., MACD indicator), +but one that is revealed gradually in +
+Strategy.next()much like +Strategy.datais. +Returnsnp.ndarrayof indicator values.
+funcis a function that returns the indicator array(s) of +same length asStrategy.data.In the plot legend, the indicator is labeled with +function name, unless
+nameoverrides it. Iffuncreturns +a tuple of arrays,namecan be a sequence of strings, and +its size must agree with the number of arrays returned.If
+plotisTrue, the indicator is plotted on the resulting +Backtest.plot().If
+overlayisTrue, the indicator is plotted overlaying the +price candlestick chart (suitable e.g. for moving averages). +IfFalse, 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.If
+scatterisTrue, the plotted indicator marker will be a +circle instead of a connected line segment (default).Additional
+*argsand**kwargsare passed tofuncand 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) ++Warning
+Rolling indicators may front-pad warm-up values with NaNs. +In this case, the backtest will only begin on the first bar when +all declared indicators have non-NaN values (e.g. bar 201 for a +strategy that uses a 200-bar MA). +This can affect results.
+
+ +def buy(self, *, size=.9999, limit=None, stop=None, sl=None, tp=None, tag=None) +
+-
++
Place a new long order and return it. For explanation of parameters, see
+Order+and its properties. +Unless you're runningBacktest(..., trade_on_close=True), +market orders are filled on next bar's open, +whereas other order types (limit, stop-limit, stop-market) are filled when +the respective conditions are met.See
+Position.close()andTrade.close()for closing existing positions.See also
Strategy.sell().
+ +def init(self) +
+-
++
Initialize the strategy. +Override this method. +Declare indicators (with
+Strategy.I()). +Precompute what needs to be precomputed or can be precomputed +in a vectorized fashion before the strategy starts.If you extend composable strategies from
+backtesting.lib, +make sure to call:super().init() +
+ +def next(self) +
+-
++
Main strategy runtime method, called as each new +
+Strategy.data+instance (row; full candlestick bar) becomes available. +This is the main method where strategy decisions +upon data precomputed inStrategy.init()+take place.If you extend composable strategies from
+backtesting.lib, +make sure to call:super().next() +
+ +def sell(self, *, size=.9999, limit=None, stop=None, sl=None, tp=None, tag=None) +
+-
++
Place a new short order and return it. For explanation of parameters, see
+Order+and its properties.++Caution
+Keep in mind that
+self.sell(size=.1)doesn't close existingself.buy(size=.1)+trade unless:-
+
- the backtest was run with
exclusive_orders=True,
+ - the underlying asset price is equal in both cases and
+the backtest was run with
spread = commission = 0.
+
Use
+Trade.close()orPosition.close()to explicitly exit trades.See also
+Strategy.buy().+Note
+If you merely want to close an existing long position, +use
+Position.close()orTrade.close().
+ - the backtest was run with
+ +class Trade +
+-
++
When an
Orderis filled, it results in an activeTrade. +Find active trades inStrategy.tradesand closed, settled trades inStrategy.closed_trades.Instance variables
+-
+
prop entry_bar
+-
++
Candlestick bar index of when the trade was entered.
+ prop entry_price
+-
++
Trade entry price.
+ prop entry_time
+-
++
Datetime of when the trade was entered.
+ prop exit_bar
+-
++
Candlestick bar index of when the trade was exited +(or None if the trade is still active).
+ prop exit_price
+-
++
Trade exit price (or None if the trade is still active).
+ prop exit_time
+-
++
Datetime of when the trade was exited.
+ prop is_long
+-
++
True if the trade is long (trade size is positive).
+ prop is_short
+-
++
True if the trade is short (trade size is negative).
+ prop pl
+-
++
Trade profit (positive) or loss (negative) in cash units. +Commissions are reflected only after the Trade is closed.
+ prop pl_pct
+-
++
Trade profit (positive) or loss (negative) in percent.
+ prop size
+-
++
Trade size (volume; negative for short trades).
+ prop sl
+-
++
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.
+ prop tag
+- + + +
prop tp
+-
++
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.
+ prop value
+-
++
Trade total value in cash (volume × price).
+
Methods
+-
+
+def close(self, portion=1.0) +
+-
++
Place new
Orderto closeportionof the trade at next market price.
+
+

