Ver no GitHub

Estratégia BreakRevert Pro

BreakRevert Pro é a conversão StockSharp do consultor especialista MetaTrader 5 BreakRevertPro.mq5. A estratégia combina a confirmação do rompimento no período de um minuto com uma tendência mais ampla e um contexto de volatilidade dos gráficos de 15 minutos e de uma hora. As pontuações de estilo de probabilidade são reproduzidas por meio de aproximações baseadas em indicadores para que o comportamento permaneça próximo do EA original enquanto segue StockSharp padrões API de alto nível.

Lógica principal

  1. Período principal (1 minuto)
    • Average True Range (ATR) estima a volatilidade intradiária.
    • Uma média móvel de preços de fechamento mede o viés direcional de curto prazo.
    • Uma segunda média móvel rastreia a frequência de grandes movimentos de vela a vela, representando a probabilidade de rompimento de Poisson do código MQL.
    • Uma média móvel exponencial de movimentos de preços absolutos produz a probabilidade de estilo exponencial usada pelo filtro de segurança original.
  2. Prazo de confirmação (15 minutos)
    • Uma média móvel simples mede a direção da tendência de médio prazo e bloqueia as negociações contra o fluxo dominante.
  3. Período de contexto (1 hora)
    • As velas horárias fornecem a tendência de período de tempo mais alto e a faixa de volatilidade necessária para validação de rompimento e verificações de achatamento de reversão à média.

Quando as probabilidades proxy de Poisson e Weibull excedem o limite de rompimento, as tendências de 1 minuto e 15 minutos estão alinhadas com o lado positivo e a volatilidade horária é elevada, a estratégia entra em uma negociação de rompimento longo. Por outro lado, quando as probabilidades caem abaixo do limite de reversão à média e a tendência horária é estável, a estratégia vende a descoberto, visando retrocessos de volta ao intervalo. As ordens de mercado são usadas para espelhar o estilo de execução imediata do consultor especialista original.

Gestão de risco

  • Um atraso de negociação configurável evita negociações excessivas, impondo uma pausa entre entradas consecutivas.
  • MaxPositions limita o número de posições abertas simultâneas. Ao reverter de uma negociação oposta, a estratégia fecha a exposição atual e abre a nova direção em uma única ordem de mercado.
  • A estimativa dinâmica de volume usa o saldo da conta, a distância de parada derivada de ATR e a porcentagem de RiskPerTrade para produzir um tamanho de lote conservador. Se o cálculo falhar, o volume mínimo do passo será usado como padrão seguro.
  • As negociações de segurança opcionais podem ser habilitadas para ambientes de validação ou teste onde pelo menos uma negociação deve aparecer. A direção do comércio de segurança segue a estimativa de tendência combinada de curto e médio prazo.
  • StartProtection() ativa o bloco de proteção integrado de StockSharp para que problemas inesperados de conexão não deixem as posições sem gerenciamento.

Parâmetros

Parâmetro Descrição
RiskPerTrade Risco por negociação em porcentagem do valor do portfólio (usado para cálculo dinâmico de lote).
LookbackPeriod Número de velas concluídas usadas para médias móveis e cálculos ATR em todos os intervalos de tempo.
BreakoutThreshold Probabilidade composta mínima necessária para uma entrada de fuga.
MeanReversionThreshold Probabilidade máxima que ainda permite posições vendidas com reversão à média.
TradeDelaySeconds Número mínimo de segundos entre entradas consecutivas.
MaxPositions Posições simultâneas máximas (usadas para exposição longa e curta).
EnableSafetyTrade Permite negociações de segurança de validação opcionais quando nenhuma posição está aberta.
SafetyTradeIntervalSeconds Período de espera entre verificações comerciais de segurança.
CandleType Período primário usado para a assinatura do sinal principal (padrão: 1 minuto).

Notas de uso

  1. Anexe a estratégia a um instrumento que suporte dados de 1 minuto e forneça velas de 15 minutos e 1 hora (StockSharp agregará frames mais altos automaticamente quando o corretor fornecer barras de minutos).
  2. Defina a propriedade Volume se um tamanho de pedido fixo for necessário. Caso contrário, a estratégia deriva um tamanho conservador do saldo da conta e ATR.
  3. Ajuste os limites e comprimentos de lookback de acordo com o perfil de volatilidade do mercado-alvo. Pares de volatilidade mais alta podem se beneficiar de limites maiores para evitar falsos rompimentos frequentes.
  4. As negociações de segurança destinam-se principalmente a cenários de validação em que o EA original executou pelo menos uma negociação, mesmo sem sinal. Desative-os para ambientes normais de negociação ao vivo.

A conversão mantém a ideia original de combinar detecção de fuga com salvaguardas de reversão, ao mesmo tempo em que conta com a estrutura de indicadores de alto nível do StockSharp para permanecer eficiente e fácil de testar.

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// BreakRevert Pro strategy converted from the MetaTrader 5 expert advisor.
/// The strategy blends breakout and mean-reversion logic using multi-timeframe candles.
/// </summary>
public class BreakRevertProStrategy : Strategy
{
	private readonly StrategyParam<decimal> _riskPerTrade;
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<decimal> _breakoutThreshold;
	private readonly StrategyParam<decimal> _meanReversionThreshold;
	private readonly StrategyParam<int> _tradeDelaySeconds;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<bool> _enableSafetyTrade;
	private readonly StrategyParam<int> _safetyTradeIntervalSeconds;
	private readonly StrategyParam<DataType> _candleType;

	private ISubscriptionHandler<ICandleMessage> _m1Subscription;
	private ISubscriptionHandler<ICandleMessage> _m15Subscription;
	private ISubscriptionHandler<ICandleMessage> _h1Subscription;

	private AverageTrueRange _m1Atr;
	private SimpleMovingAverage _m1TrendAverage;
	private SimpleMovingAverage _m15TrendAverage;
	private SimpleMovingAverage _h1TrendAverage;
	private SimpleMovingAverage _eventFrequency;
	private ExponentialMovingAverage _volatilityEma;

	private decimal _poissonProbability = 0.5m;
	private decimal _weibullProbability = 0.5m;
	private decimal _exponentialProbability = 0.5m;
	private decimal _m1Trend;
	private decimal _m15Trend;
	private decimal _h1Trend;
	private decimal _h1Volatility;
	private decimal? _previousM1Close;
	private decimal _latestAtr;
	private DateTimeOffset? _lastTradeTime;
	private DateTimeOffset? _lastSafetyCheck;
	private bool _safetyTradeSent;

	/// <summary>
	/// Initializes a new instance of the <see cref="BreakRevertProStrategy"/> class.
	/// </summary>
	public BreakRevertProStrategy()
	{
		_riskPerTrade = Param(nameof(RiskPerTrade), 1m)
		.SetDisplay("Risk %", "Risk per trade as percentage of portfolio value", "Risk")
		.SetOptimize(0.5m, 5m, 0.5m);

		_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
			.SetRange(10, 60)
		.SetDisplay("Lookback", "Number of finished candles used for statistics", "Signals")
		;

		_breakoutThreshold = Param(nameof(BreakoutThreshold), 0.1m)
		.SetDisplay("Breakout Threshold", "Minimum composite probability required for breakout entries", "Signals")
		.SetOptimize(0.2m, 0.8m, 0.05m);

		_meanReversionThreshold = Param(nameof(MeanReversionThreshold), 0.6m)
		.SetDisplay("Reversion Threshold", "Maximum probability that still allows mean-reversion trades", "Signals")
		.SetOptimize(0.2m, 0.8m, 0.05m);

		_tradeDelaySeconds = Param(nameof(TradeDelaySeconds), 300)
		.SetDisplay("Trade Delay", "Minimum delay between consecutive entries (seconds)", "Risk");

		_maxPositions = Param(nameof(MaxPositions), 1)
		.SetDisplay("Max Positions", "Maximum number of simultaneously open positions", "Risk");

		_enableSafetyTrade = Param(nameof(EnableSafetyTrade), true)
		.SetDisplay("Safety Trade", "Allow protective trades when validation requires at least one position", "Safety");

		_safetyTradeIntervalSeconds = Param(nameof(SafetyTradeIntervalSeconds), 900)
		.SetDisplay("Safety Interval", "Delay between safety trade checks (seconds)", "Safety");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Primary Candles", "Primary timeframe for signal generation", "Data");
	}

	/// <summary>
	/// Gets or sets the risk per trade in percent.
	/// </summary>
	public decimal RiskPerTrade
	{
		get => _riskPerTrade.Value;
		set => _riskPerTrade.Value = value;
	}

	/// <summary>
	/// Gets or sets the number of candles used in rolling calculations.
	/// </summary>
	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}

	/// <summary>
	/// Gets or sets the breakout probability threshold.
	/// </summary>
	public decimal BreakoutThreshold
	{
		get => _breakoutThreshold.Value;
		set => _breakoutThreshold.Value = value;
	}

	/// <summary>
	/// Gets or sets the mean-reversion probability threshold.
	/// </summary>
	public decimal MeanReversionThreshold
	{
		get => _meanReversionThreshold.Value;
		set => _meanReversionThreshold.Value = value;
	}

	/// <summary>
	/// Gets or sets the minimum delay between trades.
	/// </summary>
	public int TradeDelaySeconds
	{
		get => _tradeDelaySeconds.Value;
		set => _tradeDelaySeconds.Value = value;
	}

	/// <summary>
	/// Gets or sets the maximum simultaneous positions.
	/// </summary>
	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

	/// <summary>
	/// Gets or sets a value indicating whether safety trades are allowed.
	/// </summary>
	public bool EnableSafetyTrade
	{
		get => _enableSafetyTrade.Value;
		set => _enableSafetyTrade.Value = value;
	}

	/// <summary>
	/// Gets or sets the safety trade interval in seconds.
	/// </summary>
	public int SafetyTradeIntervalSeconds
	{
		get => _safetyTradeIntervalSeconds.Value;
		set => _safetyTradeIntervalSeconds.Value = value;
	}

	/// <summary>
	/// Gets or sets the primary candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_m1Subscription = null;
		_m15Subscription = null;
		_h1Subscription = null;

		_m1Atr = null;
		_m1TrendAverage = null;
		_m15TrendAverage = null;
		_h1TrendAverage = null;
		_eventFrequency = null;
		_volatilityEma = null;

		_poissonProbability = 0.5m;
		_weibullProbability = 0.5m;
		_exponentialProbability = 0.5m;
		_m1Trend = 0m;
		_m15Trend = 0m;
		_h1Trend = 0m;
		_h1Volatility = 0m;
		_previousM1Close = null;
		_latestAtr = 0m;
		_lastTradeTime = null;
		_lastSafetyCheck = null;
		_safetyTradeSent = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var lookback = Math.Max(1, LookbackPeriod);

		_m1Atr = new AverageTrueRange { Length = lookback };
		_m1TrendAverage = new SimpleMovingAverage { Length = lookback };
		_m15TrendAverage = new SimpleMovingAverage { Length = lookback };
		_h1TrendAverage = new SimpleMovingAverage { Length = lookback };
		_eventFrequency = new SimpleMovingAverage { Length = lookback };
		_volatilityEma = new ExponentialMovingAverage { Length = lookback };

		// Subscribe to the main one-minute flow.
		_m1Subscription = SubscribeCandles(CandleType);
		_m1Subscription
		.Bind(_m1Atr, ProcessPrimaryCandle)
		.Start();

		// Additional fifteen-minute stream provides mid-term trend confirmation.
		_m15Subscription = SubscribeCandles(TimeSpan.FromMinutes(15).TimeFrame());
		_m15Subscription
		.Bind(ProcessM15Candle)
		.Start();

		// Hourly candles track the broader context and volatility envelope.
		_h1Subscription = SubscribeCandles(TimeSpan.FromHours(1).TimeFrame());
		_h1Subscription
		.Bind(ProcessH1Candle)
		.Start();

		StartProtection(
		takeProfit: new Unit(2, UnitTypes.Percent),
		stopLoss: new Unit(1, UnitTypes.Percent)
	);
	}

	private void ProcessPrimaryCandle(ICandleMessage candle, decimal atrValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		_latestAtr = atrValue;

		var close = candle.ClosePrice;
		var time = candle.CloseTime;
		var pip = GetPipSize();

		if (_m1TrendAverage is not null)
		{
			var trendValue = _m1TrendAverage.Process(new DecimalIndicatorValue(_m1TrendAverage, close, time) { IsFinal = true }).ToDecimal();
			if (_m1TrendAverage.IsFormed)
			_m1Trend = close - trendValue;
		}

		if (_previousM1Close is decimal previousClose)
		{
			var move = Math.Abs(close - previousClose);
			var eventValue = move >= pip * 5m ? 1m : 0m;
			if (_eventFrequency is not null)
			{
				var avg = _eventFrequency.Process(new DecimalIndicatorValue(_eventFrequency, eventValue, time) { IsFinal = true }).ToDecimal();
				if (_eventFrequency.IsFormed)
				_poissonProbability = Clamp(avg, 0m, 1m);
			}

			if (_volatilityEma is not null)
			{
				var ema = _volatilityEma.Process(new DecimalIndicatorValue(_volatilityEma, move, time) { IsFinal = true }).ToDecimal();
				if (_volatilityEma.IsFormed)
				{
					var normalized = pip > 0m ? ema / (pip * 10m) : 0m;
					_exponentialProbability = Clamp(normalized, 0m, 1m);
				}
			}
		}

		_previousM1Close = close;

		var normalizedAtr = pip > 0m ? atrValue / (pip * 10m) : 0m;
		_weibullProbability = Clamp(normalizedAtr, 0m, 1m);

		EvaluateSignals(candle);
	}

	private void ProcessM15Candle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (_m15TrendAverage is null)
		return;

		var close = candle.ClosePrice;
		var trend = _m15TrendAverage.Process(new DecimalIndicatorValue(_m15TrendAverage, close, candle.CloseTime) { IsFinal = true }).ToDecimal();
		if (_m15TrendAverage.IsFormed)
		_m15Trend = close - trend;
	}

	private void ProcessH1Candle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;

		_h1Volatility = candle.HighPrice - candle.LowPrice;

		if (_h1TrendAverage is null)
		return;

		var close = candle.ClosePrice;
		var trend = _h1TrendAverage.Process(new DecimalIndicatorValue(_h1TrendAverage, close, candle.CloseTime) { IsFinal = true }).ToDecimal();
		if (_h1TrendAverage.IsFormed)
		_h1Trend = close - trend;
	}

	private void EvaluateSignals(ICandleMessage candle)
	{
		var now = candle.CloseTime;
		var pip = GetPipSize();

		if (_lastTradeTime is DateTimeOffset last && (now - last).TotalSeconds < TradeDelaySeconds)
		return;

		var tradeVolume = GetTradeVolume();
		if (tradeVolume <= 0m)
		return;

		if (Position != 0)
			return;

		var breakout = IsBreakoutSignal(pip);
		var reversion = IsMeanReversionSignal(pip);

		if (breakout)
		{
			BuyMarket();
			_lastTradeTime = now;
		}
		else if (reversion)
		{
			SellMarket();
			_lastTradeTime = now;
		}
	}

	private bool IsBreakoutSignal(decimal pip)
	{
		var trendUp = _m1Trend > 0m;
		var probabilityOk = _poissonProbability >= BreakoutThreshold || _weibullProbability >= BreakoutThreshold;
		return trendUp && probabilityOk;
	}

	private bool IsMeanReversionSignal(decimal pip)
	{
		var trendDown = _m1Trend < 0m;
		var probabilityOk = _weibullProbability <= MeanReversionThreshold || _poissonProbability <= MeanReversionThreshold;
		return trendDown && probabilityOk;
	}

	private void EnterLong(decimal volume)
	{
		var totalVolume = volume;

		if (Position < 0m)
		{
			totalVolume += Math.Abs(Position);
		}

		// Execute a market order to align with the breakout signal.
		BuyMarket(totalVolume);
	}

	private void EnterShort(decimal volume)
	{
		var totalVolume = volume;

		if (Position > 0m)
		{
			totalVolume += Math.Abs(Position);
		}

		// Execute a market order to capture the expected pullback.
		SellMarket(totalVolume);
	}

	private void CheckSafetyTrade(DateTimeOffset time, decimal volume)
	{
		if (!EnableSafetyTrade || _safetyTradeSent || Position != 0m)
		return;

		if (_lastSafetyCheck is DateTimeOffset last && (time - last).TotalSeconds < SafetyTradeIntervalSeconds)
		return;

		_lastSafetyCheck = time;

		var direction = _m1Trend + _m15Trend;
		if (direction > 0m)
		{
			BuyMarket(volume);
		}
		else
		{
			SellMarket(volume);
		}

		_safetyTradeSent = true;
		_lastTradeTime = time;
	}

	private bool HasReachedMaxExposure(int direction, decimal tradeVolume)
	{
		if (MaxPositions <= 0 || tradeVolume <= 0m)
		return false;

		var limit = MaxPositions * tradeVolume;

		return direction switch
		{
			> 0 => Position >= limit,
			< 0 => -Position >= limit,
			_ => Math.Abs(Position) >= limit,
		};
	}

	private decimal GetTradeVolume()
	{
		if (Volume > 0m)
		return Volume;

		var stepVolume = Security?.VolumeStep ?? 1m;
		var lotStep = Security?.VolumeStep ?? stepVolume;
		var minVolume = Security?.MinVolume ?? stepVolume;
		var maxVolume = Security?.MaxVolume ?? decimal.MaxValue;
		var balance = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
		var atr = Math.Max(_latestAtr, GetPipSize());

		if (stepVolume <= 0m)
		stepVolume = 1m;

		if (lotStep <= 0m)
		lotStep = stepVolume;

		if (minVolume <= 0m)
		minVolume = stepVolume;

		if (balance <= 0m || atr <= 0m)
		return minVolume;

		var riskAmount = balance * RiskPerTrade / 100m;
		if (riskAmount <= 0m)
		return minVolume;

		var riskPerUnit = atr;
		var rawVolume = riskPerUnit > 0m ? riskAmount / riskPerUnit : minVolume;
		rawVolume = Math.Max(rawVolume, minVolume);

		var normalized = Math.Floor(rawVolume / lotStep) * lotStep;
		if (normalized <= 0m)
		normalized = minVolume;

		if (maxVolume > 0m && normalized > maxVolume)
		normalized = maxVolume;

		return normalized;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep;
		if (step is null || step.Value <= 0m)
		return 0.0001m;

		return step.Value;
	}

	private static decimal Clamp(decimal value, decimal min, decimal max)
	{
		if (value < min)
		return min;

		return value > max ? max : value;
	}
}