Module backtesting.lib
Collection of common building blocks, helper auxiliary functions and composable strategy classes for reuse.
Intended for simple missing-link procedures, not reinventing of better-suited, state-of-the-art, fast libraries, such as TA-Lib, Tulipy, PyAlgoTrade, NumPy, SciPy …
Please raise ideas for additions to this collection on the issue tracker.
var OHLCV_AGG-
Dictionary of rules for aggregating resampled OHLCV data frames, e.g.
df.resample('4H', label='right').agg(OHLCV_AGG).dropna() var TRADES_AGG-
Dictionary of rules for aggregating resampled trades data, e.g.
stats['_trades'].resample('1D', on='ExitTime', label='right').agg(TRADES_AGG)
-
Return the number of bars since
conditionsequence was lastTrue, or if never, returndefault.>>> barssince(self.data.Close > self.data.Open) 3 def compute_stats(*, stats, data, trades=None, risk_free_rate=0.0)-
(Re-)compute strategy performance metrics.
statsis the statistics series as returned byBacktest.run().datais OHLC data as passed to theBacktestthestatswere obtained in.tradescan be a dataframe subset ofstats._trades(e.g. only long trades). You can also tunerisk_free_rate, used in calculation of Sharpe and Sortino ratios.>>> stats = Backtest(GOOG, MyStrategy).run() >>> only_long_trades = stats._trades[stats._trades.Size > 0] >>> long_stats = compute_stats(stats=stats, trades=only_long_trades, ... data=GOOG, risk_free_rate=.02) def cross(series1, series2)-
Return
Trueifseries1andseries2just crossed (above or below) each other.>>> cross(self.data.Close, self.sma) True def crossover(series1, series2)-
Return
Trueifseries1just crossed over (above)series2.>>> crossover(self.data.Close, self.sma) True def plot_heatmaps(heatmap, agg='max', *, ncols=3, plot_width=1200, filename='', open_browser=True)def quantile(series, quantile=None)-
If
quantile()isNone, return the quantile rank of the last value ofserieswrt former series values.If
quantile()is a value between 0 and 1, return the value ofseriesat this quantile. If used to working with percentiles, just divide your percentile amount with 100 to obtain quantiles.>>> quantile(self.data.Close[-20:], .1) 162.130 >>> quantile(self.data.Close) 0.13 def random_ohlc_data(example_data, *, frac=1.0, random_state=None)-
OHLC data generator. The generated OHLC data has basic descriptive statistics similar to the provided
example_data.fracis a fraction of data to sample (with replacement). Values greater than 1 result in oversampling.Such random data can be effectively used for stress testing trading strategy robustness, Monte Carlo simulations, significance testing, etc.
>>> from backtesting.test import EURUSD >>> ohlc_generator = random_ohlc_data(EURUSD) >>> next(ohlc_generator) # returns new random data ... >>> next(ohlc_generator) # returns new random data ... def resample_apply(rule, func, series, *args, agg=None, **kwargs)-
Apply
func(such as an indicator) toseries, resampled to a time frame specified byrule. When called from insideStrategy.init(), the result (returned) series will be automatically wrapped inStrategy.I()wrapper method.ruleis a valid Pandas offset string indicating a time frame to resampleseriesto.funcis the indicator function to apply on the resampled series.seriesis a data series (or array), such as any of theStrategy.dataseries. Due to pandas resampling limitations, this only works when input series has a datetime index.aggis the aggregation function to use on resampled groups of data. Valid values are anything accepted bypandas/resample/.agg(). Default value for dataframe input isOHLCV_AGGdictionary. Default value for series input is the appropriate entry fromOHLCV_AGGif series has a matching name, or otherwise the value"last", which is suitable for closing prices, but you might prefer another (e.g."max"for peaks, or similar).Finally, any
*argsand**kwargsthat are not already eaten by implicitStrategy.I()call are passed tofunc.For example, if we have a typical moving average function
SMA(values, lookback_period), hourly data source, and need to apply the moving average MA(10) on a daily time frame, but don't want to plot the resulting indicator, we can do:class System(Strategy): def init(self): self.sma = resample_apply( 'D', SMA, self.data.Close, 10, plot=False)The above short snippet is roughly equivalent to:
class System(Strategy): def init(self): # Strategy exposes <code>self.data</code> as raw NumPy arrays. # Let's convert closing prices back to pandas Series. close = self.data.Close.s # Resample to daily resolution. Aggregate groups # using their last value (i.e. closing price at the end # of the day). Notice `label='right'`. If it were set to # 'left' (default), the strategy would exhibit # look-ahead bias. daily = close.resample('D', label='right').agg('last') # We apply SMA(10) to daily close prices, # then reindex it back to original hourly index, # forward-filling the missing values in each day. # We make a separate function that returns the final # indicator array. def SMA(series, n): from backtesting.test import SMA return SMA(series, n).reindex(close.index).ffill() # The result equivalent to the short example above: self.sma = self.I(SMA, daily, 10, plot=False)
class FractionalBacktest (data, *args, fractional_unit=1e-08, **kwargs)-
A
Backtestthat supports fractional share trading by simple composition. It applies roughly the transformation:data = (data * fractional_unit).assign(Volume=data.Volume / fractional_unit)as left unchallenged in this FAQ entry on GitHub, then passes
data,args*, and**kwargsto its super.Parameter
fractional_unitrepresents the smallest fraction of currency that can be traded and defaults to one satoshi. For μBTC trading, passfractional_unit=1/1e6. Thus-transformed backtest does a whole-sized trading offractional_unitunits.Ancestors
Inherited members
class MultiBacktest (df_list, strategy_cls, **kwargs)-
Multi-dataset
Backtestwrapper.Run supplied
Strategyon several instruments, in parallel. Used for comparing strategy runs across many instruments or classes of instruments. Example:from backtesting.test import EURUSD, BTCUSD, SmaCross btm = MultiBacktest([EURUSD, BTCUSD], SmaCross) stats_per_ticker: pd.DataFrame = btm.run(fast=10, slow=20) heatmap_per_ticker: pd.DataFrame = btm.optimize(...)Methods
def optimize(self, **kwargs)-
Wraps
Backtest.optimize(), but returnspd.DataFramewith currency indexes in columns.heamap: pd.DataFrame = btm.optimize(...) from backtesting.plot import plot_heatmaps plot_heatmaps(heatmap.mean(axis=1)) def run(self, **kwargs)-
Wraps
Backtest.run(). Returnspd.DataFramewith currency indexes in columns.
class SignalStrategy-
A simple helper strategy that operates on position entry/exit signals. This makes the backtest of the strategy simulate a vectorized backtest. See tutorials for usage examples.
To use this helper strategy, subclass it, override its
Strategy.init()method, and set the signal vector by callingSignalStrategy.set_signal()method from within it.class ExampleStrategy(SignalStrategy): def init(self): super().init() self.set_signal(sma1 > sma2, sma1 < sma2)Remember to call
super().init()andsuper().next()in your overridden methods.Ancestors
Methods
def set_signal(self, entry_size, exit_portion=None, *, plot=True)-
Set entry/exit signal vectors (arrays).
A long entry signal is considered present wherever
entry_sizeis greater than zero, and a short signal whereverentry_sizeis less than zero, followingOrder.sizesemantics.If
exit_portionis provided, a nonzero value closes portion the position (seeTrade.close()) in the respective direction (positive values close long trades, negative short).If
plotisTrue, the signal entry/exit indicators are plotted whenBacktest.plot()is called.
Inherited members
class TrailingStrategy-
A strategy with automatic trailing stop-loss, trailing the current price at distance of some multiple of average true range (ATR). Call
TrailingStrategy.set_trailing_sl()to set said multiple (6by default). See tutorials for usage examples.Remember to call
super().init()andsuper().next()in your overridden methods.Ancestors
Methods
def set_atr_periods(self, periods=100)-
Set the lookback period for computing ATR. The default value of 100 ensures a stable ATR.
def set_trailing_pct(self, pct=0.05)-
Set the future trailing stop-loss as some percent (
0 < pct < 1) below the current price (default 5% below).Note: Stop-loss set by
pctis inexactStop-loss set by
set_trailing_pctis converted to units of ATR withmean(Close * pct / atr)and set withset_trailing_sl. def set_trailing_sl(self, n_atr=6)-
Set the future trailing stop-loss as some multiple (
n_atr) average true bar ranges away from the current price.
Inherited members