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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
class macd_zero_filtered_cross_strategy(Strategy):
def __init__(self):
super(macd_zero_filtered_cross_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12) \
.SetDisplay("Fast Period", "Short EMA period for MACD", "MACD")
self._slow_period = self.Param("SlowPeriod", 26) \
.SetDisplay("Slow Period", "Long EMA period for MACD", "MACD")
self._signal_period = self.Param("SignalPeriod", 9) \
.SetDisplay("Signal Period", "Signal line length for MACD", "MACD")
self._take_profit_points = self.Param("TakeProfitPoints", 300.0) \
.SetDisplay("Take Profit (points)", "Fixed take-profit distance in price points", "Risk Management")
self._lot_volume = self.Param("LotVolume", 1.0) \
.SetDisplay("Lot Volume", "Trading volume per order", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe that drives MACD calculations", "General")
self._previous_macd = None
self._previous_signal = None
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def SignalPeriod(self):
return self._signal_period.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def LotVolume(self):
return self._lot_volume.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(macd_zero_filtered_cross_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self.FastPeriod
macd.Macd.LongMa.Length = self.SlowPeriod
macd.SignalMa.Length = self.SignalPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(macd, self.ProcessCandle).Start()
tp = float(self.TakeProfitPoints)
if tp > 0:
self.StartProtection(Unit(tp, UnitTypes.Absolute), None)
def ProcessCandle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
macd_line = macd_value.Macd
signal_line = macd_value.Signal
if macd_line is None or signal_line is None:
return
macd_line = float(macd_line)
signal_line = float(signal_line)
if self._previous_macd is not None and self._previous_signal is not None:
cross_up = self._previous_macd < self._previous_signal and macd_line > signal_line
cross_down = self._previous_macd > self._previous_signal and macd_line < signal_line
if cross_down and self.Position > 0:
self.SellMarket(self.Position)
self._previous_macd = macd_line
self._previous_signal = signal_line
return
if cross_up and self.Position < 0:
self.BuyMarket(abs(self.Position))
self._previous_macd = macd_line
self._previous_signal = signal_line
return
if cross_up and macd_line < 0 and signal_line < 0 and self.Position <= 0:
self.BuyMarket(float(self.LotVolume))
elif cross_down and macd_line > 0 and signal_line > 0 and self.Position >= 0:
self.SellMarket(float(self.LotVolume))
self._previous_macd = macd_line
self._previous_signal = signal_line
def OnReseted(self):
super(macd_zero_filtered_cross_strategy, self).OnReseted()
self._previous_macd = None
self._previous_signal = None
def CreateClone(self):
return macd_zero_filtered_cross_strategy()