GitHub で見る

EA Trix 戦略

概要

EA Trix戦略は、TRIX ARROWSインジケーターと基本的なリスク管理ツールを組み合わせたMetaTrader 5エキスパートアドバイザーの ロジックを再現します。システムはトリプル指数移動平均(TRIX)とシグナルラインがクロスするのを待ってから新しいポジションに 入ります。シグナルローソク足に即座に反応するか、次のバーまで実行を遅らせることができ、元の「バーのクローズ時に取引」動作を エミュレートします。

取引ロジック

  1. 2つのトリプル平滑化指数移動平均を構築します:
    • TRIXはローソク足の終値にTRIX EMAの長さで3つのEMAを適用し、第3の平滑化のワンバー変化率を取ることで計算されます。
    • シグナルラインは同じ方法で計算されますが、Signal EMAの長さを使用します。
  2. クロスオーバーによって方向変化を検出します:
    • シグナルラインがTRIXのをクロスすると、戦略はロングエントリーを準備します。
    • シグナルラインがTRIXのをクロスすると、ショートエントリーを準備します。
  3. Trade On Close設定に応じて戦略は:
    • シグナルバーの終値で即座に実行するか、または
    • 注文をキューに入れ、次のバーの始値で実行します(閉じたバーで取引するMT5 EAオプションに対応)。
  4. 新しいポジションを開く前に、アルゴリズムは逆方向のエクスポージャーを自動的に反転させ、常に1つの純ポジションのみが 存在するようにします。

ポジション管理

  • ストップロス – フィル価格からのオプションの固定距離。ゼロに設定すると無効になります。
  • テイクプロフィット – オプションの利益目標。ゼロに設定すると無効になります。
  • ブレイクイーブン – 価格が選択した距離だけトレードの有利な方向に進むと、ストップがエントリー価格に移動します。
  • トレーリングストップ – 価格がトレーリング距離だけ移動した後、ストップは選択したTrailing Step最小増分でに価格を 追跡します。
  • 保護的な決済はローソク足の高値/安値を使用して完了した各ローソク足で評価されます。保護的な決済が発動すると、ポジションは 成行注文で閉じられます。

パラメーター

名前 説明
CandleType 戦略が処理するローソク足のデータタイプ(時間軸)。
Volume 新規エントリーに使用するポジションサイズ。必要に応じて既存のポジションは自動的に反転されます。
EmaPeriod TRIX曲線を計算するために使用する指数移動平均の長さ。
SignalPeriod シグナル曲線を計算するために使用する指数移動平均の長さ。
TradeOnCloseBar trueの場合、エントリーはキューに入れられ次のバーの始値で実行されます。falseの場合、シグナルバーのクローズで即座に実行されます。
StopLoss エントリー価格から保護ストップまでの距離。0に設定して無効化。
TakeProfit 利益目標までの距離。0に設定して無効化。
TrailingStop トレーリングストップが有効化される距離。0に設定して無効化。
TrailingStep トレーリングストップを更新するときに適用する最小増分。
BreakEven ストップをエントリー価格に移動するために必要な距離。0に設定して無効化。

使用上の注意

  • 戦略は単一のローソク足フィードをサブスクライブし、StockSharp高レベルAPIガイドラインで要求される完成したローソク足にのみ 依存します。
  • デフォルトのリスク管理距離は価格単位で表されます。取引される銘柄のティックサイズに応じて調整してください。
  • 注文は成行コマンド経由で送信されるため、バックテストでのフィル価格はローソク足のクローズ(またはキューに入れられた シグナルの場合は始値)と仮定されます。

変換メモ

  • 元のMQL5エキスパートは外部のTRIX ARROWSインジケーター(コード19056)を使用します。変換はカスタムバッファに依存せず、 StockSharpのExponentialMovingAverageインスタンスと変化率ロジックを使用して同じ計算を再構築します。
  • MT5のリスク管理はブローカー側のストップと指値注文に依存していました。StockSharpでは、ローソク足の極値を監視して成行注文を 発行することで保護的な決済を再現します。
  • アラート、サウンド通知、ブローカー固有のパラメーターは中核的な取引ロジックの一部ではないため省略されました。
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;
	}
}