Skip to content

Instantly share code, notes, and snippets.

@EngineeredEdge
Created June 5, 2015 15:09
Show Gist options
  • Save EngineeredEdge/46417dd8ff4ec104f36e to your computer and use it in GitHub Desktop.
Save EngineeredEdge/46417dd8ff4ec104f36e to your computer and use it in GitHub Desktop.
ma_crosserver.py
# http://www.quantstart.com/articles/My-Talk-At-The-London-Financial-Python-User-GroupS
import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from abc import ABCMeta, abstractmethod
from pandas.io.data import DataReader
class Strategy(object):
"""Strategy is an abstract base class providing an interface for
all subsequent (inherited) trading strategies.
The goal of a (derived) Strategy object is to output a list of signals,
which has the form of a time series indexed pandas DataFrame.
In this instance only a single symbol/instrument is supported."""
__metaclass__ = ABCMeta
@abstractmethod
def generate_signals(self):
"""An implementation is required to return the DataFrame of symbols
containing the signals to go long, short or hold (1, -1 or 0)."""
raise NotImplementedError("Should implement generate_signals()!")
class MovingAverageCrossStrategy(Strategy):
"""
Requires:
symbol - A stock symbol on which to form a strategy on.
bars - A DataFrame of bars for the above symbol.
short_window - Lookback period for short moving average.
long_window - Lookback period for long moving average."""
def __init__(self, symbol, bars, short_window=100, long_window=400):
self.symbol = symbol
self.bars = bars
self.short_window = short_window
self.long_window = long_window
def generate_signals(self):
"""Returns the DataFrame of symbols containing the signals
to go long, short or hold (1, -1 or 0)."""
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = 0.0
# Create the set of short and long simple moving averages over the
# respective periods
signals['short_mavg'] = pd.rolling_mean(self.bars['Close'], self.short_window, min_periods=1)
signals['long_mavg'] = pd.rolling_mean(self.bars['Close'], self.long_window, min_periods=1)
# Create a 'signal' (invested or not invested) when the short moving average crosses the long
# moving average, but only for the period greater than the shortest moving average window
signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] \
> signals['long_mavg'][self.short_window:], 1.0, 0.0)
# Take the difference of the signals in order to generate actual trading orders
signals['positions'] = signals['signal'].diff()
return signals
class Portfolio(object):
"""An abstract base class representing a portfolio of
positions (including both instruments and cash), determined
on the basis of a set of signals provided by a Strategy."""
__metaclass__ = ABCMeta
@abstractmethod
def generate_positions(self):
"""Provides the logic to determine how the portfolio
positions are allocated on the basis of forecasting
signals and available cash."""
raise NotImplementedError("Should implement generate_positions()!")
@abstractmethod
def backtest_portfolio(self):
"""Provides the logic to generate the trading orders
and subsequent equity curve (i.e. growth of total equity),
as a sum of holdings and cash, and the bar-period returns
associated with this curve based on the 'positions' DataFrame.
Produces a portfolio object that can be examined by
other classes/functions."""
raise NotImplementedError("Should implement backtest_portfolio()!")
class MarketOnClosePortfolio(Portfolio):
"""Encapsulates the notion of a portfolio of positions based
on a set of signals as provided by a Strategy.
Requires:
symbol - A stock symbol which forms the basis of the portfolio.
bars - A DataFrame of bars for a symbol set.
signals - A pandas DataFrame of signals (1, 0, -1) for each symbol.
initial_capital - The amount in cash at the start of the portfolio."""
def __init__(self, symbol, bars, signals, initial_capital=100000.0):
self.symbol = symbol
self.bars = bars
self.signals = signals
self.initial_capital = float(initial_capital)
self.positions = self.generate_positions()
def generate_positions(self):
positions = pd.DataFrame(index=self.signals.index).fillna(0.0)
positions[self.symbol] = 100*self.signals['signal'] # This strategy buys 100 shares
return positions
def backtest_portfolio(self):
portfolio = self.positions*self.bars['Adj Close']
pos_diff = self.positions.diff() #
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()
portfolio['total'] = portfolio['cash'] + portfolio['holdings']
portfolio['returns'] = portfolio['total'].pct_change()
return portfolio
def main():
# Obtain daily bars of AMZN from Yahoo Finance for the period
# 1st Jan 2009 to 1st Jan 2014
symbol = 'AMZN'
bars = DataReader(symbol, "yahoo", datetime.datetime(2009,1,1), datetime.datetime(2014,1,1))
# Create a Moving Average Cross Strategy instance
# with short and long moving average windows
mac = MovingAverageCrossStrategy(symbol, bars, short_window=40, long_window=100)
signals = mac.generate_signals()
# Create a portfolio of AMZN, with $100,000 initial capital
portfolio = MarketOnClosePortfolio(symbol, bars, signals, initial_capital=100000.0)
returns = portfolio.backtest_portfolio()
# Plot two charts to assess trades and equity curve
fig = plt.figure()
fig.patch.set_facecolor('white') # Set the outer colour to white
ax1 = fig.add_subplot(211, ylabel='Price in $')
# Plot the AMZN closing price overlaid with the moving averages
bars['Close'].plot(ax=ax1, color='r', lw=2.)
signals[['short_mavg', 'long_mavg']].plot(ax=ax1, lw=2.)
# Plot the "buy" trades against AMZN
ax1.plot(signals.ix[signals.positions == 1.0].index,
signals.short_mavg[signals.positions == 1.0],
'^', markersize=10, color='m')
# Plot the "sell" trades against AMZN
ax1.plot(signals.ix[signals.positions == -1.0].index,
signals.short_mavg[signals.positions == -1.0],
'v', markersize=10, color='k')
# Plot the equity curve in dollars
ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
returns['total'].plot(ax=ax2, lw=2.)
# Plot the "buy" and "sell" trades against the equity curve
ax2.plot(returns.ix[signals.positions == 1.0].index,
returns.total[signals.positions == 1.0],
'^', markersize=10, color='m')
ax2.plot(returns.ix[signals.positions == -1.0].index,
returns.total[signals.positions == -1.0],
'v', markersize=10, color='k')
# Plot the figure
fig.show()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment