Стратегия переносит на StockSharp советника MetaTrader 5 Exp_ColorMETRO_MMRec_Duplex. Оригинальный робот использует две независимые подсистемы индикатора ColorMETRO (для длин и для коротких позиций) и накладывает модуль MMRec, уменьшающий объём сделок после серии убыточных результатов. Перенос воспроизводит эту структуру и задействует высокоуровневый API StockSharp для подписки на свечи и выставления заявок.
Логика торговли
Для длинной и короткой стороны создаются отдельные экземпляры ColorMETRO, каждый работает на собственном типе свечей и управляет только своей стороной рынка.
Индикатор формирует пару ступенчатых RSI-каналов (быстрый и медленный). В коде хранится история значений и анализируется бар, задаваемый параметром SignalBar, что повторяет вызовы CopyBuffer из MQL5.
Лонг открывается, когда быстрый канал на выбранном баре пересекает медленный сверху вниз, а на предыдущем баре быстрый канал был выше медленного. Перед открытием новая позиция закрывает существующий шорт.
Закрытие длинной позиции происходит, если на предыдущем анализируемом баре медленный канал находился выше быстрого, что в оригинале считается сигналом к выходу.
Для шортов применяется зеркальная логика (пересечение снизу вверх для входа и быстрый канал выше медленного на предыдущем баре для выхода).
Обрабатываются только завершённые свечи; торговля начинается после того, как индикатор сообщит о готовности обоих каналов, тем самым воспроизводится прогрев из MetaTrader.
Управление капиталом (MMRec)
Свойство Strategy.Volume задаёт базовый объём. Модули умножают его на коэффициенты LongMm/ShortMm при расчёте заявок.
После каждой закрытой сделки стратегия фиксирует, была ли она убыточной (по ценам закрытия свечей, как и в MQL-версии, анализирующей историю сделок).
Если среди последних TotalTrigger сделок выбранного модуля накопилось не меньше LossTrigger убыточных, включается пониженный множитель SmallMm. При уменьшении числа убыточных сделок модуль автоматически возвращается к основному множителю.
При развороте позиции сначала фиксируется результат предыдущей сделки (обновляется счётчик MMRec), затем рассчитывается объём и открывается позиция в противоположную сторону.
Особенности индикатора
ColorMetroMmrecIndicator — это порт кастомного индикатора ColorMETRO с теми же ступенчатыми каналами и памятью тренда на основе RSI.
Индикатор публикует внутреннее значение RSI и флаг готовности, что позволяет стратегии игнорировать неполные данные точно так же, как делает оригинальный код.
Параметры
Группа
Имя
Описание
Long
LongCandleType
Тип свечей для длинного модуля ColorMETRO.
Long
LongTotalTrigger
Количество последних длинных сделок, анализируемых MMRec.
Long
LongLossTrigger
Число убыточных сделок, при котором включается пониженный множитель для лонгов.
Long
LongSmallMm
Уменьшенный множитель объёма после серии убыточных длинных сделок.
Long
LongMm
Базовый множитель объёма для длинных сделок.
Long
LongEnableOpen
Разрешение на открытие длинных позиций.
Long
LongEnableClose
Разрешение на закрытие длинных позиций.
Long
LongPeriodRsi
Период RSI внутри длинного ColorMETRO.
Long
LongStepSizeFast
Шаг быстрого канала длинного модуля.
Long
LongStepSizeSlow
Шаг медленного канала длинного модуля.
Long
LongSignalBar
Смещение по истории (в барах) при чтении значений индикатора.
Long
LongMagic
Исходный MT5 magic number (для совместимости).
Long
LongStopLossTicks
Заглушка дистанции стоп-лосса из EA (не используется).
Long
LongTakeProfitTicks
Заглушка дистанции тейк-профита из EA (не используется).
Long
LongDeviationTicks
Заглушка максимально допустимого проскальзывания (не используется).
Long
LongMarginMode
Режим MM из оригинала, сохранён в информационных целях.
Short
ShortCandleType
Тип свечей для короткого модуля ColorMETRO.
Short
ShortTotalTrigger
Количество последних коротких сделок, анализируемых MMRec.
Short
ShortLossTrigger
Число убыточных сделок, при котором включается пониженный множитель для шортов.
Short
ShortSmallMm
Уменьшенный множитель объёма после серии убыточных коротких сделок.
Short
ShortMm
Базовый множитель объёма для коротких сделок.
Short
ShortEnableOpen
Разрешение на открытие коротких позиций.
Short
ShortEnableClose
Разрешение на закрытие коротких позиций.
Short
ShortPeriodRsi
Период RSI внутри короткого ColorMETRO.
Short
ShortStepSizeFast
Шаг быстрого канала короткого модуля.
Short
ShortStepSizeSlow
Шаг медленного канала короткого модуля.
Short
ShortSignalBar
Смещение по истории при чтении значений индикатора.
Short
ShortMagic
Исходный MT5 magic number (для совместимости).
Short
ShortStopLossTicks
Заглушка дистанции стоп-лосса из EA (не используется).
Short
ShortTakeProfitTicks
Заглушка дистанции тейк-профита из EA (не используется).
Short
ShortDeviationTicks
Заглушка максимально допустимого проскальзывания (не используется).
Short
ShortMarginMode
Режим MM из оригинала, сохранён в информационных целях.
Особенности реализации
Используются подписки SubscribeCandles(...).BindEx(...), прямого доступа к буферам индикатора нет.
Параметры стоп-лосса/тейк-профита оставлены для совместимости; фактическая защита может быть реализована через StartProtection или пользовательские блоки.
Оба модуля работают с одним инструментом, но ведут собственные подписки на свечи и независимые счётчики MMRec, что повторяет структуру «дуplex» из MetaTrader.
Все комментарии в коде написаны на английском языке, а запрещённые API (GetTrades и т.п.) не используются.
Дисклеймер
Перенос воспроизводит логику оригинального советника, однако результаты зависят от провайдера данных, брокера и настроек StockSharp. Перед реальной торговлей протестируйте стратегию на истории и демо-счёте.
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()