ColorMetroDuplexStrategy is a C# conversion of the MetaTrader 5 expert Exp_ColorMETRO_Duplex. The original robot uses two independent instances of the ColorMETRO indicator to manage long and short trading modules. Each module operates on its own candle subscription, evaluates two stepped RSI envelopes produced by the ColorMETRO indicator, and optionally opens or closes positions when the fast and slow envelopes cross.
The StockSharp version keeps both modules and reproduces the same signal evaluation rules while using the high-level API for candle subscriptions, order management and indicator binding. A custom ColorMetroIndicator is included to mimic the MT5 iCustom implementation, exposing the fast and slow ColorMETRO bands together with the inner RSI value.
How it works
Two SignalModule instances are created — Long and Short — each with its own candle series, ColorMETRO settings and trade management options.
When the strategy starts, every module subscribes to its configured timeframe and binds the ColorMetroIndicator through SubscribeCandles(...).BindEx(...).
For every finished candle the indicator produces:
The fast ColorMETRO band (fast RSI envelope).
The slow ColorMETRO band (slow RSI envelope).
The underlying RSI value (used for reference only).
The module stores the indicator history and evaluates the last two values using the configured SignalBar shift (matching the CopyBuffer logic from MT5).
Trading rules:
Long module
Open: the fast band was above the slow band on the previous bar and is now below or equal to it.
Close: the slow band was above the fast band on the previous bar.
Short module
Open: the fast band was below the slow band on the previous bar and is now above or equal to it.
Close: the slow band was below the fast band on the previous bar.
Orders are routed via BuyMarket / SellMarket. The current net position is respected — opposite trades flatten the existing exposure before opening a new one.
Parameters
Each module exposes a dedicated parameter group. Defaults mirror the MT5 expert.
Shared market parameters
Long_Volume, Short_Volume — trade size (lots) used for new entries.
Long_OpenAllowed, Short_OpenAllowed — enable or disable opening trades for the module.
Long_CloseAllowed, Short_CloseAllowed — enable or disable automatic exits.
Long_MarginMode, Short_MarginMode — money-management mode kept for compatibility (no effect in this port).
Long_StopLoss, Long_TakeProfit, Long_Deviation, Short_StopLoss, Short_TakeProfit, Short_Deviation — reserved for documentation; stops and slippage control are not automated in this version.
Long_Magic, Short_Magic — original MT5 magic numbers preserved for reference.
Indicator parameters
Long_CandleType, Short_CandleType — timeframe for each ColorMETRO module.
Long_PeriodRSI, Short_PeriodRSI — RSI length used inside the ColorMETRO algorithm.
Long_StepSizeFast, Short_StepSizeFast — step (in RSI points) for the fast envelope.
Long_StepSizeSlow, Short_StepSizeSlow — step for the slow envelope.
Long_SignalBar, Short_SignalBar — bar shift used when reading the indicator buffers (identical to the MT5 SignalBar input).
Long_AppliedPrice, Short_AppliedPrice — price source for RSI calculation (close price by default).
Differences compared to MT5
Position model — StockSharp strategies work with the net position. The original expert stored separate positions via magic numbers; the port flattens the current exposure before opening the opposite side.
Money management — margin modes and deviation settings are preserved as parameters but not applied automatically. Use the Volume inputs to control size.
Stop-loss / take-profit — the MT5 expert placed protective stops with each order. The StockSharp version keeps the distances as parameters for reference, but actual stop orders must be implemented separately if needed.
Time level control — the MT5 code used global variables to ensure only one trade per signal time. In StockSharp we process each finished candle once and rely on the net position check to prevent duplicate entries.
Notes
The custom ColorMetroIndicator reproduces the MT5 logic, including the stepped RSI envelopes and trend memory. It exposes the fast/slow bands and the internal RSI for charting or debugging.
Commentary within the code is intentionally verbose to clarify the porting decisions and assist with further customization.
To enable stop-loss or take-profit automation, extend SignalModule.ProcessModule to place protective orders using StockSharp's risk controls.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Conversion of the MT5 expert "Exp_ColorMETRO_Duplex".
/// Uses RSI with step-based envelopes (fast/slow) to generate long/short signals.
/// </summary>
public class ColorMetroDuplexStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _fastStep;
private readonly StrategyParam<int> _slowStep;
private readonly StrategyParam<int> _signalCooldownBars;
// fast envelope state
private decimal? _fastMin, _fastMax;
private int _fastTrend;
private decimal? _prevFastBand;
// slow envelope state
private decimal? _slowMin, _slowMax;
private int _slowTrend;
private decimal? _prevSlowBand;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int FastStep { get => _fastStep.Value; set => _fastStep.Value = value; }
public int SlowStep { get => _slowStep.Value; set => _slowStep.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public ColorMetroDuplexStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI lookback", "Indicator");
_fastStep = Param(nameof(FastStep), 8)
.SetGreaterThanZero()
.SetDisplay("Fast Step", "Step size for fast envelope", "Indicator");
_slowStep = Param(nameof(SlowStep), 24)
.SetGreaterThanZero()
.SetDisplay("Slow Step", "Step size for slow envelope", "Indicator");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMin = _fastMax = null;
_slowMin = _slowMax = null;
_fastTrend = _slowTrend = 0;
_prevFastBand = _prevSlowBand = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMin = _fastMax = null;
_slowMin = _slowMax = null;
_fastTrend = _slowTrend = 0;
_prevFastBand = _prevSlowBand = null;
_cooldownRemaining = 0;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var fStep = (decimal)FastStep;
var sStep = (decimal)SlowStep;
// fast envelope
var fastMinCand = rsiVal - 2m * fStep;
var fastMaxCand = rsiVal + 2m * fStep;
if (_fastMin == null || _fastMax == null)
{
_fastMin = fastMinCand;
_fastMax = fastMaxCand;
_fastTrend = 0;
_slowMin = rsiVal - 2m * sStep;
_slowMax = rsiVal + 2m * sStep;
_slowTrend = 0;
return;
}
// fast trend
if (rsiVal > _fastMax) _fastTrend = 1;
else if (rsiVal < _fastMin) _fastTrend = -1;
if (_fastTrend > 0 && fastMinCand < _fastMin) fastMinCand = _fastMin.Value;
else if (_fastTrend < 0 && fastMaxCand > _fastMax) fastMaxCand = _fastMax.Value;
// slow envelope
var slowMinCand = rsiVal - 2m * sStep;
var slowMaxCand = rsiVal + 2m * sStep;
if (rsiVal > _slowMax) _slowTrend = 1;
else if (rsiVal < _slowMin) _slowTrend = -1;
if (_slowTrend > 0 && slowMinCand < _slowMin) slowMinCand = _slowMin.Value;
else if (_slowTrend < 0 && slowMaxCand > _slowMax) slowMaxCand = _slowMax.Value;
// compute band values
decimal? fastBand = null;
if (_fastTrend > 0) fastBand = fastMinCand + fStep;
else if (_fastTrend < 0) fastBand = fastMaxCand - fStep;
decimal? slowBand = null;
if (_slowTrend > 0) slowBand = slowMinCand + sStep;
else if (_slowTrend < 0) slowBand = slowMaxCand - sStep;
_fastMin = fastMinCand;
_fastMax = fastMaxCand;
_slowMin = slowMinCand;
_slowMax = slowMaxCand;
if (fastBand == null || slowBand == null)
{
_prevFastBand = fastBand;
_prevSlowBand = slowBand;
return;
}
if (_prevFastBand == null || _prevSlowBand == null)
{
_prevFastBand = fastBand;
_prevSlowBand = slowBand;
return;
}
var up = fastBand.Value;
var down = slowBand.Value;
var prevUp = _prevFastBand.Value;
var prevDown = _prevSlowBand.Value;
_prevFastBand = fastBand;
_prevSlowBand = slowBand;
// Long signal: fast crosses below slow (up crosses down downward)
var longOpen = prevUp > prevDown && up <= down;
// Short signal: fast crosses above slow (up crosses down upward)
var shortOpen = prevUp < prevDown && up >= down;
if (_cooldownRemaining == 0 && longOpen && Position == 0)
{
BuyMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && shortOpen && Position == 0)
{
SellMarket();
_cooldownRemaining = SignalCooldownBars;
}
}
}