Turn Of Month
This seasonal pattern buys equity indices a few days before month‑end and exits shortly after the new month begins, aiming to capture the "turn‑of‑the‑month" effect.
The system stays in cash outside of this window to reduce exposure.
Details
- Data: Daily index levels.
- Entry: Buy N days before month end.
- Exit: Sell M days after month start.
- Instruments: Equity index futures or ETFs.
- Risk: Flat outside scheduled window.
// TurnOfMonthStrategy.cs
// -----------------------------------------------------------------------------
// Holds positions only around turn-of-the-month window.
// Default: long from N=1 trading day before month-end close
// until D=3 trading day of new month close.
// Trigger: candle close timing.
// -----------------------------------------------------------------------------
// Date: 2 Aug 2025
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Turn-of-the-month effect strategy for index ETFs.
/// </summary>
public class TurnOfMonthStrategy : Strategy
{
private readonly StrategyParam<int> _prior;
private readonly StrategyParam<int> _after;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Number of trading days before month-end to enter.
/// </summary>
public int DaysPrior
{
get => _prior.Value;
set => _prior.Value = value;
}
/// <summary>
/// Number of trading days into new month to exit.
/// </summary>
public int DaysAfter
{
get => _after.Value;
set => _after.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
private int _cooldownRemaining;
public TurnOfMonthStrategy()
{
_prior = Param(nameof(DaysPrior), 2)
.SetDisplay("Days Prior", "Trading days before month end", "Parameters");
_after = Param(nameof(DaysAfter), 4)
.SetDisplay("Days After", "Trading days into new month", "Parameters");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
var d = candle.OpenTime.Date;
var tdLeft = TradingDaysLeftInMonth(d);
var tdNum = TradingDayNumber(d);
var inWindow = (tdLeft <= DaysPrior) || (tdNum <= DaysAfter);
if (inWindow && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
else if (!inWindow && Position > 0)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
}
private static int TradingDaysLeftInMonth(DateTime d)
{
var cnt = 0;
var cur = d;
while (cur.Month == d.Month)
{
if (cur.DayOfWeek != DayOfWeek.Saturday && cur.DayOfWeek != DayOfWeek.Sunday)
cnt++;
cur = cur.AddDays(1);
}
return cnt - 1;
}
private static int TradingDayNumber(DateTime d)
{
var n = 0;
var cur = new DateTime(d.Year, d.Month, 1);
while (cur <= d)
{
if (cur.DayOfWeek != DayOfWeek.Saturday && cur.DayOfWeek != DayOfWeek.Sunday)
n++;
cur = cur.AddDays(1);
}
return n;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, DayOfWeek, DateTime, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class turn_of_month_strategy(Strategy):
"""Turn-of-the-month effect strategy for index ETFs."""
def __init__(self):
super(turn_of_month_strategy, self).__init__()
self._prior = self.Param("DaysPrior", 2) \
.SetDisplay("Days Prior", "Trading days before month end", "Parameters")
self._after = self.Param("DaysAfter", 4) \
.SetDisplay("Days After", "Trading days into new month", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(turn_of_month_strategy, self).OnReseted()
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(turn_of_month_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
d = candle.OpenTime.Date
td_left = self._trading_days_left(d)
td_num = self._trading_day_number(d)
days_prior = int(self._prior.Value)
days_after = int(self._after.Value)
cooldown = int(self._cooldown_bars.Value)
in_window = td_left <= days_prior or td_num <= days_after
if in_window and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif not in_window and self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
def _trading_days_left(self, d):
cnt = 0
cur = d
while cur.Month == d.Month:
if cur.DayOfWeek != DayOfWeek.Saturday and cur.DayOfWeek != DayOfWeek.Sunday:
cnt += 1
cur = cur.AddDays(1)
return cnt - 1
def _trading_day_number(self, d):
n = 0
cur = DateTime(d.Year, d.Month, 1)
while cur <= d:
if cur.DayOfWeek != DayOfWeek.Saturday and cur.DayOfWeek != DayOfWeek.Sunday:
n += 1
cur = cur.AddDays(1)
return n
def CreateClone(self):
return turn_of_month_strategy()