Ver en GitHub

Estrategia ColorMetroDuplexStrategy

Descripción general

ColorMetroDuplexStrategy es una conversión en C# del experto de MetaTrader 5 Exp_ColorMETRO_Duplex. El robot original utiliza dos instancias independientes del indicador ColorMETRO para gestionar los módulos de trading largo y corto. Cada módulo opera con su propia suscripción de velas, evalúa dos envoltorios RSI escalonados producidos por el indicador ColorMETRO, y opcionalmente abre o cierra posiciones cuando los envoltorios rápido y lento se cruzan.

La versión de StockSharp mantiene ambos módulos y reproduce las mismas reglas de evaluación de señales, utilizando la API de alto nivel para suscripciones de velas, gestión de órdenes y vinculación de indicadores. Se incluye un ColorMetroIndicator personalizado para imitar la implementación de iCustom de MT5, exponiendo las bandas rápida y lenta de ColorMETRO junto con el valor RSI interno.

Cómo funciona

  1. Se crean dos instancias de SignalModuleLong y Short — cada una con su propia serie de velas, configuración de ColorMETRO y opciones de gestión de operaciones.
  2. Cuando la estrategia se inicia, cada módulo se suscribe a su marco temporal configurado y vincula el ColorMetroIndicator mediante SubscribeCandles(...).BindEx(...).
  3. Para cada vela finalizada el indicador produce:
    • La banda rápida de ColorMETRO (envoltorio RSI rápido).
    • La banda lenta de ColorMETRO (envoltorio RSI lento).
    • El valor RSI subyacente (usado solo como referencia).
  4. El módulo almacena el historial del indicador y evalúa los últimos dos valores usando el desplazamiento SignalBar configurado (correspondiendo a la lógica de CopyBuffer de MT5).
  5. Reglas de trading:
    • Módulo largo
      • Abrir: la banda rápida estaba por encima de la banda lenta en la barra anterior y ahora está por debajo o igual.
      • Cerrar: la banda lenta estaba por encima de la banda rápida en la barra anterior.
    • Módulo corto
      • Abrir: la banda rápida estaba por debajo de la banda lenta en la barra anterior y ahora está por encima o igual.
      • Cerrar: la banda lenta estaba por debajo de la banda rápida en la barra anterior.
  6. Las órdenes se enrutan mediante BuyMarket / SellMarket. Se respeta la posición neta actual — las operaciones opuestas cierran la exposición existente antes de abrir una nueva.

Parámetros

Cada módulo expone un grupo de parámetros dedicado. Los valores predeterminados reflejan el experto de MT5.

Parámetros de mercado compartidos

  • Long_Volume, Short_Volume — tamaño de operación (lotes) usado para nuevas entradas.
  • Long_OpenAllowed, Short_OpenAllowed — habilitar o deshabilitar la apertura de operaciones para el módulo.
  • Long_CloseAllowed, Short_CloseAllowed — habilitar o deshabilitar las salidas automáticas.
  • Long_MarginMode, Short_MarginMode — modo de gestión de dinero mantenido para compatibilidad (sin efecto en este port).
  • Long_StopLoss, Long_TakeProfit, Long_Deviation, Short_StopLoss, Short_TakeProfit, Short_Deviation — reservados para documentación; los stops y el control de slippage no están automatizados en esta versión.
  • Long_Magic, Short_Magic — números mágicos originales de MT5 preservados como referencia.

Parámetros del indicador

  • Long_CandleType, Short_CandleType — marco temporal para cada módulo ColorMETRO.
  • Long_PeriodRSI, Short_PeriodRSI — longitud RSI usada dentro del algoritmo ColorMETRO.
  • Long_StepSizeFast, Short_StepSizeFast — paso (en puntos RSI) para el envoltorio rápido.
  • Long_StepSizeSlow, Short_StepSizeSlow — paso para el envoltorio lento.
  • Long_SignalBar, Short_SignalBar — desplazamiento de barra usado al leer los búferes del indicador (idéntico a la entrada SignalBar de MT5).
  • Long_AppliedPrice, Short_AppliedPrice — fuente de precio para el cálculo RSI (precio de cierre por defecto).

Diferencias respecto a MT5

  • Modelo de posición — las estrategias de StockSharp trabajan con la posición neta. El experto original almacenaba posiciones separadas mediante números mágicos; el port cierra la exposición actual antes de abrir el lado opuesto.
  • Gestión de dinero — los modos de margen y la configuración de desviación se conservan como parámetros pero no se aplican automáticamente. Use las entradas de Volume para controlar el tamaño.
  • Stop-loss / take-profit — el experto MT5 colocaba stops de protección con cada orden. La versión de StockSharp mantiene las distancias como parámetros de referencia, pero las órdenes de stop reales deben implementarse por separado si es necesario.
  • Control de nivel de tiempo — el código MT5 usaba variables globales para asegurar solo una operación por tiempo de señal. En StockSharp procesamos cada vela finalizada una vez y nos apoyamos en la verificación de posición neta para prevenir entradas duplicadas.

Notas

  • El ColorMetroIndicator personalizado reproduce la lógica de MT5, incluyendo los envoltorios RSI escalonados y la memoria de tendencia. Expone las bandas rápida/lenta y el RSI interno para gráficos o depuración.
  • Los comentarios dentro del código son intencionalmente detallados para aclarar las decisiones de portado y asistir con mayor personalización.
  • Para habilitar la automatización de stop-loss o take-profit, extienda SignalModule.ProcessModule para colocar órdenes protectoras usando los controles de riesgo de StockSharp.
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;
		}
	}
}