Ver no GitHub

Estratégia Auto Stop-Loss e Take-Profit

Esta estratégia utilitária anexa automaticamente ordens protetoras de stop-loss e take-profit a cada posição aberta no instrumento configurado. Ela espelha o comportamento do expert original do MetaTrader "AutoSet SL TP", monitorando a lista de posições ativas e aplicando as restrições de distância do corretor antes de registrar as ordens protetoras.

A estratégia não abre trades por conta própria. Em vez disso, ela monitora o volume, direção e preço de execução das posições que foram criadas manualmente ou por outras estratégias. Assim que uma posição longa ou curta aparecer, o algoritmo calcula os níveis desejados de stop-loss e take-profit expressos em pips no estilo MetaTrader, ajusta os níveis para cumprir com as restrições de congelamento e parada publicadas pela bolsa, e então envia as ordens protetoras de mercado apropriadas. Quando a posição é totalmente fechada, as ordens protetoras são canceladas automaticamente.

Como funciona

  1. Assina dados de Nível1 para receber os melhores preços bid/ask juntamente com os campos opcionais StopLevel e FreezeLevel fornecidos pelo corretor.
  2. Converte as distâncias configuradas em pips para preços absolutos usando os metadados do símbolo (passo de preço e precisão decimal). Cotações de cinco e três dígitos são automaticamente escalonadas por um fator de dez para corresponder à semântica de pip do MetaTrader.
  3. Em cada atualização de cotação ou notificação de trade pessoal:
    • Ignora o sinal se não houver posição aberta ou se a direção não corresponder ao filtro configurado (somente compra, somente venda ou ambos).
    • Calcula a distância mínima permitida entre o preço de mercado e uma ordem protetora. Se o corretor não publicar níveis de congelamento/parada, o algoritmo recorre a três spreads multiplicados por 1.1 para se manter com segurança fora das zonas proibidas.
    • Determina o preço de stop-loss e take-profit em relação ao ask atual (para longos) ou bid (para curtos) e normaliza o resultado para o passo de preço do instrumento.
    • Coloca ou re-registra ordens protetoras de stop ou limite com o volume exato da posição. As ordens são substituídas apenas quando o preço alvo ou o volume muda, o que mantém as modificações na bolsa no mínimo.
  4. Se o volume da posição se tornar zero, todas as ordens protetoras pendentes são canceladas. A estratégia também cancela as ordens existentes quando a direção do trade não é mais permitida pelo filtro.

Como o algoritmo depende exclusivamente de fills externos, ele pode ser combinado com negociação discricionária, painéis ou outros sistemas automatizados que gerenciam entradas, enquanto esta estratégia garante um envelope protetor consistente.

Parâmetros

  • StopLossPips – distância do preço de mercado atual ao stop-loss em pips do MetaTrader. Um valor de 0 desabilita a ordem de stop. Padrão: 50.
  • TakeProfitPips – distância do preço de mercado atual ao take-profit em pips do MetaTrader. Um valor de 0 desabilita a ordem de take-profit. Padrão: 140.
  • DirectionFilter – especifica qual direção de posição é gerenciada:
    • Buy – proteger apenas exposição longa.
    • Sell – proteger apenas exposição curta.
    • BuySell – proteger ambos os lados (comportamento padrão no script original).

Notas práticas

  • As ordens protetoras são sempre criadas com o volume absoluto da posição. Se o corretor impuser tamanhos de lote mínimos ou máximos, a estratégia arredonda o volume para o valor permissível mais próximo antes de colocar as ordens.
  • O algoritmo usa ReRegisterOrder para ajustar ordens protetoras ativas. Isso mantém os mesmos identificadores de ordem da bolsa sempre que possível e evita cancelamentos desnecessários.
  • A distância de fallback (spread × 3 × 1.1) evita que o stop ou take-profit viole restrições ocultas da bolsa quando os níveis explícitos de congelamento/parada não são fornecidos.
  • Como a estratégia não gerencia entradas, ela pode ser iniciada antes ou depois de as posições serem abertas. Qualquer posição qualificada que já exista no momento da inicialização será protegida imediatamente após a primeira atualização de cotação.
  • Os "pips" do MetaTrader diferem dos passos de preço da bolsa em símbolos com três ou cinco dígitos decimais. A estratégia replica o Expert Advisor original multiplicando o valor do ponto de acordo, garantindo que os números configurados correspondam exatamente às configurações do MT5.

Diferenças em relação ao expert do MetaTrader

  • Em vez de modificar os atributos de stop e take-profit na posição, o StockSharp gerencia ordens protetoras de stop e limite explícitas. Essa abordagem mantém a lógica completamente transparente dentro do livro de ordens do StockSharp.
  • A versão do StockSharp usa dados de mercado de Nível1 para reconstruir os níveis de restrição do corretor. Se o provedor expuser nomes de campo diferentes para distâncias de congelamento ou parada, a estratégia os descobre automaticamente por reflexão no enum Level1Fields.
  • Cada comentário de código e mensagem de log está em inglês para manter a consistência com as diretrizes de codificação, enquanto a documentação é localizada em russo e chinês para os usuários finais.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class AutoSetStopLossTakeProfitStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public AutoSetStopLossTakeProfitStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}