The MA Reverse Strategy is a StockSharp conversion of the simple MetaTrader 4 expert advisor "MA_Reverse". The original robot
monitors how long the bid price stays above or below a 14-period simple moving average (SMA). After a long enough streak in one
direction, it opens a position betting on a short-term reversion. The StockSharp port keeps the same idea by counting the number
of consecutive finished candles closing above or below the SMA and executing a market order as soon as the configured threshold is
reached.
Trading logic
Subscribe to candles of the selected timeframe and calculate a simple moving average with the period defined by SmaPeriod.
Maintain an integer counter (StreakThreshold controls the target length) that increments while the candle close remains above
the moving average and decrements while the close stays below it. Touching the moving average resets the counter.
Once the counter hits StreakThreshold and the close is at least MinimumDeviation above the SMA, the strategy sells with a
market order. The assumption is that a prolonged bullish excursion from the moving average is likely to mean-revert.
When the counter reaches -StreakThreshold and the close is at least MinimumDeviation below the SMA, the logic mirrors the
behaviour and opens a long position.
After every trade the counter keeps its running value, just like the source EA, so that it can immediately start measuring the
next streak.
Order management
Market entries use the TradeVolume parameter. If there is an opposite position on the book, the strategy first closes it and
then opens the new trade in a single market order so that reversals match the MetaTrader behaviour.
A global take-profit is configured through StockSharp's StartProtection helper. The distance equals TakeProfitPoints
multiplied by the security price step, reproducing the "30 * Point" profit target from the MQL code. When the target is hit the
position is closed with a market order.
No stop-loss is implemented in the original expert and therefore none is added in the port. Risk control is entirely handled by
the take-profit and by the user's money management settings.
Parameters
Parameter
Description
TradeVolume
Lot size used for every market entry. The value is also used to size reversals when switching direction.
SmaPeriod
Number of candles used by the simple moving average. The default matches the 14-period moving average of the EA.
StreakThreshold
Number of consecutive closes that must stay on one side of the SMA before a reversal order is allowed.
MinimumDeviation
Minimum absolute distance between the close and the SMA that confirms the breakout condition.
TakeProfitPoints
Take-profit distance expressed in price steps. It is multiplied by the instrument's PriceStep to obtain the absolute price offset.
CandleType
Candle type (timeframe) used to calculate the SMA and evaluate the streak counters.
Notes
The counter logic works with finished candles provided by SubscribeCandles, which makes the implementation robust and
compatible with historical testing. The behaviour matches the tick-based MetaTrader version as long as the candles are fine
grained enough to capture short-term excursions.
Because StockSharp aggregates positions by default, multiple consecutive entries are managed as a single position with a single
floating take-profit distance. This is equivalent to having MetaTrader place the same take-profit on every order because the
distance from the current average entry price stays constant.
The strategy does not add its own indicator to Strategy.Indicators because the binding infrastructure manages indicator
lifetime automatically.
Always validate the price step and volume settings for your specific broker symbols so that the TakeProfitPoints parameter
produces the desired absolute target size.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving average reversal strategy converted from the MetaTrader MA_Reverse expert advisor.
/// Counts how many consecutive closes remain on one side of the SMA and opens a trade once the streak is long enough.
/// </summary>
public class MaReverseStrategy : Strategy
{
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _streakThreshold;
private readonly StrategyParam<decimal> _minimumDeviation;
private readonly StrategyParam<DataType> _candleType;
private int _streak;
public MaReverseStrategy()
{
_smaPeriod = Param(nameof(SmaPeriod), 14)
.SetDisplay("SMA Period", "Number of candles used by the moving average.", "Indicator");
_streakThreshold = Param(nameof(StreakThreshold), 3)
.SetDisplay("Streak Threshold", "Number of consecutive closes required before reversing.", "Logic");
_minimumDeviation = Param(nameof(MinimumDeviation), 0.0001m)
.SetDisplay("Minimum Deviation", "Minimum distance between price and SMA to confirm the reversal.", "Logic");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for SMA calculation.", "General");
}
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
public int StreakThreshold
{
get => _streakThreshold.Value;
set => _streakThreshold.Value = value;
}
public decimal MinimumDeviation
{
get => _minimumDeviation.Value;
set => _minimumDeviation.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_streak = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_streak = 0;
var sma = new SimpleMovingAverage { Length = SmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var closePrice = candle.ClosePrice;
var deviation = closePrice - smaValue;
if (deviation == 0m)
{
_streak = 0;
return;
}
if (deviation > 0m)
{
// Price above SMA
if (_streak < 0)
_streak = 0;
_streak++;
if (_streak >= StreakThreshold && deviation > MinimumDeviation)
{
// Long streak above SMA => sell (reversal)
if (Position >= 0)
{
SellMarket();
_streak = 0;
}
}
}
else
{
// Price below SMA
if (_streak > 0)
_streak = 0;
_streak--;
if (-_streak >= StreakThreshold && -deviation > MinimumDeviation)
{
// Long streak below SMA => buy (reversal)
if (Position <= 0)
{
BuyMarket();
_streak = 0;
}
}
}
}
}
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 ma_reverse_strategy(Strategy):
"""
MA reversal: counts consecutive closes on one side of SMA.
After streak threshold, opens a reversal trade.
"""
def __init__(self):
super(ma_reverse_strategy, self).__init__()
self._sma_period = self.Param("SmaPeriod", 14).SetDisplay("SMA Period", "SMA period", "Indicators")
self._streak_threshold = self.Param("StreakThreshold", 3).SetDisplay("Streak", "Consecutive closes needed", "Logic")
self._min_deviation = self.Param("MinDeviation", 0.0001).SetDisplay("Min Deviation", "Min distance from SMA", "Logic")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._streak = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_reverse_strategy, self).OnReseted()
self._streak = 0
def OnStarted2(self, time):
super(ma_reverse_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._sma_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, sma_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sma = float(sma_val)
deviation = close - sma
if deviation == 0:
self._streak = 0
return
if deviation > 0:
if self._streak < 0:
self._streak = 0
self._streak += 1
if self._streak >= self._streak_threshold.Value and deviation > self._min_deviation.Value:
if self.Position >= 0:
self.SellMarket()
self._streak = 0
else:
if self._streak > 0:
self._streak = 0
self._streak -= 1
if -self._streak >= self._streak_threshold.Value and -deviation > self._min_deviation.Value:
if self.Position <= 0:
self.BuyMarket()
self._streak = 0
def CreateClone(self):
return ma_reverse_strategy()