A weekly contrarian system converted from the original MQL "Contrarian_trade_MA" expert advisor. The strategy analyses weekly candle extremes together with a simple moving average to fade stretched moves at the start of a new week.
Trading Logic
Data Source: Weekly candles provided by the CandleType parameter (defaults to a 7-day timeframe).
Historical Extremes: Highest and Lowest indicators track the high and low of the previous CalcPeriod completed weeks, excluding the currently evaluated candle.
Moving Average Filter: A simple moving average of length MaPeriod applied to weekly closes acts as a directional filter.
Entry Rules:
Buy when the previous week's close is higher than the tracked high (highest < previousClose) or when the moving average is above the current weekly open.
Sell when the previous week's close is lower than the tracked low (lowest > previousClose) or when the moving average is below the current weekly open.
Only one position can be open at any time; opposing signals are ignored until the existing trade is closed.
Exit Rules:
The position is closed after being held for seven days (604,800 seconds) regardless of direction.
A protective stop is evaluated on every completed weekly candle. The stop distance is calculated from StopLossPoints * PriceStep (falls back to 1 if the instrument metadata does not specify a step).
Parameters
Name
Default
Description
CalcPeriod
4
Number of completed weeks used to compute the highest high and lowest low.
MaPeriod
7
Period of the simple moving average applied to weekly closes.
StopLossPoints
300
Distance from the entry price to the stop-loss, measured in price steps. Set to 0 to disable the stop.
Volume
0.5
Order size in lots submitted by BuyMarket/SellMarket.
CandleType
7 days
Timeframe for the candles driving all calculations.
Additional Notes
The strategy automatically retrieves the price step from Security.PriceStep. Provide this value in instrument metadata for accurate stop-loss placement.
StartProtection() is enabled to track unexpected position changes performed outside of the strategy.
Because the logic operates on completed weekly candles, fills are simulated at the weekly close of the signal bar when running in testing mode.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Contrarian MA strategy - mean reversion around SMA.
/// Buys when price crosses below SMA (contrarian dip buy).
/// Sells when price crosses above SMA (contrarian top sell).
/// Uses Highest/Lowest as extreme confirmation.
/// </summary>
public class ContrarianTradeMaWeeklyStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevSma;
private bool _hasPrev;
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ContrarianTradeMaWeeklyStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 14)
.SetDisplay("SMA Period", "SMA period", "Indicators");
_channelPeriod = Param(nameof(ChannelPeriod), 10)
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevClose = 0m; _prevSma = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, highest, lowest, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal sma, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (!_hasPrev)
{
_prevClose = close;
_prevSma = sma;
_hasPrev = true;
return;
}
var mid = (highest + lowest) / 2;
// Contrarian buy: price crosses below SMA and is near the low
if (_prevClose >= _prevSma && close < sma && close < mid && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Contrarian sell: price crosses above SMA and is near the high
else if (_prevClose <= _prevSma && close > sma && close > mid && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevClose = close;
_prevSma = sma;
}
}
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, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class contrarian_trade_ma_weekly_strategy(Strategy):
def __init__(self):
super(contrarian_trade_ma_weekly_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 14) \
.SetDisplay("SMA Period", "SMA period", "Indicators")
self._channel_period = self.Param("ChannelPeriod", 10) \
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
@property
def ma_period(self):
return self._ma_period.Value
@property
def channel_period(self):
return self._channel_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(contrarian_trade_ma_weekly_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(contrarian_trade_ma_weekly_strategy, self).OnStarted2(time)
self._has_prev = False
sma = SimpleMovingAverage()
sma.Length = self.ma_period
highest = Highest()
highest.Length = self.channel_period
lowest = Lowest()
lowest.Length = self.channel_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, highest, lowest, self.process_candle).Start()
def process_candle(self, candle, sma, highest, lowest):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sma_val = float(sma)
high_val = float(highest)
low_val = float(lowest)
if not self._has_prev:
self._prev_close = close
self._prev_sma = sma_val
self._has_prev = True
return
mid = (high_val + low_val) / 2.0
if self._prev_close >= self._prev_sma and close < sma_val and close < mid and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close <= self._prev_sma and close > sma_val and close > mid and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_sma = sma_val
def CreateClone(self):
return contrarian_trade_ma_weekly_strategy()