January Effect Strategy
The January Effect observes that small-cap stocks often outperform early in the year, possibly due to tax-loss selling in December. Traders attempt to capture this tendency by buying in late December and selling after the first few weeks of January.
Testing indicates an average annual return of about 103%. It performs best in the stocks market.
The strategy follows that schedule, entering near year-end and exiting mid-January.
A stop-loss ensures losses stay manageable if the effect 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>
/// Implementation of January Effect trading strategy.
/// Generalizes the seasonal effect: buys at the start of each month if above MA,
/// exits mid-month. Goes short mid-month if below MA, covers at end of month.
/// </summary>
public class JanuaryEffectStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _ma;
private int _cooldown;
private int _prevDayOfMonth;
/// <summary>
/// Moving average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="JanuaryEffectStrategy"/>.
/// </summary>
public JanuaryEffectStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Moving average period for trend confirmation", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for strategy", "Strategy");
_cooldownBars = Param(nameof(CooldownBars), 50)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = default;
_cooldown = 0;
_prevDayOfMonth = 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 maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var dayOfMonth = candle.OpenTime.Day;
var isNewDay = dayOfMonth != _prevDayOfMonth;
if (_cooldown > 0)
{
_cooldown--;
_prevDayOfMonth = dayOfMonth;
return;
}
// Start of month: day 1-5 buy zone
var isStartOfMonth = dayOfMonth >= 1 && dayOfMonth <= 5;
// Mid-month exit zone: day 14-17
var isMidMonth = dayOfMonth >= 14 && dayOfMonth <= 17;
// End-of-month short exit zone: day 26+
var isEndOfMonth = dayOfMonth >= 26;
// Buy at start of month if above MA
if (isStartOfMonth && isNewDay && Position == 0 && close > maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Exit long mid-month
else if (isMidMonth && isNewDay && Position > 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Short mid-month if below MA
else if (isMidMonth && isNewDay && Position == 0 && close < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Cover short at end of month
else if (isEndOfMonth && isNewDay && Position < 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevDayOfMonth = dayOfMonth;
}
}
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 january_effect_strategy(Strategy):
"""
January Effect trading strategy.
Buys at the start of each month if above MA, exits mid-month.
Goes short mid-month if below MA, covers at end of month.
"""
def __init__(self):
super(january_effect_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "Moving average period for trend confirmation", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles for strategy", "Strategy")
self._cooldown_bars = self.Param("CooldownBars", 50).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._cooldown = 0
self._prev_day_of_month = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(january_effect_strategy, self).OnReseted()
self._cooldown = 0
self._prev_day_of_month = 0
def OnStarted2(self, time):
super(january_effect_strategy, self).OnStarted2(time)
self._cooldown = 0
self._prev_day_of_month = 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)
day_of_month = candle.OpenTime.Day
cd = self._cooldown_bars.Value
is_new_day = day_of_month != self._prev_day_of_month
if self._cooldown > 0:
self._cooldown -= 1
self._prev_day_of_month = day_of_month
return
is_start_of_month = day_of_month >= 1 and day_of_month <= 5
is_mid_month = day_of_month >= 14 and day_of_month <= 17
is_end_of_month = day_of_month >= 26
# Buy at start of month if above MA
if is_start_of_month and is_new_day and self.Position == 0 and close > ma:
self.BuyMarket()
self._cooldown = cd
# Exit long mid-month
elif is_mid_month and is_new_day and self.Position > 0:
self.SellMarket()
self._cooldown = cd
# Short mid-month if below MA
elif is_mid_month and is_new_day and self.Position == 0 and close < ma:
self.SellMarket()
self._cooldown = cd
# Cover short at end of month
elif is_end_of_month and is_new_day and self.Position < 0:
self.BuyMarket()
self._cooldown = cd
self._prev_day_of_month = day_of_month
def CreateClone(self):
return january_effect_strategy()