El asesor experto original MetaTrader "Dynamic Stop Loss" no abre nuevas operaciones por sí solo. En lugar de eso, observa las posiciones existentes en el mercado y, una vez que aparece una nueva vela, reposiciona el stop-loss protector para que permanezca a una distancia fija detrás del último precio. El puerto StockSharp mantiene el mismo comportamiento: cada barra completa activa un nuevo cálculo de la parada de protección para cualquier lado que esté actualmente abierto. Si no existe ninguna posición, la estrategia simplemente permanece inactiva hasta que se detecta una nueva posición.
como funciona
La estrategia se suscribe a velas definidas por el parámetro Candle Type (período de tiempo predeterminado de 1 minuto).
Cuando se cierra una vela, el precio de cierre se multiplica por la distancia del punto seleccionado por el usuario. La distancia se convierte de puntos de estilo MetaTrader en un delta de precio absoluto a través de Security.PriceStep (retroceso a Security.Step, luego a 1).
Si se abre una posición larga, la estrategia cancela cualquier orden stop existente y coloca una nueva parada de venta en Close - Distance.
Si se abre una posición corta, el stop se mueve a Close + Distance mediante una orden stop de compra.
Cuando se cierra la posición (manualmente o mediante parada de llenado), la orden de seguimiento se cancela para evitar órdenes de protección obsoletas.
Esto produce la misma distancia de parada constantemente reanclada que la versión MQL, lo que significa que la parada puede acercarse o alejarse del mercado a medida que las velas fluctúan.
Parámetros
Nombre
Predeterminado
Descripción
StopLossPoints
800
Distancia entre el precio de mercado y el stop de protección medida en puntos del instrumento. El valor se multiplica por Security.PriceStep (recurre a Security.Step, luego 1) antes de aplicarse al precio de cierre. Establezca en 0 para deshabilitar la gestión de paradas.
CandleType
TimeFrameCandle(00:01:00)
Tipo de vela que define cuándo se recalcula el stop. Elija un período de tiempo que coincida con el gráfico utilizado en MetaTrader.
Notas de uso
La estrategia espera que las operaciones se abran mediante estrategias externas, operaciones manuales u otros componentes. Sólo gestiona el stop-loss.
Asegúrese de que los metadatos de seguridad (PriceStep, Step, volumen) estén completos para que la conversión de punto a precio coincida con el tamaño de tick del corredor. Los instrumentos cotizados con pips fraccionarios deben exponer el paso adecuado.
Debido a que el stop se recalcula en cada cierre de vela, seguirá el precio incluso cuando el mercado se mueva en contra de la posición. Esto refleja la lógica MetaTrader donde OrderModify siempre usa la última Bid/Ask menos/más la distancia configurada.
Las órdenes de parada creadas siempre reemplazan a la anterior para mantener la plataforma sincronizada con el último nivel de protección.
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()