Turnaround Tuesday Strategy
Turnaround Tuesday refers to the tendency for markets that sold off on Monday to rebound the next day. The effect is often attributed to traders overreacting after the weekend and then reversing course.
Testing indicates an average annual return of about 91%. It performs best in the stocks market.
This strategy buys at Tuesday's open when Monday was down, holding only for the session or until a modest profit target is reached.
Stops are tight to protect against continued weakness if the bounce fails to develop.
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 Turnaround Tuesday trading strategy.
/// Buys on Tuesday if previous session declined, sells on Friday or if price crosses MA.
/// Also goes short on Wednesday if previous session rallied.
/// Uses half-day detection to simulate daily sessions on intraday data.
/// </summary>
public class TurnaroundTuesdayStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _ma;
private decimal _prevMa;
private decimal _sessionOpen;
private decimal _sessionClose;
private int _prevSessionDay;
private bool _prevSessionDecline;
private bool _prevSessionRally;
private int _currentSessionDay;
private bool _enteredThisSession;
private int _cooldown;
/// <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="TurnaroundTuesdayStrategy"/>.
/// </summary>
public TurnaroundTuesdayStrategy()
{
_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), 30)
.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;
_prevMa = 0;
_sessionOpen = 0;
_sessionClose = 0;
_prevSessionDay = -1;
_prevSessionDecline = false;
_prevSessionRally = false;
_currentSessionDay = -1;
_enteredThisSession = false;
_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 maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var dayOfYear = candle.OpenTime.DayOfYear;
var dayOfWeek = (int)candle.OpenTime.DayOfWeek;
// Detect new session (new calendar day)
if (dayOfYear != _currentSessionDay)
{
// Save previous session result
if (_currentSessionDay >= 0 && _sessionOpen > 0)
{
_prevSessionDecline = _sessionClose < _sessionOpen;
_prevSessionRally = _sessionClose > _sessionOpen;
_prevSessionDay = _currentSessionDay;
}
_currentSessionDay = dayOfYear;
_sessionOpen = candle.OpenPrice;
_enteredThisSession = false;
}
_sessionClose = close;
if (_cooldown > 0)
{
_cooldown--;
_prevMa = maValue;
return;
}
// Entry: buy on any day if previous session declined and no position
if (Position == 0 && !_enteredThisSession && _prevSessionDecline && close > maValue)
{
BuyMarket();
_cooldown = CooldownBars;
_enteredThisSession = true;
_prevSessionDecline = false;
}
// Entry: sell on any day if previous session rallied and no position
else if (Position == 0 && !_enteredThisSession && _prevSessionRally && close < maValue)
{
SellMarket();
_cooldown = CooldownBars;
_enteredThisSession = true;
_prevSessionRally = false;
}
// Exit long if price crosses below MA
if (Position > 0 && _prevMa > 0 && close < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short if price crosses above MA
if (Position < 0 && _prevMa > 0 && close > maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevMa = maValue;
}
}
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 turnaround_tuesday_strategy(Strategy):
"""
Turnaround Tuesday trading strategy.
Buys if previous session declined and price above MA.
Sells if previous session rallied and price below MA.
Uses session detection via day-of-year transitions.
"""
def __init__(self):
super(turnaround_tuesday_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", 30).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._prev_ma = 0.0
self._session_open = 0.0
self._session_close = 0.0
self._prev_session_day = -1
self._prev_session_decline = False
self._prev_session_rally = False
self._current_session_day = -1
self._entered_this_session = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(turnaround_tuesday_strategy, self).OnReseted()
self._prev_ma = 0.0
self._session_open = 0.0
self._session_close = 0.0
self._prev_session_day = -1
self._prev_session_decline = False
self._prev_session_rally = False
self._current_session_day = -1
self._entered_this_session = False
self._cooldown = 0
def OnStarted2(self, time):
super(turnaround_tuesday_strategy, self).OnStarted2(time)
self._prev_ma = 0.0
self._session_open = 0.0
self._session_close = 0.0
self._prev_session_day = -1
self._prev_session_decline = False
self._prev_session_rally = False
self._current_session_day = -1
self._entered_this_session = False
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)
day_of_year = candle.OpenTime.DayOfYear
cd = self._cooldown_bars.Value
# Detect new session (new calendar day)
if day_of_year != self._current_session_day:
# Save previous session result
if self._current_session_day >= 0 and self._session_open > 0:
self._prev_session_decline = self._session_close < self._session_open
self._prev_session_rally = self._session_close > self._session_open
self._prev_session_day = self._current_session_day
self._current_session_day = day_of_year
self._session_open = float(candle.OpenPrice)
self._entered_this_session = False
self._session_close = close
if self._cooldown > 0:
self._cooldown -= 1
self._prev_ma = ma
return
# Entry: buy if previous session declined and no position
if self.Position == 0 and not self._entered_this_session and self._prev_session_decline and close > ma:
self.BuyMarket()
self._cooldown = cd
self._entered_this_session = True
self._prev_session_decline = False
# Entry: sell if previous session rallied and no position
elif self.Position == 0 and not self._entered_this_session and self._prev_session_rally and close < ma:
self.SellMarket()
self._cooldown = cd
self._entered_this_session = True
self._prev_session_rally = False
# Exit long if price crosses below MA
if self.Position > 0 and self._prev_ma > 0 and close < ma:
self.SellMarket()
self._cooldown = cd
# Exit short if price crosses above MA
if self.Position < 0 and self._prev_ma > 0 and close > ma:
self.BuyMarket()
self._cooldown = cd
self._prev_ma = ma
def CreateClone(self):
return turnaround_tuesday_strategy()