Python vs R #3: A simple moving average crossover backtest on SPY

This is the third in a series that is comparing Python and R for quantitative trading analysis. Using the zipline framework for Python and the work of Systematic Investor Toolbox for R, I implement the same moving average cross-over model in each language. Because of the OOP nature of Python there are many differences between the two languages, leading to about twice as much code. Presumably the added OO complexity is useful in much more complicated strategies.

Python

#Load the required libraries
from zipline.algorithm import TradingAlgorithm
from zipline.transforms import MovingAverage
from zipline.utils.factory import load_from_yahoo
from zipline.finance.slippage import FixedSlippage
from zipline.finance.commission import PerShare
from datetime import datetime
import matplotlib.pyplot as plt

class DualMovingAverage(TradingAlgorithm):

    def initialize(self, short_window = 50, long_window = 200):
        
      # alculate the minimum window before a trade can be made
      self.min_window = max(short_window,long_window)

      #Calculate the long and short moving averages and
      #add them to the data
      self.add_transform(MovingAverage, 'short_mavg', ['price'],
                         window_length=short_window)

      self.add_transform(MovingAverage, 'long_mavg', ['price'],
                         window_length=long_window)

      #To keep track of whether we invested in the stock or not
      self.invested = False
      
      #To be used to determine what day in the backtest its in
      self.days = 0
      
      #To match the default settings on R, set slippage and 
      #comission to 0
      self.set_slippage(FixedSlippage(spread=0.00))
      self.set_commission(PerShare(cost=0.00))  
  
    def handle_data(self, data):
      self.short_mavg = data['SPY'].short_mavg['price']
      self.long_mavg = data['SPY'].long_mavg['price']
      self.buy = False
      self.sell = False
      
      #If short MA is greater than long MA, we are past the 
      #minimum number of days and not currently invested then 
      #buy 100% of portfolio value to initiate position
      if self.short_mavg > self.long_mavg and self.days > 
        self.min_window and not self.invested:
          self.order_percent('SPY',1)
          self.invested = True
          self.buy = True
      #If short MA is less than long MA, we are past the 
      #minimum number of days and its currently invested then
      #sell 100% of portfolio to close position
      elif self.short_mavg < self.long_mavg and self.days > 
        self.min_window and self.invested:
          self.order_percent('SPY',-1)
          self.invested = False
          self.sell = True

      #Increment the number of days in backtest
      self.days = self.days + 1
      
      #Record custom values to the results
      self.record(short_mavg=self.short_mavg,
                  long_mavg=self.long_mavg,
                  buy=self.buy,
                  sell=self.sell, 
                  days=self.days)

#Main program
if __name__ == '__main__':

    #Get market data
    start = datetime(2000, 1, 1)
    end = datetime(2014, 3, 25)
    data = load_from_yahoo(stocks=['SPY'], indexes={}, 
                          start=start, end=end, 
                          adjusted=bool(1))
                           
    #Create the moving average strategy object and pass 
    #the market data
    dma = DualMovingAverage()
    results = dma.run(data)

    #Plot the equity curve and export the trades to a csv
    results.portfolio_value.plot(title="Equity Curve")
    plt.show()
    results.to_csv('/Users/jeickmeier/Desktop/python_results.csv',',')

R

#Load the required packages
load.packages('quantmod')
require(RCurl)
sit = getURLContent('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', binary=TRUE, followlocation = TRUE, ssl.verifypeer = FALSE)
con = gzcon(rawConnection(sit, 'rb'))
source(con)
close(con)
data <- new.env()

# Load historical data and adjusts for splits and dividends
tickers = spl('SPY')
getSymbols(tickers, src = 'yahoo', from = '2000-01-01', env = data, auto.assign = T)    
for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T)  

#Calculate the moving averages and lag them one day to prevent lookback bias
PreviousSMA_50 <- lag(SMA(Cl(data[['SPY']]),50))  
PreviousSMA_200 <- lag(SMA(Cl(data[['SPY']]),200))

#Sets backtesting environment
bt.prep(data, align='remove.na') 
prices = data$prices   

#Create a empty list for attaching the models to at a later stage
models = list()
 
#Specify the weights to be used in the backtest
data$weight[] = NA #Zero out any weights from previous
data$weight[] = ifelse(as.integer(PreviousSMA_50>PreviousSMA_200)==1,1,0)  #If price of SPY is above the SMA then buy

#Call the function to run the backtest given the data, which contains the prices and weights.
models$technical_model = bt.run.share(data, trade.summary=T)

#Plot equity curve and export the trades list to csv
plot(models$technical_model$equity, main="Equity Curve")
write.csv(models$technical_model$trade.summary$trades, "/Users/jeickmeier/Desktop/R_results.csv")

Python

Python Simple MA Crossover Equity Curve

R

R Simple MA Crossover Equity Curve
Next in the series will be looking at the builtin performance metrics of the languages and available backtesting packages.

By Jon Eickmeier | March 26, 2014 | analysis R Python programming quantitative comparisons trading

Comments Section

comments powered by Disqus