ダイナミックストップロス
概要
オリジナルの MetaTrader エキスパート アドバイザー「ダイナミック ストップロス」は、それ自体で新しい取引を開始しません。代わりに、既存の市場ポジションを監視し、新しいローソク足が出現すると、最新価格から一定距離後ろに留まるように保護ストップロスの位置を変更します。 StockSharp ポートは同じ動作を維持します。完了したバーごとに、現在開いている側の保護ストップの再計算がトリガーされます。ポジションが存在しない場合、ストラテジーは新しいポジションが検出されるまでアイドル状態になります。
仕組み
- この戦略は、
Candle Typeパラメーター (デフォルトの 1 分の時間枠) で定義されたローソク足をサブスクライブします。 - ローソク足が閉じると、終値にユーザーが選択したポイント距離が乗算されます。距離は、MetaTrader スタイルのポイントから、
Security.PriceStepを介して絶対価格デルタに変換されます (Security.Stepにフォールバックし、次に1にフォールバックします)。 - ロングポジションがオープンしている場合、戦略は既存のストップ注文をキャンセルし、新しい売りストップを
Close - Distanceに設定します。 - ショートポジションがオープンしている場合、ストップは買いストップ注文を使用して
Close + Distanceに移動されます。 - ポジションが(手動またはストップ約定によって)クローズされると、期限切れのプロテクション注文を避けるためにトレーリング注文がキャンセルされます。
これにより、MQL バージョンと同じ常に再固定されたストップ距離が生成されます。つまり、ローソク足の変動に応じてストップが市場に近づいたり離れたりする可能性があります。
パラメーター
| 名前 | デフォルト | 説明 |
|---|---|---|
StopLossPoints |
800 |
市場価格と保護ストップの間の距離 (商品ポイント単位で測定)。この値は終値に適用される前に、Security.PriceStep で乗算されます (Security.Step にフォールバックし、その後 1)。停止管理を無効にするには、0 に設定します。 |
CandleType |
TimeFrameCandle(00:01:00) |
ストップがいつ再計算されるかを定義するローソク足のタイプ。 MetaTrader で使用されているチャートと一致する時間枠を選択します。 |
使用上の注意
- この戦略では、外部戦略、手動操作、またはその他のコンポーネントによって取引が開始されることを想定しています。ストップロスのみを管理します。
- ポイントから価格への変換がブローカーのティック サイズと一致するように、セキュリティ メタデータ (
PriceStep、Step、ボリューム) が入力されていることを確認します。小数ピップでクォートされた商品は、適切なステップを公開する必要があります。 - ストップはローソク足の終値ごとに再計算されるため、市場がポジションに反して動いた場合でも価格に従います。これは、
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class dynamic_stop_loss_strategy(Strategy):
def __init__(self):
super(dynamic_stop_loss_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 100) \
.SetDisplay("EMA Period", "EMA trend period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for stop distance", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 1.5) \
.SetDisplay("ATR Multiplier", "ATR multiplier for stop distance", "Risk")
self._signal_cooldown = self.Param("SignalCooldownCandles", 12) \
.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading")
self._ema = None
self._atr = None
self._entry_price = 0.0
self._stop_price = 0.0
self._prev_above_ema = False
self._has_prev_signal = False
self._candles_since_trade = 0
@property
def ema_period(self):
return self._ema_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def atr_multiplier(self):
return self._atr_multiplier.Value
@property
def signal_cooldown(self):
return self._signal_cooldown.Value
def OnReseted(self):
super(dynamic_stop_loss_strategy, self).OnReseted()
self._ema = None
self._atr = None
self._entry_price = 0.0
self._stop_price = 0.0
self._prev_above_ema = False
self._has_prev_signal = False
self._candles_since_trade = self.signal_cooldown
def OnStarted2(self, time):
super(dynamic_stop_loss_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = self.ema_period
self._atr = AverageTrueRange()
self._atr.Length = self.atr_period
self._entry_price = 0.0
self._stop_price = 0.0
self._has_prev_signal = False
self._candles_since_trade = self.signal_cooldown
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(30)))
subscription.Bind(self._ema, self._atr, self._process_candle)
subscription.Start()
def _process_candle(self, candle, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self._ema.IsFormed or not self._atr.IsFormed:
return
close = float(candle.ClosePrice)
ema_val = float(ema_value)
atr_val = float(atr_value)
stop_dist = atr_val * self.atr_multiplier
above_ema = close > ema_val
if self._candles_since_trade < self.signal_cooldown:
self._candles_since_trade += 1
if self.Position > 0:
new_stop = close - stop_dist
if new_stop > self._stop_price:
self._stop_price = new_stop
if close <= self._stop_price:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
self._candles_since_trade = 0
self._prev_above_ema = above_ema
self._has_prev_signal = True
return
elif self.Position < 0:
new_stop = close + stop_dist
if new_stop < self._stop_price or self._stop_price == 0.0:
self._stop_price = new_stop
if close >= self._stop_price:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
self._candles_since_trade = 0
self._prev_above_ema = above_ema
self._has_prev_signal = True
return
if self._has_prev_signal and above_ema != self._prev_above_ema and self._candles_since_trade >= self.signal_cooldown:
if above_ema and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._stop_price = close - stop_dist
self._candles_since_trade = 0
elif not above_ema and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._stop_price = close + stop_dist
self._candles_since_trade = 0
self._prev_above_ema = above_ema
self._has_prev_signal = True
def CreateClone(self):
return dynamic_stop_loss_strategy()