Task

In this assignment, you will implement simple algorithmic trading policies and modify a very simple backtester.

In the following, you will find a Backtester1 function that gets as an input the historical price series of a single stock.

At each time interval, the backtester calls for a new order (by calling the placeOrder function), along with the current opening price. Depending upon the current position of the customer (number of owned stocks and current cash), the customer decides upon a new investment. This is currently a random decision, merely deciding the percentage of capital to leave on a stocks. Consequently, the PlaceOrder function will return one of the following orders, to realize the decided position:

  • Buy
  • Sell

PlaceOrder assumes that stocks can be traded only as integer multiples.

Task 1

Modify the program to create plots of the total wealth over time, along with the value of the stocks and cash at each time point.

Task 2

Add the following type of orders that a customer can issue

  • AddCapital
  • WithdrawCapital

The backtester should always keep track of the position of the trader and make appropriate checks, such as only allowing buying stocks allowed by the current capital.

Task 3

Add an interest_rate such that at the beginning of each trading day, the cash earns a fixed interest. Also, include transaction costs (a fixed percentage of the trade) to be deduced from each transaction.

Task 4

Modify the system such that it allows for open selling, that is selling without actually owning any stock. At the end of the trading day, any open positions should be cleared by the closing price.

Task 5

Think and implement a trading policy of your imagination, such as estimating the trend in the last few days, and coming up with a smarter decision than random. Compare your policy with the random policy in terms of earnings or losses.

Task 6

Modify the program such that you allow for pairs trading. Modify the backtester such that you input a pair of stocks. Now the generated investment decisions must be portfolio. Repeat task 5 for the pairs case.

Read some example Data


In [30]:
import pandas as pd
import pandas.io.data as web

import numpy as np
import datetime

msft = pd.read_csv("msft.csv", index_col=0, parse_dates=True)
aapl = pd.read_csv("aapl.csv", index_col=0, parse_dates=True)

In [25]:
msft['2012-01']


Out[25]:
Open High Low Close Volume AdjClose
Date
2012-01-03 26.549999 26.959999 26.389999 26.770000 64731500 23.461752
2012-01-04 26.820000 27.469999 26.780001 27.400000 80516100 24.013895
2012-01-05 27.379999 27.730000 27.290001 27.680000 56081400 24.259293
2012-01-06 27.530001 28.190001 27.530001 28.110001 99455500 24.636154
2012-01-09 28.049999 28.100000 27.719999 27.740000 59706800 24.311878
2012-01-10 27.930000 28.150000 27.750000 27.840000 60014400 24.399520
2012-01-11 27.430000 27.980000 27.370001 27.719999 65582400 24.294349
2012-01-12 27.870001 28.020000 27.650000 28.000000 49370800 24.539747
2012-01-13 27.930000 28.250000 27.790001 28.250000 60196100 24.758852
2012-01-17 28.400000 28.650000 28.170000 28.260000 72395300 24.767617
2012-01-18 28.309999 28.400000 27.969999 28.230000 64860600 24.741323
2012-01-19 28.160000 28.440001 28.030001 28.120001 74053500 24.644918
2012-01-20 28.820000 29.740000 28.750000 29.709999 165902900 26.038424
2012-01-23 29.549999 29.950001 29.350000 29.730000 76078100 26.055953
2012-01-24 29.469999 29.570000 29.180000 29.340000 51703300 25.714150
2012-01-25 29.070000 29.650000 29.070000 29.559999 59231700 25.906961
2012-01-26 29.610001 29.700001 29.400000 29.500000 49102800 25.854377
2012-01-27 29.450001 29.530001 29.170000 29.230000 44187700 25.617743
2012-01-30 28.969999 29.620001 28.830000 29.610001 51114800 25.950783
2012-01-31 29.660000 29.700001 29.230000 29.530001 50572400 25.880670

A reference implementation


In [55]:
InitialCash = 1000
Position = {'Cash': InitialCash, 'Stocks': 0.0} 

def DecideTargetPosition():
    ## Randomly decide a portfolio output percentage of capital to put into stocks
    return np.random.choice([0.0, 0.5, 1.0])        
        
def Capital(price):
    return Position['Cash'] + Position['Stocks']*price
        
def PlaceOrder(price):
    p = DecideTargetPosition()
    capital = Capital(price)
        
    numLots = np.floor(capital*p/price)
        
    TargetPosition = {'Cash': capital-numLots*price, 'Stocks': numLots}
        
    if TargetPosition['Stocks'] > Position['Stocks']:
        # Buy 
        order = ('Buy', TargetPosition['Stocks']-Position['Stocks'])
        return order
    elif TargetPosition['Stocks'] < Position['Stocks']:
        # Sell
        order = ('Sell', -TargetPosition['Stocks']+Position['Stocks'])
        return order
    else:
        # Do nothing
        None
            
def UpdatePosition(deltaCash, deltaStock):
    Position['Cash'] += deltaCash
    Position['Stocks'] += deltaStock
    return


def BackTester1(series, interest_rate):
    
    openPrice = series['Open']
    closePrice = series['Close']
        
    for k in openPrice.keys():
        price = openPrice[k]
        order = PlaceOrder(price) 
        
        if order is None:
            continue
            
        print order
        
        if order[0]=='Buy':
            deltaCash = -price*order[1]
            deltaStock = order[1]
            UpdatePosition(deltaCash, deltaStock)
        elif order[0]=='Sell':
            deltaCash = price*order[1]
            deltaStock = -order[1]            
            UpdatePosition(deltaCash, deltaStock)
        else:
            None
            
        price = closePrice[k]
        print k, Capital(price)
    
InitialCash = 1000
Position = {'Cash': InitialCash, 'Stocks': 0.0} 

BackTester1(msft['2012-01'], 0.05)


('Buy', 18.0)
2012-01-03 00:00:00 1003.960018
('Buy', 19.0)
2012-01-04 00:00:00 1026.320018
('Sell', 19.0)
2012-01-05 00:00:00 1030.979999
('Buy', 19.0)
2012-01-06 00:00:00 1049.740017
('Sell', 37.0)
2012-01-12 00:00:00 1040.860017
('Buy', 18.0)
2012-01-20 00:00:00 1056.879999
('Sell', 18.0)
2012-01-23 00:00:00 1053.999999
('Buy', 17.0)
2012-01-26 00:00:00 1052.129982
('Sell', 17.0)
2012-01-30 00:00:00 1043.119965
('Buy', 35.0)
2012-01-31 00:00:00 1038.57

A cleaner implementation with the use of class constructs


In [47]:
class Customer:
    def __init__(self, cash=10000):
        self.Position = {'Cash': cash, 'Stocks': 0.0} 

    def DecideTargetPosition(self):
        ## Randomly decide a portfolio output percentage of capital to put into stocks
        return np.random.choice([0.0, 0.5, 1.0])        
        
    def Capital(self, price):
        return self.Position['Cash'] + self.Position['Stocks']*price
        
    def PlaceOrder(self, price):
        p = self.DecideTargetPosition()
        capital = self.Capital(price)
        
        numLots = np.floor(capital*p/price)
        
        TargetPosition = {'Cash': capital-numLots*price, 'Stocks': numLots}
        
        if TargetPosition['Stocks']>self.Position['Stocks']:
            # Buy 
            return ('Buy', TargetPosition['Stocks']-self.Position['Stocks'])
        elif TargetPosition['Stocks']<self.Position['Stocks']:
            # Sell
            return ('Sell', -TargetPosition['Stocks']+self.Position['Stocks'])
        else:
            # Do nothing
            None
            
    def GetPosition(self):
        return self.Position
    def UpdatePosition(self, deltaCash, deltaStock):
        self.Position['Cash'] += deltaCash
        self.Position['Stocks'] += deltaStock
        return

def BackTester(series, customer, interest_rate):
    
    openPrice = series['Open']
    closePrice = series['Close']
        
    for k in openPrice.keys():
        price = openPrice[k]
        order = customer.PlaceOrder(price) 
        
        if order is None:
            continue
            
        print order
        
        if order[0]=='Buy':
            deltaCash = -price*order[1]
            deltaStock = order[1]
            customer.UpdatePosition(deltaCash, deltaStock)
        elif order[0]=='Sell':
            deltaCash = price*order[1]
            deltaStock = -order[1]            
            customer.UpdatePosition(deltaCash, deltaStock)
        else:
            None
            
        price = closePrice[k]
        print k, customer.Capital(price)
    
Cash = 1000
cust = Customer(cash=Cash)

BackTester(msft['2012-01'], cust, 0.05)


('Buy', 37.0)
2012-01-03 00:00:00 1008.140037
('Sell', 19.0)
2012-01-04 00:00:00 1020.430037
('Buy', 19.0)
2012-01-05 00:00:00 1031.170056
('Sell', 37.0)
2012-01-06 00:00:00 1025.620093
('Buy', 37.0)
2012-01-11 00:00:00 1036.350056
('Sell', 37.0)
2012-01-12 00:00:00 1041.90013
('Buy', 37.0)
2012-01-13 00:00:00 1053.74013
('Sell', 37.0)
2012-01-17 00:00:00 1059.29013
('Buy', 18.0)
2012-01-18 00:00:00 1057.850148
('Buy', 19.0)
2012-01-20 00:00:00 1101.400111
('Sell', 19.0)
2012-01-23 00:00:00 1098.720129
('Buy', 19.0)
2012-01-25 00:00:00 1104.970092
('Sell', 37.0)
2012-01-26 00:00:00 1106.820166
('Buy', 18.0)
2012-01-27 00:00:00 1102.860148