November is here and that means the start of a historically strong period for the U.S. markets. This is a well known seasonality period which runs from November to May. While it’s true the U.S. markets experienced a Death Cross formation not very long ago, the market has since rebounded nicely. The Death Cross formation is still in effect but it may give way to its opposite formation, the Golden Cross, very soon. If the Golden Cross formation does take hold, this would simply be another indication that the market may be posed for a new run.
In this article I would like to take a closer look at the seasonality bias which so many investors and traders talk about.
First, I would like to test the popular trading idea of buying the S&P in November and selling in May. I will test this on the cash market going back to 1983. The number of shares to buy will be adjusted based upon a fixed amount of risk. In this case, $5,000 is risked per trade based upon the volatility of the market. As this is a simple market study no slippage or commissions were taken into account. The EasyLanguage code looks like this:CurrentMonth = Month( Date ); If ( CurrentMonth = BuyMonth ) And ( MP = 0 ) Then
I decided to test the strategy on the S&P cash index going back to 1960. The following assumptions were made:
From here we can plug into the input values the buy month (November) and sell month (May). Doing this we generate the following equity graph:
It sure looks like these months have a long bias as those are some nice results. What would the equity curve look like if we reverse the BuyMonth and SellMonth? That is, let’s buy in May and sell in November. Below is the equity curve for this inverted system.
The early ears of 1965 to about 1970 the equity curve went lower and lower. Then it started climbing reaching an equity peak of 1987. During this period, the strategy was producing positive results. That equity peak occurred during 1987 should be familiar. That was the year we had the massive one day market crash on October 19th known as Black Monday. The Dow Jones Industrial Average dropped 22% in one day. Since that event the behavior of market participants has been altered. This is not unlike the radical market changes which occurred after the 2000 market peek where much of the trending characteristics of the major markets were replaced by mean reversion tendencies.
So far the basic seasonality study looks interesting. However, keep in mind that we do not have any stops in place. Nor do we have any entry filter that would prevent us from opening a trade during a bear market. If the market is falling strongly when our BuyMonth rolls around we may not wish to purchase right away. Likewise, we have no exit filter to prevent us from exiting when the market may be on a strong rise during the month of May. It’s conceivable that the market may be experiencing a strong bull run when our usual SellMonth of May rolls around.
In order to avoid buying and selling at the wrong times I’m going to introduce a 40-period simple moving average (SMA) to act as a short-term trend filter. I picked 40 days because this represents about two months worth of trading. This filter will be used to prevent us from immediately buying into a falling market or selling into a rising market. For example, if our SellMonth of May rolls around and the market happens to be rising (trading above the 40-period SMA), we do not sell just yet. We wait until unit price closes below the short-term SMA. The same idea is applied to the buying side, but reversed. We will not go long until price closed above the short-term SMA.
Within EasyLanguage we can create a buy/sell confirmation flag called BuyFlag and SellFlag which will indicate when the proper go-long or sell conditions appear based upon our short-term trend filter.
if ( MinorTrendLen > 0 ) Then BuyFlag = Close > Average( Close, MinorTrendLen ) Else BuyFlag = true; If ( MinorTrendLen > 0 ) Then SellFlag = Close < Average( Close, MinorTrendLen ) Else SellFlag = true;
The MinorTrendLen variable is actually an input value which holds the look-back period to be used in the SMA calculation. You will notice there is an additional check to see if the look-back period is zero. This is done so we can enable or disable this SMA filter. If you enter zero for the look-back period, the code will always set our BuyFlag and SellFlag to true. This effectively disables our short-term market filter. This is a handy way to enable and disable filters from the system inputs.
Below is the performance of both our Baseline system and the system with our SMA filter:
We increased the net profit, profit factor, average profit per trade, annual rate of return, and the expectancy score. The max intraday drawdown fell as well. Overall it looks like the SMA filter adds value.
A standard MACD filter is a well known indicator that may help with timing. I’m going to add a MACD calculation, using the default settings, and only open a new trade when the MACD line is above zero. Likewise, I’m only going to sell when the MACD line is below zero. Within EasyLanguage we can create a MACD filter by creating two Boolean flags called MACDBull and MACDBear which will indicate when the proper major market trend is in our favor.
If ( MACD_Filter ) Then Begin MyMACD = MACD( Close, FastLength, SlowLength ); MACDAvg = XAverage( MyMACD, MACDLength ); MACDDiff = MyMACD - MACDAvg; If ( MyMACD crosses over 0 ) Then Begin MACDBull = True; MACDBear = False; End Else If ( MyMACD crosses under 0 ) Then Begin MACDBull = False; MACDBear = True; End; End Else Begin MACDBull = True; MACDBear = True; End;
Below are the results with the MACD filter:
Utilizing the MACD filter and comparing it to our baseline system, we reduced every so slightly many of the performance metrics except average net profit per trade. Overall, it does not appear to be better than our SMA filter.
For our final filter I will try the RSI indicator with its default loopback period of 14. Again, like the MACD calculation, I want price moving in our direction so I want the RSI calculation to be above zero when opening a position and below zero when closing a position.
If ( RSI_Filter ) Then Begin RSIBull = RSI(Close, 14 ) > 50; RSIBear = RSI(Close, 14 ) < 50; End Else Begin RSIBull = true; RSIBear = true; End;
The RSI filter performed better than the MACD than the short-term SMA filter depending upon what metrics you find important. We do see the RSI filter produces the highest profit factor and percent winners. Yet, the net profit is about the same as the SMA filter and the Expectancy Score is lower. In the end it does appear applying a SMA filter or an RSI filter can improve the baseline results. Both filters are rather simple to implement and were tested for this article with their default values. You of course could take this much further.
It certainly appears there is a significant seasonal edge to the S&P market. The very trading rules we used above for the S&P cash market could be applied to the SPY and DIA ETF markets. I’ve tested those ETFs and they produce very similar results. The S&P futures market also produces similar results. It even appears to work well on some stocks. Keep in mind this market study did not utilize any market stops. How can this study be used? With a little work an automated trading system could be built from this study. Another use would be to apply this study as a filter for trading other systems.
This seasonality filter could be applied to both automated trading systems or even discretionary trading. It may not be much help for intraday trading, but it may be worth testing. Anyway, just being aware of these major market cycles can be helpful in understanding what’s going on with today’s markets and where they may be going in the near future. Hope you found this study helpful.