This strategy is a C# conversion of the MetaTrader expert Exp_X2MACandle_MMRec. It observes the colour of a doubly smoothed candle, produced by the original X2MA custom indicator, to decide when to open or close positions. The StockSharp version recreates the dual smoothing pipeline and keeps a lightweight money management layer that cuts the trading volume after a configurable number of recent losses.
The algorithm processes completed candles only. It subscribes to a configurable timeframe, applies two chained moving averages to the candle OHLC values, derives a synthetic candle colour (green, grey or red) and uses colour transitions with a user-selectable bar shift to trigger actions. Long trades are opened when the colour changes from bullish to anything else. Short trades follow the symmetric condition. Position exits are aligned with the same colour checks and can be enabled or disabled separately for each side.
Indicator logic
Each candle is smoothed twice. Both stages may use different methods and lengths.
Smoothing options map to StockSharp indicators:
Simple → SimpleMovingAverage
Exponential → ExponentialMovingAverage
Smoothed → SmoothedMovingAverage (RMA)
Weighted → WeightedMovingAverage
Jurik → JurikMovingAverage (Phase parameter is honoured when available).
The synthetic candle body is flattened whenever the absolute open/close difference is below GapPoints * Security.StepPrice.
Colours are assigned as follows: open < close → 2 (bullish), open > close → 0 (bearish), otherwise → 1 (neutral).
Signals are evaluated on bar SignalBar + 1 (two bars back with the default setting) so that orders are submitted only after a full candle confirms the colour change.
Money management
The original expert dynamically reduced the position size after a series of losses using historical deal statistics. StockSharp does not expose the exact MetaTrader history, so the port keeps an internal queue of recent closed trades.
The queue length is controlled by HistoryDepth and the volume falls back to ReducedVolume once LossTrigger or more losses are detected inside the window.
The strategy records trade outcomes using candle close prices when a manual exit is triggered. Stop-loss/take-profit orders from the MetaTrader version are not recreated. You can add your own protection rules through StockSharp's risk managers if required.
Parameters
Name
Description
CandleType
Timeframe of the candles used for smoothing and trading.
FirstMethod, FirstLength, FirstPhase
Primary smoothing method, length and Jurik phase.
SecondMethod, SecondLength, SecondPhase
Secondary smoothing method, length and Jurik phase.
GapPoints
Body flattening threshold in price steps.
SignalBar
Shift (0 = latest finished candle) used when reading the colour buffers.
AllowLongEntry / AllowShortEntry
Enable opening long or short positions.
AllowLongExit / AllowShortExit
Enable closing long or short positions.
NormalVolume
Standard order size (lots, shares, contracts).
ReducedVolume
Order size used after the configured number of losses.
HistoryDepth
Number of recent trades inspected for losses (0 disables history tracking).
LossTrigger
Loss count that activates the reduced volume (0 disables the switch).
Usage notes
The strategy operates on a single security returned by GetWorkingSecurities().
Signals and exits are processed once per finished candle to avoid duplicate orders.
Set ReducedVolume equal to NormalVolume if you want to disable the volume reduction while keeping the history statistics.
Because the port relies on candle close prices to classify trades, the loss counter may differ slightly from MetaTrader when slippage or partial fills occur. The documentation should help you adjust parameters to achieve similar behaviour.
Stops and take profits from the MQL version are not recreated automatically. Use StockSharp risk managers (StartProtection) if you need platform-level protection.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class ExpX2MaCandleMmRecStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast, _prevSlow;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public ExpX2MaCandleMmRecStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 8).SetGreaterThanZero().SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 25).SetGreaterThanZero().SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null; _prevSlow = null;
var fast = new WeightedMovingAverage { Length = FastPeriod };
var slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast; _prevSlow = slow;
if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_x2_ma_candle_mm_rec_strategy(Strategy):
def __init__(self):
super(exp_x2_ma_candle_mm_rec_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 8) \
.SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 25) \
.SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(exp_x2_ma_candle_mm_rec_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(exp_x2_ma_candle_mm_rec_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = WeightedMovingAverage()
fast.Length = self.FastPeriod
slow = WeightedMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_above and not curr_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return exp_x2_ma_candle_mm_rec_strategy()