Ver no GitHub

Estratégia Zone Recovery Button

A estratégia Zone Recovery Button é uma conversão direta do expert advisor do MetaTrader "ZONE RECOVERY BUTTON VER1" (MQL/25347). O robô original dependia de botões BUY/SELL no gráfico para iniciar uma cesta com hedge. Nesta versão StockSharp, o painel manual é substituído por parâmetros, enquanto a lógica de recuperação, take-profits monetários/percentuais, trailing stop em moeda e proteção equity-stop são preservados.

Quando a estratégia recebe uma direção inicial, ela abre uma ordem a mercado inicial. Sempre que o preço atravessa a largura de zona configurada, o sistema empilha uma operação oposta com volume aumentado. A cesta é fechada quando o take-profit de referência é atingido, o lucro flutuante alcança o alvo monetário/percentual configurado, o trailing stop devolve lucro demais ou o limite de equity-stop é violado.

Regras de negociação

  1. Direção inicial - emula pressionar o botão BUY ou SELL. A estratégia abre a primeira ordem imediatamente quando recebe dados e tem permissão para negociar. Depois de fechar a cesta, ela pode reiniciar automaticamente com a mesma direção.
  2. Recuperação por zona - em cada passo de recuperação, o algoritmo alterna a direção. Para ciclos comprados, ele vende quando o preço cai abaixo de Base Price - Zone Width, depois compra novamente quando o mercado retorna acima da base. Para ciclos vendidos, a lógica é espelhada.
  3. Escalonamento de volume - cada hedge adicional multiplica o volume anterior ou adiciona um incremento fixo, reproduzindo as configurações "Lots"/"Multiply" do EA.
  4. Controles de take-profit - a cesta é fechada por:
    • take-profit baseado em pips medido a partir do preço de referência;
    • alvo monetário na moeda da conta;
    • alvo percentual calculado a partir do valor atual do portfólio;
    • lógica trailing que trava ganhos quando o lucro flutuante excede um limite e depois devolve mais do que o drawdown permitido;
    • equity-stop emergencial que compara a perda flutuante atual com o maior equity observado durante o ciclo.

Parâmetros

Parâmetro Padrão Descrição
CandleType TimeSpan.FromMinutes(5) Tipo de candle usado para monitorar movimentos de preço.
StartDirection Buy Direção inicial do ciclo (BUY/SELL/NONE).
AutoRestart true Reinicia automaticamente um novo ciclo depois que a cesta anterior fecha.
TakeProfitPips 200 Distância em pips entre o preço base e o alvo de take-profit em pips.
ZoneRecoveryPips 10 Distância em pips que aciona o próximo hedge na direção oposta.
InitialVolume 0.01 Volume (lotes) da primeira operação.
UseVolumeMultiplier true Se habilitado, cada hedge multiplica o volume anterior; caso contrário, VolumeIncrement é adicionado.
VolumeMultiplier 2 Multiplicador aplicado quando UseVolumeMultiplier é true.
VolumeIncrement 0.01 Incremento de volume quando UseVolumeMultiplier é false.
MaxTrades 100 Número máximo de operações na cesta.
UseMoneyTakeProfit false Habilita fechamento quando o lucro flutuante excede MoneyTakeProfit.
MoneyTakeProfit 40 Alvo de lucro na moeda da conta.
UsePercentTakeProfit false Habilita fechamento quando o lucro flutuante excede PercentTakeProfit por cento do saldo.
PercentTakeProfit 10 Alvo de lucro em percentual do valor atual do portfólio.
EnableTrailing true Habilita trailing de lucro em moeda.
TrailingProfitThreshold 40 Nível de lucro que ativa trailing.
TrailingDrawdown 10 Drawdown permitido a partir do pico de lucro flutuante antes de fechar a cesta.
UseEquityStop true Habilita o equity stop emergencial.
TotalEquityRiskPercent 1 Perda flutuante máxima (em percentual do pico de equity) antes de zerar.

Observações

  • A estratégia funciona com qualquer instrumento que forneça valores PriceStep e StepPrice. Esses parâmetros são necessários para converter distâncias em pips para unidades de preço e moeda.
  • Como o StockSharp usa um modelo de posição líquida, a grade de hedge é simulada internamente. A estratégia mantém sua própria lista de passos de operação para reproduzir o cálculo de lucro do MetaTrader.
  • A lógica trailing opera sobre o lucro flutuante da cesta ativa. Ela não usa trailing stops baseados em ordens.
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 ZoneRecoveryButtonStrategy : 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 ZoneRecoveryButtonStrategy()
	{
		_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;
	}
}