Ver no GitHub

Macd Pattern Trader v03 (porta StockSharp)

Visão geral

Macd Pattern Trader v03 é uma estratégia StockSharp de alto nível convertida do consultor especialista MetaTrader 4 MacdPatternTraderv03. O robô original procura na linha principal MACD uma formação de reversão de três picos e aplica regras parciais de realização de lucro com base em médias móveis. Esta porta C# preserva a lógica padrão ao usar StockSharp assinaturas, indicadores e auxiliares de pedido.

A estratégia foi projetada para configurações de exaustão de tendências em pares de FX líquidos, mas pode ser aplicada a qualquer instrumento que exponha uma curva MACD suave. O período padrão é de velas de 30 minutos, correspondendo ao consultor original, e o tamanho da negociação padrão é um contrato (ou lote equivalente em termos de StockSharp).

Indicadores e fluxo de dados

  • MACD (EMA rápida 5, EMA lenta 13, Sinal 1) — indicador principal usado para detectar a estrutura triplo topo/triplo fundo. A linha de sinal não é usada; a estratégia depende apenas da linha principal MACD.
  • EMA(7) e EMA(21) — médias curtas e médias usadas durante o gerenciamento de posições.
  • SMA(98) e EMA(365) — filtros lentos que formam o gatilho de expansão.

A implementação assina o tipo de vela configurado e vincula os indicadores por meio de Bind / BindEx. Apenas velas finalizadas são processadas para evitar atuação em dados incompletos.

Regras de entrada

Configuração curta

  1. Arme a configuração quando a linha principal MACD ultrapassar o nível Ativação superior (padrão 0,0030).
  2. Registre o primeiro pico assim que MACD imprimir um máximo local acima dos valores anteriores e anteriores e, em seguida, cair abaixo do Limite Superior (padrão 0,0045).
  3. Registre o segundo pico se MACD retornar acima do limite, atingir um máximo local mais alto e cair novamente abaixo do limite.
  4. Confirme o padrão quando ocorrer um terceiro rollover com MACD permanecendo abaixo do limite por três barras consecutivas e o último máximo local for inferior ao anterior.
  5. Se não existir nenhuma posição longa, nivele qualquer exposição longa restante e abra uma posição curta com o volume configurado.

Configuração longa

  1. Arme a configuração quando a linha principal MACD cair abaixo do nível Ativação inferior (padrão -0,0030).
  2. Registre o primeiro vale assim que MACD imprimir um mínimo local abaixo dos dois valores anteriores e, em seguida, subir acima do Limite Inferior (padrão -0,0045).
  3. Registre o segundo vale se MACD cair abaixo do limite, atingir um mínimo inferior e subir acima do limite novamente.
  4. Confirme o padrão de alta quando um terceiro aumento for observado com MACD permanecendo acima do limite por três velas e o último mínimo for superior ao anterior.
  5. Achate qualquer exposição curta restante e compre o volume configurado.

A lógica espelha os sinalizadores stops, stops1 e aop_ok* aninhados no arquivo MQ4 original, incluindo redefinições sempre que MACD ultrapassa a banda de ativação.

Gestão comercial

  • Escalonamento horizontal — quando o lucro não realizado (calculado como (Close − Entry) * Position) excede ProfitThreshold (padrão 5 unidades de preço), a estratégia aplica duas saídas escalonadas:
    • Estágio 1 (longo): o fechamento da vela anterior deve ficar acima de EMA(21). A estratégia vende um terço da posição longa inicial. Para posições vendidas, o requisito é o fechamento anterior abaixo de EMA(21) e um terço do volume vendido inicial é recomprado.
    • Estágio 2 (longo): a máxima da vela anterior deve ultrapassar a média de SMA(98) e EMA(365). Metade da posição longa original está fechada. Os shorts refletem isso com o mínimo anterior caindo abaixo do filtro médio.
  • Posição residual — o que resta após a sequência de escalonamento não ser gerenciada por esta porta, correspondendo à origem EA.
  • Ordens de risco — a versão MetaTrader colocou ordens de stop-loss e take-profit com base em máximos e mínimos contínuos. Como StockSharp gerencia ordens de proteção de maneira diferente, esta porta não anexa automaticamente paradas/alvos. Os usuários podem combinar a estratégia com StartProtection() ou um módulo de risco externo, se necessário.

Parâmetros

Nome Padrão Descrição
Volume 1 Tamanho da negociação enviado em cada entrada.
CandleType Período de 30 minutos Série de velas usada para cálculos de indicadores.
FastEmaLength / SlowEmaLength 5/13 MACD períodos EMA rápidos e lentos.
UpperThreshold / LowerThreshold 0,0045 / −0,0045 Faixa de exaustão onde acontecem as confirmações de padrões.
UpperActivation / LowerActivation 0,0030 / −0,0030 Banda externa que arma as configurações de baixa/alta.
EmaOneLength / EmaTwoLength 21/07 EMAs auxiliares para visualização e lógica de escalonamento.
SmaLength 98 Lento SMA usado junto com EMA(365) durante as saídas do estágio dois.
EmaFourLength 365 EMA de longo prazo usado durante as saídas do estágio dois.
ProfitThreshold 5 PnL mínimo não realizado (preço * unidades de volume) necessário antes da expansão.

Notas práticas

  • Certifique-se de que o adaptador intermediário suporte redução parcial de posição. O EA original fechou 1/3 e 1/2 porções; esta porta replica as mesmas frações usando ordens de mercado.
  • Como as ordens de proteção não são anexadas automaticamente, considere ativar StartProtection() ou adicionar regras de risco personalizadas se precisar de interrupções bruscas.
  • O limite de lucro é expresso em unidades de preço bruto * volume. Ajuste-o de acordo com o tamanho do pip ou valor do tick do instrumento para corresponder à suposição de “5 unidades monetárias” do código MQ4 original.
  • A estratégia espera uma dinâmica MACD suave; ruído excessivo ou instrumentos ilíquidos podem impedir o disparo da lógica de três picos.

Diferenças da versão MQ4

  • Usa ligações de indicadores StockSharp em vez de chamadas iMACD repetidas.
  • O cálculo do lucro não realizado depende de Position e PositionAvgPrice, o que significa que as regras de arredondamento do corretor podem ser diferentes das OrderProfit() de MetaTrader.
  • As ordens stop-loss e take-profit não são geradas automaticamente; ferramentas manuais de risco devem ser adicionadas, se necessário.
  • O parâmetro MQ4 sum_bars_bup não está presente porque não foi utilizado na origem original.
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 pattern strategy inspired by the MetaTrader advisor "MacdPatternTraderv03".
/// </summary>
public class MacdPatternTraderV03Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<decimal> _upperThreshold;
	private readonly StrategyParam<decimal> _upperActivation;
	private readonly StrategyParam<decimal> _lowerThreshold;
	private readonly StrategyParam<decimal> _lowerActivation;
	private readonly StrategyParam<int> _emaOneLength;
	private readonly StrategyParam<int> _emaTwoLength;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<int> _emaFourLength;
	private readonly StrategyParam<decimal> _profitThreshold;

	private decimal? _previousMacd;
	private decimal? _olderMacd;
	private decimal _entryPrice;

	private bool _isAboveUpperActivation;
	private bool _firstUpperDropConfirmed;
	private bool _secondUpperDropConfirmed;
	private bool _sellReady;
	private decimal _firstUpperPeak;
	private decimal _secondUpperPeak;

	private bool _isBelowLowerActivation;
	private bool _firstLowerRiseConfirmed;
	private bool _secondLowerRiseConfirmed;
	private bool _buyReady;
	private decimal _firstLowerTrough;
	private decimal _secondLowerTrough;

	private decimal? _emaTwoValue;
	private decimal? _smaValue;
	private decimal? _emaFourValue;

	private ICandleMessage _previousCandle;

	private int _longScaleStage;
	private int _shortScaleStage;
	private decimal _initialLongPosition;
	private decimal _initialShortPosition;

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public MacdPatternTraderV03Strategy()
	{

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Time frame used for calculations", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 5)
		.SetDisplay("Fast EMA", "Fast period used inside MACD", "MACD");

		_slowEmaLength = Param(nameof(SlowEmaLength), 13)
		.SetDisplay("Slow EMA", "Slow period used inside MACD", "MACD");

		_upperThreshold = Param(nameof(UpperThreshold), 50m)
		.SetDisplay("Upper Threshold", "Level that confirms bearish exhaustion", "MACD");

		_upperActivation = Param(nameof(UpperActivation), 30m)
		.SetDisplay("Upper Activation", "Level that arms the bearish pattern", "MACD");

		_lowerThreshold = Param(nameof(LowerThreshold), -50m)
		.SetDisplay("Lower Threshold", "Level that confirms bullish exhaustion", "MACD");

		_lowerActivation = Param(nameof(LowerActivation), -30m)
		.SetDisplay("Lower Activation", "Level that arms the bullish pattern", "MACD");

		_emaOneLength = Param(nameof(EmaOneLength), 7)
		.SetDisplay("EMA #1", "Short EMA used for scaling out", "Management");

		_emaTwoLength = Param(nameof(EmaTwoLength), 21)
		.SetDisplay("EMA #2", "Second EMA used for scaling out", "Management");

		_smaLength = Param(nameof(SmaLength), 98)
		.SetDisplay("SMA", "Simple moving average used for scaling out", "Management");

		_emaFourLength = Param(nameof(EmaFourLength), 365)
		.SetDisplay("EMA #4", "Slow EMA used for scaling out", "Management");

		_profitThreshold = Param(nameof(ProfitThreshold), 5m)
		.SetDisplay("Profit Threshold", "Unrealized PnL required before scaling out", "Management");
	}


	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Fast EMA length inside MACD.
	/// </summary>
	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length inside MACD.
	/// </summary>
	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	/// <summary>
	/// Upper threshold that marks MACD exhaustion for shorts.
	/// </summary>
	public decimal UpperThreshold
	{
		get => _upperThreshold.Value;
		set => _upperThreshold.Value = value;
	}

	/// <summary>
	/// Upper activation level that arms the short pattern.
	/// </summary>
	public decimal UpperActivation
	{
		get => _upperActivation.Value;
		set => _upperActivation.Value = value;
	}

	/// <summary>
	/// Lower threshold that marks MACD exhaustion for longs.
	/// </summary>
	public decimal LowerThreshold
	{
		get => _lowerThreshold.Value;
		set => _lowerThreshold.Value = value;
	}

	/// <summary>
	/// Lower activation level that arms the long pattern.
	/// </summary>
	public decimal LowerActivation
	{
		get => _lowerActivation.Value;
		set => _lowerActivation.Value = value;
	}

	/// <summary>
	/// Short EMA used for position management.
	/// </summary>
	public int EmaOneLength
	{
		get => _emaOneLength.Value;
		set => _emaOneLength.Value = value;
	}

	/// <summary>
	/// Second EMA used for position management.
	/// </summary>
	public int EmaTwoLength
	{
		get => _emaTwoLength.Value;
		set => _emaTwoLength.Value = value;
	}

	/// <summary>
	/// SMA length used for position management.
	/// </summary>
	public int SmaLength
	{
		get => _smaLength.Value;
		set => _smaLength.Value = value;
	}

	/// <summary>
	/// Slow EMA used for position management.
	/// </summary>
	public int EmaFourLength
	{
		get => _emaFourLength.Value;
		set => _emaFourLength.Value = value;
	}

	/// <summary>
	/// Minimum unrealized PnL before scaling out (in price units * volume).
	/// </summary>
	public decimal ProfitThreshold
	{
		get => _profitThreshold.Value;
		set => _profitThreshold.Value = value;
	}

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

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

		var macd = new MovingAverageConvergenceDivergence();
		macd.ShortMa.Length = FastEmaLength;
		macd.LongMa.Length = SlowEmaLength;

		var emaOne = new ExponentialMovingAverage { Length = EmaOneLength };
		var emaTwo = new ExponentialMovingAverage { Length = EmaTwoLength };
		var sma = new SimpleMovingAverage { Length = SmaLength };
		var emaFour = new ExponentialMovingAverage { Length = EmaFourLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(macd, emaOne, emaTwo, sma, emaFour, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, macd);
			DrawIndicator(area, emaOne);
			DrawIndicator(area, emaTwo);
			DrawIndicator(area, sma);
			DrawIndicator(area, emaFour);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal macdMain, decimal emaOne, decimal emaTwo, decimal sma, decimal emaFour)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_emaTwoValue = emaTwo;
		_smaValue = sma;
		_emaFourValue = emaFour;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			CacheMacd(macdMain);
			_previousCandle = candle;
			return;
		}

		if (_previousMacd is null || _olderMacd is null)
		{
			CacheMacd(macdMain);
			_previousCandle = candle;
			return;
		}

		var macdPrev = _previousMacd.Value;
		var macdPrev2 = _olderMacd.Value;

		EvaluateSellPattern(macdMain, macdPrev, macdPrev2);
		EvaluateBuyPattern(macdMain, macdPrev, macdPrev2);
		ManageOpenPosition(candle);

		CacheMacd(macdMain);
		_previousCandle = candle;
	}

	private void EvaluateSellPattern(decimal macdCurrent, decimal macdPrevious, decimal macdPrevious2)
	{
		if (macdCurrent > UpperActivation)
		_isAboveUpperActivation = true;

		if (_isAboveUpperActivation && macdCurrent < macdPrevious && macdPrevious > macdPrevious2 && macdPrevious > _firstUpperPeak && !_firstUpperDropConfirmed)
		_firstUpperPeak = macdPrevious;

		if (_firstUpperPeak > 0m && macdCurrent < UpperThreshold)
		_firstUpperDropConfirmed = true;

		if (macdCurrent < UpperActivation)
		{
			ResetSellPattern();
			return;
		}

		if (_firstUpperDropConfirmed && macdCurrent > UpperThreshold && macdCurrent < macdPrevious && macdPrevious > macdPrevious2 && macdPrevious > _firstUpperPeak && macdPrevious > _secondUpperPeak && !_secondUpperDropConfirmed)
		_secondUpperPeak = macdPrevious;

		if (_secondUpperPeak > 0m && macdCurrent < UpperThreshold)
		_secondUpperDropConfirmed = true;

		if (_secondUpperDropConfirmed && macdCurrent < UpperThreshold && macdPrevious < UpperThreshold && macdPrevious2 < UpperThreshold && macdCurrent < macdPrevious && macdPrevious > macdPrevious2 && macdPrevious < _secondUpperPeak)
		_sellReady = true;

		if (!_sellReady)
		return;

		EnterShort();
	}

	private void EvaluateBuyPattern(decimal macdCurrent, decimal macdPrevious, decimal macdPrevious2)
	{
		if (macdCurrent < LowerActivation)
		_isBelowLowerActivation = true;

		if (_isBelowLowerActivation && macdCurrent > macdPrevious && macdPrevious < macdPrevious2 && macdPrevious < _firstLowerTrough && !_firstLowerRiseConfirmed)
		_firstLowerTrough = macdPrevious;

		if (_firstLowerTrough < 0m && macdCurrent > LowerThreshold)
		_firstLowerRiseConfirmed = true;

		if (macdCurrent > LowerActivation)
		{
			ResetBuyPattern();
			return;
		}

		if (_firstLowerRiseConfirmed && macdCurrent < LowerThreshold && macdCurrent > macdPrevious && macdPrevious < macdPrevious2 && macdPrevious < _firstLowerTrough && macdPrevious < _secondLowerTrough && !_secondLowerRiseConfirmed)
		_secondLowerTrough = macdPrevious;

		if (_secondLowerTrough < 0m && macdCurrent > LowerThreshold)
		_secondLowerRiseConfirmed = true;

		if (_secondLowerRiseConfirmed && macdCurrent > LowerThreshold && macdPrevious > LowerThreshold && macdPrevious2 > LowerThreshold && macdCurrent > macdPrevious && macdPrevious < macdPrevious2 && macdPrevious > _secondLowerTrough)
		_buyReady = true;

		if (!_buyReady)
		return;

		EnterLong();
	}

	private void EnterShort()
	{
		var currentPosition = Position;
		var flattenVolume = currentPosition > 0m ? currentPosition : 0m;
		if (flattenVolume > 0m)
			SellMarket(flattenVolume);

		var entryVolume = Volume + Math.Max(0m, Position);
		if (entryVolume <= 0m)
		{
			ResetSellPattern();
			_sellReady = false;
			return;
		}

		SellMarket(entryVolume);
		_entryPrice = _previousCandle?.ClosePrice ?? 0m;
		_initialShortPosition = Math.Abs(Position);
		_shortScaleStage = 0;
		_longScaleStage = 0;
		_sellReady = false;
		ResetSellPattern();
		ResetBuyPattern();
	}

	private void EnterLong()
	{
		var currentPosition = Position;
		var flattenVolume = currentPosition < 0m ? -currentPosition : 0m;
		if (flattenVolume > 0m)
			BuyMarket(flattenVolume);

		var entryVolume = Volume + Math.Max(0m, -Position);
		if (entryVolume <= 0m)
		{
			ResetBuyPattern();
			_buyReady = false;
			return;
		}

		BuyMarket(entryVolume);
		_entryPrice = _previousCandle?.ClosePrice ?? 0m;
		_initialLongPosition = Math.Max(0m, Position);
		_longScaleStage = 0;
		_shortScaleStage = 0;
		_buyReady = false;
		ResetBuyPattern();
		ResetSellPattern();
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		if (Position == 0m)
		{
			_longScaleStage = 0;
			_shortScaleStage = 0;
			_initialLongPosition = 0m;
			_initialShortPosition = 0m;
			return;
		}

		var previousCandle = _previousCandle;
		if (previousCandle is null)
		return;

		var profitThreshold = ProfitThreshold;
		if (profitThreshold <= 0m)
		return;

		var unrealized = GetUnrealizedPnL(candle);
		if (unrealized < profitThreshold)
		return;

		if (Position > 0m)
		{
			if (_emaTwoValue is decimal emaTwo && previousCandle.ClosePrice > emaTwo && _longScaleStage == 0)
			{
				var volume = Math.Min(Position, _initialLongPosition / 3m);
				if (volume > 0m)
				{
					SellMarket(volume);
					_longScaleStage = 1;
				}
			}

			if (_smaValue is decimal sma && _emaFourValue is decimal emaFour && previousCandle.HighPrice > (sma + emaFour) / 2m && _longScaleStage == 1)
			{
				var volume = Math.Min(Position, _initialLongPosition / 2m);
				if (volume > 0m)
				{
					SellMarket(volume);
					_longScaleStage = 2;
				}
			}
		}
		else if (Position < 0m)
		{
			var shortPosition = -Position;
			if (_emaTwoValue is decimal emaTwo && previousCandle.ClosePrice < emaTwo && _shortScaleStage == 0)
			{
				var volume = Math.Min(shortPosition, _initialShortPosition / 3m);
				if (volume > 0m)
				{
					BuyMarket(volume);
					_shortScaleStage = 1;
				}
			}

			if (_smaValue is decimal sma && _emaFourValue is decimal emaFour && previousCandle.LowPrice < (sma + emaFour) / 2m && _shortScaleStage == 1)
			{
				var volume = Math.Min(shortPosition, _initialShortPosition / 2m);
				if (volume > 0m)
				{
					BuyMarket(volume);
					_shortScaleStage = 2;
				}
			}
		}
	}

	private void CacheMacd(decimal macdValue)
	{
		_olderMacd = _previousMacd;
		_previousMacd = macdValue;
	}

	private decimal GetUnrealizedPnL(ICandleMessage candle)
	{
		if (Position == 0m)
			return 0m;

		if (_entryPrice == 0m)
			return 0m;

		var diff = candle.ClosePrice - _entryPrice;
		return diff * Position;
	}

	private void ResetSellPattern()
	{
		_isAboveUpperActivation = false;
		_firstUpperDropConfirmed = false;
		_secondUpperDropConfirmed = false;
		_sellReady = false;
		_firstUpperPeak = 0m;
		_secondUpperPeak = 0m;
	}

	private void ResetBuyPattern()
	{
		_isBelowLowerActivation = false;
		_firstLowerRiseConfirmed = false;
		_secondLowerRiseConfirmed = false;
		_buyReady = false;
		_firstLowerTrough = 0m;
		_secondLowerTrough = 0m;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousMacd = null;
		_olderMacd = null;
		_entryPrice = 0m;

		_isAboveUpperActivation = false;
		_firstUpperDropConfirmed = false;
		_secondUpperDropConfirmed = false;
		_sellReady = false;
		_firstUpperPeak = 0m;
		_secondUpperPeak = 0m;

		_isBelowLowerActivation = false;
		_firstLowerRiseConfirmed = false;
		_secondLowerRiseConfirmed = false;
		_buyReady = false;
		_firstLowerTrough = 0m;
		_secondLowerTrough = 0m;

		_emaTwoValue = null;
		_smaValue = null;
		_emaFourValue = null;

		_previousCandle = null;

		_longScaleStage = 0;
		_shortScaleStage = 0;
		_initialLongPosition = 0m;
		_initialShortPosition = 0m;
	}
}