ColorMetroDuplexStrategy — это перенос советника MetaTrader 5 Exp_ColorMETRO_Duplex на платформу StockSharp. Оригинальный робот использует два независимых модуля ColorMETRO: первый управляет длинными позициями, второй — короткими. Каждый модуль анализирует свечи своего таймфрейма, получает из индикатора пару ступенчатых RSI-каналов и по их пересечению принимает решение об открытии или закрытии сделок.
В C# версии сохранена структура из двух модулей и реализован отдельный индикатор ColorMetroIndicator, полностью повторяющий алгоритм MT5. Все расчёты выполняются через высокоуровневый API: SubscribeCandles, BindEx, BuyMarket / SellMarket и т.д.
Логика работы
При инициализации создаются два SignalModule — Long и Short.
В методе OnStarted для каждого модуля оформляется подписка на свечи и подключается ColorMetroIndicator.
С каждой закрывшейся свечой индикатор возвращает:
быстрый канал (fast band),
медленный канал (slow band),
внутреннее значение RSI.
Модуль сохраняет историю значений и анализирует две последние точки с учётом смещения SignalBar, как это делалось через CopyBuffer в MT5.
Правила торговли:
Long
Открытие: на предыдущей свече быстрый канал был выше медленного, на текущей (с учётом SignalBar) — опустился ниже либо сравнялся.
Закрытие: на предыдущей свече медленный канал находился выше быстрого.
Short
Открытие: быстрый канал был ниже медленного и перешёл выше либо сравнялся.
Закрытие: медленный канал был ниже быстрого.
При поступлении сигнала стратегия проверяет текущий нетто-позиционный объём. В случае смены направления сначала закрывается противоположная позиция, затем открывается новая.
Параметры
Параметры сгруппированы по модулям; значения по умолчанию соответствуют исходному советнику.
Торговые настройки
Long_Volume, Short_Volume — объём сделки (лоты).
Long_OpenAllowed, Short_OpenAllowed — разрешение на открытие позиций соответствующим модулем.
Long_CloseAllowed, Short_CloseAllowed — разрешение на автоматическое закрытие.
Long_MarginMode, Short_MarginMode — режим управления капиталом (оставлен для совместимости, не используется).
Long_StopLoss, Long_TakeProfit, Long_Deviation, а также зеркальные параметры для Short — информационные поля, в портированной версии стоп-заявки автоматически не выставляются.
Long_Magic, Short_Magic — оригинальные магические номера MT5.
Настройки индикатора
Long_CandleType, Short_CandleType — таймфреймы свечей для каждого модуля.
Long_PeriodRSI, Short_PeriodRSI — период RSI внутри ColorMETRO.
Long_StepSizeFast, Short_StepSizeFast — шаг быстрого канала (в пунктах RSI).
Long_SignalBar, Short_SignalBar — смещение, указывающее какую свечу использовать для сигналов.
Long_AppliedPrice, Short_AppliedPrice — тип цены для расчёта RSI (по умолчанию close).
Отличия от версии MT5
Нетто-позиция. В MT5 модули управляли собственными позициями по магическим номерам. StockSharp оперирует совокупной позицией по инструменту, поэтому при смене направления происходит полное закрытие и затем открытие в противоположную сторону.
Управление риском. Режимы MarginMode и значения стопов сохранены как параметры, но не влияют на размер заявки — его задаёт Volume.
Стоп-приказы. В оригинале ордеры сопровождались stop-loss и take-profit. Здесь расстояния указаны только для справки; при необходимости их можно задать вручную в коде.
Контроль времени. MT5 использовал глобальные переменные, чтобы не открывать повторные позиции в пределах одного уровня времени. В StockSharp обработка выполняется один раз на закрытии свечи, а фильтрацию дубликатов обеспечивает проверка текущей позиции.
Дополнительные замечания
ColorMetroIndicator реализует шаговые RSI-каналы и хранит предыдущее состояние тренда, что важно для получения идентичных сигналов с MT5.
В коде присутствуют подробные комментарии на английском языке, поясняющие ключевые решения при портировании.
Для добавления стопов, трейлинг-логики или альтернативного мани-менеджмента можно расширить методы ProcessModule и SignalModule.
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;
}
}
}