Ver en GitHub

Estrategia ProMart MACD Martingale

Esta estrategia es un port de StockSharp del experto histórico MQL MartGreg_1 / ProMart. Combina dos configuraciones MACD con un modelo de dimensionamiento de posición martingala controlado. El MACD primario busca mínimos y máximos locales en el momentum, mientras que el MACD secundario confirma la dirección de la pendiente reciente. Después de cada operación cerrada, la estrategia sigue el patrón del indicador nuevamente (cuando la última operación fue rentable) o inmediatamente invierte la dirección (tras una pérdida) mientras potencialmente dobla el tamaño de la siguiente orden.

Lógica de trading

  • Señales
    • Construir dos indicadores MACD en la serie de velas seleccionada:
      • MACD1 (rápido=5, lento=20, señal=3) actúa como detector de patrones.
      • MACD2 (rápido=10, lento=15, señal=3) confirma la pendiente a corto plazo.
    • Evaluar señales solo en velas completadas usando los tres valores MACD1 anteriores y los dos valores MACD2 anteriores (reflejando la lógica MQL que miraba una barra atrás).
    • Configuración larga: MACD1 forma un valle local (MACD1[t-1] > MACD1[t-2] < MACD1[t-3]) y MACD2 está subiendo (MACD2[t-2] > MACD2[t-1]).
    • Configuración corta: MACD1 forma un pico local mientras MACD2 está cayendo.
    • Si la última operación cerrada fue rentable, la estrategia espera el próximo setup válido. Tras una operación perdedora abre la dirección opuesta inmediatamente, independientemente de la forma actual del MACD, replicando la reversión martingala original.
  • Gestión de posiciones
    • Las operaciones se abren con órdenes de mercado y se monitorean en cada vela terminada.
    • Los niveles de stop-loss y take-profit se calculan en puntos de precio desde el precio de entrada. Si el máximo/mínimo de la vela alcanza cualquier nivel, la posición se cierra a mercado y se registra el resultado de la operación.
    • No se abre ninguna nueva operación en la misma vela que cerró una posición; la estrategia espera la siguiente barra, igual que el experto MQL que actuaba en el primer tick de una nueva barra.
  • Dimensionamiento martingala
    • Un volumen base se deriva del capital del portafolio dividido por BalanceDivider y alineado al paso de volumen del instrumento (cayendo de vuelta a la propiedad Volume o al volumen mínimo del instrumento cuando es necesario).
    • Después de una operación perdedora, la siguiente posición puede doblar el volumen de la orden anterior, hasta MaxDoublingCount veces consecutivas. La rentabilidad reinicia el contador de doblamiento.
    • El volumen siempre está limitado por el volumen máximo del instrumento para evitar el sobredimensionamiento.

Parámetros

Parámetro Descripción Predeterminado
BalanceDivider Divisor aplicado al capital del portafolio para calcular el volumen base de la orden. 1000
MaxDoublingCount Número máximo de doblajes de volumen consecutivos tras pérdidas. 1
StopLossPoints Distancia del stop-loss medida en puntos de precio (PriceStep * StopLossPoints). 500
TakeProfitPoints Distancia del take-profit medida en puntos de precio. 1500
Macd1Fast / Macd1Slow / Macd1Signal Períodos para el MACD primario que detecta valles/picos. 5 / 20 / 3
Macd2Fast / Macd2Slow / Macd2Signal Períodos para el filtro de pendiente del MACD secundario. 10 / 15 / 3
CandleType Tipo de datos de la serie de velas (predeterminado: marco temporal de 1 minuto). TimeSpan.FromMinutes(1).TimeFrame()

Notas

  • La implementación aproxima los rellenos de stop-loss y take-profit intrabar usando los máximos y mínimos de las velas porque el ejemplo de StockSharp opera en velas terminadas.
  • El volumen de la posición cae de vuelta a la propiedad Volume de la estrategia o al volumen mínimo del instrumento cuando los datos del portafolio no están disponibles.
  • Aún no se proporciona versión Python; solo se incluye la estrategia C#.
  • Siempre validar la configuración en datos históricos antes de habilitar el trading real. El componente martingala aumenta significativamente el riesgo.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD-based martingale strategy. Uses two MACD indicators to detect turning points.
/// Doubles volume after a loss up to MaxDoublingCount times.
/// </summary>
public class ProMartMacdMartingaleStrategy : Strategy
{
	private readonly StrategyParam<int> _maxDoublingCount;
	private readonly StrategyParam<int> _macd1Fast;
	private readonly StrategyParam<int> _macd1Slow;
	private readonly StrategyParam<int> _macd2Fast;
	private readonly StrategyParam<int> _macd2Slow;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _macd1History = new();
	private readonly List<decimal> _macd2History = new();

	private decimal _entryPrice;
	private bool _inPosition;
	private bool _isLong;
	private bool _lastTradeWasLoss;
	private int _martingaleCounter;
	private decimal _currentVolume;

	public int MaxDoublingCount
	{
		get => _maxDoublingCount.Value;
		set => _maxDoublingCount.Value = value;
	}

	public int Macd1Fast
	{
		get => _macd1Fast.Value;
		set => _macd1Fast.Value = value;
	}

	public int Macd1Slow
	{
		get => _macd1Slow.Value;
		set => _macd1Slow.Value = value;
	}

	public int Macd2Fast
	{
		get => _macd2Fast.Value;
		set => _macd2Fast.Value = value;
	}

	public int Macd2Slow
	{
		get => _macd2Slow.Value;
		set => _macd2Slow.Value = value;
	}

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

	public ProMartMacdMartingaleStrategy()
	{
		_maxDoublingCount = Param(nameof(MaxDoublingCount), 2)
			.SetNotNegative()
			.SetDisplay("Max Doubling", "Maximum number of volume doublings after losses.", "Risk");

		_macd1Fast = Param(nameof(Macd1Fast), 5)
			.SetGreaterThanZero()
			.SetDisplay("MACD1 Fast", "Fast EMA period for the primary MACD.", "Signal");

		_macd1Slow = Param(nameof(Macd1Slow), 20)
			.SetGreaterThanZero()
			.SetDisplay("MACD1 Slow", "Slow EMA period for the primary MACD.", "Signal");

		_macd2Fast = Param(nameof(Macd2Fast), 10)
			.SetGreaterThanZero()
			.SetDisplay("MACD2 Fast", "Fast EMA period for the secondary MACD.", "Filter");

		_macd2Slow = Param(nameof(Macd2Slow), 15)
			.SetGreaterThanZero()
			.SetDisplay("MACD2 Slow", "Slow EMA period for the secondary MACD.", "Filter");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Data type used for signal generation.", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_macd1History.Clear();
		_macd2History.Clear();
		_entryPrice = 0;
		_inPosition = false;
		_isLong = false;
		_lastTradeWasLoss = false;
		_martingaleCounter = 0;
		_currentVolume = 0;
	}

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

		_macd1History.Clear();
		_macd2History.Clear();
		_inPosition = false;
		_isLong = false;
		_lastTradeWasLoss = false;
		_martingaleCounter = 0;
		_currentVolume = Volume;
		_entryPrice = 0;

		var macd1 = new MovingAverageConvergenceDivergence(
			new ExponentialMovingAverage { Length = Macd1Slow },
			new ExponentialMovingAverage { Length = Macd1Fast });

		var macd2 = new MovingAverageConvergenceDivergence(
			new ExponentialMovingAverage { Length = Macd2Slow },
			new ExponentialMovingAverage { Length = Macd2Fast });

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

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

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

		_macd1History.Add(macd1Value);
		_macd2History.Add(macd2Value);

		if (_macd1History.Count > 4)
			_macd1History.RemoveAt(0);
		if (_macd2History.Count > 3)
			_macd2History.RemoveAt(0);

		// Check exit for open position
		if (_inPosition)
		{
			var pnl = _isLong
				? candle.ClosePrice - _entryPrice
				: _entryPrice - candle.ClosePrice;

			// Detect reversal to exit
			var shouldExit = false;
			if (_macd1History.Count >= 3)
			{
				var m0 = _macd1History[^1];
				var m1 = _macd1History[^2];
				var m2 = _macd1History[^3];

				if (_isLong && m0 < m1 && m1 > m2)
					shouldExit = true;
				else if (!_isLong && m0 > m1 && m1 < m2)
					shouldExit = true;
			}

			if (shouldExit)
			{
				if (_isLong)
					SellMarket();
				else
					BuyMarket();

				_lastTradeWasLoss = pnl < 0;
				if (_lastTradeWasLoss && _martingaleCounter < MaxDoublingCount)
				{
					_currentVolume *= 2;
					_martingaleCounter++;
				}
				else
				{
					_currentVolume = Volume;
					_martingaleCounter = 0;
				}

				_inPosition = false;
				return;
			}
		}

		// Check entry
		if (!_inPosition && _macd1History.Count >= 3 && _macd2History.Count >= 2)
		{
			var m0 = _macd1History[^1];
			var m1 = _macd1History[^2];
			var m2 = _macd1History[^3];
			var f0 = _macd2History[^1];
			var f1 = _macd2History[^2];

			// MACD1 turns up from bottom + MACD2 confirms
			var buySignal = m0 > m1 && m1 < m2 && f1 > f0;
			var sellSignal = m0 < m1 && m1 > m2 && f1 < f0;

			if (buySignal && Position <= 0)
			{
				BuyMarket();
				_inPosition = true;
				_isLong = true;
				_entryPrice = candle.ClosePrice;
			}
			else if (sellSignal && Position >= 0)
			{
				SellMarket();
				_inPosition = true;
				_isLong = false;
				_entryPrice = candle.ClosePrice;
			}
		}
	}
}