Ver en GitHub

Estrategia Color XMUV con Filtro de Tiempo

Esta estrategia porta el asesor experto de MetaTrader Exp_ColorXMUV_Tm a StockSharp. Recrea la línea suavizada Color XMUV original y el filtro de ventana de tiempo mientras usa la API de trading de alto nivel de StockSharp. La estrategia sigue el color de la línea suavizada: una transición a verde azulado (subiendo) activa la gestión de largos mientras una transición a magenta (bajando) impulsa la gestión de cortos.

Lógica central

  • Para cada candle finalizado la estrategia construye un precio compuesto similar a la versión MQL ((H + Close)/2 en barras alcistas, (L + Close)/2 en barras bajistas, o Close para barras doji).
  • El precio compuesto pasa por el método de suavizado solicitado. Los métodos comunes (SMA, EMA, SMMA/RMA, LWMA y Jurik) están implementados con indicadores StockSharp. Las opciones exóticas como T3 o VIDYA recurren a una EMA porque StockSharp no expone equivalentes directos. El parámetro phase se mantiene para compatibilidad de configuración incluso cuando el indicador subyacente lo ignora.
  • El "color" de Color XMUV se reconstruye comparando el último valor suavizado con el anterior. Las pendientes ascendentes se asignan a color alcista, las pendientes descendentes a color bajista y los valores sin cambio a color neutral.
  • SignalBar define cuántas barras completamente terminadas mirar atrás al evaluar una señal (por ejemplo, el valor predeterminado de 1 significa que la lógica espera confirmación en la barra anterior a la más reciente).
  • Un flip alcista (color anterior no alcista, color actual alcista) cierra cualquier posición corta y opcionalmente abre o agrega a una posición larga. Un flip bajista realiza las acciones simétricas para operaciones cortas.
  • El filtro de tiempo imita el EA original: fuera de la ventana de trading la estrategia cierra inmediatamente las posiciones existentes e ignora nuevas entradas. El filtro soporta sesiones nocturnas (hora de inicio posterior a la hora de fin).
  • StopLossPoints y TakeProfitPoints se traducen en distancias absolutas usando el paso de precio del instrumento y se registran con StartProtection para que StockSharp gestione las salidas del lado del servidor donde sea posible.

Gestión del riesgo y las posiciones

  • Las órdenes se dimensionan con el parámetro OrderVolume. Al invertir la dirección la estrategia agrega el valor absoluto de la posición actual para que la reversión cierre la operación anterior y abra una nueva en una sola transacción.
  • El stop-loss y take-profit opcionales se convierten de valores de puntos a distancias de precio absolutas. Ponga cualquier parámetro en cero para deshabilitar la respectiva capa de protección.
  • Las salidas de posiciones activadas por el flip de color respetan los interruptores EnableBuyExits y EnableSellExits, permitiendo control independiente de la gestión de largos y cortos.

Parámetros

  • Candle Type – Serie de candles usada para cálculos (predeterminado candles de 4 horas).
  • Order Volume – Tamaño base de la orden de mercado.
  • Enable Long Entries / Enable Short Entries – Permitir apertura de posiciones en flips alcistas/bajistas.
  • Close Longs / Close Shorts – Habilitar salidas automáticas en transiciones de color opuesto.
  • Use Time Filter – Restringir trading a la sesión configurada.
  • Start Hour / Start Minute / End Hour / End Minute – Límites de la sesión de trading. Cuando el inicio es posterior al fin, la sesión se extiende a través de la medianoche.
  • Smoothing Method – Algoritmo de media móvil para la línea Color XMUV. Las opciones sin implementación nativa en StockSharp son reemplazadas por EMA y están documentadas arriba.
  • Length – Longitud de suavizado (debe ser positivo).
  • Phase – Parámetro de phase auxiliar retenido para compatibilidad de configuración.
  • Signal Bar – Número de barras completadas para retrasar la verificación de señal. Poner en cero para actuar sobre la barra cerrada más reciente.
  • Stop Loss (pts) / Take Profit (pts) – Offsets expresados en puntos de precio; cero deshabilita la respectiva capa.

Notas

  • El expert MQL se basaba en bibliotecas de suavizado externas. Cuando dichos modos de suavizado no están disponibles en StockSharp (ParMA, VIDYA, T3) la implementación sustituye una EMA. Documente estas alternativas cuando comparta la estrategia con usuarios.
  • La estrategia almacena solo el historial mínimo de color requerido por SignalBar, cumpliendo con la directriz del repositorio que desaconseja construir cachés de datos personalizados.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend-following strategy that replicates the Color XMUV expert advisor with a trading session filter.
/// </summary>
public class ColorXmuvTimeStrategy : Strategy
{
	private readonly StrategyParam<int> _maxColorHistory;

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<bool> _enableBuyEntries;
	private readonly StrategyParam<bool> _enableSellEntries;
	private readonly StrategyParam<bool> _enableBuyExits;
	private readonly StrategyParam<bool> _enableSellExits;
	private readonly StrategyParam<bool> _useTimeFilter;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _startMinute;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<int> _endMinute;
	private readonly StrategyParam<SmoothMethods> _xmaMethod;
	private readonly StrategyParam<int> _xLength;
	private readonly StrategyParam<int> _xPhase;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private readonly List<TrendColors> _colorHistory = new();

	private IIndicator _xma = null!;
	private decimal? _previousXmuv;

	/// <summary>
	/// Type of candles for the indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Volume for new market orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Allow opening long positions.
	/// </summary>
	public bool EnableBuyEntries
	{
		get => _enableBuyEntries.Value;
		set => _enableBuyEntries.Value = value;
	}

	/// <summary>
	/// Allow opening short positions.
	/// </summary>
	public bool EnableSellEntries
	{
		get => _enableSellEntries.Value;
		set => _enableSellEntries.Value = value;
	}

	/// <summary>
	/// Allow closing long positions on bearish signals.
	/// </summary>
	public bool EnableBuyExits
	{
		get => _enableBuyExits.Value;
		set => _enableBuyExits.Value = value;
	}

	/// <summary>
	/// Allow closing short positions on bullish signals.
	/// </summary>
	public bool EnableSellExits
	{
		get => _enableSellExits.Value;
		set => _enableSellExits.Value = value;
	}

	/// <summary>
	/// Enable restriction of trading by time window.
	/// </summary>
	public bool UseTimeFilter
	{
		get => _useTimeFilter.Value;
		set => _useTimeFilter.Value = value;
	}

	/// <summary>
	/// Start hour for the trading session (00-23).
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Start minute for the trading session (00-59).
	/// </summary>
	public int StartMinute
	{
		get => _startMinute.Value;
		set => _startMinute.Value = value;
	}

	/// <summary>
	/// End hour for the trading session (00-23).
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// End minute for the trading session (00-59).
	/// </summary>
	public int EndMinute
	{
		get => _endMinute.Value;
		set => _endMinute.Value = value;
	}

	/// <summary>
	/// Smoothing method used by the Color XMUV line.
	/// </summary>
	public SmoothMethods XmaMethod
	{
		get => _xmaMethod.Value;
		set => _xmaMethod.Value = value;
	}

	/// <summary>
	/// Length of the smoothing window.
	/// </summary>
	public int XLength
	{
		get => _xLength.Value;
		set => _xLength.Value = value;
	}

	/// <summary>
	/// Auxiliary phase parameter retained from the original expert advisor.
	/// </summary>
	public int XPhase
	{
		get => _xPhase.Value;
		set => _xPhase.Value = value;
	}

	/// <summary>
	/// Number of completed bars to delay signal confirmation.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Maximum number of stored trend color values.
	/// </summary>
	public int MaxColorHistory
	{
		get => _maxColorHistory.Value;
		set
		{
			_maxColorHistory.Value = value;
			TrimColorHistory();
		}
	}

	/// <summary>
	/// Stop loss size in points (converted to absolute price using the instrument price step).
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit size in points (converted to absolute price using the instrument price step).
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="ColorXmuvTimeStrategy"/>.
	/// </summary>
	public ColorXmuvTimeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Source candles for the Color XMUV line", "General");

		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Order Volume", "Size of market orders", "Trading");

		_enableBuyEntries = Param(nameof(EnableBuyEntries), true)
		.SetDisplay("Enable Long Entries", "Allow entering long positions", "Trading");

		_enableSellEntries = Param(nameof(EnableSellEntries), true)
		.SetDisplay("Enable Short Entries", "Allow entering short positions", "Trading");

		_enableBuyExits = Param(nameof(EnableBuyExits), true)
		.SetDisplay("Close Longs", "Close long positions on bearish flips", "Trading");

		_enableSellExits = Param(nameof(EnableSellExits), true)
		.SetDisplay("Close Shorts", "Close short positions on bullish flips", "Trading");

		_useTimeFilter = Param(nameof(UseTimeFilter), false)
		.SetDisplay("Use Time Filter", "Restrict trading to the specified session", "Time Filter");

		_startHour = Param(nameof(StartHour), 0)
		.SetRange(0, 23)
		.SetDisplay("Start Hour", "Trading session start hour", "Time Filter");

		_startMinute = Param(nameof(StartMinute), 0)
		.SetRange(0, 59)
		.SetDisplay("Start Minute", "Trading session start minute", "Time Filter");

		_endHour = Param(nameof(EndHour), 23)
		.SetRange(0, 23)
		.SetDisplay("End Hour", "Trading session end hour", "Time Filter");

		_endMinute = Param(nameof(EndMinute), 59)
		.SetRange(0, 59)
		.SetDisplay("End Minute", "Trading session end minute", "Time Filter");

		_xmaMethod = Param(nameof(XmaMethod), SmoothMethods.Sma)
		.SetDisplay("Smoothing Method", "Algorithm for the Color XMUV line", "Indicator");

		_xLength = Param(nameof(XLength), 14)
		.SetGreaterThanZero()
		.SetDisplay("Length", "Smoothing length", "Indicator");

		_xPhase = Param(nameof(XPhase), 15)
		.SetDisplay("Phase", "Additional phase parameter for exotic smoothers", "Indicator");

		_signalBar = Param(nameof(SignalBar), 1)
		.SetRange(0, 10)
		.SetDisplay("Signal Bar", "Number of completed bars to delay signals", "Indicator");

		_maxColorHistory = Param(nameof(MaxColorHistory), 64)
		.SetRange(2, 512)
		.SetDisplay("Max Color History", "Maximum stored trend color values", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 0m)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pts)", "Stop loss distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m)
		.SetNotNegative()
		.SetDisplay("Take Profit (pts)", "Take profit distance in points", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_colorHistory.Clear();
		_previousXmuv = null;
		_xma = null!;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_xma = CreateMovingAverage(XmaMethod, XLength);

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		StartProtection(CreateTakeProfitUnit(), CreateStopLossUnit());
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		{
			return;
		}

		var price = CalculateSignalPrice(candle);
		var indicatorValue = _xma.Process(new DecimalIndicatorValue(_xma, price, candle.OpenTime) { IsFinal = true });

		if (!_xma.IsFormed)
		{
			_previousXmuv = indicatorValue.ToDecimal();
			return;
		}

		var xmuv = indicatorValue.ToDecimal();
		var color = DetermineColor(xmuv);
		StoreColor(color);
		_previousXmuv = xmuv;

		if (!TryGetSignalColors(SignalBar, out var currentColor, out var previousColor))
		{
			return;
		}

		// No bound indicators via .Bind, always allow.

		var inSession = !UseTimeFilter || IsInsideSession(candle.CloseTime);

		if (!inSession)
		{
			ForceExitIfNeeded();
			return;
		}

		var bullishFlip = currentColor == TrendColors.Bullish && previousColor != TrendColors.Bullish;
		var bearishFlip = currentColor == TrendColors.Bearish && previousColor != TrendColors.Bearish;

		if (bullishFlip)
		{
			if (Position < 0 && EnableSellExits)
			{
				// Close short position
				BuyMarket();
			}
			else if (Position == 0 && EnableBuyEntries)
			{
				// Open new long
				BuyMarket();
			}
		}
		else if (bearishFlip)
		{
			if (Position > 0 && EnableBuyExits)
			{
				// Close long position
				SellMarket();
			}
			else if (Position == 0 && EnableSellEntries)
			{
				// Open new short
				SellMarket();
			}
		}
	}

	private decimal CalculateSignalPrice(ICandleMessage candle)
	{
		if (candle.ClosePrice < candle.OpenPrice)
		{
			return (candle.LowPrice + candle.ClosePrice) / 2m;
		}

		if (candle.ClosePrice > candle.OpenPrice)
		{
			return (candle.HighPrice + candle.ClosePrice) / 2m;
		}

		return candle.ClosePrice;
	}

	private TrendColors DetermineColor(decimal currentXmuv)
	{
		if (_previousXmuv is not decimal previous)
		{
			return TrendColors.Neutral;
		}

		if (currentXmuv > previous)
		{
			return TrendColors.Bullish;
		}

		if (currentXmuv < previous)
		{
			return TrendColors.Bearish;
		}

		return TrendColors.Neutral;
	}

	private void StoreColor(TrendColors color)
	{
		var maxSize = Math.Clamp(SignalBar + 2, 2, MaxColorHistory);
		_colorHistory.Add(color);

		if (_colorHistory.Count > maxSize)
		{
			_colorHistory.RemoveAt(0);
		}
	}

	private void TrimColorHistory()
	{
		var limit = Math.Max(2, MaxColorHistory);

		while (_colorHistory.Count > limit)
		{
			_colorHistory.RemoveAt(0);
		}
	}

	private bool TryGetSignalColors(int offset, out TrendColors current, out TrendColors previous)
	{
		current = TrendColors.Neutral;
		previous = TrendColors.Neutral;

		var count = _colorHistory.Count;
		if (count <= offset)
		{
			return false;
		}

		var index = count - 1 - offset;
		if (index <= 0)
		{
			return false;
		}

		current = _colorHistory[index];
		previous = _colorHistory[index - 1];
		return true;
	}

	private bool IsInsideSession(DateTimeOffset time)
	{
		var start = new TimeSpan(StartHour, StartMinute, 0);
		var end = new TimeSpan(EndHour, EndMinute, 0);
		var moment = time.TimeOfDay;

		if (start == end)
		{
			return moment >= start && moment < end;
		}

		if (start < end)
		{
			return moment >= start && moment <= end;
		}

		return moment >= start || moment <= end;
	}

	private void ForceExitIfNeeded()
	{
		if (Position > 0 && EnableBuyExits)
		{
			SellMarket();
		}
		else if (Position < 0 && EnableSellExits)
		{
			BuyMarket();
		}
	}

	private Unit CreateStopLossUnit()
	{
		if (StopLossPoints <= 0 || Security?.PriceStep is not decimal step || step <= 0)
		{
			return default;
		}

		return new Unit(step * StopLossPoints, UnitTypes.Absolute);
	}

	private Unit CreateTakeProfitUnit()
	{
		if (TakeProfitPoints <= 0 || Security?.PriceStep is not decimal step || step <= 0)
		{
			return default;
		}

		return new Unit(step * TakeProfitPoints, UnitTypes.Absolute);
	}

	private IIndicator CreateMovingAverage(SmoothMethods method, int length)
	{
		return method switch
		{
			SmoothMethods.Sma => new SMA { Length = length },
			SmoothMethods.Ema => new EMA { Length = length },
			SmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
			SmoothMethods.Lwma => new WeightedMovingAverage { Length = length },
			SmoothMethods.Jjma => new EMA { Length = length },
			SmoothMethods.Jurx => new EMA { Length = length },
			SmoothMethods.Parma => new WeightedMovingAverage { Length = length },
			SmoothMethods.T3 => new EMA { Length = length },
			SmoothMethods.Vidya => new EMA { Length = length },
			SmoothMethods.Ama => new KaufmanAdaptiveMovingAverage { Length = length },
			_ => new EMA { Length = length },
		};
	}

	private enum TrendColors
	{
		Bearish = 0,
		Neutral = 1,
		Bullish = 2
	}

	/// <summary>
	/// Smoothing methods supported by the Color XMUV indicator.
	/// </summary>
	public enum SmoothMethods
	{
		Sma,
		Ema,
		Smma,
		Lwma,
		Jjma,
		Jurx,
		Parma,
		T3,
		Vidya,
		Ama
	}
}