Ver no GitHub

Estratégia de ruptura da MartinGale

Visão geral

A Estratégia de Breakout MartinGale é um sistema de acompanhamento de breakout convertido do consultor especialista MetaTrader 4 MartinGaleBreakout. O robô original entra em posições após detectar velas anormalmente grandes e aplica um mecanismo de recuperação estilo martingale para recuperar perdas anteriores. Esta porta StockSharp reproduz o comportamento usando a estratégia de alto nível API com assinaturas de velas e parâmetros de gerenciamento de dinheiro.

A estratégia monitora uma série de velas configuráveis, procurando velas cujo intervalo seja pelo menos três vezes maior que o intervalo médio das dez barras anteriores. Quando tal vela fecha fortemente em uma direção, a estratégia abre uma posição de mercado nessa direção. Se a posição for fechada com uma perda que exceda um limite configurável, o modo de recuperação aumenta a distância de obtenção de lucro para compensar o rebaixamento realizado.

Lógica de negociação

  1. Assine a série de velas selecionada (velas de 15 minutos por padrão).
  2. Mantenha as 11 velas concluídas mais recentes para avaliar a volatilidade anormal.
  3. Detecte um rompimento de alta quando:
    • A vela atual é três vezes maior que o intervalo médio das dez velas anteriores.
    • A vela fecha na metade superior do seu alcance.
  4. Detecte um rompimento de baixa usando condições simétricas.
  5. Abra uma posição de mercado na direção do rompimento se:
    • Nenhuma outra posição está aberta no momento.
    • A exposição de capital estimada está abaixo do percentual de saldo configurado.
  6. Fechar posições e redefinir metas de lucros/perdas quando:
    • O lucro flutuante atinge o limite de lucro.
    • A perda flutuante atinge o limite de stop loss.
  7. Quando ocorrer um stop loss, mude para o modo de recuperação:
    • Aumente a distância de lucro pelo multiplicador configurado.
    • Expanda o limite de stop loss para a porcentagem máxima permitida.
    • Continue negociando até que a próxima meta seja alcançada e, em seguida, redefina para a configuração básica.

Parâmetros

Nome Descrição Padrão
TakeProfitPoints Distância básica de take-profit expressa em pontos de instrumento. 50
BalancePercentageAvailable Parcela máxima do saldo da conta que pode ser alocada para uma única negociação. 50%
TakeProfitBalancePercent Lucro alvo como porcentagem do saldo da conta. 0.1%
StopLossBalancePercent Rebaixamento máximo antes de acionar a recuperação. 10%
StartRecoveryFactor Parte do stop loss usada antes de ativar o modo de recuperação. 0.2
TakeProfitPointsMultiplier Multiplicador aplicado à distância de lucro durante a recuperação. 1
CandleType Série de velas usada para cálculos de breakout. 15-minute

Dimensionamento de posição e controle de risco

  • A estratégia calcula o volume necessário para atingir o take-profit monetário configurado usando o tamanho e o valor do tick do instrumento.
  • Os volumes são normalizados para as restrições de troca (passo, mínimo, máximo).
  • A exposição de capital estimada não deve exceder o percentual de saldo configurado.
  • O modo de recuperação expande dinamicamente a meta de lucro após uma perda, emulando o comportamento original do martingale enquanto mantém as posições limitadas a uma única negociação aberta.

Notas

  • A estratégia depende de informações sobre o saldo da carteira; inicialize-o com uma conexão de portfólio antes de começar.
  • O tratamento de comissões reflete o EA original, concentrando-se em lucros e perdas flutuantes derivados da posição atual.
  • Não são utilizadas ordens pendentes – as entradas e saídas são realizadas apenas com ordens de mercado.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy with martingale-style recovery.
/// Detects abnormally large candles relative to recent history and enters in the breakout direction.
/// After a stop-loss, enters recovery mode with a wider take-profit target.
/// </summary>
public class MartinGaleBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _requiredHistory;
	private readonly StrategyParam<decimal> _breakoutFactor;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<decimal> _recoveryMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _ranges = new();
	private decimal _entryPrice;
	private Sides? _entrySide;
	private bool _recovering;

	public int RequiredHistory
	{
		get => _requiredHistory.Value;
		set => _requiredHistory.Value = value;
	}

	public decimal BreakoutFactor
	{
		get => _breakoutFactor.Value;
		set => _breakoutFactor.Value = value;
	}

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

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

	public decimal RecoveryMultiplier
	{
		get => _recoveryMultiplier.Value;
		set => _recoveryMultiplier.Value = value;
	}

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

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

		_breakoutFactor = Param(nameof(BreakoutFactor), 2.5m)
			.SetDisplay("Breakout Factor", "Multiplier for abnormal candle detection", "General");

		_takeProfitPct = Param(nameof(TakeProfitPct), 0.5m)
			.SetDisplay("TP %", "Take profit percent of entry", "Trading");

		_stopLossPct = Param(nameof(StopLossPct), 0.3m)
			.SetDisplay("SL %", "Stop loss percent of entry", "Trading");

		_recoveryMultiplier = Param(nameof(RecoveryMultiplier), 1.5m)
			.SetDisplay("Recovery Mult", "TP multiplier during recovery", "Trading");

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

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

		_ranges.Clear();
		_entryPrice = 0;
		_entrySide = null;
		_recovering = 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 close = candle.ClosePrice;
		var range = candle.HighPrice - candle.LowPrice;

		// Check exit
		if (Position != 0 && _entryPrice > 0)
		{
			var tpPct = _recovering ? TakeProfitPct * RecoveryMultiplier : TakeProfitPct;

			if (_entrySide == Sides.Buy)
			{
				var pnl = (close - _entryPrice) / _entryPrice * 100m;
				if (pnl >= tpPct || pnl <= -StopLossPct)
				{
					var wasLoss = pnl < 0;
					SellMarket();
					_entryPrice = 0;
					_entrySide = null;
					_recovering = wasLoss;
					AddRange(range);
					return;
				}
			}
			else if (_entrySide == Sides.Sell)
			{
				var pnl = (_entryPrice - close) / _entryPrice * 100m;
				if (pnl >= tpPct || pnl <= -StopLossPct)
				{
					var wasLoss = pnl < 0;
					BuyMarket();
					_entryPrice = 0;
					_entrySide = null;
					_recovering = wasLoss;
					AddRange(range);
					return;
				}
			}
		}

		// Entry - only when flat
		if (Position == 0 && _ranges.Count >= RequiredHistory)
		{
			decimal sum = 0;
			for (int i = 0; i < _ranges.Count; i++)
				sum += _ranges[i];
			var avgRange = sum / _ranges.Count;

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

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

		AddRange(range);
	}

	private void AddRange(decimal range)
	{
		_ranges.Add(range);
		while (_ranges.Count > RequiredHistory)
			_ranges.RemoveAt(0);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_ranges.Clear();
		_entryPrice = 0;
		_entrySide = null;
		_recovering = false;

		base.OnReseted();
	}
}