MACD Null gefiltertes Kreuz
Überblick
MACD Zero Filtered Cross ist eine C#-Portierung des MetaTrader 4 Expert Advisors Robot_MACD_12.26.9. Die Original-Roboteruhren für
Überschneidungen zwischen der MACD-Linie und ihrer Signallinie, filtert jedoch neue Trades, sodass lange Einträge nur während beider Linien erfolgen
bleiben unterhalb der Nullachse und kurze Einträge treten nur auf, solange beide Linien oberhalb der Achse bleiben. Die StockSharp-Version behält die
Dieselbe Crossover-Logik fügt Risikokontrollen hinzu, die in das Framework integriert sind (Portfolio-Balance-Filterung und einheitliche Gewinnmitnahme).
Management) und legt jeden konfigurierbaren Wert durch Strategieparameter offen, die die Optimierung unterstützen.
Die Strategie setzt auf fertige Kerzen aus einem konfigurierbaren Zeitrahmen. Indikatorwerte werden vom integrierten Gerät bereitgestellt
MovingAverageConvergenceDivergenceSignal-Indikator, der sicherstellt, dass die Strategie mit dem übergeordneten API und kompatibel bleibt
respektiert die Nutzungsrichtlinien von BindEx.
Strategielogik
Indikatorberechnung
- MACD Linie – Differenz zwischen einem schnellen und einem langsamen exponentiellen gleitenden Durchschnitt (Standardlängen: 12 und 26).
- Signallinie – exponentieller gleitender Durchschnitt, angewendet auf die Linie MACD (Standardlänge: 9).
- Nullfilter – das Vorzeichen beider Linien relativ zu Null bestimmt, ob ein Crossover einen Positionseintrag auslösen kann.
Einreisebestimmungen
- Lange Einrichtung
- Die Linie MACD muss sich oberhalb der Signallinie (
MACD[t-1] < Signal[t-1] und MACD[t] > Signal[t]) kreuzen.
- Sowohl die MACD-Linie als auch die Signallinie müssen nach dem Übergang unter Null liegen.
- Die aktuelle Nettoposition muss flach oder short sein; Bestehende Shorts werden unmittelbar vor dem Versuch einer Long-Position geschlossen.
- Ein optionaler Balance-Filter erfordert, dass der Portfoliowert einen konfigurierbaren Mindestwert überschreitet, bevor eine neue Order gesendet wird.
- Kurze Einrichtung
- Die Linie MACD muss sich unterhalb der Signallinie (
MACD[t-1] > Signal[t-1] und MACD[t] < Signal[t]) kreuzen.
- Nach dem Übergang müssen beide Indikatorlinien über Null liegen.
- Die aktuelle Nettoposition muss flach oder lang sein; Bestehende Long-Positionen werden abgeflacht, bevor eine neue Short-Position gesendet wird.
- Der Balance-Filter wird symmetrisch auf Short-Einträge angewendet.
Ausgangsregeln
- Crossover Exit – wenn die MACD-Linie die Signallinie gegenüber der aktuellen Position wieder kreuzt, wird die Strategie geschlossen
der offene Handel am Markt. Dies spiegelt das ursprüngliche EA wider, das die Position bei einem gegnerischen Crossover zuvor immer abgeflacht hat
auf der Suche nach neuen Möglichkeiten.
- Fester Take-Profit – ein einheitenbasierter Take-Profit (ausgedrückt in Preispunkten) wird über
StartProtection angewendet. Das Niveau stimmt überein
den MQL-Parameter TakeProfit und verwendet den Punktwert des Instruments.
Risiko- und Kapitalmanagement
- Volumenhandhabung – der Parameter
LotVolume spiegelt die MT4-Losgröße wider. Die Strategie übermittelt für jeden Eintrag genau dieses Volumen.
- Balance-Filter – der Parameter
MinimumBalancePerVolume multipliziert das angeforderte Volumen, um das minimale Portfolio zu bestimmen
Wert erforderlich, bevor neue Einträge zulässig sind. Wenn die Prüfung des Kontostands fehlschlägt, protokolliert die Strategie eine Nachricht und überspringt den Handel.
Entspricht dem ursprünglichen Schutz der freien Marge.
- Datenintegrität – Signale werden nur bei fertigen Kerzen verarbeitet und nachdem
IsFormedAndOnlineAndAllowTrading() dies bestätigt
Sowohl der Anschluss als auch die Anzeigen sind bereit.
Parameter
| Parameter |
Beschreibung |
FastPeriod |
EMA Länge der schnellen MACD-Komponente. |
SlowPeriod |
EMA Länge der langsamen MACD-Komponente. |
SignalPeriod |
EMA Länge der MACD Signalleitung. |
TakeProfitPoints |
Abstand zum schützenden Take-Profit in Preispunkten. Zum Deaktivieren auf 0 setzen. |
LotVolume |
Basisauftragsvolumen, entspricht der Eingabe „Lots“ der MT4-Version. |
MinimumBalancePerVolume |
Erforderlicher Mindestportfoliowert pro gehandelter Volumeneinheit vor der Eröffnung einer Position. Auf 0 setzen, um den Filter zu überspringen. |
CandleType |
Zeitrahmen, der zum Aufbau von Kerzen und zur Versorgung der Indikatorkette verwendet wird. |
Zusätzliche Hinweise
- Die Strategie nutzt die
BindEx-Überladung, sodass der MACD-Indikator sowohl den MACD- als auch den Signalwert auf einmal liefern kann
Rückruf ohne manuelle Anrufe an GetValue.
- Alle Kommentare im C#-Code sind in englischer Sprache verfasst und entsprechen den Projektrichtlinien.
- Für diese Strategie gibt es keine Python-Übersetzung. Im Paket API wird nur die C#-Implementierung bereitgestellt.
- Um das ursprüngliche MT4-Verhalten möglichst genau zu reproduzieren, wählen Sie einen Kerzenzeitrahmen aus, der mit dem Diagramm übereinstimmt, in dem der EA früher ausgeführt wurde
und halten Sie den Volumenparameter im Einklang mit der zuvor gehandelten Losgröße.
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()