Ver no GitHub

Mean Reversion Momentum Strategy

Overview

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

  1. Signal timeframe – the strategy operates on the selected candle series (default 15 minutes).
  2. 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.
  3. Trend filter – fast LWMA (length FastMaLength) must be above the slow LWMA (SlowMaLength) for longs and below for shorts.
  4. 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.
  5. 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();
	}
}