GitHub で見る

e-TurboFx Steps戦略

概要

e-TurboFx戦略は、もともとMetaTrader 5向けに書かれたモメンタム枯渇リバーサルシステムです。最近完成したローソク足を監視し、ローソク足のボディが同じ方向に拡大し続けるシーケンスを探します。増加する一連の弱気ローソク足は投げ売りを示し、潜在的なロングセットアップとなります。一方、増加する一連の強気ローソク足は潜在的なショートの機会を告げます。StockSharpポートはローソク足サブスクリプションと自動化されたポジション保護を持つ高レベルAPIを使用します。

取引ロジック

  • 選択されたCandleTypeの最後のDepthAnalysis本の完成したローソク足を検査します。
  • 始値より低く終値した連続するローソク足(弱気)の数と、始値より高く終値した連続するローソク足(強気)の数を数えます。
  • ボディサイズの進行を追跡します:シーケンス内の各新しいローソク足は前のものより絶対的に大きなボディを持たなければなりません。この条件が失敗すると、シーケンスはリセットされます。
  • ロングエントリー: DepthAnalysis本の連続した弱気ローソク足で厳密に拡大するボディがあれば成行買いをトリガーします。ただし、現在ポジションが開いていないことが条件です。
  • ショートエントリー: DepthAnalysis本の連続した強気ローソク足で厳密に拡大するボディがあれば成行売りをトリガーします。同様に、ポジションがフラットの時のみです。
  • ポジションがアクティブな間、戦略はトレードの積み重ねを避けるためシグナル検出を一時停止します。リスク管理は開始時に設定された組み込み保護ブロックに委任されます。

ポジション管理

  • StartProtectionは価格ステップ(取引所のティック)で測定された距離を使用してストップロスとテイクプロフィットの注文を自動的に登録します。距離をゼロに設定すると対応する保護注文が無効になります。
  • 戦略は常に1つのオープンポジションのみを保持します。前のトレードが閉じた後に新しいシグナルが現れると、ローソク足シーケンスは新鮮なマーケットデータに基づいてゼロから再構築されます。
  • マーケットエントリーはTradeVolumeパラメーターを使用します。UIでパラメーターを変更すると、戦略のボリュームがすぐに更新されます。

パラメーター

パラメーター 説明 デフォルト
DepthAnalysis 拡張パターンを検証するために使用される最近完成したローソク足の数。値が高いほど取引前により長い連続が必要です。 3
TakeProfitSteps 取引所価格ステップ(ティック)でのテイクプロフィット距離。0はテイクプロフィットを無効にします。 120
StopLossSteps 取引所価格ステップ(ティック)でのストップロス距離。0はストップロスを無効にします。 70
TradeVolume 各マーケットエントリーで送信される注文ボリューム。 0.1
CandleType 分析のためにサブスクライブされるローソク足データタイプ(時間軸)。 1時間時間軸

すべての数値パラメーターには最適化メタデータがあり、必要に応じてStockSharpの最適化に含めることができます。

注意事項と推奨事項

  • 元のMQL5エキスパートアドバイザーはすべてのティックでローソク足データを再計算しました。StockSharpの実装は完成したローソク足イベントと内部カウンターで同じ動作を実現します。
  • 戦略はローソク足のボディ比較に依存するため、選択した時間軸に対して敏感です。時間軸が短いほど多くのシグナルが生成されますが、より厳密なストップが必要になる場合があります。
  • ストップロスとテイクプロフィットの距離がステップで定義されたものから価格に正しく変換されるように、接続されたインストゥルメントが有効なPriceStepを公開していることを確認してください。
  • ライブ取引を実行する前に、Designer/Backtesterで動作を検証して、ストップと目標距離が選択したインストゥルメントに合っていることを確認してください。
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 reversal strategy that tracks consecutive candles with expanding bodies.
/// </summary>
public class ETurboFxStepsStrategy : Strategy
{
	private readonly StrategyParam<int> _depthAnalysis;
	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 recent candles analysed for momentum confirmation.
	/// </summary>
	public int DepthAnalysis
	{
		get => _depthAnalysis.Value;
		set => _depthAnalysis.Value = value;
	}

	/// <summary>
	/// Take profit distance measured 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 measured in price steps (ticks).
	/// A value of zero disables the protective stop.
	/// </summary>
	public decimal StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Volume used when sending market orders.
	/// </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="ETurboFxStepsStrategy" /> class.
	/// </summary>
	public ETurboFxStepsStrategy()
	{
		_depthAnalysis = Param(nameof(DepthAnalysis), 3)
			.SetGreaterThanZero()
			.SetDisplay("Depth Analysis", "Number of finished candles used 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 entries", "Trading Rules")
			
			.SetOptimize(0.1m, 0.5m, 0.1m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe 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 protective orders once the strategy starts.
			StartProtection(stopLossUnit, takeProfitUnit);
		}

		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;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position != 0)
		{
			// Do not look for new signals while a position is active.
			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
		{
			// Neutral candle breaks both sequences.
			ResetState();
		}
	}

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

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

		if (_bearishSequence == 0 || bodySize > _previousBearishBody)
		{
			// Body is larger than the previous bearish candle, extend the sequence.
			_bearishSequence++;
		}
		else
		{
			// Sequence restarts because body did not expand.
			_bearishSequence = 1;
		}

		_previousBearishBody = bodySize;

		if (_bearishSequence >= DepthAnalysis)
		{
			// Expanding bearish bodies suggest exhaustion that can trigger a long entry.
			BuyMarket();
			ResetBearishSequence();
		}
	}

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

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

		if (_bullishSequence == 0 || bodySize > _previousBullishBody)
		{
			// Body is larger than the previous bullish candle, extend the sequence.
			_bullishSequence++;
		}
		else
		{
			// Sequence restarts because body did not expand.
			_bullishSequence = 1;
		}

		_previousBullishBody = bodySize;

		if (_bullishSequence >= DepthAnalysis)
		{
			// Expanding bullish bodies suggest potential reversal to the downside.
			SellMarket();
			ResetBullishSequence();
		}
	}

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

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

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