Ver en GitHub

Estrategia de ruptura de MartinGale

Descripción general

La Estrategia de ruptura de MartinGale es un sistema de seguimiento de rupturas convertido del MetaTrader 4 asesor experto MartinGaleBreakout. El robot original ingresa a posiciones después de detectar velas anormalmente grandes y aplica un mecanismo de recuperación estilo martingala para recuperar pérdidas anteriores. Este puerto StockSharp reproduce el comportamiento utilizando la estrategia de alto nivel API con suscripciones de velas y parámetros de administración de dinero.

La estrategia monitorea una serie de velas configurables, buscando velas cuyo rango sea al menos tres veces mayor que el rango promedio de las diez barras anteriores. Cuando dicha vela se cierra con fuerza en una dirección, la estrategia abre una posición de mercado en esa dirección. Si la posición se cierra con una pérdida que excede un umbral configurable, el modo de recuperación aumenta la distancia de toma de ganancias para compensar la reducción realizada.

Lógica de trading

  1. Suscríbete a la serie de velas seleccionada (velas de 15 minutos por defecto).
  2. Mantenga las 11 velas terminadas más recientes para evaluar una volatilidad anormal.
  3. Detecta una ruptura alcista cuando:
    • La vela actual es tres veces mayor que el rango promedio de las diez velas anteriores.
    • La vela cierra en la mitad superior de su rango.
  4. Detecte una ruptura bajista utilizando las condiciones simétricas.
  5. Abra una posición de mercado en la dirección de ruptura si:
    • Actualmente no hay ningún otro puesto disponible.
    • La exposición de capital estimada está por debajo del porcentaje de saldo configurado.
  6. Cerrar posiciones y restablecer objetivos de pérdidas/ganancias cuando:
    • El beneficio flotante alcanza el umbral de obtención de beneficios.
    • La pérdida flotante alcanza el umbral de stop-loss.
  7. Cuando se produzca un stop-loss, cambie al modo de recuperación:
    • Aumente la distancia de toma de ganancias mediante el multiplicador configurado.
    • Amplíe el límite de stop-loss al porcentaje máximo permitido.
    • Continúe operando hasta alcanzar el siguiente objetivo, luego restablezca la configuración base.

Parámetros

Nombre Descripción Predeterminado
TakeProfitPoints Distancia base de toma de ganancias expresada en puntos del instrumento. 50
BalancePercentageAvailable Participación máxima del saldo de la cuenta que se puede asignar a una sola operación. 50%
TakeProfitBalancePercent Beneficio objetivo como porcentaje del saldo de la cuenta. 0.1%
StopLossBalancePercent Reducción máxima antes de desencadenar la recuperación. 10%
StartRecoveryFactor Parte del stop-loss utilizado antes de activar el modo de recuperación. 0.2
TakeProfitPointsMultiplier Multiplicador aplicado a la distancia de obtención de beneficios durante la recuperación. 1
CandleType Serie de velas utilizadas para cálculos de ruptura. 15-minute

Dimensionamiento de posiciones y control de riesgos

  • La estrategia calcula el volumen requerido para lograr la toma de ganancias monetaria configurada utilizando el tamaño y el valor del tick del instrumento.
  • Los volúmenes están normalizados para las restricciones de intercambio (paso, mínimo, máximo).
  • La exposición de capital estimada no debe exceder el porcentaje de saldo configurado.
  • El modo de recuperación expande dinámicamente el objetivo de obtención de ganancias después de una pérdida, emulando el comportamiento martingala original mientras mantiene las posiciones limitadas a una única operación abierta.

Notas

  • La estrategia se basa en la información del saldo de la cartera; inicialícelo con una conexión de cartera antes de comenzar.
  • El manejo de comisiones refleja el EA original al centrarse en las pérdidas y ganancias flotantes derivadas de la posición actual.
  • No se utilizan órdenes pendientes; las entradas y salidas se realizan únicamente con órdenes 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();
	}
}