Ver no GitHub

Martingale Estratégia de ruptura

Visão geral

A Martingale Estratégia de Breakout é uma versão StockSharp do MetaTrader consultor especialista MartinGaleBreakout.mq5. O sistema espera por velas de rompimento anormalmente grandes e coloca uma única ordem de mercado na direção do rompimento. Embora o EA original rastreia um "número mágico" para gerenciar suas posições, a implementação do StockSharp depende do contexto da estratégia, portanto o comportamento é efetivamente o mesmo quando a estratégia é executada isoladamente.

O algoritmo se concentra em duas ideias principais:

  1. Detecção de ruptura – a estratégia examina o tamanho de cada vela finalizada e compara-a com o intervalo médio da dez velas anteriores. Quando o intervalo atual é três vezes maior que a média e a vela fecha fortemente no direção do rompimento, um sinal de negociação é produzido.
  2. Martingalerecuperação estilo – a estratégia monitora lucros e perdas flutuantes. Sempre que o PnL não realizado atingir o limite de perda configurado, ele fecha imediatamente todas as posições abertas e aumenta a próxima meta de lucro para que a negociação seguinte tenta recuperar a perda. Assim que a meta aumentada for atingida, os limites serão redefinidos para os valores originais.

A porta mantém todos os parâmetros de gerenciamento de dinheiro do código MQL5, incluindo a porcentagem do saldo reservada para margem, o metas de lucros e perdas baseadas em porcentagem e o multiplicador que expande a distância de obtenção de lucro durante a fase de recuperação.

Lógica de negociação

  1. Assine a série de velas configuradas e aguarde as velas finalizadas.
  2. Calcule o intervalo da vela (High - Low) e mantenha um buffer de tamanho fixo com os dez intervalos anteriores para determinar o média de referência usada para detecção de fuga.
  3. Calcule o PnL flutuante acompanhando os preços médios de entrada para os lados comprado e vendido. Se o PnL não realizado exceder o meta de lucro ou violar o limite de stop-loss, fechar imediatamente todas as posições e redefinir o estado de recuperação como no consultor especialista original.
  4. Ignore a colocação de ordens enquanto a estratégia já mantém uma posição ou quando a negociação não é permitida pelo estado da conexão.
  5. Quando uma vela de alta aparecer, dimensione o pedido para que o lucro esperado corresponda à meta atual. O lucro a distância nas etapas de preço é multiplicada durante a recuperação, exatamente como o parâmetro TP_Points_Multiplier do EA.
  6. Valide o volume calculado em relação aos limites do instrumento (mínimo, máximo e passo) e certifique-se de que a margem necessária não excede a alocação de saldo configurada ou os fundos livres disponíveis. Se as restrições forem respeitadas, envie um ordem de compra de mercado.
  7. Repita o mesmo processo para rompimentos de baixa, enviando uma ordem de venda a mercado.

A combinação dessas regras recria o comportamento do sistema MetaTrader original, incluindo a transição de entrada e saída do modo de recuperação após um evento de stop-loss.

Parâmetros

Parâmetro Descrição Padrão
TakeProfitPoints Distância entre o preço de entrada e o preço de realização de lucro expresso em etapas de preço. 50
BalancePercentAvailable Porcentagem máxima do saldo da conta que pode ser reservada para margem em uma única negociação. 50
TakeProfitPercentOfBalance Lucro alvo expresso como uma percentagem do saldo atual. 0.1
StopLossPercentOfBalance Tamanho do stop loss expresso como uma porcentagem do saldo atual. 10
RecoveryStartFraction Fração do stop loss usada antes de mudar para o modo de recuperação. 0.1
RecoveryPointsMultiplier Multiplicador aplicado à distância de lucro durante a recuperação. 1
CandleType Fonte de dados de velas usada pela estratégia (período de tempo, velas de tick, etc.). 15-minute time frame

Notas adicionais

  • O cálculo do volume replica o auxiliar MetaTrader CalcLotWithTP. Ele deriva o tamanho do lote necessário para atingir o atual meta de lucro para um determinado movimento de preço e, em seguida, normaliza o resultado para a etapa de volume do instrumento.
  • As verificações de margem são realizadas com o mesmo espírito de CheckVolumeValue e o filtro de porcentagem de saldo usado em MQL versão. As ordens são rejeitadas quando a margem exigida excede a parcela permitida do saldo ou os fundos livres informados pelo o portfólio.
  • A estratégia cancela todos os pedidos ativos antes de nivelar as posições para que o comportamento corresponda ao auxiliar CloseAllOrders de o consultor especialista original.
  • O buffer de intervalo interno armazena apenas dez valores e é equivalente à iteração sobre iHigh/iLow na origem EA. Não são necessários dados históricos além das últimas dez velas.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy that detects abnormally large candles and enters in the breakout direction.
/// Uses a simple martingale recovery: after a losing trade, the next entry is taken more aggressively.
/// </summary>
public class MartingaleBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _lookback;
	private readonly StrategyParam<decimal> _breakoutMultiplier;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<DataType> _candleType;

	private readonly decimal[] _rangeBuffer = new decimal[10];
	private int _rangeBufferCount;
	private int _rangeBufferIndex;
	private decimal _rangeBufferSum;

	private decimal _entryPrice;
	private Sides? _entrySide;
	private bool _lastWasLoss;

	public int Lookback
	{
		get => _lookback.Value;
		set => _lookback.Value = value;
	}

	public decimal BreakoutMultiplier
	{
		get => _breakoutMultiplier.Value;
		set => _breakoutMultiplier.Value = value;
	}

	public decimal TakeProfitPct
	{
		get => _takeProfitPct.Value;
		set => _takeProfitPct.Value = value;
	}

	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public MartingaleBreakoutStrategy()
	{
		_lookback = Param(nameof(Lookback), 10)
			.SetDisplay("Lookback", "Number of candles for average range", "General");

		_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 3m)
			.SetDisplay("Breakout Mult", "Multiplier above avg range for breakout", "General");

		_takeProfitPct = Param(nameof(TakeProfitPct), 1m)
			.SetDisplay("Take Profit %", "Take profit as percentage of entry price", "Trading");

		_stopLossPct = Param(nameof(StopLossPct), 0.5m)
			.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle type", "General");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_rangeBufferCount = 0;
		_rangeBufferIndex = 0;
		_rangeBufferSum = 0m;
		_entryPrice = 0m;
		_entrySide = null;
		_lastWasLoss = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		var closePrice = candle.ClosePrice;

		// Check exit conditions first
		if (Position != 0 && _entryPrice > 0)
		{
			var tp = _lastWasLoss ? TakeProfitPct * 1.5m : TakeProfitPct;
			var sl = StopLossPct;

			if (_entrySide == Sides.Buy)
			{
				var pnlPct = (closePrice - _entryPrice) / _entryPrice * 100m;
				if (pnlPct >= tp || pnlPct <= -sl)
				{
					_lastWasLoss = pnlPct < 0;
					SellMarket();
					_entryPrice = 0;
					_entrySide = null;
					UpdateRangeStatistics(candle);
					return;
				}
			}
			else if (_entrySide == Sides.Sell)
			{
				var pnlPct = (_entryPrice - closePrice) / _entryPrice * 100m;
				if (pnlPct >= tp || pnlPct <= -sl)
				{
					_lastWasLoss = pnlPct < 0;
					BuyMarket();
					_entryPrice = 0;
					_entrySide = null;
					UpdateRangeStatistics(candle);
					return;
				}
			}
		}

		// Entry logic - only when flat
		if (Position == 0)
		{
			var range = candle.HighPrice - candle.LowPrice;

			if (_rangeBufferCount >= _rangeBuffer.Length)
			{
				var avgRange = _rangeBufferSum / _rangeBuffer.Length;

				if (range > avgRange * BreakoutMultiplier)
				{
					var body = candle.ClosePrice - candle.OpenPrice;

					if (body > 0 && body > range * 0.4m)
					{
						// Bullish breakout
						BuyMarket();
						_entryPrice = closePrice;
						_entrySide = Sides.Buy;
					}
					else if (body < 0 && Math.Abs(body) > range * 0.4m)
					{
						// Bearish breakout
						SellMarket();
						_entryPrice = closePrice;
						_entrySide = Sides.Sell;
					}
				}
			}
		}

		UpdateRangeStatistics(candle);
	}

	private void UpdateRangeStatistics(ICandleMessage candle)
	{
		var range = candle.HighPrice - candle.LowPrice;

		if (_rangeBufferCount < _rangeBuffer.Length)
		{
			_rangeBuffer[_rangeBufferIndex] = range;
			_rangeBufferSum += range;
			_rangeBufferCount++;
			_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
			return;
		}

		_rangeBufferSum -= _rangeBuffer[_rangeBufferIndex];
		_rangeBuffer[_rangeBufferIndex] = range;
		_rangeBufferSum += range;
		_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		Array.Clear(_rangeBuffer);
		_rangeBufferCount = 0;
		_rangeBufferIndex = 0;
		_rangeBufferSum = 0m;
		_entryPrice = 0m;
		_entrySide = null;
		_lastWasLoss = false;

		base.OnReseted();
	}
}