#!/usr/bin/env python # coding: utf-8 # # pybacktest tutorial # # This tutorial will give you a quick overview of **pybacktest**'s features. To do so, we will backtest classic MA crossover trading strategy: # # * go long when short MA crosses above long MA # * go short when short MA crosses below long MA # * entry rules are also exit rules, thus making strategy reversible # # Package is available for download at https://github.com/ematvey/pybacktest # In[1]: from __future__ import print_function import pybacktest # obviously, you should install pybacktest before importing it import pandas as pd # **pybacktest** expects bars to be in `pandas.DataFrame` indexed by datetimestamps, with columns named `O`, `H`, `L`, `C`. Actually, in this version it would only check `O`, and only if trade prices are not explicitly specified. # # Lets load data from yahoo using helper. # In[2]: ohlc = pybacktest.load_from_yahoo('SPY') ohlc.tail() # Now time to define strategy. To do so, all we need to do is to create binary `Series` with signals, and, optionally, trade price `Series` with float elements. # # Could this be any simpler? # In[3]: short_ma = 50 long_ma = 200 ms = ohlc.C.rolling(short_ma).mean() ml = ohlc.C.rolling(long_ma).mean() buy = cover = (ms > ml) & (ms.shift() < ml.shift()) # ma cross up sell = short = (ms < ml) & (ms.shift() > ml.shift()) # ma cross down print('> Short MA\n%s\n' % ms.tail()) print('> Long MA\n%s\n' % ml.tail()) print('> Buy/Cover signals\n%s\n' % buy.tail()) print('> Short/Sell signals\n%s\n' % sell.tail()) # Time to run backtest. `Backtest` will try to extract signals and prices and bars from whatever dict-like object you passed as first argument. Could be dict, could be pandas.DataFrame or anything. # # To make thing easier, pass local namespace (extracted py calling `locals()`), that contains every variable you created up to this point. # In[4]: bt = pybacktest.Backtest(locals(), 'ma_cross') # `Backtest` runs lazily, i.e. it calculates anything only when you call properties from it. There properties: # In[5]: print(list(filter(lambda x: not x.startswith('_'), dir(bt)))) print('\n> bt.signals\n%s' % bt.signals.tail()) print('\n> bt.trades\n%s' % bt.trades.tail()) print('\n> bt.positions\n%s' % bt.positions.tail()) print('\n> bt.equity\n%s' % bt.equity.tail()) print('\n> bt.trade_price\n%s' % bt.trade_price.tail()) # Some of popular performance statistics could be requested by calling `summary` method of `Backtest`. # In[6]: bt.summary() # Now lets look at equity curve. # In[7]: get_ipython().run_line_magic('matplotlib', 'inline') import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams['figure.figsize'] = (15.0, 8.0) bt.plot_equity() # But what if you want to see what exactly was going on during backtest run? Well, `Backtest` can plot trades as they happened for you. Legend is hidden by default to save space. # In[8]: bt.plot_trades() ohlc.C.rolling(short_ma).mean().plot(c='green') ohlc.C.rolling(long_ma).mean().plot(c='blue') plt.legend(loc='upper left') pass # Can you see anything there? I can't. That's why we have special a very special `trdplot` property, which allowes you specify what period do you want to plot using standard `pandas` indexing mechanisms. Same trick could be done with equity curve using `eqplot` property. # In[9]: bt.trdplot['2004':'2007'] ohlc.C['2004':'2007'].rolling(short_ma).mean().plot(c='green') ohlc.C['2004':'2007'].rolling(long_ma).mean().plot(c='blue') pass # That's the most of it.