The Mean Reversion strategy is a direct port of the MetaTrader expert advisor Mean reversion.mq4. The StockSharp version keeps the original trading idea: buy after an extended series of declining closes and sell after a similar bullish run. Entries are confirmed by trend alignment using two linear weighted moving averages, momentum strength on a higher timeframe, and a monthly MACD filter.
Once in position, the strategy recreates the money management rules from the MQL version: configurable stop-loss and take-profit in pips, optional break-even relocation, and a trailing stop that locks in profits as the market moves in the trade's favor.
Trading Logic
Signal timeframe – the strategy operates on the selected candle series (default 15 minutes).
Exhaustion detection – it collects the last BarsToCount closes. A long setup requires the most recent close to be lower than each of the previous closes, signalling a sell-off. A short setup needs the opposite condition.
Trend filter – fast LWMA (length FastMaLength) must be above the slow LWMA (SlowMaLength) for longs and below for shorts.
Momentum filter – the momentum indicator (period MomentumLength) is calculated on the MetaTrader-style higher timeframe (M15 → H1, H1 → D1, etc.). At least one of the last three momentum readings must deviate from 100 by more than MomentumThreshold.
MACD confirmation – a monthly MACD (12/26/9) must have the main line above the signal line for longs and below for shorts.
If every condition is satisfied the strategy opens a position using OrderVolume. Opposite trades flatten the current position before reversing.
Position Management
Stop-loss & take-profit – configured in pips via StopLossPips and TakeProfitPips.
Break-even – when enabled, the stop is moved to the entry price plus BreakEvenOffsetPips after price advances by BreakEvenTriggerPips.
Trailing stop – if EnableTrailing is true and unrealised profit exceeds TrailingStopPips, the stop trails price with step TrailingStepPips.
All price conversions use the instrument pip size to match MetaTrader behaviour.
Parameters
Name
Description
Default
OrderVolume
Order size used for market entries.
1
CandleType
Primary candle series used for signals.
M15
BarsToCount
Number of previous closes checked for exhaustion.
10
FastMaLength
Fast LWMA period.
6
SlowMaLength
Slow LWMA period.
85
MomentumLength
Momentum period on the higher timeframe.
14
MomentumThreshold
Minimum absolute deviation from 100 for momentum confirmation.
0.3
StopLossPips
Stop-loss distance in pips.
20
TakeProfitPips
Take-profit distance in pips.
50
UseBreakEven
Enable stop relocation to break-even.
false
BreakEvenTriggerPips
Profit in pips needed before moving the stop.
30
BreakEvenOffsetPips
Extra pips added when moving to break-even.
30
EnableTrailing
Activate trailing stop management.
true
TrailingStopPips
Profit in pips required to start trailing.
40
TrailingStepPips
Distance maintained by the trailing stop.
40
Notes
The higher timeframe for momentum follows MetaTrader steps: M1→M15, M5→M30, M15→H1, M30→H4, H1→D1, H4→W1, D1→MN1, W1→MN1.
MACD confirmation always uses the monthly timeframe (MN1).
The strategy expects timeframe-based candle types; tick or range candles are not supported.
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>
/// Simplified from "Mean Reversion" MetaTrader expert.
/// Buys after multi-bar sell-off when RSI is oversold, sells after multi-bar rally when RSI is overbought.
/// Uses consecutive bar count for exhaustion detection with RSI confirmation.
/// </summary>
public class MeanReversionMomentumStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _barsToCount;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private RelativeStrengthIndex _rsi;
private readonly List<decimal> _closeHistory = new();
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int BarsToCount
{
get => _barsToCount.Value;
set => _barsToCount.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public MeanReversionMomentumStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
_barsToCount = Param(nameof(BarsToCount), 5)
.SetGreaterThanZero()
.SetDisplay("Bars To Count", "Number of consecutive bars for exhaustion detection", "Signal");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period for confirmation", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetDisplay("RSI Overbought", "RSI level for sell signal", "Signals");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetDisplay("RSI Oversold", "RSI level for buy signal", "Signals");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_closeHistory.Clear();
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
_closeHistory.Add(candle.ClosePrice);
if (_closeHistory.Count > BarsToCount + 1)
_closeHistory.RemoveAt(0);
if (!_rsi.IsFormed || _closeHistory.Count < BarsToCount + 1)
return;
var volume = Volume;
if (volume <= 0)
volume = 1;
// Count consecutive down bars
var downCount = 0;
for (int i = _closeHistory.Count - 1; i >= 1; i--)
{
if (_closeHistory[i] < _closeHistory[i - 1])
downCount++;
else
break;
}
// Count consecutive up bars
var upCount = 0;
for (int i = _closeHistory.Count - 1; i >= 1; i--)
{
if (_closeHistory[i] > _closeHistory[i - 1])
upCount++;
else
break;
}
// Multi-bar sell-off + RSI oversold -> mean reversion buy
if (downCount >= BarsToCount && rsiValue < RsiOversold)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
if (Position <= 0)
BuyMarket(volume);
}
// Multi-bar rally + RSI overbought -> mean reversion sell
else if (upCount >= BarsToCount && rsiValue > RsiOverbought)
{
if (Position > 0)
SellMarket(Position);
if (Position >= 0)
SellMarket(volume);
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_closeHistory.Clear();
_rsi = null;
base.OnReseted();
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class mean_reversion_momentum_strategy(Strategy):
def __init__(self):
super(mean_reversion_momentum_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._bars_to_count = self.Param("BarsToCount", 5)
self._rsi_period = self.Param("RsiPeriod", 14)
self._rsi_overbought = self.Param("RsiOverbought", 70.0)
self._rsi_oversold = self.Param("RsiOversold", 30.0)
self._close_history = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BarsToCount(self):
return self._bars_to_count.Value
@BarsToCount.setter
def BarsToCount(self, value):
self._bars_to_count.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiOverbought(self):
return self._rsi_overbought.Value
@RsiOverbought.setter
def RsiOverbought(self, value):
self._rsi_overbought.Value = value
@property
def RsiOversold(self):
return self._rsi_oversold.Value
@RsiOversold.setter
def RsiOversold(self, value):
self._rsi_oversold.Value = value
def OnReseted(self):
super(mean_reversion_momentum_strategy, self).OnReseted()
self._close_history = []
def OnStarted2(self, time):
super(mean_reversion_momentum_strategy, self).OnStarted2(time)
self._close_history = []
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._process_candle).Start()
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi_val = float(rsi_value)
bars_to_count = self.BarsToCount
self._close_history.append(close)
while len(self._close_history) > bars_to_count + 1:
self._close_history.pop(0)
if len(self._close_history) < bars_to_count + 1:
return
# Count consecutive down bars
down_count = 0
for i in range(len(self._close_history) - 1, 0, -1):
if self._close_history[i] < self._close_history[i - 1]:
down_count += 1
else:
break
# Count consecutive up bars
up_count = 0
for i in range(len(self._close_history) - 1, 0, -1):
if self._close_history[i] > self._close_history[i - 1]:
up_count += 1
else:
break
# Multi-bar sell-off + RSI oversold -> mean reversion buy
if down_count >= bars_to_count and rsi_val < float(self.RsiOversold):
if self.Position <= 0:
self.BuyMarket()
# Multi-bar rally + RSI overbought -> mean reversion sell
elif up_count >= bars_to_count and rsi_val > float(self.RsiOverbought):
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return mean_reversion_momentum_strategy()