Ver en GitHub

Stop Loss dinámico

Descripción general

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

  1. La estrategia se suscribe a velas definidas por el parámetro Candle Type (período de tiempo predeterminado de 1 minuto).
  2. 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).
  3. Si se abre una posición larga, la estrategia cancela cualquier orden stop existente y coloca una nueva parada de venta en Close - Distance.
  4. Si se abre una posición corta, el stop se mueve a Close + Distance mediante una orden stop de compra.
  5. 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;
	}
}