Trading idea: Buy on last day of month and sell first day of month end of day [under construction]

Hi,
From the trading ideas post I am working on "buy on last day of month and sell first day of month end of day".

I used zipline to set it up since open price cannot be retrieved in Quantopian. I want to implement this still in Quantopian. For the moment I just want to share the code of the date calculations, which are a bit more work than in .NET.

For the iteration I would like to show the results in a heatmap. But no clue so far how that works.

It seems the maximum profit is about 22K over more than 10 years with 50K investment.

J.

from zipline import TradingAlgorithm
from zipline.transforms import MovingAverage
import zipline as zp
import zipline.finance.performance as pf

from datetime import datetime
from datetime import timedelta
from dateutil.relativedelta import relativedelta

from dateutil.rrule import DAILY, rrule, MO, TU, WE, TH, FR

import pytz
import matplotlib.pyplot as plt
import pandas as pd
from pandas.io.data import *
from pandas import Panel, DataFrame, Series, read_csv, concat

import numpy as np

import math

import sys

"""Buy the last day of the month and sell the first month, the end of the day"""

self.max_notional = self.capital_base*.50
self.STK =  stock
self.invested = False

self.investment = 0

self.scheduled_sell_date = None

self.profit = None

self.totalProfit = 0
def handle_data(self, data):
current_date = data['Open'].dt # data[self.STK]['date']
open_price = data['Open'].price # data[self.STK]['open']
close_price = data['Close'].price #data[self.STK]['close']
sell = False
#current_STK_shares = self.portfolio.positions[self.STK].amount
#current_STK_value = self.portfolio.positions_value
#current_accountvalue = self.portfolio.cash + self.portfolio.positions_value

# Has short mavg crossed long mavg?

if(not self.invested):
if(current_date.month == 12):
last_day_of_month = datetime(current_date.year+1, 1, 1, 0, 0, 0, 0, pytz.utc) - timedelta(days=1)
else:
last_day_of_month = datetime(current_date.year, current_date.month+1, 1, 0, 0, 0, 0, pytz.utc) - timedelta(days=1)

self.invested = True;
self.scheduled_sell_date = rrule(DAILY, dtstart=last_day_of_month, byweekday=(MO,TU,WE,TH,FR))[1+sellDayAfterFirstOfMonth]
print 'buy on %s day: %s.' % (current_date, current_date.strftime('%A'))
print 'sell planned for: %s day %s.' % (self.scheduled_sell_date, self.scheduled_sell_date.strftime('%A'))

self.num_shares = math.floor(self.max_notional / open_price)
self.order(self.STK, self.num_shares)

self.investment = self.num_shares * open_price

elif(self.invested and current_date >= self.scheduled_sell_date):
self.invested = False;
print 'sold on %s day: %s.' % (current_date, current_date.strftime('%A'))

#self.num_shares = math.floor(self.max_notional / close_price)
self.order(self.STK, -1*self.num_shares)

sell = True

self.profit = (self.num_shares * close_price) - self.investment
self.totalProfit = self.totalProfit  + self.profit

print 'profit %f on investment of %f. Total: %f' % (self.profit, self.investment, self.totalProfit)

self.investment = 0

self.record(open_price=open_price,
close_price=close_price,
sell=sell,
investment=self.investment,
num_shares=self.portfolio.positions[self.STK].amount,
profit = self.profit)

def daterange(start_date, end_date):
return rrule(DAILY, dtstart=start_date, until=end_date, byweekday=(MO,TU,WE,TH,FR))

#start of main()
SYMBOL = 'SPY'

sellDayAfterFirstOfMonth=0

start = datetime(2000, 1, 1, 0, 0, 0, 0, pytz.utc)
end =  datetime(2013, 12, 1, 0, 0, 0, 0, pytz.utc)
date_range = daterange(start, end)

#data.to_csv('c:\\data\\test-long.csv', encoding='utf-8')
data = pd.DataFrame.from_csv('c:\\data\\test-long.csv', index_col=0, parse_dates=True, encoding='utf-8')

#close_key = 'Close'
#df = pd.DataFrame({key: d[close_key] for key, d in data.iteritems()})
data.index = data.index.tz_localize(pytz.utc)

sellDay = np.arange(0,25,1)

maxSH = 0

for j, lmi in enumerate(sellDay):
print smi, lmi
if smi==lmi:
continue

perf = dma.run(data)

print dma.totalProfit

#sharpe = [risk['sharpe'] for risk in dma.risk_report['twelve_month']]
#print "Monthly Sharpe ratios:", sharpe
SH[i,j] = dma.totalProfit#dma.portfolio.portfolio_value#max(sharpe)
#print "portfolio value:", dma.portfolio.portfolio_value

if SH[i,j] > maxSH:
maxSH = SH[i,j]
print 'maxSH:', maxSH
else:
print 'SH[i,j]:', SH[i,j]
#pnl = backtest(ohlc, ccThresh=cc, coThresh=co)
#SH[i,j] = sharpe(pnl)

print 'maxSH:', maxSH

i,j = np.unravel_index(SH.argmax(), SH.shape)
print "value", SH[i,j]
print 'Optimum sellDay %.2f' % sellDay[j]

input('wait for user key...')

4 responses

Excellent work Quant Trader! It would really be interesting seeing something like this in a "quantopian" format. I'm new to programming, I know way more about finance in general and I also know more about math then programming. So your skills are much appreciated.

Regarding the date programming - buy at 11.am hold until close - I asked Ed Bartosh in this thread: https://www.quantopian.com/posts/help-with-an-sp-and-500-atr-strategy and he made an example you can see there.

Also this strategy I wrote about is suppose to be a low return strategy but what's interesting is if it has a high Sharpe Ratio.
Because if the Sharpe Ratio is high then we can use a lot of leverage since this is an intraday strategy.

Also the strategy is to look at stocks which are negative the second half of the last day of the month, and negative the first half of the first day of the month and then buy at midday the first day of the month. I understand that you code it to buy at last day of the month and then sell the next day, just playing the shift between months?

Strategy example:
IBM trades negative from 11.am. until close 31th of march.
IBM trades negative from open until 11 a.m. on 1th of april.
Then buy at 11 a.m. and sell at closing time.

From your code it looks like you used SPY my intuition is telling me that a security that's smaller in size and less well known would probably give a better return.
Per Fama & French three factor model small stocks and cheap stocks are our friend, but in this strategy I don't think that will have an effect. But somehow less well-known stocks should have a longer time delay in orders from institutional investors and retail traders. Stocks that are mostly traded manually maybe.

So what would be cool is to run back-tests on single stocks and stocks in a certain industry or sector and see which ones over time had the best return from this strategy. And then perhaps on can model it and optimize it.

Cheers

Dear Patrick,

I am working on Trading idea one:
"Over the past 16 years, buying the close on SPY (the S&P 500 ETF) on the last day of the month and selling one day later would result in a successful trade 63% of the time with an average return of 0.37% (as opposed
to 0.03% and a 50%-50% success rate if you buy any random day during this period)."

I have modified my code for quantopian..but it seems that the import of the dateutil library is not allowed. I have no intention to recode it. So lets wait and see if the dateutil is allowed. Also I am waiting till a new version of zipline is released, which is better in sync with Quantopian. Now I have quite a bit of rework.

J.

I was inspired by David Edwards in a thread where he uses the Zipline trading calendar, which contains enough data to determine when you are at the first trading day of the month and what the open and close times are for each trading day.

Anyway, here is my backtest. I thought that my simple code might be helpful to those of you trying to find open and close times and trading days. I know this beats the pants off of my old method of importing trading days from Quandl :)

55
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 538b4be08ffb6a0722ee1b1f
There was a runtime error.