GitHub で見る

e-TurboFx クラシック戦略

概要

e-TurboFx Classic 戦略は、MQL/7262/e-TurboFx.mq4 にある MetaTrader 4 エキスパート アドバイザの直接 C# ポートです。徐々に実体が大きくなる強いローソク足が連続した後の勢いの枯渇を検出し、反対方向にエントリーします。 StockSharp バージョンでは、キャンドル サブスクリプション、自動保護命令、UI フレンドリーなパラメーターを備えた高レベルの戦略 API が使用されます。

取引ロジック

  1. 構成されたキャンドル タイプをサブスクライブし、完成したキャンドルのみを検査します。
  2. 膨張を検出するには、キャンドル本体のサイズ (|close - open|) を測定します。
  3. 2 つのカウンターを維持します。
    • 弱気シーケンス – 前の弱気ローソク足よりも実体が大きい連続した弱気ローソク足をカウントします。
    • 強気のシーケンス – 前の強気のローソク足よりも実体が大きい連続した強気のローソク足をカウントします。
  4. ドージ (オープンとクローズが等しい) が表示されたとき、またはポジションがすでにオープンしているときは、両方のシーケンスをリセットします。これは、一度に 1 つの取引のみを維持する元の EA の動作を模倣しています。
  5. ロングエントリー: 弱気シーケンスの長さが設定された SequenceLength に達すると、成行買い注文を送信し、すぐにカウンターをリセットします。
  6. ショートエントリー: 強気のシーケンスの長さが SequenceLength に達したら、成行売り注文を送信し、カウンターをリセットします。
  7. オプションのストップロスとテイクプロフィットのレベルは、ポイント距離から StockSharp の価格ステップに変換されます。

したがって、アルゴリズムは、各ローソク足が同じ方向に加速する降伏のような動きを待ちます。次の反転注文は、その極端な勢いを弱めようとします。

実装の詳細

  • SubscribeCandles().Bind(ProcessCandle) を使用して、インジケーターを手動で管理せずに完成したキャンドルを処理します。
  • StartProtection と統合されているため、ストップロスとテイクプロフィットの距離が取引所の価格ステップ (UnitTypes.Step) に変換されます。
  • パラメータは Param(...) を通じて登録されるため、UI に表示され、最適化できます。
  • この戦略は、有効な PriceStep を公開するあらゆる手段で機能します。それ以外の場合、停止/ターゲット距離は 0 のままでなければなりません。
  • ポジションがアクティブである間は、注文のスタックを拒否した元の MQL スクリプトと同様に、シグナル検出が一時停止され、内部カウンターがクリアされます。

パラメーター

パラメータ 説明 デフォルト
SequenceLength エントリーをトリガーするために必要な、拡張ボディを持つ連続して完成したキャンドルの数。 3
TakeProfitSteps 価格ステップ (ティック) で測定される利食い距離。 0 はターゲットを無効にします。 120
StopLossSteps 価格ステップ (ティック) で測定されるストップロス距離。 0 は停止を無効にします。 70
TradeVolume 市場参入のボリューム。これを変更すると、Volume プロパティが即座に更新されます。 0.1
CandleType 分析に使用されるローソク足の時間枠。デフォルトは 1 時間足のローソク足です。 1 hour

使用上の注意

  • この戦略ではクリーンなローソク足データが期待されます。インストゥルメントまたはタイムフレームを切り替えると、カウンターが新しいローソク足のみを反映するようにキャッシュが再構築されます。
  • このシステムは厳密な実体拡張に依存しているため、小さなローソク体または同等のローソク体はシーケンスをリセットします。ノイズの多い時間枠で取引する場合は、SequenceLength を調整します。
  • 複数のタイムフレームと出来高の組み合わせをバックテストして、スプレッドやスリッページを補うほど頻繁に消尽の動きがある商品を見つけます。
  • ライブ取引を有効にする前に、必ずサンドボックス環境で動作を検証してください。
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>
/// Momentum exhaustion strategy converted from the original e-TurboFx MQL4 expert adviser.
/// </summary>
public class ETurboFxClassicStrategy : Strategy
{
	private readonly StrategyParam<int> _sequenceLength;
	private readonly StrategyParam<decimal> _takeProfitSteps;
	private readonly StrategyParam<decimal> _stopLossSteps;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private int _bearishSequence;
	private int _bullishSequence;
	private decimal _previousBearishBody;
	private decimal _previousBullishBody;

	/// <summary>
	/// Number of consecutive candles required to trigger a signal.
	/// </summary>
	public int SequenceLength
	{
		get => _sequenceLength.Value;
		set => _sequenceLength.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps (ticks).
	/// A value of zero disables the take profit order.
	/// </summary>
	public decimal TakeProfitSteps
	{
		get => _takeProfitSteps.Value;
		set => _takeProfitSteps.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps (ticks).
	/// A value of zero disables the protective stop.
	/// </summary>
	public decimal StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Order volume sent with each market entry.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set
		{
			_tradeVolume.Value = value;
			Volume = value;
		}
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="ETurboFxClassicStrategy" /> class.
	/// </summary>
	public ETurboFxClassicStrategy()
	{
		_sequenceLength = Param(nameof(SequenceLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Sequence Length", "Number of consecutive finished candles analysed for pattern detection", "Trading Rules")
			
			.SetOptimize(2, 6, 1);

		_takeProfitSteps = Param(nameof(TakeProfitSteps), 120m)
			.SetNotNegative()
			.SetDisplay("Take Profit (steps)", "Take profit distance in price steps (ticks)", "Risk Management")
			
			.SetOptimize(60m, 180m, 20m);

		_stopLossSteps = Param(nameof(StopLossSteps), 70m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (steps)", "Stop loss distance in price steps (ticks)", "Risk Management")
			
			.SetOptimize(40m, 120m, 10m);

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume used for market entries", "Trading Rules")
			
			.SetOptimize(0.1m, 0.5m, 0.1m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame of the candles analysed by the strategy", "Market Data");
	}

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

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

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

		ResetState();
		Volume = TradeVolume;

		var takeProfitUnit = CreateStepUnit(TakeProfitSteps);
		var stopLossUnit = CreateStepUnit(StopLossSteps);

		if (takeProfitUnit != null || stopLossUnit != null)
		{
			// Configure protection block only once when the strategy starts.
			StartProtection(
				takeProfit: takeProfitUnit,
				stopLoss: stopLossUnit,
				isStopTrailing: false,
				useMarketOrders: true);
		}

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

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

	private Unit CreateStepUnit(decimal steps)
	{
		if (steps <= 0)
			return null;

		return new Unit(steps, UnitTypes.Absolute);
	}

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

		// no indicators to check

		if (Position != 0)
		{
			// Ignore new signals while a position is active and rebuild the sequences afterwards.
			ResetState();
			return;
		}

		var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);

		if (candle.ClosePrice < candle.OpenPrice)
		{
			HandleBearishCandle(bodySize);
		}
		else if (candle.ClosePrice > candle.OpenPrice)
		{
			HandleBullishCandle(bodySize);
		}
		else
		{
			// Flat candles break both sequences because momentum stalled.
			ResetState();
		}
	}

	private void HandleBearishCandle(decimal bodySize)
	{
		ResetBullishSequence();

		if (bodySize <= 0)
		{
			ResetBearishSequence();
			return;
		}

		if (_bearishSequence == 0 || bodySize > _previousBearishBody)
		{
			// Body expanded compared to the previous bearish candle.
			_bearishSequence++;
		}
		else
		{
			// Restart the sequence when the body fails to expand.
			_bearishSequence = 1;
		}

		_previousBearishBody = bodySize;

		if (_bearishSequence >= SequenceLength)
		{
			// A string of expanding bearish candles hints a bullish reversal.
			BuyMarket();
			ResetBearishSequence();
		}
	}

	private void HandleBullishCandle(decimal bodySize)
	{
		ResetBearishSequence();

		if (bodySize <= 0)
		{
			ResetBullishSequence();
			return;
		}

		if (_bullishSequence == 0 || bodySize > _previousBullishBody)
		{
			// Body expanded compared to the previous bullish candle.
			_bullishSequence++;
		}
		else
		{
			// Restart the sequence when the body fails to expand.
			_bullishSequence = 1;
		}

		_previousBullishBody = bodySize;

		if (_bullishSequence >= SequenceLength)
		{
			// A string of expanding bullish candles hints a bearish reversal.
			SellMarket();
			ResetBullishSequence();
		}
	}

	private void ResetBearishSequence()
	{
		_bearishSequence = 0;
		_previousBearishBody = 0m;
	}

	private void ResetBullishSequence()
	{
		_bullishSequence = 0;
		_previousBullishBody = 0m;
	}

	private void ResetState()
	{
		ResetBearishSequence();
		ResetBullishSequence();
	}
}