MACD Cruz filtrada cero
Descripción general
MACD Zero Filtered Cross es un puerto C# del MetaTrader 4 asesor experto Robot_MACD_12.26.9. El robot original vigila
cruza entre la línea MACD y su línea de señal, pero filtra nuevas operaciones para que las entradas largas solo ocurran mientras ambas líneas
permanecen por debajo del eje cero y las entradas cortas sólo se producen mientras ambas líneas permanecen por encima del eje. La versión StockSharp mantiene el
misma lógica cruzada, agrega controles de riesgo que se integran con el marco (filtrado del saldo de la cartera y toma de ganancias unificada
gestión), y expone cada valor configurable a través de parámetros de estrategia que apoyan la optimización.
La estrategia se basa en velas terminadas durante un período de tiempo configurable. Los valores del indicador son proporcionados por el incorporado
indicador MovingAverageConvergenceDivergenceSignal, lo que garantiza que la estrategia siga siendo compatible con el nivel alto API y
respeta las pautas de uso de BindEx.
Lógica estratégica
Cálculo del indicador
- MACD línea – diferencia entre una media móvil exponencial rápida y lenta (longitudes predeterminadas: 12 y 26).
- Línea de señal: media móvil exponencial aplicada a la línea MACD (longitud predeterminada: 9).
- Filtro cero: el signo de ambas líneas en relación con cero determina si un cruce puede desencadenar una entrada de posición.
Reglas de entrada
- Configuración larga
- La línea MACD debe cruzar por encima de la línea de señal (
MACD[t-1] < Signal[t-1] y MACD[t] > Signal[t]).
- Tanto la línea MACD como la línea de señal deben estar por debajo de cero después del cruce.
- La posición neta actual debe ser plana o corta; Los cortos existentes se cierran inmediatamente antes de intentar un largo.
- Un filtro de saldo opcional requiere que el valor de la cartera supere un mínimo configurable antes de enviar un nuevo pedido.
- Configuración corta
- La línea MACD debe cruzar por debajo de la línea de señal (
MACD[t-1] > Signal[t-1] y MACD[t] < Signal[t]).
- Ambas líneas del indicador deben estar por encima de cero después del cruce.
- La posición neta actual debe ser plana o larga; Los largos existentes se aplanan antes de enviar un nuevo corto.
- El filtro de saldo se aplica simétricamente a las entradas cortas.
reglas de salida
- Salida cruzada: cuando la línea MACD vuelve a cruzar la línea de señal contra la posición actual, la estrategia se cierra
el comercio abierto en el mercado. Esto refleja el EA original, que siempre aplanó la posición en un cruce opuesto antes
buscando nuevas oportunidades.
- Toma de ganancias fija: una toma de ganancias basada en unidades (expresada en puntos de precio) se aplica a través de
StartProtection. El nivel coincide
el parámetro MQL TakeProfit y utiliza el valor de puntos del instrumento.
Gestión de riesgos y capital
- Manejo de volumen: el parámetro
LotVolume refleja el tamaño del lote MT4. La estrategia envía ese volumen exacto para cada entrada.
- Filtro de saldo: el parámetro
MinimumBalancePerVolume multiplica el volumen solicitado para determinar la cartera mínima
valor requerido antes de que se permitan nuevas entradas. Si la verificación del saldo falla, la estrategia registra un mensaje y omite la operación,
igualando la salvaguardia original del margen libre.
- Integridad de los datos: las señales se procesan solo en velas terminadas y después de que
IsFormedAndOnlineAndAllowTrading() confirme que
Tanto la conexión como los indicadores están listos.
Parámetros
| Parámetro |
Descripción |
FastPeriod |
EMA longitud del componente rápido MACD. |
SlowPeriod |
EMA longitud del componente lento MACD. |
SignalPeriod |
EMA longitud de la línea de señal MACD. |
TakeProfitPoints |
Distancia a la toma de ganancias protectora en puntos de precio. Establezca en 0 para desactivar. |
LotVolume |
Volumen base de pedidos, equivalente a la entrada de “Lotes” de la versión MT4. |
MinimumBalancePerVolume |
Valor mínimo de cartera requerido por unidad de volumen negociado antes de abrir una posición. Establezca en 0 para omitir el filtro. |
CandleType |
Marco de tiempo utilizado para construir velas y alimentar la cadena del indicador. |
Notas adicionales
- La estrategia utiliza la sobrecarga
BindEx para que el indicador MACD pueda suministrar tanto el valor MACD como el de señal en un solo
devolución de llamada sin llamadas manuales a GetValue.
- Todos los comentarios dentro del código C# están escritos en inglés, de acuerdo con las pautas del proyecto.
- No existe una traducción de Python para esta estrategia; solo la implementación de C# se proporciona en el paquete API.
- Para replicar el comportamiento original de MT4 lo más fielmente posible, seleccione un período de tiempo de vela que coincida con el gráfico donde solía ejecutarse EA
y mantener el parámetro de volumen consistente con el tamaño del 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()