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):
notional=0
money = []
for stock in context.stocks:
price = data[stock].price
money.append(price*context.portfolio.positions[stock].amount)
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:
order(stock,-100)
notional = notional - price*100
elif price > vwap * 1.005 and notional < context.max_notional:
order(stock,+100)
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:
order(stock,-2000)
notional = notional - price*100
elif price > vwap * 1.005 and notional < context.max_notional:
order(stock,+100000)
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 |
No comments:
Post a Comment