GitHub で見る

ダイナミックストップロス

概要

オリジナルの MetaTrader エキスパート アドバイザー「ダイナミック ストップロス」は、それ自体で新しい取引を開始しません。代わりに、既存の市場ポジションを監視し、新しいローソク足が出現すると、最新価格から一定距離後ろに留まるように保護ストップロスの位置を変更します。 StockSharp ポートは同じ動作を維持します。完了したバーごとに、現在開いている側の保護ストップの再計算がトリガーされます。ポジションが存在しない場合、ストラテジーは新しいポジションが検出されるまでアイドル状態になります。

仕組み

  1. この戦略は、Candle Type パラメーター (デフォルトの 1 分の時間枠) で定義されたローソク足をサブスクライブします。
  2. ローソク足が閉じると、終値にユーザーが選択したポイント距離が乗算されます。距離は、MetaTrader スタイルのポイントから、Security.PriceStep を介して絶対価格デルタに変換されます (Security.Step にフォールバックし、次に 1 にフォールバックします)。
  3. ロングポジションがオープンしている場合、戦略は既存のストップ注文をキャンセルし、新しい売りストップを Close - Distance に設定します。
  4. ショートポジションがオープンしている場合、ストップは買いストップ注文を使用して Close + Distance に移動されます。
  5. ポジションが(手動またはストップ約定によって)クローズされると、期限切れのプロテクション注文を避けるためにトレーリング注文がキャンセルされます。

これにより、MQL バージョンと同じ常に再固定されたストップ距離が生成されます。つまり、ローソク足の変動に応じてストップが市場に近づいたり離れたりする可能性があります。

パラメーター

名前 デフォルト 説明
StopLossPoints 800 市場価格と保護ストップの間の距離 (商品ポイント単位で測定)。この値は終値に適用される前に、Security.PriceStep で乗算されます (Security.Step にフォールバックし、その後 1)。停止管理を無効にするには、0 に設定します。
CandleType TimeFrameCandle(00:01:00) ストップがいつ再計算されるかを定義するローソク足のタイプ。 MetaTrader で使用されているチャートと一致する時間枠を選択します。

使用上の注意

  • この戦略では、外部戦略、手動操作、またはその他のコンポーネントによって取引が開始されることを想定しています。ストップロスのみを管理します。
  • ポイントから価格への変換がブローカーのティック サイズと一致するように、セキュリティ メタデータ (PriceStepStep、ボリューム) が入力されていることを確認します。小数ピップでクォートされた商品は、適切なステップを公開する必要があります。
  • ストップはローソク足の終値ごとに再計算されるため、市場がポジションに反して動いた場合でも価格に従います。これは、OrderModify が常に最新の Bid/Ask から設定された距離を引いた値/プラスした値を使用する MetaTrader ロジックを反映しています。
  • 作成されたストップ注文は常に以前のストップ注文を置き換えて、プラットフォームを最新の保護レベルと同期させます。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Dynamic Stop Loss strategy: EMA trend with ATR-based dynamic stop management.
/// Enters on EMA trend direction, exits when price moves against by ATR distance.
/// </summary>
public class DynamicStopLossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _entryPrice;
	private decimal _stopPrice;
	private bool _prevAboveEma;
	private bool _hasPrevSignal;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public DynamicStopLossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for stop distance", "Indicators");
		_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
			.SetDisplay("ATR Multiplier", "ATR multiplier for stop distance", "Risk");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 12)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_stopPrice = 0m;
		_prevAboveEma = false;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_entryPrice = 0;
		_stopPrice = 0;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
	}

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

		var close = candle.ClosePrice;
		var stopDist = atr * AtrMultiplier;
		var aboveEma = close > ema;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (Position > 0)
		{
			var newStop = close - stopDist;
			if (newStop > _stopPrice) _stopPrice = newStop;
			if (close <= _stopPrice)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_candlesSinceTrade = 0;
				_prevAboveEma = aboveEma;
				_hasPrevSignal = true;
				return;
			}
		}
		else if (Position < 0)
		{
			var newStop = close + stopDist;
			if (newStop < _stopPrice || _stopPrice == 0) _stopPrice = newStop;
			if (close >= _stopPrice)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_candlesSinceTrade = 0;
				_prevAboveEma = aboveEma;
				_hasPrevSignal = true;
				return;
			}
		}

		if (_hasPrevSignal && aboveEma != _prevAboveEma && _candlesSinceTrade >= SignalCooldownCandles)
		{
			if (aboveEma && Position <= 0)
			{
				BuyMarket();
				_entryPrice = close;
				_stopPrice = close - stopDist;
				_candlesSinceTrade = 0;
			}
			else if (!aboveEma && Position >= 0)
			{
				SellMarket();
				_entryPrice = close;
				_stopPrice = close + stopDist;
				_candlesSinceTrade = 0;
			}
		}

		_prevAboveEma = aboveEma;
		_hasPrevSignal = true;
	}
}