Tuesday, 7 April 2015

Quantopian Algorithmic Trading Strategy: Multiple Securities

This post demonstrates algorithmic strategy similar to our initial Basic Trading Strategy, though uses multiple securities. We have chosen to backtest JP Morgan Chase, Bank of America and Citigroup, the three largest banks in America. These securities have all been traded for the length of our backtest (April 2014 to April 2015).

import datetime
import pytz

def initialize(context):
    context.stocks = symbols('JPM', 'BAC', 'C')
    context.vwap = {}
    context.price = {}

    context.max_notional = 1000000.1
    context.min_notional = -1000000.0

     utc = pytz.timezone('UTC')
    context.d=datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=utc)

def handle_data(context, data):
    money = []

    for stock in context.stocks:
        price = data[stock].price
        notional = notional + context.portfolio.positions[stock].amount * price
        tradeday = data[stock].datetime

    for stock in context.stocks:  
        vwap = data[stock].vwap(5)
        price = data[stock].price

       if price < vwap * 0.995 and notional > context.min_notional:
            notional = notional - price*100
        elif price > vwap * 1.005 and notional < context.max_notional:
            notional = notional + price*100

    if (context.d + datetime.timedelta(days=1)) < tradeday:
        log.debug(str(notional) + ' - notional start ' + tradeday.strftime('%m/%d/%y'))
        context.d = tradeday

    record(jpm_money=money[0], bac_money=money[1], c_money=money[2])

Function meanings: 

  • Firstly we import required libraries. Datetime represents the timestamp of the last trade of the security. Timezones are either a pytz timezone object or a string conforming to the pytz timezone database. 
  • Secondly we initialise each security by calling their symbols 
  • vwap(days) calculates the volume weighted average price over the given number of trading days, including today's trailing data
  • context.max_notional, context.min_notional is also run in our initialize function as it sets our maximum and minimum position size. That is, it sets a limit on the absolute magnitude of any position held by the algorithm for a given security in terms of dollar value. As the limit has not been set for each security, it applies to all
  • We also initialise time variables for logging purposes, in this case we convert the timezone to UTC
  • We start with our position as 0  (notional=0)
  • For each stock, the algorithm computes our position at the start of each frame, finding price, notional (amount of stock positions x price) and trading day
  • Next the algorithm goes through each security again, finding the price and calculating volume weighted average price over the past 5 days. If the price is slightly below the vwap, and the position limit has not been reached, the sell order is executed and our position is updated
  • Similarly, if price is slightly above the vwap, and the max position value has not been reached, the buy order is executed to ride the upswing and our position is updated
  • If this is the first trade of the day, the notional value is logged. 
  • We are also going to record the amount of money we have in each security. 
This strategy generated negative returns of -2.3%. 

However, if we increase the number of shares purchased during the upswing to 120 000 and increased the number of shares sold during downswings to 1000, we get an outstanding 77.3% return. Strangely, further manipulation of these numbers (increases and decreases) showed me inconsistent combinations which even touched on negative returns. 

As this strategy is based around each stock's volume weighted average price, increase the length of days in calculating vwap from 5 to 7 days increases return to 74.6%. However, increasing it to 8 days gives a 24.9% return and increasing it to 10 days generates a negative 64.4% return. 

Using a similar strategy on stocks in the energy sector- BHP Billiton, Anadarko Petroleum Corporation and Exxon Mobil Corporation, a 70% return is generated when 2000 shares are sold during downswings and 100,000 are bought during upswings. 

if price < vwap * 0.995 and notional > context.min_notional:
            notional = notional - price*100
elif price > vwap * 1.005 and notional < context.max_notional:
            notional = notional + price*100 

Comparing different sector stocks: Microsoft (technology), Johnson and Johnson (pharmaceuticals) and Toyota (manufacturing), we managed to tailor the strategy to give a 185.8% return. Changing parts of the algorithm did produce volatile results similar to our previous stocks, however we realised that the main reason of the low returns was due to the algorithm's poor performance when the benchmark returns increased early in 2015. In order to capitalise on the overall gains, we lowered the buy order cutoff mark - instead of waiting for a 0.5% increase in price signal, the algorithm now executes a buy when there is a 0.2% increase in price signal.

elif price > vwap * 1.005 

elif price > vwap * 1.002
price < vwap * 0.9995 and price < vwap * 0.9995elif price > vwap * 1.002 

Seeing as the algorithm still performed relatively poorly when the benchmark was low, we also decided to adjust the sell strategy to selling when current stock price reached less than 99.95% vwap rather than 99.5% vwap. This generated the highest return so far: 415.5%! 

No comments:

Post a Comment