GitHub で見る

デュアルストップロス戦略

この戦略は、MetaTrader エキスパート Dual StopLoss.mq4 の動作を再現します。これはリスク管理レイヤーとして機能します。オープンポジションに付けられた保護的なストップロス注文を監視し、ストップがトリガーされる数ポイント前にそれらのポジションをクローズします。早期終了は、トレーダーの最初のストップ位置を尊重しながら、非常にボラティリティの高い動きでのマイナスのスリッページを回避するように設計されています。

仕組み

  1. この戦略は、レベル 1 データをサブスクライブして、ブローカーによって公開された現在の最高の買値/売値と StopLevel (または同等の) 距離を追跡します。
  2. 新しい価格が到着するか注文/取引が変更されるたびに、管理対象証券に属する最も近いアクティブな逆指値注文を検索します。
  3. 市場価格とその保護ストップの間の距離が、構成可能なしきい値と比較されます。
    • しきい値 = WhenToClosePoints × pointValue + stopLevelDistance
    • pointValue は、MetaTrader の Point と一致します (ほとんどの FX ペアでは 0.0001、セキュリティ設定から自動検出されます)。
    • stopLevelDistance は、利用可能な場合はレベル 1 フィールド (StopLevelMinStopPriceStopPrice、または StopDistance) から取得され、それ以外の場合はゼロです。
  4. 残りの距離がしきい値以下の場合、ポジションは成行注文を使用して直ちにクローズされます。

このロジックはロングポジションとショートポジションの両方をカバーします。ロングポジションの場合、最良の買い値が売りストップ価格と比較されます。ショートポジションの場合、最良のアスクが買いストップ価格と比較されます。アクティブ状態のストップ注文とストップリミット注文のみが考慮されます。

パラメーター

パラメータ 説明
ポイントを閉じるタイミング 早期終了をトリガーする停止レベルからの距離 (MetaTrader ポイント)。デフォルト: 10。ブローカーの最小ストップレベル距離のみに依存するには、0 に設定します。

注意事項と制限事項

  • この戦略は、それ自体でポジションをオープンするわけではありません。すでに存在し、保護的なストップ注文があるポジションのみを管理します。
  • ブローカーが課す最小距離を考慮する場合は、基盤となるコネクタ/ブローカーがレベル 1 データを通じてストップ レベル値を提供していることを確認してください。その情報が欠落している場合でも、設定されたポイント距離のみを使用して戦略は機能します。
  • StartProtection() の呼び出しにより、StockSharp に組み込まれた安全装置が有効になり、戦略が開始された後も非常口がアクティブなままになります。
  • ストップはストラテジーの Orders コレクションから検出されます。保護ストップがこのリストに表示されるように、同じ戦略インスタンスを通じて登録されていることを確認してください。
  • 同じ方向に複数の逆指値注文が存在する場合、市場に最も近い逆指値注文が使用されます。

使い方のヒント

  1. 戦略をポートフォリオ/証券に添付します。ポジションは手動または別のシステムによってオープンされますが、保護ストップは同じ戦略コンテキストの下に配置されます。
  2. 停止前に必要なクッションの量に合わせて WhenToClosePoints を設定します。この値は、MetaTrader とまったく同じように解釈されます (価格単位ではなくポイント)。
  3. 戦略を開始し、ログを監視します。市場価格がストップに近づくと、この戦略はポジションを積極的に閉じる成行注文を発行します。
  4. このモジュールを他のエントリーまたはポジションサイジング戦略と組み合わせて、完全な取引ワークフローを作成します。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Dual Stoploss strategy: dual SMA crossover with confirmation.
/// Buys when fast SMA crosses above mid SMA and mid is above slow, sells on opposite.
/// </summary>
public class DualStoplossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _midPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal _prevFast;
	private decimal _prevMid;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int MidPeriod { get => _midPeriod.Value; set => _midPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public DualStoplossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast SMA", "Fast SMA period", "Indicators");
		_midPeriod = Param(nameof(MidPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Mid SMA", "Mid SMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow SMA", "Slow SMA period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevMid = 0;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var fast = new SimpleMovingAverage { Length = FastPeriod };
		var mid = new SimpleMovingAverage { Length = MidPeriod };
		var slow = new SimpleMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, mid, slow, ProcessCandle).Start();
	}

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

		if (_hasPrev)
		{
			if (_prevFast <= _prevMid && fastValue > midValue && midValue > slowValue && Position <= 0)
				BuyMarket();
			else if (_prevFast >= _prevMid && fastValue < midValue && midValue < slowValue && Position >= 0)
				SellMarket();
		}
		else
		{
			if (fastValue > midValue && midValue > slowValue && Position <= 0)
				BuyMarket();
			else if (fastValue < midValue && midValue < slowValue && Position >= 0)
				SellMarket();
		}

		_prevFast = fastValue;
		_prevMid = midValue;
		_hasPrev = true;
	}
}