Ver no GitHub

Estratégia Color XMUV com Filtro de Tempo

Esta estratégia porta o consultor especialista MetaTrader Exp_ColorXMUV_Tm para o StockSharp. Recria a linha suavizada Color XMUV original e o filtro de janela de tempo enquanto usa a API de negociação de alto nível do StockSharp. A estratégia segue a cor da linha suavizada: uma transição para azul-esverdeado (subindo) aciona o gerenciamento de comprados, enquanto uma transição para magenta (caindo) impulsiona o gerenciamento de vendidos.

Lógica central

  • Para cada candle finalizado a estratégia constrói um preço composto semelhante à versão MQL ((H + Close)/2 em barras de alta, (L + Close)/2 em barras de baixa, ou Close para barras doji).
  • O preço composto é passado pelo método de suavização solicitado. Os métodos comuns (SMA, EMA, SMMA/RMA, LWMA e Jurik) estão implementados com indicadores StockSharp. Opções exóticas como T3 ou VIDYA recorrem a uma EMA porque o StockSharp não expõe equivalentes diretos. O parâmetro de phase é mantido para compatibilidade de configuração, mesmo quando o indicador subjacente o ignora.
  • A "cor" do Color XMUV é reconstruída comparando o último valor suavizado com o anterior. Inclinações ascendentes são mapeadas para a cor de alta, inclinações descendentes para cor de baixa e valores inalterados para cor neutra.
  • SignalBar define quantas barras completamente terminadas verificar ao avaliar um sinal (por exemplo, o valor padrão de 1 significa que a lógica aguarda confirmação na barra anterior à mais recente).
  • Um flip de alta (cor anterior não de alta, cor atual de alta) fecha qualquer posição vendida e opcionalmente abre ou adiciona a uma posição comprada. Um flip de baixa realiza as ações simétricas para operações vendidas.
  • O filtro de tempo imita o EA original: fora da janela de trading a estratégia fecha imediatamente as posições existentes e ignora novas entradas. O filtro suporta sessões noturnas (hora de início posterior à hora de fim).
  • StopLossPoints e TakeProfitPoints são traduzidos em distâncias absolutas usando o passo de preço do instrumento e registrados com StartProtection para que o StockSharp gerencie saídas no lado do servidor onde possível.

Gestão de risco e posições

  • As ordens são dimensionadas com o parâmetro OrderVolume. Ao inverter a direção, a estratégia adiciona o valor absoluto da posição atual para que a reversão feche a operação antiga e abra uma nova em uma única transação.
  • Stop-loss e take-profit opcionais são convertidos de valores de pontos para distâncias de preço absolutas. Defina qualquer parâmetro como zero para desabilitar a respectiva camada de proteção.
  • As saídas de posições acionadas pelo flip de cor respeitam os interruptores EnableBuyExits e EnableSellExits, permitindo controle independente do gerenciamento de comprados e vendidos.

Parâmetros

  • Candle Type – Série de candles usada para cálculos (padrão candles de 4 horas).
  • Order Volume – Tamanho base da ordem de mercado.
  • Enable Long Entries / Enable Short Entries – Permitir abertura de posições em flips de alta/baixa.
  • Close Longs / Close Shorts – Habilitar saídas automáticas em transições de cor opostas.
  • Use Time Filter – Restringir a negociação à sessão configurada.
  • Start Hour / Start Minute / End Hour / End Minute – Limites da sessão de negociação. Quando o início é posterior ao fim, a sessão se estende pela meia-noite.
  • Smoothing Method – Algoritmo de média móvel para a linha Color XMUV. As opções sem implementação nativa no StockSharp são substituídas por EMA e estão documentadas acima.
  • Length – Comprimento de suavização (deve ser positivo).
  • Phase – Parâmetro de phase auxiliar retido para compatibilidade de configuração.
  • Signal Bar – Número de barras completadas para atrasar a verificação de sinal. Definir como zero para agir na barra fechada mais recente.
  • Stop Loss (pts) / Take Profit (pts) – Offsets expressos em pontos de preço; zero desabilita a respectiva camada.

Notas

  • O expert MQL dependia de bibliotecas de suavização externas. Quando tais modos de suavização não estão disponíveis no StockSharp (ParMA, VIDYA, T3) a implementação substitui por uma EMA. Documente essas alternativas ao compartilhar a estratégia com usuários.
  • A estratégia armazena apenas o histórico mínimo de cores exigido por SignalBar, cumprindo a diretriz do repositório que desencoraja a construção de caches de dados 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
	}
}