Ver no GitHub

Estratégia MACD com Filtro de Linha Zero e Take Profit

Visão Geral

Esta estratégia replica o expert original do MetaTrader 5 "Robot_MACD" que opera cruzamentos da linha de sinal do MACD com filtros adicionais de linha zero. Opera em um único instrumento e busca reversões de momentum confirmadas pela posição da linha MACD em relação ao zero. Um take profit de distância fixa é anexado a cada ordem, espelhando o alvo de lucro baseado em pontos da implementação original.

Dados e Indicadores

  • Dados primários: subscrição de uma única vela (período padrão de 5 minutos). O período pode ser alterado com o parâmetro CandleType para se adaptar ao mercado negociado.
  • Indicadores:
    • MovingAverageConvergenceDivergenceSignal (MACD + sinal + histograma). Os padrões são EMAs de 12/26 com uma linha de sinal de 9 períodos, correspondendo aos parâmetros MQL.

Lógica de Negociação

  1. Aguardar o cálculo do MACD fornecer os valores atual e anterior das linhas MACD e de sinal.
  2. Identificar cruzamentos de alta e de baixa:
    • Cruzamento de alta: MACD anterior ≤ sinal anterior e MACD atual > sinal atual.
    • Cruzamento de baixa: MACD anterior ≥ sinal anterior e MACD atual < sinal atual.
  3. Gestão de posições:
    • Fechar uma posição comprada quando um cruzamento de baixa aparece.
    • Fechar uma posição vendida quando um cruzamento de alta aparece.
  4. Critérios de entrada (apenas quando não há posição aberta e há capital suficiente):
    • Entrar comprado em um cruzamento de alta enquanto tanto o MACD quanto o sinal permanecerem abaixo de zero.
    • Entrar vendido em um cruzamento de baixa enquanto tanto o MACD quanto o sinal permanecerem acima de zero.
  5. Anexar um take profit fixo no momento do registro da ordem chamando StartProtection com uma distância absoluta medida em pontos de preço. A distância equivale ao valor de ponto configurado multiplicado pelo passo de preço do instrumento.

Gestão de Risco

  • Cada ordem tem um take profit anexado definido por TakeProfitPoints. Não há stop-loss na lógica base, preservando a paridade com o EA fonte.
  • A estratégia verifica se o valor do portfólio é pelo menos MinimumCapitalPerVolume * VolumePerTrade antes de colocar uma nova ordem. Isso emula a proteção de margem livre (FreeMargin() < 1000 * Lots) da versão MQL.

Parâmetros

Parâmetro Descrição Valor padrão
MacdFast Período EMA rápido para MACD. 12
MacdSlow Período EMA lento para MACD. 26
MacdSignal Período de suavização da linha de sinal. 9
TakeProfitPoints Distância do take profit expressa em pontos de preço. 300
VolumePerTrade Volume de negociação (lotes) utilizado para cada entrada. 1
MinimumCapitalPerVolume Valor mínimo do portfólio exigido por lote negociado. 1000
CandleType Tipo de vela (período) utilizado para alimentar o indicador MACD. Velas de 5 minutos

Notas de Implementação

  • As ordens são executadas com BuyMarket/SellMarket, idêntico ao EA que usava ordens de mercado via CTrade.
  • Os filtros de linha zero impedem entradas na metade oposta do histograma MACD, assim como no script MQL.
  • A verificação do valor do portfólio depende de Portfolio.CurrentValue. Se o ambiente de negociação não fornecer este valor, a proteção passa automaticamente, mantendo a estratégia utilizável para contas simuladas.
  • A seção de desenho no gráfico plota velas, o indicador MACD e marcadores de negociação quando uma área de gráfico está disponível na aplicação host.
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 cross strategy with zero line filter and fixed take profit.
/// </summary>
public class MacdZeroFilterTakeProfitStrategy : Strategy
{
	private readonly StrategyParam<int> _macdFast;
	private readonly StrategyParam<int> _macdSlow;
	private readonly StrategyParam<int> _macdSignal;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<decimal> _volumePerTrade;
	private readonly StrategyParam<decimal> _minimumCapitalPerVolume;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private decimal? _previousMacd;
	private decimal? _previousSignal;

	/// <summary>
	/// Initializes a new instance of the <see cref="MacdZeroFilterTakeProfitStrategy"/> class.
	/// </summary>
	public MacdZeroFilterTakeProfitStrategy()
	{
		_macdFast = Param(nameof(MacdFast), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast Period", "Fast EMA period used by MACD", "Indicators")
			
			.SetOptimize(8, 20, 2);

		_macdSlow = Param(nameof(MacdSlow), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow Period", "Slow EMA period used by MACD", "Indicators")
			
			.SetOptimize(20, 40, 2);

		_macdSignal = Param(nameof(MacdSignal), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal Period", "Signal smoothing period for MACD", "Indicators")
			
			.SetOptimize(6, 15, 1);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (points)", "Take profit distance expressed in price points", "Risk Management")
			
			.SetOptimize(100, 600, 50);

		_volumePerTrade = Param(nameof(VolumePerTrade), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Number of lots to trade on each entry", "General")
			
			.SetOptimize(0.5m, 5m, 0.5m);

		_minimumCapitalPerVolume = Param(nameof(MinimumCapitalPerVolume), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Capital per Volume", "Minimum portfolio value required per traded lot", "Risk Management")
			
			.SetOptimize(500m, 5000m, 500m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");
	}

	/// <summary>
	/// Fast EMA period used by MACD.
	/// </summary>
	public int MacdFast
	{
		get => _macdFast.Value;
		set => _macdFast.Value = value;
	}

	/// <summary>
	/// Slow EMA period used by MACD.
	/// </summary>
	public int MacdSlow
	{
		get => _macdSlow.Value;
		set => _macdSlow.Value = value;
	}

	/// <summary>
	/// Signal smoothing period for MACD.
	/// </summary>
	public int MacdSignal
	{
		get => _macdSignal.Value;
		set => _macdSignal.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Number of lots to trade on each entry.
	/// </summary>
	public decimal VolumePerTrade
	{
		get => _volumePerTrade.Value;
		set => _volumePerTrade.Value = value;
	}

	/// <summary>
	/// Minimum portfolio value required per traded lot.
	/// </summary>
	public decimal MinimumCapitalPerVolume
	{
		get => _minimumCapitalPerVolume.Value;
		set => _minimumCapitalPerVolume.Value = value;
	}

	/// <summary>
	/// Timeframe for MACD calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

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

		_previousMacd = null;
		_previousSignal = null;
	}

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

		Volume = VolumePerTrade;

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFast },
				LongMa = { Length = MacdSlow },
			},
			SignalMa = { Length = MacdSignal }
		};

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

		var step = Security?.PriceStep ?? 1m;
		StartProtection(
			takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
			stopLoss: new Unit(0m),
			useMarketOrders: true);

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

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
	{
		// Use only completed candles to avoid double counting signals.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure the strategy is ready to trade and has a working connector.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdTyped)
			return;

		if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
			return;

		if (_previousMacd is null || _previousSignal is null)
		{
			_previousMacd = macd;
			_previousSignal = signal;
			return;
		}

		var previousMacd = _previousMacd.Value;
		var previousSignal = _previousSignal.Value;

		var crossedUp = previousMacd <= previousSignal && macd > signal;
		var crossedDown = previousMacd >= previousSignal && macd < signal;

		if (Position > 0 && crossedDown)
		{
			// Close long position on bearish crossover.
			SellMarket();
		}
		else if (Position < 0 && crossedUp)
		{
			// Close short position on bullish crossover.
			BuyMarket();
		}

		if (Position == 0)
		{
			var requiredCapital = MinimumCapitalPerVolume * VolumePerTrade;
			if (HasEnoughCapital(requiredCapital))
			{
				if (crossedUp && macd < 0m && signal < 0m)
				{
					// Enter long when MACD crosses above signal under the zero line.
					BuyMarket();
				}
				else if (crossedDown && macd > 0m && signal > 0m)
				{
					// Enter short when MACD crosses below signal above the zero line.
					SellMarket();
				}
			}
		}

		_previousMacd = macd;
		_previousSignal = signal;
	}

	private bool HasEnoughCapital(decimal requiredCapital)
	{
		var currentValue = Portfolio?.CurrentValue;

		if (currentValue is null)
			return true;

		if (currentValue.Value >= requiredCapital)
			return true;

		this.LogInfo($"Insufficient capital: available {currentValue.Value:F2}, required {requiredCapital:F2}.");
		return false;
	}
}