Ver no GitHub

Stop Loss Dinâmico

Visão geral

O consultor especialista MetaTrader original "Dynamic Stop Loss" não abre novas negociações por conta própria. Em vez disso, observa as posições de mercado existentes e, assim que uma nova vela aparece, reposiciona o stop-loss protetor para que fique a uma distância fixa atrás do preço mais recente. A porta StockSharp mantém o mesmo comportamento: cada barra completada aciona um recálculo da parada de proteção para qualquer lado que esteja aberto no momento. Se não existir nenhuma posição, a estratégia simplesmente fica inativa até que uma nova posição seja detectada.

Como funciona

  1. A estratégia assina velas definidas pelo parâmetro Candle Type (período padrão de 1 minuto).
  2. Quando uma vela fecha, o preço de fechamento é multiplicado pela distância do ponto selecionado pelo usuário. A distância é convertida de pontos no estilo MetaTrader em um delta de preço absoluto via Security.PriceStep (substituição para Security.Step e depois para 1).
  3. Se uma posição longa estiver aberta, a estratégia cancela qualquer ordem stop existente e coloca um novo stop de venda em Close - Distance.
  4. Se uma posição curta estiver aberta, o stop é movido para Close + Distance usando uma ordem stop de compra.
  5. Quando a posição é fechada (manualmente ou pelo stop fill), a ordem móvel é cancelada para evitar ordens de proteção obsoletas.

Isso produz a mesma distância de parada constantemente ancorada que a versão MQL, o que significa que a parada pode se aproximar e se afastar do mercado à medida que as velas flutuam.

Parâmetros

Nome Padrão Descrição
StopLossPoints 800 Distância entre o preço de mercado e o stop de proteção medido em pontos do instrumento. O valor é multiplicado por Security.PriceStep (substituto para Security.Step e depois 1) antes de ser aplicado ao preço de fechamento. Defina como 0 para desativar o gerenciamento de parada.
CandleType TimeFrameCandle(00:01:00) Tipo de vela que define quando o stop é recalculado. Escolha um período que corresponda ao gráfico usado em MetaTrader.

Notas de uso

  • A estratégia espera que as negociações sejam abertas por estratégias externas, operações manuais ou outros componentes. Ele apenas gerencia o stop-loss.
  • Certifique-se de que os metadados de segurança (PriceStep, Step, volume) sejam preenchidos para que a conversão ponto-preço corresponda ao tamanho do tick do corretor. Instrumentos cotados com pips fracionários devem expor o passo adequado.
  • Como o stop é recalculado a cada fechamento de vela, ele seguirá o preço mesmo quando o mercado se mover contra a posição. Isso reflete a lógica MetaTrader onde OrderModify sempre usa o último Bid/Ask menos/mais a distância configurada.
  • As ordens stop criadas sempre substituem as anteriores para manter a plataforma sincronizada com o nível de proteção mais recente.
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;
	}
}