Ver en GitHub

Martingale Estrategia de ruptura

Descripción general

La Martingale estrategia de ruptura es una StockSharp adaptación del MetaTrader asesor experto MartinGaleBreakout.mq5. el sistema espera velas de ruptura anormalmente grandes y coloca una orden de mercado única en la dirección de ruptura. Mientras que el EA original rastrea un "número mágico" para gestionar sus posiciones, la implementación de StockSharp depende del contexto de la estrategia, por lo que el comportamiento es efectivamente el mismo cuando la estrategia se ejecuta de forma aislada.

El algoritmo se centra en dos ideas centrales:

  1. Detección de ruptura: la estrategia examina el tamaño de cada vela terminada y la compara con el rango promedio de la diez velas anteriores. Cuando el rango actual es tres veces mayor que el promedio y la vela cierra con fuerza en el dirección de la ruptura, se produce una señal comercial.
  2. Recuperación al estilo Martingale: la estrategia realiza un seguimiento de las pérdidas y ganancias flotantes. Siempre que el PnL no realizado alcance el umbral de pérdida configurado, cierra inmediatamente todas las posiciones abiertas y aumenta el siguiente objetivo de ganancias para que la siguiente operación intenta recuperar la pérdida. Una vez que se alcanza el objetivo aumentado, los umbrales se restablecen a los valores originales.

El puerto mantiene todos los parámetros de administración de dinero del código MQL5, incluido el porcentaje de saldo reservado para margen, el objetivos de pérdidas y ganancias basados en porcentajes, y el multiplicador que amplía la distancia de obtención de beneficios durante la fase de recuperación.

Lógica comercial

  1. Suscríbase a la serie de velas configuradas y espere las velas terminadas.
  2. Calcule el rango de la vela (High - Low) y mantenga un búfer de tamaño fijo con los diez rangos anteriores para determinar el promedio de referencia utilizado para la detección de rupturas.
  3. Calcule el PnL flotante siguiendo los precios de entrada promedio para los lados largo y corto. Si el PnL no realizado excede el objetivo de ganancias o supera el umbral de stop-loss, cierre inmediatamente todas las posiciones y restablezca el estado de recuperación como en el asesor experto original.
  4. Omita la colocación de órdenes mientras la estrategia ya tenga una posición o cuando el estado de conexión no permita operar.
  5. Cuando aparezca una vela de ruptura alcista, dimensione la orden para que la ganancia esperada coincida con el objetivo actual. la toma de ganancias La distancia en los pasos de precio se multiplica durante la recuperación, exactamente como el parámetro TP_Points_Multiplier del EA.
  6. Valide el volumen calculado con los límites del instrumento (mínimo, máximo y paso) y asegúrese de que se cumpla el margen requerido. no excede la asignación de saldo configurada o los fondos gratuitos disponibles. Si se respetan las restricciones, envíe un orden de compra de mercado.
  7. Repita el mismo proceso para rupturas bajistas, enviando en su lugar una orden de venta de mercado.

La combinación de estas reglas recrea el comportamiento del sistema MetaTrader original, incluida la transición hacia y desde del modo de recuperación después de un evento de stop-loss.

Parámetros

Parámetro Descripción Predeterminado
TakeProfitPoints Distancia entre el precio de entrada y el precio de obtención de beneficios expresado en incrementos de precio. 50
BalancePercentAvailable Porcentaje máximo del saldo de la cuenta que se puede reservar para margen en una sola operación. 50
TakeProfitPercentOfBalance Beneficio objetivo expresado como porcentaje del saldo actual. 0.1
StopLossPercentOfBalance Tamaño del stop-loss expresado como porcentaje del saldo actual. 10
RecoveryStartFraction Fracción del stop-loss utilizado antes de cambiar al modo de recuperación. 0.1
RecoveryPointsMultiplier Multiplicador aplicado a la distancia de obtención de beneficios durante la recuperación. 1
CandleType Fuente de datos de velas utilizada por la estrategia (período de tiempo, velas de tick, etc.). 15-minute time frame

Notas adicionales

  • El cálculo del volumen replica el MetaTrader ayudante CalcLotWithTP. Deduce el tamaño del lote necesario para alcanzar el precio actual. objetivo de ganancias para un movimiento de precio determinado y luego normaliza el resultado al paso de volumen del instrumento.
  • Las comprobaciones de márgenes se realizan con el mismo espíritu que CheckVolumeValue y el filtro de porcentaje de saldo utilizado en MQL versión. Las órdenes se rechazan cuando el margen requerido excede la parte permitida del saldo o los fondos libres informados por la cartera.
  • La estrategia cancela todas las órdenes activas antes de aplanar las posiciones para que el comportamiento coincida con el ayudante CloseAllOrders de el asesor experto original.
  • El búfer de rango interno almacena solo diez valores y equivale a iterar sobre iHigh/iLow en la fuente EA. No Se requieren datos históricos más allá de las últimas diez 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();
	}
}