GitHub で見る

ダブラー ヘッジ・トレーリング戦略

概要

ダブラー ヘッジ・トレーリング戦略は、MetaTrader 5のエキスパートアドバイザーDoubler.mq5のStockSharp高レベルAPI変換です。アルゴリズムはエクスポージャーが存在しないときに即座に対称的なロングとショートの成行ポジションを開き、次に独立したストップロス、テイクプロフィット、トレーリングストップのルールで両方のレッグを管理します。変換は元のMQLプログラムのヘッジング動作を保持しながら、リスク管理をStockSharpのプリミティブ(成行注文、Level1サブスクリプション、戦略パラメーター)に適応させます。

方向性戦略とは異なり、システムは各レッグが自身の保護ロジックで退出するまで両方向をアクティブに保ちます。両方のレッグがフラットになると、戦略はヘッジを再作成し、継続的に対になったエクスポージャーを維持します。

主な特徴

  • 自動ヘッジング – 戦略にアクティブなポジションがないときに、同じボリュームの買い注文と売り注文を1つずつ開きます。
  • Pipベースのリスク管理 – ストップロス、テイクプロフィット、トレーリングオフセットはpipsで設定され、証券の価格ステップと小数点精度を検査することで内部的に価格ステップに変換されます(3/5桁の銘柄は自動的に10倍に拡大されます)。
  • レッグごとの独立したトレーリング – 各レッグは現在の最良bid/askを追跡します。価格がTrailingStopPips + TrailingStepPipsを超えて有利に動くと、トレーリングステップ条件を尊重しながらストップレベルがTrailingStopPips分進みます。これは元のEAロジックを正確に反映します。
  • ボリューム検証 – 注文ボリュームはMinVolumeMaxVolumeVolumeStepに対して検証され、要求されたサイズが取引所の制約に違反する場合は例外を発生させます。
  • オプションの診断LogTradeDetailsフラグはテスト中またはライブモニタリング中に役立つ詳細な情報メッセージ(エントリー、エグジット、トレーリング調整)を有効にします。

パラメーター

パラメーター 説明 デフォルト 注意
OrderVolume 各ヘッジレッグのボリューム(買い注文と売り注文)。 1 取引所のボリューム制限を尊重する必要があります;最も近いVolumeStepに正規化されます。
StopLossPips pipsでのストップロス距離。 150 0はストップロスを無効にします。
TakeProfitPips pipsでのテイクプロフィット距離。 300 0はテイクプロフィットを無効にします。
TrailingStopPips pipsでのトレーリングストップ距離。 5 ゼロより大きい場合、TrailingStepPipsも正である必要があります。
TrailingStepPips トレーリングストップが進む前の最小追加移動。 5 ストップが頻繁に動きすぎるのを防ぐガードレール。
LogTradeDetails 執行とトレーリング更新の詳細ログを有効にします。 false デバッグ実行のためにtrueに設定します。

トレードロジック

エントリー

  1. Level1更新(最良bid/ask)をサブスクライブします。
  2. _longPosition_shortPositionの両方がnullで、保留中のエントリー注文がない場合、2つの成行注文を登録します:OrderVolumeをそれぞれの買い注文と売り注文。
  3. 執行が確認された後、戦略はエントリー価格、初期ストップ/テイクレベルを記録し、トレーリングトラッカーをリセットします。

リスク管理

  • ストップロス – 各レッグの初期ストップはエントリー価格からStopLossPipsの距離に配置されます。ストップ距離が0の場合は保護ストップが完全に無効になります。
  • テイクプロフィットTakeProfitPipsでの対称的なテイクプロフィット。値が0の場合は利益目標を無効にします。
  • 強制クローズNormalizeVolumeが無効なサイズ(小さすぎる/大きすぎる、またはVolumeStepと一致しない)を検出した場合、戦略は無効な注文の送信を防ぐために例外を発生させます。

トレーリングストップの動作

  1. 価格が少なくともTrailingStopPips + TrailingStepPips有利に動くと、ストップはcurrentPrice ± TrailingStopPipsに進みます。
  2. トレーリングステップチェックはMQL条件を再現します:ストップは新しいレベルが既存のストップよりも少なくともTrailingStepPips分価格に近い場合、またはまだストップが存在しない場合にのみ動きます。
  3. ロングポジションの場合は最良bidを参照価格として使用し、ショートポジションの場合は最良askを使用して、エグジットレベルが現実的な執行価格を反映するようにします。

エグジット

  • 各レッグはストップロス、トレーリングストップ、またはテイクプロフィットの条件が満たされると独立して退出します。エグジット注文は成行注文として登録され、レッグがフラットになると内部状態がクリアされます。
  • 両方のレッグが閉じられた後、次のLevel1更新が新しいヘッジペアを起動します。

データ要件

  • Level1(最良bid/ask) – エントリー価格のスナップショット、トレーリング計算、エグジットトリガーに必要です。
  • ローソク足やトレードのサブスクリプションは必要ありません;戦略はLevel1更新にのみ反応します。

変換に関する注意

  • pip距離は証券PriceStepで乗算することで絶対価格オフセットに変換されます。3桁または5桁で引用される銘柄は自動的に×10の調整を受け、MetaTraderのエキスパートで使用されるpip定義と一致します。
  • 戦略はStockSharpの高レベルStrategyメソッド(RegisterOrderStartProtectionSubscribeLevel1)に依存し、低レベルのコネクター操作を避けます。
  • ヘッジングはブローカー/ポートフォリオがネットポジションを使用している場合でも、ロングとショートのレッグが追跡されるよう内部PositionStateオブジェクトを通じて実装されています。
  • 変換は自己完結しており、リポジトリのテストハーネスを変更または必要としません。
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>
/// Doubler strategy using double EMA confirmation with trailing stop management.
/// Enters long when both fast and medium EMAs are above slow EMA.
/// Enters short when both fast and medium EMAs are below slow EMA.
/// </summary>
public class DoublerStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _medPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _med;
	private ExponentialMovingAverage _slow;

	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Medium EMA period.
	/// </summary>
	public int MedPeriod
	{
		get => _medPeriod.Value;
		set => _medPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public DoublerStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_medPeriod = Param(nameof(MedPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Medium Period", "Medium EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 150)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

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

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

		_fast = null;
		_med = null;
		_slow = null;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_med = new ExponentialMovingAverage { Length = MedPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _med, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal medValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_fast.IsFormed || !_med.IsFormed || !_slow.IsFormed)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 100;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 100;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 100;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 100;
				return;
			}
		}

		// Double confirmation: both fast and med above slow for long
		if (fastValue > slowValue && medValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 100;
		}
		// Double confirmation: both fast and med below slow for short
		else if (fastValue < slowValue && medValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 100;
		}
	}
}