Ver no GitHub

Estratégia EA Trix

Visão Geral

A estratégia EA Trix replica a lógica do assessor especialista MetaTrader 5 que combina o indicador TRIX ARROWS com ferramentas básicas de gestão de risco. O sistema aguarda que a média móvel exponencial tripla (TRIX) e sua linha de sinal se cruzem antes de entrar em novas posições. Pode reagir imediatamente na vela de sinal ou atrasar a execução até a próxima barra, emulando o comportamento original de "operar no fechamento da barra".

Lógica de Trading

  1. Construir duas médias móveis exponenciais triplicadas:
    • TRIX é calculado aplicando três EMAs com o comprimento TRIX EMA ao fechamento da vela e tomando a taxa de mudança de uma barra do terceiro suavizamento.
    • A linha de sinal é calculada da mesma forma, mas usa o comprimento Signal EMA.
  2. Detectar mudanças de direção através de cruzamentos:
    • Quando a linha de sinal cruza acima do TRIX, a estratégia prepara uma entrada comprada.
    • Quando a linha de sinal cruza abaixo do TRIX, ela prepara uma entrada vendida.
  3. Dependendo da configuração Trade On Close, a estratégia:
    • Executa imediatamente ao preço de fechamento da barra de sinal; ou
    • Coloca a ordem na fila e a executa na abertura da próxima barra (correspondendo à opção do EA MT5 para operar em barras fechadas).
  4. Antes de abrir uma nova posição, o algoritmo reverte automaticamente qualquer exposição contrária para que apenas uma posição líquida exista a qualquer momento.

Gestão de Posições

  • Stop loss – distância fixa opcional a partir do preço de preenchimento. Desabilitado quando definido como zero.
  • Take profit – alvo de lucro opcional. Desabilitado quando definido como zero.
  • Break-even – uma vez que o preço avança a favor da operação pela distância selecionada, o stop é movido para o preço de entrada.
  • Trailing stop – após o preço se mover pela distância de trailing, o stop segue o preço com o incremento mínimo de Trailing Step selecionado.
  • As saídas protetoras são avaliadas em cada vela completada usando os valores high/low da vela. Quando uma saída protetora é acionada, a posição é fechada com uma ordem de mercado.

Parâmetros

Nome Descrição
CandleType Tipo de dados (período) das velas processadas pela estratégia.
Volume Tamanho de posição usado para novas entradas. As posições existentes são revertidas automaticamente quando necessário.
EmaPeriod Comprimento das médias móvies exponenciais usadas para calcular a curva TRIX.
SignalPeriod Comprimento das médias móveis exponenciais usadas para calcular a curva de sinal.
TradeOnCloseBar Se true, as entradas são colocadas na fila e executadas na abertura da próxima barra. Se false, a execução acontece imediatamente no fechamento da barra de sinal.
StopLoss Distância do preço de entrada ao stop protetor. Definir como 0 para desabilitar.
TakeProfit Distância ao alvo de lucro. Definir como 0 para desabilitar.
TrailingStop Distância para o trailing stop ser ativado. Definir como 0 para desabilitar.
TrailingStep Incremento mínimo aplicado ao atualizar o trailing stop.
BreakEven Distância necessária para mover o stop ao preço de entrada. Definir como 0 para desabilitar.

Notas de Uso

  • A estratégia subscreve um único feed de velas e depende exclusivamente de velas completadas conforme exigido pelas diretrizes da API de alto nível do StockSharp.
  • As distâncias padrão de gestão de risco são expressas em unidades de preço. Ajuste-as de acordo com o tamanho do tick do instrumento negociado.
  • Como as ordens são enviadas via comandos de mercado, o preço de preenchimento é assumido como o fechamento da vela (ou abertura para sinais na fila) em backtests.

Notas de Conversão

  • O expert MQL5 original usa o indicador externo TRIX ARROWS (código 19056). A conversão reconstrói os mesmos cálculos usando instâncias de ExponentialMovingAverage do StockSharp e lógica de taxa de mudança sem depender de buffers personalizados.
  • O gerenciamento de risco do MT5 dependia de ordens stop e limit do lado do broker. No StockSharp, as saídas protetoras são replicadas monitorando os extremos das velas e emitindo ordens de mercado.
  • Alertas, notificações de som e parâmetros específicos do broker foram omitidos porque não fazem parte da lógica de trading central.
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>
/// TRIX cross strategy based on the "TRIX ARROWS" expert advisor.
/// Opens a long position when the signal line crosses above TRIX and a short position on the opposite crossover.
/// Includes optional stop loss, take profit, break-even and trailing stop logic.
/// </summary>
public class EaTrixStrategy : Strategy
{
	private enum SignalDirections
	{
		Buy,
		Sell,
	}

	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _trailingStep;
	private readonly StrategyParam<decimal> _breakEven;
	private readonly StrategyParam<bool> _tradeOnCloseBar;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _trixEma1 = null!;
	private ExponentialMovingAverage _trixEma2 = null!;
	private ExponentialMovingAverage _trixEma3 = null!;
	private ExponentialMovingAverage _signalEma1 = null!;
	private ExponentialMovingAverage _signalEma2 = null!;
	private ExponentialMovingAverage _signalEma3 = null!;

	private decimal? _prevThirdTrix;
	private decimal? _prevThirdSignal;
	private decimal? _prevTrix;
	private decimal? _prevSignal;

	private SignalDirections? _pendingSignal;
	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Stop loss distance in price units. Set to zero to disable.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in price units. Set to zero to disable.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in price units. Set to zero to disable.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	/// <summary>
	/// Minimal step for trailing stop updates.
	/// </summary>
	public decimal TrailingStep
	{
		get => _trailingStep.Value;
		set => _trailingStep.Value = value;
	}

	/// <summary>
	/// Break-even trigger distance. The stop is moved to the entry price when the distance is reached.
	/// </summary>
	public decimal BreakEven
	{
		get => _breakEven.Value;
		set => _breakEven.Value = value;
	}

	/// <summary>
	/// Trade using signals confirmed on the previous closed bar.
	/// When disabled the strategy reacts immediately on the bar that generated the crossover.
	/// </summary>
	public bool TradeOnCloseBar
	{
		get => _tradeOnCloseBar.Value;
		set => _tradeOnCloseBar.Value = value;
	}

	/// <summary>
	/// EMA length used to build the TRIX series.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// EMA length used to build the signal series.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="EaTrixStrategy"/>.
	/// </summary>
	public EaTrixStrategy()
	{
		_stopLoss = Param(nameof(StopLoss), 50m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss distance", "Risk")
			;

		_takeProfit = Param(nameof(TakeProfit), 150m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit distance", "Risk")
			;

		_trailingStop = Param(nameof(TrailingStop), 10m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk")
			;

		_trailingStep = Param(nameof(TrailingStep), 1m)
			.SetNotNegative()
			.SetDisplay("Trailing Step", "Minimal trailing step", "Risk")
			;

		_breakEven = Param(nameof(BreakEven), 2m)
			.SetNotNegative()
			.SetDisplay("Break Even", "Break-even trigger distance", "Risk")
			;

		_tradeOnCloseBar = Param(nameof(TradeOnCloseBar), true)
			.SetDisplay("Trade On Close", "Confirm signals on closed bars", "General");

		_emaPeriod = Param(nameof(EmaPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("TRIX EMA", "TRIX EMA length", "Indicators")
			;

		_signalPeriod = Param(nameof(SignalPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Signal EMA", "Signal EMA length", "Indicators")
			;

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

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

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

		_prevThirdTrix = null;
		_prevThirdSignal = null;
		_prevTrix = null;
		_prevSignal = null;
		_pendingSignal = null;

		ClearPositionState();
	}

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

		// no protection

		_trixEma1 = new ExponentialMovingAverage { Length = EmaPeriod };
		_trixEma2 = new ExponentialMovingAverage { Length = EmaPeriod };
		_trixEma3 = new ExponentialMovingAverage { Length = EmaPeriod };

		_signalEma1 = new ExponentialMovingAverage { Length = SignalPeriod };
		_signalEma2 = new ExponentialMovingAverage { Length = SignalPeriod };
		_signalEma3 = new ExponentialMovingAverage { Length = SignalPeriod };

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

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

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

		HandlePendingSignal(candle);

		ManageActivePosition(candle);

		if (!TryCalculateIndicators(candle, out var trix, out var signal))
			return;

		if (_prevTrix is null || _prevSignal is null)
		{
			_prevTrix = trix;
			_prevSignal = signal;
			return;
		}

		if (!_trixEma3.IsFormed || !_signalEma3.IsFormed)
		{
			_prevTrix = trix;
			_prevSignal = signal;
			return;
		}

		var crossUp = _prevSignal < _prevTrix && signal > trix;
		var crossDown = _prevSignal > _prevTrix && signal < trix;

		if (crossUp)
		{
			if (TradeOnCloseBar)
				_pendingSignal = SignalDirections.Buy;
			else
				ExecuteSignal(SignalDirections.Buy, candle, candle.ClosePrice);
		}
		else if (crossDown)
		{
			if (TradeOnCloseBar)
				_pendingSignal = SignalDirections.Sell;
			else
				ExecuteSignal(SignalDirections.Sell, candle, candle.ClosePrice);
		}

		_prevTrix = trix;
		_prevSignal = signal;
	}

	private void HandlePendingSignal(ICandleMessage candle)
	{
		if (_pendingSignal is null)
			return;

		if (!_trixEma3.IsFormed || !_signalEma3.IsFormed)
			return;

		ExecuteSignal(_pendingSignal.Value, candle, candle.OpenPrice);
		_pendingSignal = null;
	}

	private void ExecuteSignal(SignalDirections direction, ICandleMessage candle, decimal fillPrice)
	{
		if (Volume <= 0m)
			return;

		var volume = Volume;

		switch (direction)
		{
			case SignalDirections.Buy:
				if (Position < 0m)
					volume += Math.Abs(Position);

				if (volume > 0m)
					BuyMarket();

				_entryPrice = fillPrice;
				_stopPrice = StopLoss > 0m ? fillPrice - StopLoss : null;
				_takePrice = TakeProfit > 0m ? fillPrice + TakeProfit : null;
				break;

			case SignalDirections.Sell:
				if (Position > 0m)
					volume += Position;

				if (volume > 0m)
					SellMarket();

				_entryPrice = fillPrice;
				_stopPrice = StopLoss > 0m ? fillPrice + StopLoss : null;
				_takePrice = TakeProfit > 0m ? fillPrice - TakeProfit : null;
				break;
		}
	}

	private void ManageActivePosition(ICandleMessage candle)
	{
		if (Position > 0m && _entryPrice is decimal longEntry)
		{
			if (BreakEven > 0m && candle.HighPrice - longEntry >= BreakEven && (_stopPrice is null || _stopPrice < longEntry))
				_stopPrice = longEntry;

			if (TrailingStop > 0m)
			{
				var move = candle.HighPrice - longEntry;
				if (move >= TrailingStop)
				{
					var newStop = candle.HighPrice - TrailingStop;
					if (_stopPrice is null || newStop - _stopPrice >= TrailingStep)
						_stopPrice = newStop;
				}
			}

			if (_takePrice is decimal tp && candle.HighPrice >= tp)
			{
				SellMarket();
				ClearPositionState();
				return;
			}

			if (_stopPrice is decimal sl && candle.LowPrice <= sl)
			{
				SellMarket();
				ClearPositionState();
			}
		}
		else if (Position < 0m && _entryPrice is decimal shortEntry)
		{
			if (BreakEven > 0m && shortEntry - candle.LowPrice >= BreakEven && (_stopPrice is null || _stopPrice > shortEntry))
				_stopPrice = shortEntry;

			if (TrailingStop > 0m)
			{
				var move = shortEntry - candle.LowPrice;
				if (move >= TrailingStop)
				{
					var newStop = candle.LowPrice + TrailingStop;
					if (_stopPrice is null || _stopPrice - newStop >= TrailingStep)
						_stopPrice = newStop;
				}
			}

			if (_takePrice is decimal tp && candle.LowPrice <= tp)
			{
				BuyMarket();
				ClearPositionState();
				return;
			}

			if (_stopPrice is decimal sl && candle.HighPrice >= sl)
			{
				BuyMarket();
				ClearPositionState();
			}
		}
		else if (Position == 0m)
		{
			ClearPositionState();
		}
	}

	private bool TryCalculateIndicators(ICandleMessage candle, out decimal trix, out decimal signal)
	{
		trix = 0m;
		signal = 0m;

		var ema1 = _trixEma1.Process(new DecimalIndicatorValue(_trixEma1, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
		var ema2 = _trixEma2.Process(new DecimalIndicatorValue(_trixEma2, ema1, candle.OpenTime) { IsFinal = true }).ToDecimal();
		var ema3 = _trixEma3.Process(new DecimalIndicatorValue(_trixEma3, ema2, candle.OpenTime) { IsFinal = true }).ToDecimal();

		if (_prevThirdTrix is null)
		{
			_prevThirdTrix = ema3;
			return false;
		}

		trix = _prevThirdTrix != 0m ? (ema3 - _prevThirdTrix.Value) / _prevThirdTrix.Value : 0m;
		_prevThirdTrix = ema3;

		var signal1 = _signalEma1.Process(new DecimalIndicatorValue(_signalEma1, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
		var signal2 = _signalEma2.Process(new DecimalIndicatorValue(_signalEma2, signal1, candle.OpenTime) { IsFinal = true }).ToDecimal();
		var signalBase = _signalEma3.Process(new DecimalIndicatorValue(_signalEma3, signal2, candle.OpenTime) { IsFinal = true }).ToDecimal();

		if (_prevThirdSignal is null)
		{
			_prevThirdSignal = signalBase;
			return false;
		}

		signal = _prevThirdSignal != 0m ? (signalBase - _prevThirdSignal.Value) / _prevThirdSignal.Value : 0m;
		_prevThirdSignal = signalBase;

		return true;
	}

	private void ClearPositionState()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}
}