Ver no GitHub

MACD Cruzamento filtrado zero

Visão geral

MACD Zero Filtered Cross é uma porta C# do MetaTrader 4 consultor especialista Robot_MACD_12.26.9. O robô original observa cruzamentos entre a linha MACD e sua linha de sinal, mas filtra novas negociações para que entradas longas ocorram apenas enquanto ambas as linhas permanecem abaixo do eixo zero e entradas curtas ocorrem apenas enquanto ambas as linhas permanecem acima do eixo. A versão StockSharp mantém o mesma lógica cruzada, adiciona controles de risco que se integram à estrutura (filtragem de saldo de portfólio e take-profit unificado gerenciamento) e expõe todos os valores configuráveis por meio de parâmetros estratégicos que suportam a otimização.

A estratégia depende de velas finalizadas em um período configurável. Os valores dos indicadores são fornecidos pelo built-in Indicador MovingAverageConvergenceDivergenceSignal, garantindo que a estratégia permaneça compatível com o API de alto nível e respeita as diretrizes de uso de BindEx.

Lógica estratégica

Cálculo do indicador

  • MACD linha – diferença entre uma média móvel exponencial rápida e lenta (comprimentos padrão: 12 e 26).
  • Linha de sinal – média móvel exponencial aplicada à linha MACD (comprimento padrão: 9).
  • Filtro zero – o sinal de ambas as linhas em relação a zero determina se um cruzamento pode acionar uma entrada de posição.

Regras de entrada

  • Configuração longa
    • A linha MACD deve cruzar acima da linha de sinal (MACD[t-1] < Signal[t-1] e MACD[t] > Signal[t]).
    • Tanto a linha MACD quanto a linha de sinal devem estar abaixo de zero após o cruzamento.
    • A posição líquida atual deve ser plana ou curta; as posições curtas existentes são fechadas imediatamente antes de tentar uma posição comprada.
    • Um filtro de saldo opcional exige que o valor do portfólio exceda um mínimo configurável antes que um novo pedido seja enviado.
  • Configuração curta
    • A linha MACD deve cruzar abaixo da linha de sinal (MACD[t-1] > Signal[t-1] e MACD[t] < Signal[t]).
    • Ambas as linhas indicadoras devem estar acima de zero após o cruzamento.
    • A posição líquida atual deve ser plana ou longa; as posições compradas existentes são achatadas antes que uma nova venda seja enviada.
    • O filtro de saldo é aplicado simetricamente às entradas curtas.

Regras de saída

  • Saída cruzada – quando a linha MACD cruza de volta através da linha de sinal contra a posição atual, a estratégia fecha o comércio aberto no mercado. Isso reflete o EA original, que sempre achatava a posição em um cruzamento oposto antes em busca de novas oportunidades.
  • Take-profit fixo – um take-profit baseado em unidade (expresso em faixas de preço) é aplicado via StartProtection. O nível corresponde o parâmetro MQL TakeProfit e usa o valor do ponto do instrumento.

Gestão de risco e capital

  • Manuseio de volume – o parâmetro LotVolume reflete o tamanho do lote MT4. A estratégia envia esse volume exato para cada entrada.
  • Filtro de saldo – o parâmetro MinimumBalancePerVolume multiplica o volume solicitado para determinar o portfólio mínimo valor necessário antes que novas entradas sejam permitidas. Se a verificação do saldo falhar, a estratégia registra uma mensagem e ignora a negociação, correspondendo à salvaguarda original de margem livre.
  • Integridade dos dados – os sinais são processados apenas em velas finalizadas e após IsFormedAndOnlineAndAllowTrading() confirmar que tanto a conexão quanto os indicadores estão prontos.

Parâmetros

Parâmetro Descrição
FastPeriod EMA comprimento do componente rápido MACD.
SlowPeriod EMA comprimento do componente lento MACD.
SignalPeriod EMA comprimento da linha de sinal MACD.
TakeProfitPoints Distância até o take-profit protetor em faixas de preço. Defina como 0 para desativar.
LotVolume Volume base do pedido, equivalente à entrada “Lotes” da versão MT4.
MinimumBalancePerVolume Valor mínimo da carteira exigido por unidade de volume negociado antes de abrir uma posição. Defina como 0 para ignorar o filtro.
CandleType Período usado para construir velas e alimentar a cadeia de indicadores.

Notas adicionais

  • A estratégia usa a sobrecarga BindEx para que o indicador MACD possa fornecer os valores de MACD e de sinal em um único retorno de chamada sem chamadas manuais para GetValue.
  • Todos os comentários dentro do código C# são escritos em inglês, correspondendo às diretrizes do projeto.
  • Não há tradução em Python para esta estratégia; apenas a implementação C# é fornecida no pacote API.
  • Para replicar melhor o comportamento original do MT4, selecione um período de vela que corresponda ao gráfico onde o EA costumava ser executado e manter o parâmetro de volume consistente com o tamanho do lote negociado anteriormente.
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>
/// Port of the MetaTrader 4 expert advisor Robot_MACD_12.26.9.
/// Trades MACD signal-line crossovers, but only enters longs while both lines stay below zero and shorts while they stay above zero.
/// Includes an optional balance filter and a fixed take-profit expressed in instrument points.
/// </summary>
public class MacdZeroFilteredCrossStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _lotVolume;
	private readonly StrategyParam<decimal> _minimumBalancePerVolume;
	private readonly StrategyParam<DataType> _candleType;

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

	/// <summary>
	/// Fast EMA length used by MACD.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA length used by MACD.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Signal line smoothing length for MACD.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

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

	/// <summary>
	/// Base trading volume that mirrors the "Lots" setting in the original robot.
	/// </summary>
	public decimal LotVolume
	{
		get => _lotVolume.Value;
		set => _lotVolume.Value = value;
	}

	/// <summary>
	/// Minimum account value required per traded volume unit before opening new positions.
	/// </summary>
	public decimal MinimumBalancePerVolume
	{
		get => _minimumBalancePerVolume.Value;
		set => _minimumBalancePerVolume.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public MacdZeroFilteredCrossStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Short EMA period for MACD", "MACD")
			
			.SetOptimize(6, 18, 1);

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Long EMA period for MACD", "MACD")
			
			.SetOptimize(20, 40, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Signal Period", "Signal line length for MACD", "MACD")
			
			.SetOptimize(6, 12, 1);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300m)
			.SetNotNegative()
			.SetDisplay("Take Profit (points)", "Fixed take-profit distance in price points", "Risk Management");

		_lotVolume = Param(nameof(LotVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Lot Volume", "Trading volume per order", "Trading")
			.SetOptimize(1m, 5m, 1m);

		_minimumBalancePerVolume = Param(nameof(MinimumBalancePerVolume), 1000m)
			.SetNotNegative()
			.SetDisplay("Balance per Volume", "Required balance per volume unit before opening trades", "Risk Management");

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

	/// <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)
	{

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastPeriod },
				LongMa = { Length = SlowPeriod },
			},
			SignalMa = { Length = SignalPeriod }
		};

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

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

		if (TakeProfitPoints > 0m)
		{
			StartProtection(new Unit(TakeProfitPoints, UnitTypes.Absolute), null);
		}

		base.OnStarted2(time);
	}

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

		// Skip processing when the strategy is not ready or trading is disabled.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var typed = (MovingAverageConvergenceDivergenceSignalValue)macdValue;

		// Ensure both MACD and signal components are available before calculating.
		if (typed.Macd is not decimal macdLine || typed.Signal is not decimal signalLine)
			return;

		if (_previousMacd is decimal prevMacd && _previousSignal is decimal prevSignal)
		{
			var crossUp = prevMacd < prevSignal && macdLine > signalLine;
			var crossDown = prevMacd > prevSignal && macdLine < signalLine;

			// Close existing long position when MACD crosses below the signal line.
			if (crossDown && Position > 0m)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_previousMacd = macdLine;
				_previousSignal = signalLine;
				return;
			}

			// Close existing short position when MACD crosses above the signal line.
			if (crossUp && Position < 0m)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_previousMacd = macdLine;
				_previousSignal = signalLine;
				return;
			}

			// Enter long only when the crossover happens below zero (momentum still negative).
			if (crossUp && macdLine < 0m && signalLine < 0m && Position <= 0m && HasRequiredBalance())
			{
				var volume = LotVolume;
				BuyMarket(volume);
			}

			// Enter short only when the crossover happens above zero (momentum still positive).
			else if (crossDown && macdLine > 0m && signalLine > 0m && Position >= 0m && HasRequiredBalance())
			{
				var volume = LotVolume;
				SellMarket(volume);
			}
		}

		_previousMacd = macdLine;
		_previousSignal = signalLine;
	}

	private bool HasRequiredBalance()
	{
		// If portfolio information is not available, assume requirements are met.
		var balance = Portfolio?.CurrentValue;
		if (balance is null)
			return true;

		var required = MinimumBalancePerVolume * LotVolume;
		if (required <= 0m)
			return true;

		if (balance.Value >= required)
			return true;

		return false;
	}
}