I'm going to try the example from this. https://s3.amazonaws.com/quantstart/media/powerpoint/an-introduction-to-backtesting.pdf
In [1]:
import datetime
import pandas as pd
from pandas_datareader import data, wb
import numpy as np
import Quandl
import matplotlib.pylab as pylab
%matplotlib inline
In [2]:
ibm = Quandl.get("GOOG/NYSE_IBM")
In [3]:
ibm.head()
Out[3]:
In [6]:
start_date = datetime.datetime(2009,1,1)
end_date = datetime.datetime(2014,1,1)
amzn = data.DataReader("AMZN", "yahoo", start_date, end_date)
In [8]:
amzn.head()
Out[8]:
In [34]:
amzn['Close'].diff().head()
Out[34]:
In [9]:
from abc import ABCMeta, abstractmethod
In [10]:
class Strategy(object):
__metaclass__ = ABCMeta
def __init__(self, symbol, bars, **kwargs):
self.symbol = symbol
self.bars = bars
self.short_window = kwargs.get('short_window', 40)
self.long_window = kwargs.get('long_window', 100)
@abstractmethod
def generate_signals(self):
raise NotImplementedError("Should implement generate_signals()!")
In [11]:
class MovingAverageCrossStrategy(Strategy):
def generate_signals(self):
# Create DataFrame and initialise signal series to zero
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = 0
# Create the short/long simple moving averages
signals['short_mavg'] = pd.rolling_mean(self.bars['Adj Close'], self.short_window, min_periods=1)
signals['long_mavg'] = pd.rolling_mean(self.bars['Adj Close'], self.long_window, min_periods=1)
# When the short SMA exceeds the long SMA, set the ‘signals’ Series to 1 (else 0)
signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] >
signals['long_mavg'][self.short_window:], 1, 0)
# Take the difference of the signals in order to generate actual trading orders
signals['positions'] = signals['signal'].diff()
return signals
In [14]:
mac = MovingAverageCrossStrategy("AMZN", amzn, short_window=40, long_window=100)
In [15]:
signals = mac.generate_signals()
In [36]:
signals.describe()
Out[36]:
In [17]:
signals.plot()
Out[17]:
In [76]:
signals[signals['positions'] == 1]
Out[76]:
In [81]:
signals['positions'].head()
Out[81]:
In [129]:
sum = (signals['positions'].fillna(0) * amzn['Adj Close']).to_frame().sum(axis=1).cumsum()
sum.tail()
Out[129]:
In [130]:
signals[signals['positions'] < 0]
Out[130]:
In [18]:
class Portfolio(object):
@abstractmethod
def generate_positions(self):
raise NotImplementedError("Should implement generate_positions")
def backtest_portfolio(self):
raise NotImplementedError("Should implement backtest_portfolio")
In [174]:
class MarketOnClosePortfolio(Portfolio):
def __init__(self, symbol, bars, signals, **kwargs):
self.symbol = symbol
self.bars = bars
self.signals = signals
self.positions = signals['positions']
self.initial_capital = kwargs.get('initial_capital', 100000)
def generate_positions(self):
# Generate a pandas DataFrame to store quantity held at any “bar” timeframe
positions = pd.DataFrame(index=self.signals.index).fillna(0.0)
positions[self.symbol] = 100 * self.signals['signal'] # Transact 100 shares on a signal
return positions
def backtest_portfolio(self):
# Create a new DataFrame ‘portfolio’ to store the market value of an open position
# positions = self.generate_positions()
portfolio = self.positions * self.bars['Adj Close'] * 100
#pos_diff = self.positions.diff()
return portfolio
# Create a ‘holdings’ Series that totals all open position market values
# and a ‘cash’ column that stores remaining cash in account
#portfolio['holdings'] = (self.positions*self.bars['Adj Close']).sum(axis=1)
#portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Adj Close']).sum(axis=1).cumsum()
# Sum up the cash and holdings to create full account ‘equity’, then create the percentage returns
#portfolio['total'] = portfolio['cash'] + portfolio['holdings']
#portfolio['returns'] = portfolio['total'].pct_change()
#return portfolio
In [175]:
portfolio = MarketOnClosePortfolio("AMZN", amzn, signals, initial_capital=100000.0).backtest_portfolio()
In [179]:
portfolio.plot()
Out[179]:
In [164]:
portfolio.sum()
Out[164]:
In [188]:
ticker = 'amzn'
px = data.DataReader(ticker, 'yahoo')
def generate_signals(px):
signals = pd.DataFrame(index=px.index)
signals['signal'] = 0
short_ma = pd.rolling_mean(px['Adj Close'], 40, min_periods=1)
long_ma = pd.rolling_mean(px['Adj Close'], 100, min_periods=1)
signals['signal'] = np.where(short_ma > long_ma, 1, 0)
return signals['signal'].shift(1) # remember to lag your signals :)
px['Signals'] = generate_signals(px)
px['Daily P&L'] = px['Adj Close'].diff() * px['Signals'] * 100
px['Total P&L'] = px['Daily P&L'].cumsum()
print(px[px['Signals'] > 0])
In [ ]:
In [ ]: