Ver no GitHub

Estratégia Color Fisher M11

Visão geral

Color Fisher M11 é uma estratégia seguidora de tendência que replica o consultor especializado Exp_ColorFisher_m11 do MetaTrader 5. Usa uma variante personalizada do Fisher Transform que pinta candles com cinco estados de cor para destacar momentum extremo de alta e de baixa. Os sinais são atrasados por um número configurável de candles fechados para evitar negociar com dados incompletos, enquanto interruptores opcionais permitem desabilitar entradas ou saídas para cada lado de forma independente.

Lógica do indicador

A estratégia constrói o indicador Color Fisher em tempo real:

  • Determina a máxima mais alta e a mínima mais baixa sobre a janela Range Periods.
  • Normaliza o preço médio do candle atual dentro desse intervalo e aplica Price Smoothing (estilo EMA) para estabilizar as oscilações.
  • Aplica o Fisher Transform com um fator adicional Index Smoothing para criar o valor final do oscilador.
  • Classifica o oscilador em cinco bandas de cor discretas usando os limites High Level e Low Level:
    • 0 – forte impulso de alta acima do nível alto.
    • 1 – momentum de alta moderado entre zero e o nível alto.
    • 2 – zona neutra em torno de zero.
    • 3 – momentum de baixa moderado entre zero e o nível baixo.
    • 4 – forte impulso de baixa abaixo do nível baixo.

O sinal é avaliado Signal Bar candles atrás, imitando o comportamento do Consultor Especializado original. O estado de cor anterior também é rastreado para detectar novas transições para as bandas extremas.

Regras de negociação

  • Entrada comprada – permitida quando Enable Buy Entry é verdadeiro, a cor atrasada é igual a 0 (forte alta) e a cor anterior é diferente de 0. Qualquer exposição vendida é revertida e a posição torna-se comprada.
  • Entrada vendida – permitida quando Enable Sell Entry é verdadeiro, a cor atrasada é igual a 4 (forte baixa) e a cor anterior é diferente de 4. Qualquer exposição comprada é revertida e a posição torna-se vendida.
  • Saída comprada – ativada quando Enable Buy Exit é verdadeiro e a cor atrasada passa para 3 ou 4, sinalizando controle de baixa.
  • Saída vendida – ativada quando Enable Sell Exit é verdadeiro e a cor atrasada passa para 0 ou 1, sinalizando controle de alta.

Para evitar múltiplas ordens por sinal, a estratégia lembra o tempo de fechamento da próxima barra para cada direção e recusa novas entradas até que o próximo candle seja concluído.

Gestão de risco

Stop Loss (pts) e Take Profit (pts) convertem as distâncias originais em pips em passos de preço absolutos usando o passo de preço do instrumento. Quando uma distância positiva é fornecida, ordens protetoras são ativadas através de StartProtection. Defina qualquer valor como zero para desativar essa proteção.

Parâmetros

  • Range Periods – comprimento do lookback para o intervalo alto/baixo usado pelo Fisher Transform (padrão 10).
  • Price Smoothing – fator de suavização pré-transformação, 0…0.99 (padrão 0.3).
  • Index Smoothing – fator de suavização pós-transformação, 0…0.99 (padrão 0.3).
  • High Level / Low Level – limites que definem extremos de alta e de baixa (padrão +1.01 e –1.01).
  • Signal Bar – número de candles fechados para atrasar a avaliação do sinal (padrão 1).
  • Enable Buy Entry / Enable Sell Entry – interruptores para abrir novas negociações compradas ou vendidas.
  • Enable Buy Exit / Enable Sell Exit – interruptores para permitir saídas impulsionadas pelo indicador.
  • Stop Loss (pts) / Take Profit (pts) – distâncias protetoras expressas em passos de preço.
  • Candle Type – período para a assinatura de candles; padrão: candles de 4 horas.

Notas

  • A estratégia usa bindings de alto nível do StockSharp (SubscribeCandles().BindEx) e não armazena coleções históricas além do histórico mínimo de cores necessário para o sinal atrasado.
  • Nesta versão não há port Python, de acordo com a especificação.
  • Adicione a estratégia a uma área do gráfico para visualizar tanto o preço quanto o oscilador Color Fisher calculado.
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>
/// Strategy based on the Color Fisher Transform indicator.
/// Replicates the logic of the MQL5 expert Exp_ColorFisher_m11 with configurable entries and exits.
/// </summary>
public class ColorFisherM11Strategy : Strategy
{
	private readonly StrategyParam<int> _rangePeriods;
	private readonly StrategyParam<decimal> _priceSmoothing;
	private readonly StrategyParam<decimal> _indexSmoothing;
	private readonly StrategyParam<decimal> _highLevel;
	private readonly StrategyParam<decimal> _lowLevel;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<bool> _enableBuyEntry;
	private readonly StrategyParam<bool> _enableSellEntry;
	private readonly StrategyParam<bool> _enableBuyExit;
	private readonly StrategyParam<bool> _enableSellExit;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private ColorFisherM11Indicator _colorFisher;
	private readonly List<int> _colorHistory = new();
	private DateTimeOffset? _nextLongTime;
	private DateTimeOffset? _nextShortTime;

	/// <summary>
	/// Range length used to determine the Fisher Transform input window.
	/// </summary>
	public int RangePeriods
	{
		get => _rangePeriods.Value;
		set => _rangePeriods.Value = value;
	}

	/// <summary>
	/// Price smoothing factor (0..1) applied before the Fisher Transform.
	/// </summary>
	public decimal PriceSmoothing
	{
		get => _priceSmoothing.Value;
		set => _priceSmoothing.Value = value;
	}

	/// <summary>
	/// Fisher index smoothing factor (0..1) applied after the transform.
	/// </summary>
	public decimal IndexSmoothing
	{
		get => _indexSmoothing.Value;
		set => _indexSmoothing.Value = value;
	}

	/// <summary>
	/// Upper threshold used for bullish color classification.
	/// </summary>
	public decimal HighLevel
	{
		get => _highLevel.Value;
		set => _highLevel.Value = value;
	}

	/// <summary>
	/// Lower threshold used for bearish color classification.
	/// </summary>
	public decimal LowLevel
	{
		get => _lowLevel.Value;
		set => _lowLevel.Value = value;
	}

	/// <summary>
	/// Number of closed bars to wait before acting on a signal.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Enable long entries.
	/// </summary>
	public bool EnableBuyEntry
	{
		get => _enableBuyEntry.Value;
		set => _enableBuyEntry.Value = value;
	}

	/// <summary>
	/// Enable short entries.
	/// </summary>
	public bool EnableSellEntry
	{
		get => _enableSellEntry.Value;
		set => _enableSellEntry.Value = value;
	}

	/// <summary>
	/// Enable closing of existing long positions based on the indicator.
	/// </summary>
	public bool EnableBuyExit
	{
		get => _enableBuyExit.Value;
		set => _enableBuyExit.Value = value;
	}

	/// <summary>
	/// Enable closing of existing short positions based on the indicator.
	/// </summary>
	public bool EnableSellExit
	{
		get => _enableSellExit.Value;
		set => _enableSellExit.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Candle type and timeframe used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ColorFisherM11Strategy"/> class.
	/// </summary>
	public ColorFisherM11Strategy()
	{
		_rangePeriods = Param(nameof(RangePeriods), 3)
			.SetGreaterThanZero()
			.SetDisplay("Range Periods", "Lookback window for highs and lows", "Indicator");

		_priceSmoothing = Param(nameof(PriceSmoothing), 0.3m)
			.SetNotNegative()
			.SetRange(0.0001m, 0.99m)
			.SetDisplay("Price Smoothing", "Smoothing factor applied before Fisher transform", "Indicator");

		_indexSmoothing = Param(nameof(IndexSmoothing), 0.3m)
			.SetNotNegative()
			.SetRange(0.0001m, 0.99m)
			.SetDisplay("Index Smoothing", "Smoothing factor applied after Fisher transform", "Indicator");

		_highLevel = Param(nameof(HighLevel), 0.05m)
			.SetDisplay("High Level", "Upper level for bullish color", "Indicator");

		_lowLevel = Param(nameof(LowLevel), -0.05m)
			.SetDisplay("Low Level", "Lower level for bearish color", "Indicator");

		_signalBar = Param(nameof(SignalBar), 0)
			.SetNotNegative()
			.SetDisplay("Signal Bar", "Bars to delay signal execution", "Trading");

		_enableBuyEntry = Param(nameof(EnableBuyEntry), true)
			.SetDisplay("Enable Buy Entry", "Allow opening long positions", "Trading");

		_enableSellEntry = Param(nameof(EnableSellEntry), true)
			.SetDisplay("Enable Sell Entry", "Allow opening short positions", "Trading");

		_enableBuyExit = Param(nameof(EnableBuyExit), true)
			.SetDisplay("Enable Buy Exit", "Allow closing long positions", "Trading");

		_enableSellExit = Param(nameof(EnableSellExit), true)
			.SetDisplay("Enable Sell Exit", "Allow closing short positions", "Trading");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pts)", "Protective stop distance in price steps", "Protection");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
			.SetNotNegative()
			.SetDisplay("Take Profit (pts)", "Target distance in price steps", "Protection");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for indicator calculation", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_colorFisher?.Reset();
		_colorHistory.Clear();
		_nextLongTime = null;
		_nextShortTime = null;
	}

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

		_colorFisher = new ColorFisherM11Indicator
		{
			RangePeriods = RangePeriods,
			PriceSmoothing = PriceSmoothing,
			IndexSmoothing = IndexSmoothing,
			HighLevel = HighLevel,
			LowLevel = LowLevel,
			MinRange = Security?.PriceStep ?? 0.0001m
		};

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

		var step = Security?.PriceStep ?? 1m;
		Unit stopLossUnit = StopLossPoints > 0 ? new Unit(step * StopLossPoints, UnitTypes.Absolute) : null;
		Unit takeProfitUnit = TakeProfitPoints > 0 ? new Unit(step * TakeProfitPoints, UnitTypes.Absolute) : null;

		if (stopLossUnit != null || takeProfitUnit != null)
			StartProtection(stopLoss: stopLossUnit, takeProfit: takeProfitUnit);

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _colorFisher);
			DrawOwnTrades(area);
		}
	}

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

		_colorFisher.Process(new CandleIndicatorValue(_colorFisher, candle));
		UpdateHistory(_colorFisher.LastColor);

		if (!_colorFisher.IsFormed)
			return;

		// indicator already checked via IsFormed above

		var signalColor = GetColor(SignalBar);
		var previousColor = GetColor(SignalBar + 1);

		if (signalColor is null || previousColor is null)
			return;

		if (EnableSellExit && signalColor < 2 && Position < 0)
		{
			BuyMarket();
		}

		if (EnableBuyExit && signalColor > 2 && Position > 0)
		{
			SellMarket();
		}

		var allowLong = !_nextLongTime.HasValue || candle.CloseTime >= _nextLongTime.Value;
		var allowShort = !_nextShortTime.HasValue || candle.CloseTime >= _nextShortTime.Value;

		if (EnableBuyEntry && allowLong && signalColor <= 1 && previousColor > 1 && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket();
			_nextLongTime = candle.CloseTime;
		}
		else if (EnableSellEntry && allowShort && signalColor >= 3 && previousColor < 3 && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket();
			_nextShortTime = candle.CloseTime;
		}
	}

	private void UpdateHistory(int color)
	{
		_colorHistory.Insert(0, color);
		var max = Math.Max(SignalBar + 2, 5);
		while (_colorHistory.Count > max)
		{
			try { _colorHistory.RemoveAt(_colorHistory.Count - 1); } catch { break; }
		}
	}

	private int? GetColor(int index)
	{
		if (index < 0 || index >= _colorHistory.Count)
			return null;

		return _colorHistory[index];
	}

	private sealed class ColorFisherM11Indicator : BaseIndicator
	{
		public int RangePeriods { get; set; } = 10;
		public decimal PriceSmoothing { get; set; } = 0.3m;
		public decimal IndexSmoothing { get; set; } = 0.3m;
		public decimal HighLevel { get; set; } = 1.01m;
		public decimal LowLevel { get; set; } = -1.01m;
		public decimal MinRange { get; set; } = 0.0001m;
		public int LastColor { get; private set; } = 2;

		private readonly List<decimal> _highs = new();
		private readonly List<decimal> _lows = new();
		private decimal _prevFish;
		private decimal _prevIndex;
		private bool _hasPrevIndex;
		private int _count;

		protected override IIndicatorValue OnProcess(IIndicatorValue input)
		{
			var candle = input.GetValue<ICandleMessage>();
			if (candle == null)
				return new DecimalIndicatorValue(this, decimal.Zero, input.Time);

			_highs.Add(candle.HighPrice);
			_lows.Add(candle.LowPrice);
			_count++;

			var length = Math.Max(1, RangePeriods);
			while (_highs.Count > length)
			{
				_highs.RemoveAt(0);
				_lows.RemoveAt(0);
			}

			var highest = decimal.MinValue;
			var lowest = decimal.MaxValue;
			for (var i = 0; i < _highs.Count; i++)
			{
				if (_highs[i] > highest) highest = _highs[i];
				if (_lows[i] < lowest) lowest = _lows[i];
			}

			var range = highest - lowest;
			var minRange = MinRange <= 0m ? 0.0001m : MinRange;
			if (range < minRange)
				range = minRange;

			var midPrice = (candle.HighPrice + candle.LowPrice) / 2m;
			var priceLocation = range != 0m ? (midPrice - lowest) / range : 0.99m;
			priceLocation = 2m * priceLocation - 1m;

			var prevFish = _hasPrevIndex ? _prevFish : priceLocation;
			var fish = PriceSmoothing * prevFish + (1m - PriceSmoothing) * priceLocation;
			var smoothed = Math.Min(Math.Max(fish, -0.99m), 0.99m);

			decimal fisherRaw;
			var diff = 1m - smoothed;
			if (diff == 0m)
			{
				fisherRaw = 0m;
			}
			else
			{
				var ratio = (1m + smoothed) / diff;
				fisherRaw = (decimal)Math.Log((double)ratio);
			}

			var prevIndex = _hasPrevIndex ? _prevIndex : fisherRaw;
			var value = IndexSmoothing * prevIndex + (1m - IndexSmoothing) * fisherRaw;

			_prevFish = fish;
			_prevIndex = value;
			_hasPrevIndex = true;

			IsFormed = _count >= length;

			var color = 2;
			if (value > 0m)
				color = value > HighLevel ? 0 : 1;
			else if (value < 0m)
				color = value < LowLevel ? 4 : 3;

			LastColor = color;

			return new DecimalIndicatorValue(this, value, input.Time) { IsFinal = true };
		}

		public override void Reset()
		{
			base.Reset();
			_highs.Clear();
			_lows.Clear();
			_prevFish = 0m;
			_prevIndex = 0m;
			_hasPrevIndex = false;
			_count = 0;
			LastColor = 2;
		}
	}
}