August 26

2 comments

Riding the Market Waves: How to Surf Seasonal Trends to Trading Success

By Jeff Swanson

August 26, 2024

automated trading, Automated Trading Development, EasyLanguage, ES, Jeff Swanson, market seasonality, market studies, Migrated, S&P Emini, seasonality, sell in may, stock index futures

Remember that old Wall Street chestnut, "Sell in May and go away"? Well, grab your favorite beverage and settle in, because we're about to dive into this age-old wisdom and see if it really holds water.

Now, I don't know about you, but I've always been a bit skeptical of these catchy market phrases. They sound great at cocktail parties, but do they actually work? That's exactly what I set out to discover.

In this article, we're going to break down our Market Seasonality Study step by step.

Below is a graph of the market with a "buy" at the start of the strong period and a "sell" at the end of the strong period.

Testing the "Buy in November, Sell in May" Strategy

To evaluate the popular trading concept of buying the S&P in November and selling in May, I set up a test with the following parameters:

  • Market: S&P cash market
  • Time period: 1960 to present
  • Position sizing: Risk-adjusted, with $5,000 risked per trade based on market volatility
  • Transaction costs: Not included (no slippage or commissions accounted for)

The core logic of this strategy can be implemented in EasyLanguage as follows:

CurrentMonth = Month(Date);
If (CurrentMonth = BuyMonth) And (MP = 0) Then
Buy("Buy Month") iShares contracts next bar at market;
If (CurrentMonth = SellMonth) Then
Sell("Sell Month") iShares contracts next bar at market;

This code does the following:

  1. Determines the current month
  2. If it's the buy month (November) and we're not in the market, it enters a long position
  3. If it's the sell month (May), it exits the position

By using this simple logic, we can test whether this well-known market adage holds up over a long period of historical data.

Test Parameters and Assumptions

I decided to test the strategy on the S&P cash index, with data going back to 1960. The following assumptions were made for this test:

  • Starting account size: $100,000
  • Test period: January 1960 through May 2024
  • Position sizing: Full account size used when opening a new position
  • Equity management: P&L not accumulated to the starting equity
  • Transaction costs: No deductions for commissions and slippage
  • Risk management: No stops were used

With these parameters set, we can now input the key months for our seasonality strategy:

  • Buy month: November
  • Sell month: May

Using these inputs, we generated the following equity graph:

Buy November, Sell May

Buy November, Sell May

"Sell in May and Go Away" Strategy Results

The equity curve we generated visually represents the performance of our "Sell in May and Go Away" strategy over the 64-year test period. It allows us to see the long-term trend as well as any significant drawdowns or periods of exceptional growth. And I must say, it sure looks like these months have a long bias - those are some impressive results!

Let's break down the numbers:

  • Total Profit: $4,640,085
  • Max Intra-day Drawdown: $1,403,538
  • Net Profit vs Drawdown Ratio: 3.3

In simpler terms, for every dollar of drawdown, the strategy is making just over three dollars in profit. That's a respectable ratio by most trading standards.

Inverting the Strategy

But here's where it gets really interesting. What would happen if we flipped our strategy on its head? Instead of buying in November and selling in May, what if we did the opposite - buying in May and selling in November?

To find out, I inverted the BuyMonth and SellMonth inputs in our test. Here's the resulting equity curve:

Buy May, Sell November

Buy May, Sell November

Analyzing the Inverted Strategy Results

The inverted equity curve provides a fascinating contrast to our original strategy, allowing us to visually compare the performance of buying during the traditionally "weak" months versus the "strong" months.

Let's break down the performance of this inverted strategy:

  1. 1960-1970: The equity curve consistently declined.
  2. 1970-1978: It bottomed out and began to climb.
  3. 1978-1997: Steady growth, reaching an equity peak in 1997.
  4. 1997-2008: Entered a drawdown period, bottoming in 2008.
  5. 2008-2021: Recovered, reaching new equity highs in 2020 and 2021.

Key metrics for the inverted strategy:

  • Total Profit: $94,148
  • Max Intra-day Drawdown: $92,357
  • Net Profit vs Drawdown Ratio: 1.0

This 1:1 ratio indicates that you must endure a dollar of drawdown for every dollar of profit - a much less attractive risk-reward proposition compared to our original strategy.

Let's focus on the strong season period from November-May moving forward. Can we add a filter to improve the results?

Implementing a Simple Moving Average Filter

To refine our strategy and avoid potentially unfavorable entry and exit points, we're introducing a 30-period simple moving average (SMA) as a short-term trend filter. This addition aims to prevent us from:

  1. Buying immediately into a falling market
  2. Selling immediately into a rising market

Here's how it works:

  • For Selling: If May (our SellMonth) arrives and the market is rising (price above the 30-period SMA), we delay selling until the price closes below the SMA.
  • For Buying: If November (our BuyMonth) arrives, we only buy when the price closes above the SMA.

To implement this in EasyLanguage, we create two flags: BuyFlag and SellFlag. These indicate when the proper conditions for buying or selling are met based on 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 an input that determines the SMA's look-back period. An additional check allows us to enable or disable the SMA filter:

  • If MinorTrendLen > 0: The SMA filter is active
  • If MinorTrendLen = 0: Both flags are always true, effectively disabling the filter

This flexibility lets us easily compare performance with and without the filter.

Strong Seasonality Trade (November-May) With Filters

Baseline

SMA Filter

Net Profit

$4,640,095

$5,961,005

Profit Factor

4.42

4.81

Total Trades

64

64

Avg.Trade Net Profit

$72,501

$93,140

Annual Rate of Return

9.54%

9.93%

Max Drawdown(Intraday)

$1,403,538

$1,856,564

NP vs Drawdown

3.3

3.2

We increased the net profit, profit factor, average profit per trade, and annual rate of return. There is a slight decline in the NP vs Drawdown ratio, but it's tiny. We can decently pull more profit by applying a simple moving average.

MACD Filter

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:

Strong Seasonality Trade (November-May) With Filters

Baseline

SMA Filter

MACD Filter

Net Profit

$4,640,095

$5,961,005

$4,685,543

Profit Factor

4.42

4.81

5.35

Total Trades

64

64

63

Avg.Trade Net Profit

$72,501

$93,140

$74,373

Annual Rate of Return

9.54%

9.93%

9.55%

Max Drawdown (Intraday)

$1,403,538

$1,856,564

$1,205,264

NP vs Drawdown

3.3

3.2

3.8

Utilizing the MACD filter and comparing it to our baseline system, we can see we don't make as much more as the simple moving average filter, however, we increased our risk adjusted return with a NP vs DD of 3.8. So, if drawdown is your concern and not just pure profit it looks the MACD filter is slightly better.

RSI 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;

Strong Seasonality Trade (November-May) With Filters

Baseline

SMA Filter

MACD Filter

RSI Filter

Net Profit

$4,640,095

$5,961,005

$4,685,543

$4,080,657

Profit Factor

4.42

4.81

5.35

4.24

Total Trades

64

64

63

64

Avg.Trade Net Profit

$72,501

$93,140

$74,373

$63,760

Annual Rate of Return

9.54%

9.93%

9.55%

9.34%

Max Drawdown (Intraday)

$1,403,538

$1,856,564

$1,205,264

$1,291,784

NP vs Drawdown

3.3

3.2

3.8

3.1

Filter Comparison Results

The RSI filter performed worse than both the MACD and SMA filters.

In the end, it appears that applying either an SMA filter or an MACD filter can improve the baseline results. Both filters are relatively simple to implement and were tested for this article using their default values. Of course, this simple study could be expanded much further.

Applying Filters to the Weak Seasonality Period

After performing the different filter tests and selecting the simple moving average filter as the most effective, I began to wonder how our three filters would perform if applied to the weak seasonality period. Below are the performance results.

Weak Seasonality Trade (May-November) With Filters

No Filter

SMA Filter

MACD Filter

RSI Filter

Net Profit

$94,148

$244,406

$221,672

$121,740

Profit Factor

1.42

1.67

1.79

1.52

Total Trades

64

64

63

64

Avg.Trade Net Profit

$1,471

$3,818

$3,518

$1,902

Annual Rate of Return

3.64%

5.03%

4.88%

4.00%

Max Drawdown (Intraday)

$92,357

$128,921

$106,121

$100,544

NP vs Drawdown

1.0

1.7

2.0

1.2

We can see that adding MACD Filter produces the best results in terms risk adjusted return (NP vs Drawdown). The SMA Filter products the best results in terms of net profit. Both provide a radical change when compared to the No Filter situation. 

Conclusion

It certainly appears there is a significant seasonal edge in the S&P market. The trading rules we used above for the S&P cash market could be applied to the SPY and DIA ETF markets. I've tested these ETFs, and they produce very similar results. The S&P futures market also yields comparable outcomes. Interestingly, this approach even seems to work well for some individual stocks.

Keep in mind that this market study did not utilize any market stops. This is not a complete trading system!

Practical Use: Market Regime Filter For Stock Index Markets

With some additional work, an automated trading system could be built from this study. Another application would be to use this study as a filter for other trading systems. I envision using these results as a regime filter for your existing trading systems that trade the stock index markets.

Strong Season Bull/Bear Filter

  • Bull Market = if price is above 30-day SMA
  • Bear Market = if price is at or below 30-day SMA

Weak Season Bull/Bear Filter

  • Bull Market = if MACD is above zero
  • Bear Market = if MACD is at or below zero

This seasonality filter could be applied to both automated trading systems and discretionary trading. While it may not be particularly helpful for intraday trading, it's worth testing in that context as well.

Being aware of these major market cycles can be invaluable in understanding current market conditions and potential future directions. This knowledge can enhance your overall market perspective and potentially improve your trading decisions.

I hope you found this study helpful and that it provides you with new insights for your trading strategies.

Jeff Swanson

About the author

Jeff has built and traded automated trading systems for the futures markets since 2008. He is the creator of the online courses System Development Master Class and Alpha Compass. Jeff is also the founder of EasyLanguage Mastery - a website and mission to empower the EasyLanguage trader with the proper knowledge and tools to become a profitable trader.

  • Jeff,

    Thanks for the post. Curious how this strategy would compare to buy-and-hold?

    I show 55.61 on Jan 1960 and 5,648.40 the Aug 2024 closing price of $SPX.X.

    At 55.61, $100,000 would be able to buy 1,798.24 shs. At the current price of 5,648.40, the 1,798.24 shs. would be worth $10,157,166 – a bit more than the $5.961 million from the strategy. What am I missing?

    • What you’re missing in your assessment is the drawdown. First, calculate the drawdown you would experience with a buy-and-hold strategy. Then, calculate the Net Profit vs. Drawdown ratio and compare that to the strategy. With this strategy, you won’t endure severe drawdowns and will have a mechanical method for re-entering the market. Most people tend to sell during a drawdown and never re-enter. This strategy offers the advantage of managing drawdowns, making it easier to stay in the market compared to holding, which is difficult for most people to maintain.

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Learn To Code & Build Strategies
    Using EasyLanguage. 

    >