Month of Year Effect Strategy
The Month of Year Effect captures performance differences observed in various months. For example, equities often rally in November and December but can be weak during September.
Testing indicates an average annual return of about 88%. It performs best in the stocks market.
The system goes long or short at the beginning of each month based on those historical averages, exiting by month-end.
Stops are used to protect capital if the usual seasonal behavior fails to appear.
Details
- Entry Criteria: calendar effect triggers
- Long/Short: Both
- Exit Criteria: stop-loss or opposite signal
- Stops: Yes, percent based
- Default Values:
CandleType= 15 minuteStopLoss= 2%
- Filters:
- Category: Seasonality
- Direction: Both
- Indicators: Seasonality
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: Yes
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Month of Year seasonal trading strategy.
/// Enters long in historically strong months (Nov-Jan) and short in weak months (Feb, May, Sep).
/// Uses MA trend filter and cooldown between trades.
/// </summary>
public class MonthOfYearStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _ma;
private decimal _prevMa;
private decimal _prevClose;
private int _lastTradeMonth;
private int _lastTradeHalf;
private int _cooldown;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// MA period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MonthOfYearStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetDisplay("MA Period", "SMA period", "Indicators")
.SetRange(10, 50);
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(10, 2000);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = default;
_prevMa = 0;
_prevClose = 0;
_lastTradeMonth = 0;
_lastTradeHalf = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ma)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var month = candle.OpenTime.Month;
var half = candle.OpenTime.Day <= 15 ? 1 : 2;
if (_cooldown > 0)
{
_cooldown--;
_prevMa = ma;
_prevClose = close;
return;
}
// Exit logic: MA cross
if (Position > 0 && close < ma && _prevMa > 0 && _prevClose >= _prevMa)
{
SellMarket();
_cooldown = CooldownBars;
_lastTradeMonth = month;
_lastTradeHalf = half;
}
else if (Position < 0 && close > ma && _prevMa > 0 && _prevClose <= _prevMa)
{
BuyMarket();
_cooldown = CooldownBars;
_lastTradeMonth = month;
_lastTradeHalf = half;
}
// Entry logic: seasonal month-half based
if (Position == 0 && (month != _lastTradeMonth || half != _lastTradeHalf))
{
// First half of month: buy if above MA
if (half == 1 && close > ma)
{
BuyMarket();
_cooldown = CooldownBars;
_lastTradeMonth = month;
_lastTradeHalf = half;
}
// Second half of month: sell if below MA
else if (half == 2 && close < ma)
{
SellMarket();
_cooldown = CooldownBars;
_lastTradeMonth = month;
_lastTradeHalf = half;
}
}
_prevMa = ma;
_prevClose = close;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class month_of_year_strategy(Strategy):
"""
Month of Year seasonal trading strategy.
Uses first/second half of month with MA trend filter and cooldown.
"""
def __init__(self):
super(month_of_year_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "SMA period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 100).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._prev_ma = 0.0
self._prev_close = 0.0
self._last_trade_month = 0
self._last_trade_half = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(month_of_year_strategy, self).OnReseted()
self._prev_ma = 0.0
self._prev_close = 0.0
self._last_trade_month = 0
self._last_trade_half = 0
self._cooldown = 0
def OnStarted2(self, time):
super(month_of_year_strategy, self).OnStarted2(time)
self._prev_ma = 0.0
self._prev_close = 0.0
self._last_trade_month = 0
self._last_trade_half = 0
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ma = float(ma_val)
month = candle.OpenTime.Month
half = 1 if candle.OpenTime.Day <= 15 else 2
cd = self._cooldown_bars.Value
if self._cooldown > 0:
self._cooldown -= 1
self._prev_ma = ma
self._prev_close = close
return
# Exit logic: MA cross
if self.Position > 0 and close < ma and self._prev_ma > 0 and self._prev_close >= self._prev_ma:
self.SellMarket()
self._cooldown = cd
self._last_trade_month = month
self._last_trade_half = half
elif self.Position < 0 and close > ma and self._prev_ma > 0 and self._prev_close <= self._prev_ma:
self.BuyMarket()
self._cooldown = cd
self._last_trade_month = month
self._last_trade_half = half
# Entry logic: seasonal month-half based
if self.Position == 0 and (month != self._last_trade_month or half != self._last_trade_half):
# First half of month: buy if above MA
if half == 1 and close > ma:
self.BuyMarket()
self._cooldown = cd
self._last_trade_month = month
self._last_trade_half = half
# Second half of month: sell if below MA
elif half == 2 and close < ma:
self.SellMarket()
self._cooldown = cd
self._last_trade_month = month
self._last_trade_half = half
self._prev_ma = ma
self._prev_close = close
def CreateClone(self):
return month_of_year_strategy()