GitHub で見る

Tunnel Gen4 ヘッジ・グリッド戦略

この戦略は、StockSharpの高レベルAPIを使用してMetaTrader「Tunnel gen4」エキスパートアドバイザーのロジックを再現します。最初の買い/売りペアを開くことでマーケットニュートラルなヘッジを維持し、価格が設定可能な数のピップを移動したら突破方向にポジションを倍増し、第2アンカーを超えて同じ距離が再びカバーされたときにバスケット全体から出ます。

取引ロジック

  • 初期ヘッジ: エクスポージャーが存在しなくなると、戦略はボリューム StartVolume で同時の市場買い注文と売り注文を送信します。最初の約定がすべての後続の決定の参照価格を定義します。
  • ステップ検出: 設定された StepPips は、インストルメントのティックサイズを使用して価格オフセットに変換されます(3桁および5桁のforex見積もりに対する自動調整あり)。Level 1ストリームからの最良bid/askの更新がこのオフセットと比較されます。
  • 補強注文: 最良bidが最初の約定から少なくとも1ステップ上昇すると、ベースボリュームの2倍の売り注文が送信されます。最良askが少なくとも1ステップ下落すると、同じサイズの買い注文が代わりに発行されます。この注文の最初の約定が第2アンカーになります。
  • サイクル終了: 第2アンカーがアクティブになった後、いずれかの方向へのさらなるステップサイズの移動がすべてのオープンポジションの完全清算を引き起こします。両側が閉じられると、状態がリセットされ、新しいサイクルを開始できます。
  • ボリューム検証: 戦略の起動時に、初期ボリュームと倍増ボリュームの両方がインストルメントの最小、最大、増分要件を満たしていることを確認し、コネクターに送信されたすべての注文が実行可能であることを保証します。

エントリー条件

ロング補強

  • 初期ヘッジから少なくとも1つのオープンポジションがある。
  • 第2アンカーがまだ作成されていない。
  • 現在の最良ask価格が first_fill_price - StepPips_in_price 以下である。

ショート補強

  • 初期ヘッジから少なくとも1つのオープンポジションがある。
  • 第2アンカーがまだ作成されていない。
  • 現在の最良bid価格が first_fill_price + StepPips_in_price 以上である。

エグジット管理

  • バスケットクローズ: 第2アンカーが定義された後、最良bidが second_anchor + StepOffset を超えて上昇するか、最良askが second_anchor - StepOffset を下回って落ちると、累積ロングおよびショートエクスポージャーをクローズするために成行注文が送信されます。クローズ注文はすべての取引が確認された後にのみ状態がリセットされることを保証するために追跡されます。
  • 状態リセット: 両側がクローズされ、アクティブなクローズ注文が残っていない後、戦略は内部アンカーをクリアし、新しいヘッジが開かれるのを待ちます。

データとインジケーター

  • Level 1サブスクリプションはステップ比較に使用される最良bidおよびask価格を配信します。
  • 追加のインジケーターは不要です。ロジック全体が生の見積もり更新で機能します。
  • 価格ステップ変換はMetaTraderのポイント対ピップ調整を模倣するため、3桁または5桁のforexシンボルはソースエキスパートと同じ動作をします。

パラメーター

パラメーター 説明
StartVolume 初期ヘッジを形成する買い注文と売り注文のボリューム。
StepPips 補強注文と後続のバスケット出口を引き起こすピップでの距離。

実装上の注意

  • StockSharpはアセットごとのネットポジションを維持します。戦略はMetaTraderエキスパートが使用した別々のロングおよびショートチケットをエミュレートするために内部エクスポージャーカウンターを保持し、バスケットをクローズするときに累積ボリュームで成行注文を発行します。
  • ロジックはリアルタイムのスプレッドに依存するため、バックテストとライブ取引セッションの両方でLevel 1データを提供してください。bid/ask情報がないと取引ループが無効になります。
  • 出口条件が満たされるまでヘッジの両側が共存できると仮定しているため、取引口座が同じインストルメントに対して同時の買い注文と売り注文をサポートしていることを確認してください。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Tunnel strategy that uses Bollinger Bands to define a price channel.
/// Buys when price crosses above the lower band (reversal from oversold),
/// sells when price crosses below the upper band (reversal from overbought).
/// </summary>
public class TunnelGen4Strategy : Strategy
{
	private readonly StrategyParam<int> _bbLength;
	private readonly StrategyParam<decimal> _bbWidth;
	private readonly StrategyParam<decimal> _stepPips;

	private BollingerBands _bb;

	private decimal _prevClose;
	private decimal _prevUpper;
	private decimal _prevLower;
	private decimal _entryPrice;

	/// <summary>
	/// Bollinger Bands period length.
	/// </summary>
	public int BbLength
	{
		get => _bbLength.Value;
		set => _bbLength.Value = value;
	}

	/// <summary>
	/// Bollinger Bands width (standard deviations).
	/// </summary>
	public decimal BbWidth
	{
		get => _bbWidth.Value;
		set => _bbWidth.Value = value;
	}

	/// <summary>
	/// Step distance expressed in pips for profit target.
	/// </summary>
	public decimal StepPips
	{
		get => _stepPips.Value;
		set => _stepPips.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public TunnelGen4Strategy()
	{
		_bbLength = Param(nameof(BbLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Length", "Bollinger Bands period", "Indicator");

		_bbWidth = Param(nameof(BbWidth), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("BB Width", "Bollinger Bands width", "Indicator");

		_stepPips = Param(nameof(StepPips), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Step (pips)", "Distance between tunnel anchors", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_bb = null;
		_prevClose = 0;
		_prevUpper = 0;
		_prevLower = 0;
		_entryPrice = 0;
	}

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

		_bb = new BollingerBands
		{
			Length = BbLength,
			Width = BbWidth
		};

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.BindEx(_bb, OnProcess);
		subscription.Start();
	}

	private void OnProcess(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (BollingerBandsValue)value;
		if (bb.UpBand is not decimal upper ||
			bb.LowBand is not decimal lower)
			return;

		if (!_bb.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			_prevUpper = upper;
			_prevLower = lower;
			return;
		}

		var close = candle.ClosePrice;

		// Buy signal: price crosses above lower band from below
		if (_prevClose < _prevLower && close >= lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
		}
		// Sell signal: price crosses below upper band from above
		else if (_prevClose > _prevUpper && close <= upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
		}

		// Exit on profit target if in position
		if (Position > 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice + StepPips * pipValue;
			if (close >= target)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice - StepPips * pipValue;
			if (close <= target)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		_prevClose = close;
		_prevUpper = upper;
		_prevLower = lower;
	}
}