Ver no GitHub

Estratégia de Padrão MACD AO

Visão geral

Esta estratégia é uma fiel portagem para StockSharp do consultor especialista FORTRADER MACD.mq5. Implementa o padrão "AOP" que observa o oscilador MACD em busca de excursões profundas afastadas da linha zero seguidas de um gancho de retorno em direção à neutralidade. Quando o gancho é confirmado, a estratégia entra na direção da reversão esperada e aplica imediatamente alvos fixos de stop-loss e take-profit expressos em pips.

Lógica da estratégia

Preparação de dados

  • Opera na série de velas selecionada pelo parâmetro CandleType (velas de 5 minutos por padrão).
  • Utiliza um indicador MACD padrão com períodos rápido, lento e sinal configuráveis (padrões 12/26/9).
  • Armazena os valores da linha principal MACD das três velas concluídas mais recentes para reproduzir o acesso baseado em índice do MQL (iMACD(...,1..3)).

Configuração curta (gancho de baixa)

  1. Armação – quando a linha principal MACD da última vela fechada cai abaixo de BearishExtremeLevel (padrão −0.0015), a estratégia começa a observar uma reversão.
  2. Recuo neutro – quando o MACD sobe de volta acima de BearishNeutralLevel (padrão −0.0005), a etapa de validação do gancho fica ativa.
  3. Confirmação do gancho – os três valores MACD anteriores devem formar um máximo local (macd₁ < macd₂ > macd₃) enquanto o valor mais recente permanece abaixo do nível neutro e o valor mais antigo permanece acima. Isso recria o padrão original que garante que o momentum está desvanecendo.
  4. Entrada – se nenhuma posição comprada estiver aberta (Position <= 0), uma ordem de venda a mercado de OrderVolume é enviada. Os níveis de proteção são calculados imediatamente: stop-loss acima da entrada em StopLossPips e take-profit abaixo em TakeProfitPips (convertidos para preço por GetPipSize).
  5. Qualquer leitura positiva do MACD cancela a configuração e reinicia a máquina de estado de baixa até que apareça um novo trecho negativo profundo.

Configuração longa (gancho de alta)

  1. Armação – quando o MACD sobe acima de BullishExtremeLevel (padrão +0.0015), o modo de observação de alta é ativado.
  2. Cancelamento imediato – se o MACD cair abaixo de zero, o cenário de alta é abandonado, espelhando a lógica do MQL.
  3. Recuo neutro – uma queda de volta abaixo de BullishNeutralLevel (padrão +0.0005) prepara a confirmação do gancho.
  4. Confirmação do gancho – os três valores MACD armazenados devem criar um mínimo local (macd₁ > macd₂ < macd₃) respeitando os limiares neutros.
  5. Entrada – se não houver exposição curta (Position >= 0), a estratégia compra a mercado com OrderVolume e define stop-loss e take-profit em torno da entrada simetricamente às regras curtas.

Gestão de risco

  • O stop-loss e o take-profit estão sempre ativos via _stopPrice e _takePrice. São avaliados em cada vela concluída usando a máxima/mínima registrada para emular a execução do lado do broker no EA original.
  • Os pips são convertidos para preços absolutos usando Security.PriceStep. Para símbolos FX de 3 e 5 dígitos, o passo é multiplicado por 10 para corresponder ao ajuste do MQL para pips fracionários.
  • Sempre que a estratégia sai de uma posição por causa dos níveis de proteção, os limpa imediatamente e aguarda uma nova configuração nas próximas velas.

Parâmetros

Parâmetro Descrição Padrão
CandleType Série de dados de velas processada pela estratégia. Período de 5 minutos
OrderVolume Volume enviado com cada ordem de mercado. 0.1
TakeProfitPips Distância ao alvo de lucro em pips. Marcado para otimização. 60
StopLossPips Distância ao stop-loss em pips. Marcado para otimização. 70
MacdFastPeriod Comprimento da EMA rápida para MACD. 12
MacdSlowPeriod Comprimento da EMA lenta para MACD. 26
MacdSignalPeriod Comprimento da EMA sinal para MACD. 9
BearishExtremeLevel Limiar negativo do MACD que arma oportunidades curtas. −0.0015
BearishNeutralLevel Limiar negativo do MACD usado para validar o gancho de baixa. −0.0005
BullishExtremeLevel Limiar positivo do MACD que arma oportunidades longas. +0.0015
BullishNeutralLevel Limiar positivo do MACD usado para validar o gancho de alta. +0.0005

Notas adicionais

  • A estratégia reage apenas uma vez por vela concluída, imitando o guardião PrevBars original no MQL.
  • A gestão de stop-loss/take-profit é puramente baseada em preço; não há ajustes de trailing ou reentradas até que o ciclo completo da máquina de estado se repita.
  • Projetado para contas de cobertura no EA fonte, mas esta portagem impõe uma única posição líquida verificando Position antes de enviar novas ordens.
  • Nenhuma versão Python foi fornecida conforme solicitado.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD-based reversal strategy that reproduces the FORTRADER AOP pattern.
/// </summary>
public class MacdAoPatternStrategy : Strategy
{
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<decimal> _bearishExtremeLevel;
	private readonly StrategyParam<decimal> _bearishNeutralLevel;
	private readonly StrategyParam<decimal> _bullishExtremeLevel;
	private readonly StrategyParam<decimal> _bullishNeutralLevel;
	private readonly StrategyParam<DataType> _candleType;

	private MACD _macd = null!;

	private decimal? _macdPrev1;
	private decimal? _macdPrev2;
	private decimal? _macdPrev3;

	private bool _bearishStageArmed;
	private bool _bearishTriggerReady;
	private bool _bearishSignalPending;

	private bool _bullishStageArmed;
	private bool _bullishTriggerReady;
	private bool _bullishSignalPending;

	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Distance to the take-profit level measured in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Distance to the stop-loss level measured in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Volume used for each market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Fast EMA period for the MACD indicator.
	/// </summary>
	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period for the MACD indicator.
	/// </summary>
	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	/// <summary>
	/// Signal line EMA period for the MACD indicator.
	/// </summary>
	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	/// <summary>
	/// MACD level that arms the bearish setup when the oscillator stretches deeply negative.
	/// </summary>
	public decimal BearishExtremeLevel
	{
		get => _bearishExtremeLevel.Value;
		set => _bearishExtremeLevel.Value = value;
	}

	/// <summary>
	/// MACD level that confirms the bearish hook back toward the zero line.
	/// </summary>
	public decimal BearishNeutralLevel
	{
		get => _bearishNeutralLevel.Value;
		set => _bearishNeutralLevel.Value = value;
	}

	/// <summary>
	/// MACD level that arms the bullish setup when the oscillator stretches deeply positive.
	/// </summary>
	public decimal BullishExtremeLevel
	{
		get => _bullishExtremeLevel.Value;
		set => _bullishExtremeLevel.Value = value;
	}

	/// <summary>
	/// MACD level that confirms the bullish hook back toward the zero line.
	/// </summary>
	public decimal BullishNeutralLevel
	{
		get => _bullishNeutralLevel.Value;
		set => _bullishNeutralLevel.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="MacdAoPatternStrategy"/>.
	/// </summary>
	public MacdAoPatternStrategy()
	{
		_takeProfitPips = Param(nameof(TakeProfitPips), 60)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk")
			;

		_stopLossPips = Param(nameof(StopLossPips), 70)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk")
			;

		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Volume for every market order", "Orders");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "Fast EMA length", "Indicators");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow", "Slow EMA length", "Indicators");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal", "Signal EMA length", "Indicators");

		_bearishExtremeLevel = Param(nameof(BearishExtremeLevel), -100m)
			.SetDisplay("Bearish Extreme", "Negative MACD level that arms shorts", "Signals");

		_bearishNeutralLevel = Param(nameof(BearishNeutralLevel), -30m)
			.SetDisplay("Bearish Neutral", "Negative MACD level that confirms the hook", "Signals");

		_bullishExtremeLevel = Param(nameof(BullishExtremeLevel), 100m)
			.SetDisplay("Bullish Extreme", "Positive MACD level that arms longs", "Signals");

		_bullishNeutralLevel = Param(nameof(BullishNeutralLevel), 30m)
			.SetDisplay("Bullish Neutral", "Positive MACD level that confirms the hook", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Source series for the strategy", "General");
	}

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

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

		_macdPrev1 = null;
		_macdPrev2 = null;
		_macdPrev3 = null;

		_bearishStageArmed = false;
		_bearishTriggerReady = false;
		_bearishSignalPending = false;

		_bullishStageArmed = false;
		_bullishTriggerReady = false;
		_bullishSignalPending = false;

		_stopPrice = null;
		_takePrice = null;
	}

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

		Volume = OrderVolume;

		_macd = new MACD();
		_macd.ShortMa.Length = MacdFastPeriod;
		_macd.LongMa.Length = MacdSlowPeriod;

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(_macd, ProcessCandle).Start();
	}

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

		// First handle protective exits using the finished candle range.
		HandlePositionExit(candle);

		if (!_macd.IsFormed)
		{
			UpdateMacdHistory(macdLine);
			return;
		}

		if (_macdPrev1 is null || _macdPrev2 is null || _macdPrev3 is null)
		{
			UpdateMacdHistory(macdLine);
			return;
		}

		var macd1 = _macdPrev1.Value;
		var macd2 = _macdPrev2.Value;
		var macd3 = _macdPrev3.Value;

		// --- Bearish sequence --------------------------------------------------

		if (macd1 < BearishExtremeLevel && !_bearishStageArmed)
		{
			// Arm the bearish setup after a deep negative MACD reading.
			_bearishStageArmed = true;
		}

		if (macd1 > BearishNeutralLevel && _bearishStageArmed)
		{
			// MACD returned toward zero, prepare for the hook confirmation.
			_bearishStageArmed = false;
			_bearishTriggerReady = true;
		}

		var bearishHook = _bearishTriggerReady &&
			macd1 < macd2 &&
			macd2 > macd3 &&
			macd1 < BearishNeutralLevel &&
			macd2 > BearishNeutralLevel;

		if (bearishHook)
		{
			// Confirm the bearish hook pattern.
			_bearishTriggerReady = false;
			_bearishSignalPending = true;
		}

		if (macd1 > 0)
		{
			// Positive MACD invalidates the bearish scenario.
			ResetBearishState();
		}

		if (_bearishSignalPending && Position <= 0)
		{
			// Execute the short entry with predefined stop-loss and take-profit.
			SellMarket();

			var pip = GetPipSize();
			var entryPrice = candle.ClosePrice;
			_stopPrice = entryPrice + StopLossPips * pip;
			_takePrice = entryPrice - TakeProfitPips * pip;

			ResetBearishState();
		}

		// --- Bullish sequence --------------------------------------------------

		if (macd1 > BullishExtremeLevel && !_bullishStageArmed)
		{
			// Arm the bullish setup after a strong positive MACD expansion.
			_bullishStageArmed = true;
		}

		if (macd1 < 0)
		{
			// Negative MACD cancels the bullish scenario immediately.
			ResetBullishState();
		}
		else if (macd1 < BullishNeutralLevel && _bullishStageArmed)
		{
			// MACD retraced toward zero, allow the hook confirmation.
			_bullishStageArmed = false;
			_bullishTriggerReady = true;
		}

		var bullishHook = _bullishTriggerReady &&
			macd1 > macd2 &&
			macd2 < macd3 &&
			macd1 > BullishNeutralLevel &&
			macd2 < BullishNeutralLevel;

		if (bullishHook)
		{
			// Confirm the bullish hook pattern.
			_bullishTriggerReady = false;
			_bullishSignalPending = true;
		}

		if (_bullishSignalPending && Position >= 0)
		{
			// Execute the long entry with the configured targets.
			BuyMarket();

			var pip = GetPipSize();
			var entryPrice = candle.ClosePrice;
			_stopPrice = entryPrice - StopLossPips * pip;
			_takePrice = entryPrice + TakeProfitPips * pip;

			ResetBullishState();
		}

		UpdateMacdHistory(macdLine);
	}

	private void HandlePositionExit(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var exitVolume = Math.Abs(Position);

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				// Long stop-loss hit inside the finished candle range.
				SellMarket();
				ResetProtectionLevels();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				// Long take-profit reached.
				SellMarket();
				ResetProtectionLevels();
			}
		}
		else if (Position < 0)
		{
			var exitVolume = Math.Abs(Position);

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				// Short stop-loss triggered within the candle.
				BuyMarket();
				ResetProtectionLevels();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				// Short take-profit reached.
				BuyMarket();
				ResetProtectionLevels();
			}
		}
	}

	private void UpdateMacdHistory(decimal macdValue)
	{
		_macdPrev3 = _macdPrev2;
		_macdPrev2 = _macdPrev1;
		_macdPrev1 = macdValue;
	}

	private void ResetBearishState()
	{
		_bearishStageArmed = false;
		_bearishTriggerReady = false;
		_bearishSignalPending = false;
	}

	private void ResetBullishState()
	{
		_bullishStageArmed = false;
		_bullishTriggerReady = false;
		_bullishSignalPending = false;
	}

	private void ResetProtectionLevels()
	{
		_stopPrice = null;
		_takePrice = null;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		var decimals = Security?.Decimals;

		if (decimals == 3 || decimals == 5)
			return step * 10m;

		return step;
	}
}