Ver no GitHub

Estratégia ProMart MACD Martingale

Esta estratégia é um port do StockSharp do especialista MQL histórico MartGreg_1 / ProMart. Ela combina duas configurações MACD com um modelo de dimensionamento de posição martingale controlado. O MACD primário busca mínimos e máximos locais no momentum, enquanto o MACD secundário confirma a direção da inclinação recente. Após cada operação fechada, a estratégia segue o padrão do indicador novamente (quando a última operação foi lucrativa) ou imediatamente inverte a direção (após uma perda) enquanto potencialmente dobra o tamanho da próxima ordem.

Lógica de trading

  • Sinais
    • Construir dois indicadores MACD na série de velas selecionada:
      • MACD1 (rápido=5, lento=20, sinal=3) atua como detector de padrões.
      • MACD2 (rápido=10, lento=15, sinal=3) confirma a inclinação de curto prazo.
    • Avaliar sinais apenas em velas concluídas usando os três valores MACD1 anteriores e os dois valores MACD2 anteriores (refletindo a lógica MQL que olhava uma barra atrás).
    • Configuração comprada: MACD1 forma um vale local (MACD1[t-1] > MACD1[t-2] < MACD1[t-3]) e MACD2 está subindo (MACD2[t-2] > MACD2[t-1]).
    • Configuração vendida: MACD1 forma um pico local enquanto MACD2 está caindo.
    • Se a última operação fechada foi lucrativa, a estratégia aguarda o próximo setup válido. Após uma operação perdedora abre a direção oposta imediatamente, independentemente da forma atual do MACD, replicando a reversão martingale original.
  • Gestão de posições
    • As operações são abertas com ordens de mercado e monitoradas em cada vela concluída.
    • Os níveis de stop-loss e take-profit são calculados em pontos de preço a partir do preço de entrada. Se o máximo/mínimo da vela atingir qualquer nível, a posição é fechada a mercado e o resultado da operação é registrado.
    • Nenhuma nova operação é aberta na mesma vela que fechou uma posição; a estratégia aguarda a próxima barra, assim como o especialista MQL que agia no primeiro tick de uma nova barra.
  • Dimensionamento martingale
    • Um volume base é derivado do patrimônio do portfólio dividido por BalanceDivider e alinhado ao passo de volume do instrumento (recorrendo à propriedade Volume ou ao volume mínimo do instrumento quando necessário).
    • Após uma operação perdedora, a próxima posição pode dobrar o volume da ordem anterior, até MaxDoublingCount vezes consecutivas. A lucratividade reinicia o contador de doblamento.
    • O volume é sempre limitado pelo volume máximo do instrumento para evitar o superdimensionamento.

Parâmetros

Parâmetro Descrição Padrão
BalanceDivider Divisor aplicado ao patrimônio do portfólio para calcular o volume base da ordem. 1000
MaxDoublingCount Número máximo de dobramentos de volume consecutivos após perdas. 1
StopLossPoints Distância do stop-loss medida em pontos de preço (PriceStep * StopLossPoints). 500
TakeProfitPoints Distância do take-profit medida em pontos de preço. 1500
Macd1Fast / Macd1Slow / Macd1Signal Períodos para o MACD primário que detecta vales/picos. 5 / 20 / 3
Macd2Fast / Macd2Slow / Macd2Signal Períodos para o filtro de inclinação do MACD secundário. 10 / 15 / 3
CandleType Tipo de dados da série de velas (padrão: período de 1 minuto). TimeSpan.FromMinutes(1).TimeFrame()

Notas

  • A implementação aproxima os preenchimentos de stop-loss e take-profit intrabar usando os máximos e mínimos das velas porque o exemplo do StockSharp opera em velas concluídas.
  • O volume da posição recorre à propriedade Volume da estratégia ou ao volume mínimo do instrumento quando os dados do portfólio não estão disponíveis.
  • Ainda não há versão Python; apenas a estratégia C# está incluída.
  • Sempre validar a configuração em dados históricos antes de habilitar o trading real. O componente martingale aumenta significativamente o risco.
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;
			}
		}
	}
}