Auf GitHub ansehen

EA Trix Strategie

Übersicht

Die EA Trix Strategie repliziert die Logik des MetaTrader 5 Expert Advisors, der den TRIX ARROWS-Indikator mit grundlegenden Risikomanagement-Tools kombiniert. Das System wartet darauf, dass der dreifach exponentielle gleitende Durchschnitt (TRIX) und seine Signallinie kreuzen, bevor neue Positionen eingegangen werden. Es kann entweder sofort auf die Signalkerze reagieren oder die Ausführung bis zur nächsten Kerze verzögern und damit das ursprüngliche "Trade-at-Close-Bar"-Verhalten emulieren.

Handelslogik

  1. Zwei dreifach geglättete exponentielle gleitende Durchschnitte aufbauen:
    • TRIX wird berechnet, indem drei EMAs mit der TRIX EMA-Länge auf den Kerzenschluss angewendet werden und die Einbalken-Änderungsrate der dritten Glättung genommen wird.
    • Die Signallinie wird auf die gleiche Weise berechnet, verwendet aber die Signal EMA-Länge.
  2. Richtungsänderungen durch Kreuzungen erkennen:
    • Wenn die Signallinie über TRIX kreuzt, bereitet die Strategie einen Long-Einstieg vor.
    • Wenn die Signallinie unter TRIX kreuzt, bereitet sie einen Short-Einstieg vor.
  3. Je nach Trade On Close-Einstellung wird die Strategie entweder:
    • Sofort beim Schlusskurs der Signalkerze ausführen; oder
    • Die Order in die Warteschlange stellen und beim Öffnungskurs der nächsten Kerze ausführen (entsprechend der MT5 EA-Option für den Handel auf geschlossenen Kerzen).
  4. Vor dem Öffnen einer neuen Position kehrt der Algorithmus automatisch jedes entgegengesetzte Engagement um, damit nur eine Nettoposition zu einem Zeitpunkt existiert.

Positionsmanagement

  • Stop Loss – optionaler fester Abstand vom Füllkurs. Deaktiviert wenn auf null gesetzt.
  • Take Profit – optionales Gewinnziel. Deaktiviert wenn auf null gesetzt.
  • Break-even – sobald der Preis um die ausgewählte Distanz zugunsten des Trades voranschreitet, wird der Stop zum Eintrittspreis verschoben.
  • Trailing Stop – nachdem der Preis sich um die Trailing-Distanz bewegt hat, folgt der Stop dem Preis mit dem ausgewählten Mindestinkrement Trailing Step.
  • Schutzausstiege werden bei jeder abgeschlossenen Kerze mit den Kerzen-Hoch/Tief-Werten ausgewertet. Wenn ein Schutzausstieg auslöst, wird die Position mit einer Marktorder geschlossen.

Parameter

Name Beschreibung
CandleType Datentyp (Zeitrahmen) der von der Strategie verarbeiteten Kerzen.
Volume Positionsgröße für neue Einstiege. Bestehende Positionen werden bei Bedarf automatisch umgekehrt.
EmaPeriod Länge der exponentiellen gleitenden Durchschnitte zur Berechnung der TRIX-Kurve.
SignalPeriod Länge der exponentiellen gleitenden Durchschnitte zur Berechnung der Signalkurve.
TradeOnCloseBar Wenn true, werden Einstiege in die Warteschlange gestellt und beim nächsten Kerzenöffnungskurs ausgeführt. Wenn false, erfolgt die Ausführung sofort beim Signalkerzenschluss.
StopLoss Abstand vom Eintrittspreis zum Schutz-Stop. Auf 0 setzen zum Deaktivieren.
TakeProfit Abstand zum Gewinnziel. Auf 0 setzen zum Deaktivieren.
TrailingStop Abstand zur Aktivierung des Trailing Stops. Auf 0 setzen zum Deaktivieren.
TrailingStep Minimales Inkrement beim Aktualisieren des Trailing Stops.
BreakEven Erforderlicher Abstand zum Verschieben des Stops zum Eintrittspreis. Auf 0 setzen zum Deaktivieren.

Verwendungshinweise

  • Die Strategie abonniert einen einzelnen Kerzen-Feed und verlässt sich ausschließlich auf abgeschlossene Kerzen, wie von den StockSharp High-Level-API-Richtlinien gefordert.
  • Standard-Risikomanagement-Abstände werden in Kurseinheiten ausgedrückt. Passen Sie sie entsprechend der Tick-Größe des gehandelten Instruments an.
  • Da Orders über Marktbefehle gesendet werden, wird angenommen, dass der Füllkurs in Backtests der Kerzenschluss (oder Öffnungskurs für in der Warteschlange stehende Signale) ist.

Konvertierungshinweise

  • Der ursprüngliche MQL5-Expert verwendet den externen TRIX ARROWS-Indikator (Code 19056). Die Konvertierung rekonstruiert dieselben Berechnungen mit StockSharp ExponentialMovingAverage-Instanzen und Änderungsraten-Logik ohne Abhängigkeit von benutzerdefinierten Puffern.
  • Das MT5-Risikomanagement basierte auf brokerseitigen Stop- und Limit-Orders. In StockSharp werden Schutzausstiege durch Überwachung der Kerzenextreme und Ausgabe von Marktorders repliziert.
  • Alarmierung, Klangbenachrichtigungen und brokerspezifische Parameter wurden ausgelassen, da sie nicht Teil der zentralen Handelslogik sind.
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;
	}
}