Buy in November, Sell in May Strategy Framework

Thanks to Jeff Swanson for the basis of this post

I like to post something educational at least once a month.  Sometimes, it’s difficult to come up with stuff to write about.  Jeff really got me thinking with his Buy November… post.  Check out his post “Riding the Market Waves:  How to Surf Seasonal Trends to Trading Success.”  Hopefully you have read his post and now have returned.  As you know, the gist of his post was to buy in November and sell in May.  Jeff was gracious enough to provide analysis, source and suggestions for improvement for this base strategy.

Why Change Jeff’s Code to a Framework?

I found Jeff’s post most intriguing, so the first think I start thinking about is how could I optimize the buy and sell months, a max loss, the three entry filters that he provided and in addition add a sell short option.  If you have read my books, you know I like to develop frameworks for further research when I program an algorithm or strategy.  Here is how I developed the framework:

  1. Optimize the entry month from January to December or 1 to 12.
  2. Optimize the exit month from January to December or 1 to 12.
  3. Optimize to go long or go short or 1 to 2 (to go short any number other than 1 really).
input: startMonth(11),endMonth(5),
longOrShort(1),


currentMonth = Month(Date of tomorrow);
If currentMonth = startMonth and mp = 0 and entriesThisMonth = 0 Then
begin
// a trade can only occur if canBuy is True - start month is active as
// long as the filtering allows it. Until the filter is in alignment
// keep looking for a trading during the ENTIRE startMonth
if longOrShort = 1 and canBuy then
entriesThisMonth = 1;
if longOrShort = 1 and canBuy then
buy("Buy Month") iShares contracts next bar at market;
if longOrShort <> 1 and canShort then
sellShort("Short Month") iShares contracts next bar at market;
if longOrShort = -1 and canShort then
entriesThisMonth = 1;
end;

if CurrentMonth = endMonth Then
begin
if longOrShort = 1 then
sell("L-xit Month") currentShares contracts next bar at market
else
buyToCover("S-xit Month") currentShares contracts next bar at market;
end;

if mp = 1 then
sell("l-xitMM") next bar at entryPrice - maxTradeRisk/bigPointValue stop;
if mp =-1 then
buyToCover("s-xitMM") next bar at entryPrice + maxTradeRisk/bigPointValue stop;
Snippet of the bones with extra flavor to enter and exit on certain months

You can see that I have provided the three inputs:

  1. startMonth
  2. endMonth
  3. longOrShort

I get the currentMonth by peeking at the date of tomorrow and passing this date to the month function.  If tomorrow is the first day of the month that I want to enter a long or short and the current market position (mp), and entriesThisMonth = 0, then a long or short position will be initiated.  If the filters I describe a little later allow it, I know that I will be executing a trade tomorrow, and I can go ahead assign a 1 to entries this month.  Why do I do this?  Just wait and you will see.   Long entries depend on the variable longOrShort being equal to 1 and the toggle canBuy set to True.  What is canBuy.  Just wait and you will see.  The sell short is similar, but conversely longOrShort needs to not equal 1.  In addition, canShort needs to be true too.

If the currentMonth = endMonth, then based on the market position a sell or a buy to cover will be executed.

How to add filters to Determine canBuy and canShort

inputs: 
useMACDFilter(1), MACDFast(9), MACDSlow(26), MACDAvgLen(9), MACDLevel(0),
useMAFilter(0), MALength(30),
useRSIFilter(0), RSILength(14), RSILevel(50)

RSIVal = rsi(close,RSILength);
MAVal = xAverage(close,MALength);
MACDVal = macd(close,MACDFast,MACDSlow);
MACDAvg = xAverage(MACDVal,MACDAvgLen);

if useMACDFilter = 1 then
begin
canBuy = MACDVal > MACDLevel;
canShort = MACDVal < MACDLevel;
end;

if useMAFilter = 1 then
begin
canBuy = close > MAVal and canBuy;
canShort = close < MAVal and canShort;
end;

if useRSIFilter = 1 then
begin
canBuy = RSIVal > RSILevel and canBuy;
canShort = RSIVal < RSILevel and canShort;
end;
Calculate Filter Components and then test them

You cannot optimize a True to False toggle, but you can optimize 0 for off and 1 for on.  Here the useFilterName inputs are initially set to 0 or off.  Each filter indicator has respective inputs so that the filters can be calculated with the user’s input.  If the filters are equal to one, then a test to turn canBuy and canShort to on or off is laid out in the code.  Each test depends on either the state of price compared to the indicator value, or the indicator’s relationship to a user defined level or value.

Will this code test all the combination of the filters?

Yes!  F1 is Filter 1 and F2 is Filter 2 and F3 is Filter 3.  By optimizing each filter from 0 to 1, you will span this search space.

  • F1 = On; F2 = Off; F3 = Off
  • F1 = On; F2 = On; F3 = Off
  • F1 = On; F2 = On; F3 = On
  • F1 = Off; F2 = On; F3 = Off
  • F1 = Off; F2 = On; F3 = On
  • F1 = Off; F2 = Off: F3 = On
  • F1 = On; F2 = Off; F3 = On

You will notice I initially set canBuy and canShort to True and then turn them off if an offending filter occurs.  Notice how I AND the results for Filter 2 and Filter 3 with canBuy or canShort.  Doing this allows me to cascade the filter combinations.  I do want to test when all filters are in alignment.  In other words, they must all be True to initiate a position.

Should the Filters be Active During the Entire Entry Month?

What if the first day of the month arrives and you can’t initiate a trade due to a conflict of one of the filters.  Should we allow a trade later in the entry month if the filters align properly?  If we are testing 25 years of history and allow for entry later on in the month, we could definitely generate as close to 25 trades as possible.   This line of code keeps the potential of a trade open for the entire month.


// only set entriesThisMonth to true
// when all the stars align - might enter a long
// trade on the last day of the month

if longOrShort = 1 and canBuy then
entriesThisMonth = 1;
Keep the entire start month active

Some Tricky Code

I wanted to allow a money management exit on a contract basis.  I had to devise some code that would not allow me to reenter the startMonth if I got stopped out prematurely in the startMonth (the same month as entry.)

if entriesThisMonth = 1 and monthOfTomorrow <> startMonth then
entriesThisMonth = 0;
This code resets entriesThisMonth

If a position is initiated, I know entriesThisMonth will be set to one.  If I enter into another month that is not the startMonth then entriesThisMonth is set to 0.  This prevents reentry in case we get stopped out in the same month we initially enter a position.  In other words, entriesThisMonth stays one until a new month is observed.  And we can’t enter when entriesThisMonth is equal to one.

Full Code

input: startMonth(11),endMonth(5),
longOrShort(1),
useMACDFilter(1),MACDFast(9),MACDSlow(26),MACDAvgLen(9),MACDLevel(0),
useMAFilter(0),MALength(30),
useRSIFilter(0),RSILength(14),RSILevel(50),
startAccountSize(100000),
marketRiskLen(30),
riskPerTrade(5000),
maxTradeRisk(5000);

vars: currentMonth(0),mp(0),iShares(0),entriesThisMonth(0),monthOfTomorrow(0),
RSIVal(0),MAVal(0),MACDVal(0),MACDAvg(0),canBuy(True),canShort(True);

mp = marketPosition;
iShares = riskPerTrade/bigPointValue/avgTrueRange(marketRiskLen);

RSIVal = rsi(close,RSILength);
MAVal = xAverage(close,MALength);
MACDVal = macd(close,MACDFast,MACDSlow);
MACDAvg = xAverage(MACDVal,MACDAvgLen);

canBuy = True;
canShort = True;

mp = marketPosition;

monthOfTomorrow = month(date of tomorrow);

if entriesThisMonth = 1 and monthOfTomorrow <> startMonth then
entriesThisMonth = 0;

if useMACDFilter = 1 then
begin
canBuy = MACDVal > MACDLevel;
canShort = MACDVal < MACDLevel;
end;

if useMAFilter = 1 then
begin
canBuy = close > MAVal and canBuy;
canShort = close < MAVal and canShort;
end;

if useRSIFilter = 1 then
begin
canBuy = RSIVal > RSILevel and canBuy;
canShort = RSIVal < RSILevel and canShort;
end;

currentMonth = Month(Date of tomorrow);
//print(d," ",currentMonth," ",startMonth," ",entriesThisMonth);
If currentMonth = startMonth and mp = 0 and entriesThisMonth = 0 Then
begin
// print(d," ",currentMonth," ",canBuy);
if longOrShort = 1 and canBuy then
entriesThisMonth = 1;
if longOrShort = 1 and canBuy then
buy("Buy Month") iShares contracts next bar at market;
if longOrShort <> 1 and canShort then
sellShort("Short Month") iShares contracts next bar at market;
if longOrShort = -1 and canShort then
entriesThisMonth = 1;
end;

if CurrentMonth = endMonth Then
begin
if longOrShort = 1 then
sell("L-xit Month") currentShares contracts next bar at market
else
buyToCover("S-xit Month") currentShares contracts next bar at market;
end;

if mp = 1 then
sell("l-xitMM") next bar at entryPrice - maxTradeRisk/bigPointValue stop;
if mp =-1 then
buyToCover("s-xitMM") next bar at entryPrice + maxTradeRisk/bigPointValue stop;
Complete Code Framework

Here is the best equity curve I uncovered when I optimized the startMonth from 1 to 12 and the endMonth from 1 to 12 and the maxTradeRisk per contract and the three entry filters.  Entering in November when the moving average filter aligns and exiting on the first day of August and risking $5,500 per contract produced this equity curve.

Enter November get out the beginning of August

The test returned what would basically be similar to a buy and hold scenario; the difference being you only hold the trade between seven and eight months of the year and risk only $5,500 per contract.  If you get stopped out, you wait until November to get back in – whenever the moving average filter allows.  Net profit to draw down ratio is north of 4.0.

Last Comment

If I optimize from 1 to 12 for the start month and 1 to 12 for end month, will this not cause an error?  What if the two values equal?  I mean I can’t enter and exit in the same month – a one-day trade?  You could make the code smarter, but it doesn’t matter.  As a user you will know better than to use the same number and the optimizer will test the combination with the same number, but the results will fall off the table.   In this case, error trapping doesn’t prevent a necessarily unwanted or dangerous scenario.