Ver no GitHub

Estratégia do Plano Diretor de Saída

Visão geral

MasterExitPlanStrategy reproduz a lógica de gerenciamento de risco do consultor especialista MetaTrader "Plano Mestre de Saída" usando o API de alto nível de StockSharp. A estratégia não abre novas negociações. Em vez disso, supervisiona a exposição existente, aplica uma combinação de regras de stop ocultas e visíveis, rastreia as ordens pendentes e fecha tudo quando o capital atinge uma meta de lucro configurada.

A implementação assina velas de um minuto para emular as chamadas iOpen(symbol, PERIOD_M1, 1) do script original. Todos os cronômetros são acionados pelo agendador de estratégia e avaliados a cada segundo, correspondendo ao comportamento do loop MetaTrader EventSetTimer(1).

Recursos

  • Saída da meta de capital – fecha todas as posições quando os ganhos de capital do portfólio atingem o percentual configurado.
  • Níveis de stop estáticos e dinâmicos – monitora as distâncias de stop a partir do preço de entrada e as âncoras dinâmicas baseadas em minutos.
  • Tratamento de parada oculta – executa saídas de proteção internamente em vez de depender de ordens de câmbio.
  • Módulo trailing stop – é ativado após um ganho mínimo de dinheiro e segue o stop com compensação de spread.
  • Acompanhamento de ordens pendentes – registra automaticamente novamente as ordens buy-stop e sell-stop para que elas sigam o mercado.

Parâmetros

Nome Descrição Padrão
EnableTargetEquity Permitir a liquidação da meta de capital. false
TargetEquityPercent Percentual do saldo atual utilizado como meta. 1
EnableStopLoss Ative o stop loss estilo corretor estático. false
StopLossPoints Distância de parada estática (MetaTrader pontos). 2000
EnableDynamicStopLoss Amarre a parada brusca ao minuto anterior aberto. false
DynamicStopLossPoints Distância de parada dinâmica (pontos). 2000
EnableHiddenStopLoss Ative o stop loss estático oculto. false
HiddenStopLossPoints Distância de parada estática oculta (pontos). 800
EnableHiddenDynamicStopLoss Habilite a parada dinâmica oculta com base no minuto aberto. false
HiddenDynamicStopLossPoints Distância de parada dinâmica oculta (pontos). 800
EnableTrailingStop Habilite o módulo de parada móvel. false
TrailingStopPoints Distância final mantida atrás do preço (pontos). 5
TrailingTargetPercent Lucro mínimo em% do saldo antes da ativação do trailing. 0.2
SureProfitPoints Pontos extras que devem ser protegidos antes de armar o trailing stop. 30
EnableTrailPendingOrders Habilite o rastreamento de ordens de parada ativas (entradas). false
TrailPendingOrderPoints Compensação em pontos para ordens de stop pendentes. 10

Notas de uso

  1. Anexe a estratégia a um título que já é gerenciado por outro módulo de entrada ou ordens manuais. Defina Volume de acordo com os contratos que você precisa fechar durante o nivelamento.
  2. Forneça um portfólio que relate Portfolio.CurrentValue. A estratégia usa esse valor para aproximar AccountBalance e AccountEquity de MetaTrader. Se o valor estiver faltando, a lógica de meta de capital permanecerá inativa.
  3. A estratégia avalia as melhores cotações de compra/venda ao verificar as condições de stop. Certifique-se de que os dados de nível 1 estejam disponíveis para que os cálculos com reconhecimento de propagação sejam significativos.
  4. Paradas ocultas e saídas móveis são implementadas como ordens de mercado gerenciadas por software. As ordens stop do lado da corretora não são criadas; o comportamento reflete a natureza "oculta" do EA original.

Diferenças da versão MQL

  • Os níveis de stop são aplicados através da emissão de ordens de mercado quando os limites são violados. O EA original modificou o campo OrderStopLoss; StockSharp usa monitoramento ativo.
  • Os cálculos de parada dinâmica baseiam-se na última vela de um minuto concluída, entregue por meio de SubscribeCandles. Se esta assinatura estiver faltando, as regras dinâmicas permanecerão desativadas.
  • O rastreamento de ordens pendentes ignora ordens de stop de proteção criadas por outras estratégias porque o próprio MasterExitPlanStrategy não as registra.
  • As verificações de patrimônio usam Portfolio.CurrentValue (substituição para Portfolio.BeginValue) em vez de AccountBalance/AccountEquity.

Teste

A estratégia não contém testes automatizados. Use o testador do StockSharp com dados históricos para verificar o comportamento dos seus instrumentos antes de implantar na produção.

namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Master Exit Plan strategy: EMA trend following with ATR-based trailing stop exit.
/// Enters on EMA crossover, exits when price retraces by ATR multiple.
/// </summary>
public class MasterExitPlanStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;

	private decimal _entryPrice;
	private decimal _trailStop;
	private bool _wasBullish;
	private bool _hasTrendState;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }

	public MasterExitPlanStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 60)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for trailing stop", "Risk");
		_atrMultiplier = Param(nameof(AtrMultiplier), 3m)
			.SetDisplay("ATR Multiplier", "ATR multiplier for trailing distance", "Risk");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0;
		_trailStop = 0;
		_wasBullish = false;
		_hasTrendState = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_entryPrice = 0;
		_trailStop = 0;
		_wasBullish = false;
		_hasTrendState = false;
		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastEma, slowEma, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;

		var close = candle.ClosePrice;
		var range = candle.HighPrice - candle.LowPrice;
		if (range <= 0) return;

		var trailDist = range * AtrMultiplier;
		var isBullish = fastValue > slowValue;
		var crossedUp = _hasTrendState && !_wasBullish && isBullish;
		var crossedDown = _hasTrendState && _wasBullish && !isBullish;

		if (Position > 0)
		{
			var newStop = close - trailDist;
			if (newStop > _trailStop)
				_trailStop = newStop;
			if (close < _trailStop)
			{
				SellMarket();
				_trailStop = 0;
				_entryPrice = 0;
				_wasBullish = isBullish;
				_hasTrendState = true;
				return;
			}
			else if (crossedDown)
			{
				SellMarket();
				_trailStop = 0;
				_entryPrice = 0;
				_wasBullish = isBullish;
				_hasTrendState = true;
				return;
			}
		}
		else if (Position < 0)
		{
			var newStop = close + trailDist;
			if (_trailStop == 0 || newStop < _trailStop)
				_trailStop = newStop;
			if (close > _trailStop)
			{
				BuyMarket();
				_trailStop = 0;
				_entryPrice = 0;
				_wasBullish = isBullish;
				_hasTrendState = true;
				return;
			}
			else if (crossedUp)
			{
				BuyMarket();
				_trailStop = 0;
				_entryPrice = 0;
				_wasBullish = isBullish;
				_hasTrendState = true;
				return;
			}
		}

		if (Position == 0)
		{
			if (crossedUp)
			{
				BuyMarket();
				_entryPrice = close;
				_trailStop = close - trailDist;
			}
			else if (crossedDown)
			{
				SellMarket();
				_entryPrice = close;
				_trailStop = close + trailDist;
			}
		}

		_wasBullish = isBullish;
		_hasTrendState = true;
	}
}