This strategy ports the MetaTrader 5 expert advisor Exp_ColorMETRO_MMRec_Duplex to StockSharp. The original robot runs two independent ColorMETRO indicator modules (one long, one short) and applies an MMRec (money management recalculation) overlay that shrinks the position size after repeated losses. The C# version mirrors that behaviour while using StockSharp's high-level API for candle subscriptions and order routing.
Trading Logic
Two distinct ColorMETRO indicators operate on configurable candle types. The long module only manages long exposure while the short module controls short exposure.
Each indicator produces a fast and a slow stepped RSI envelope. The strategy mimics the MQL5 CopyBuffer calls by storing historical values and inspecting the bar defined by SignalBar.
A long entry is generated when the fast band crosses below the slow band on the inspected bar while the previous bar still had the fast band above the slow band. Any open short position is flattened before opening the new long.
Long exits occur when the slow band on the previous inspected bar sits above the fast band, signalling a bearish regime in the original EA.
Short entries and exits mirror the long logic (crossing above for entries, fast line above the slow line on the previous bar for exits).
Only finished candles are processed and trading is blocked until the indicator reports both bands as ready, reproducing the MetaTrader warm-up period.
Money Management (MMRec)
Strategy.Volume defines the reference lot size. The long and short modules multiply it by their respective LongMm/ShortMm coefficients when sizing new orders.
After every completed trade the strategy records whether the result was a loss (based on candle close prices, just like the EA that inspects historical deals).
If the most recent TotalTrigger trades for a module contain at least LossTrigger losers, the module switches to the reduced multiplier (SmallMm). Once the loss count drops below the threshold the default multiplier is restored automatically.
Position reversals first finalise the existing trade's result (updating the MMRec counters) before sizing and opening the opposite direction.
Indicator Notes
ColorMetroMmrecIndicator is a faithful port of the ColorMETRO custom indicator. It feeds the same fast/slow envelopes driven by an RSI core with step tracking and trend memory.
The indicator exposes the internal RSI and a readiness flag so that the strategy can ignore incomplete values exactly as the MQL implementation does.
Parameters
Group
Name
Description
Long
LongCandleType
Candle type used for the long ColorMETRO module.
Long
LongTotalTrigger
Number of completed long trades inspected when evaluating MMRec.
Long
LongLossTrigger
Loss count that activates the reduced long multiplier.
Long
LongSmallMm
Reduced multiplier applied to long trades after a loss streak.
Long
LongMm
Default multiplier for long trades.
Long
LongEnableOpen
Enables opening long positions.
Long
LongEnableClose
Enables closing long positions.
Long
LongPeriodRsi
RSI length used inside the long ColorMETRO indicator.
Long
LongStepSizeFast
Fast envelope step size for the long module.
Long
LongStepSizeSlow
Slow envelope step size for the long module.
Long
LongSignalBar
Historical shift (in closed bars) used when reading indicator values.
Long
LongMagic
Original MT5 magic number, kept for reference.
Long
LongStopLossTicks
Stop-loss distance placeholder from the EA (not enforced).
Long
LongTakeProfitTicks
Take-profit distance placeholder from the EA (not enforced).
Long
LongDeviationTicks
Allowed slippage placeholder from the EA (not enforced).
Long
LongMarginMode
MM mode flag retained for compatibility (logic uses raw multipliers).
Short
ShortCandleType
Candle type used for the short ColorMETRO module.
Short
ShortTotalTrigger
Number of completed short trades inspected when evaluating MMRec.
Short
ShortLossTrigger
Loss count that activates the reduced short multiplier.
Short
ShortSmallMm
Reduced multiplier applied to short trades after a loss streak.
Short
ShortMm
Default multiplier for short trades.
Short
ShortEnableOpen
Enables opening short positions.
Short
ShortEnableClose
Enables closing short positions.
Short
ShortPeriodRsi
RSI length used inside the short ColorMETRO indicator.
Short
ShortStepSizeFast
Fast envelope step size for the short module.
Short
ShortStepSizeSlow
Slow envelope step size for the short module.
Short
ShortSignalBar
Historical shift (in closed bars) used when reading indicator values.
Short
ShortMagic
Original MT5 magic number, kept for reference.
Short
ShortStopLossTicks
Stop-loss distance placeholder from the EA (not enforced).
Short
ShortTakeProfitTicks
Take-profit distance placeholder from the EA (not enforced).
Short
ShortDeviationTicks
Allowed slippage placeholder from the EA (not enforced).
Short
ShortMarginMode
MM mode flag retained for compatibility (logic uses raw multipliers).
Implementation Notes
The strategy relies on SubscribeCandles(...).BindEx(...) and avoids direct buffer access, aligning with the conversion guidelines.
Protective stops from the EA are left as parameters only; users can attach StartProtection or custom risk modules if needed.
Both modules share the same security instance but keep their own candle subscriptions and MMRec counters, matching the duplex layout from MetaTrader.
All in-code comments are provided in English and the logic refrains from using prohibited API calls such as GetTrades.
Disclaimer
This port reproduces the logical structure of the original EA, but execution quality depends on the connected broker, data feed, and StockSharp configuration. Always validate behaviour on historical and demo data before trading live capital.
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>
/// ColorMETRO MMRec Duplex strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExpColorMetroMmrecDuplexStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public ExpColorMetroMmrecDuplexStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_color_metro_mmrec_duplex_strategy(Strategy):
def __init__(self):
super(exp_color_metro_mmrec_duplex_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(exp_color_metro_mmrec_duplex_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(exp_color_metro_mmrec_duplex_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_color_metro_mmrec_duplex_strategy()