Auf GitHub ansehen

MACD Nulllinie-Filter Take-Profit-Strategie

Überblick

Diese Strategie repliziert den originalen MetaTrader 5-Experten "Robot_MACD", der MACD-Signallinienkruzungen mit zusätzlichen Nulllinienfiltern handelt. Sie operiert auf einem einzelnen Instrument und sucht nach Momentum-Umkehrungen, die durch die Position der MACD-Linie relativ zur Null bestätigt werden. An jede Order wird ein Take-Profit mit festem Abstand angehängt, der das punktbasierte Gewinnziel der ursprünglichen Implementierung widerspiegelt.

Daten und Indikatoren

  • Primärdaten: Einzelne Kerzensubskription (Standard-Zeitrahmen 5 Minuten). Der Zeitrahmen kann mit dem Parameter CandleType geändert werden, um sich dem gehandelten Markt anzupassen.
  • Indikatoren:
    • MovingAverageConvergenceDivergenceSignal (MACD + Signal + Histogramm). Die Standardwerte sind 12/26-EMAs mit einer 9-Perioden-Signallinie, passend zu den MQL-Parametern.

Handelslogik

  1. Warten, bis die MACD-Berechnung sowohl aktuelle als auch vorherige Werte der MACD- und Signallinie liefert.
  2. Bullische und bärische Kreuzungen identifizieren:
    • Bullische Kreuzung: vorheriger MACD ≤ vorherige Signal und aktueller MACD > aktuelle Signal.
    • Bärische Kreuzung: vorheriger MACD ≥ vorherige Signal und aktueller MACD < aktuelle Signal.
  3. Positionsmanagement:
    • Eine Long-Position schließen, wenn eine bärische Kreuzung erscheint.
    • Eine Short-Position schließen, wenn eine bullische Kreuzung erscheint.
  4. Einstiegskriterien (nur wenn keine Position offen ist und ausreichend Kapital vorhanden):
    • Long einsteigen bei einer bullischen Kreuzung während sowohl MACD als auch Signal unter null bleiben.
    • Short einsteigen bei einer bärischen Kreuzung während sowohl MACD als auch Signal über null bleiben.
  5. Einen festen Take-Profit zum Zeitpunkt der Orderregistrierung anhängen, indem StartProtection mit einem absoluten Abstand in Preispunkten aufgerufen wird. Der Abstand entspricht dem konfigurierten Punktwert multipliziert mit dem Preisschritt des Wertpapiers.

Risikomanagement

  • Jede Order hat einen angehängten Take-Profit, der durch TakeProfitPoints definiert wird. Es gibt keinen Stop-Loss in der Basislogik, um die Parität mit dem Quell-EA zu erhalten.
  • Die Strategie prüft, ob der Portfoliowert mindestens MinimumCapitalPerVolume * VolumePerTrade beträgt, bevor eine neue Order platziert wird. Dies emuliert die Free-Margin-Absicherung (FreeMargin() < 1000 * Lots) aus der MQL-Version.

Parameter

Parameter Beschreibung Standardwert
MacdFast Schnelle EMA-Periode für MACD. 12
MacdSlow Langsame EMA-Periode für MACD. 26
MacdSignal Glättungsperiode der Signallinie. 9
TakeProfitPoints Take-Profit-Abstand in Preispunkten. 300
VolumePerTrade Handelsvolumen (Lots) für jeden Einstieg. 1
MinimumCapitalPerVolume Mindestportfoliowert je gehandeltem Lot. 1000
CandleType Kerzentyp (Zeitrahmen) für den MACD-Indikator. 5-Minuten-Kerzen

Implementierungshinweise

  • Orders werden mit BuyMarket/SellMarket ausgeführt, identisch mit dem EA, der Marktorders via CTrade nutzte.
  • Die Nulllinienfilter verhindern Einstiege in der entgegengesetzten Hälfte des MACD-Histogramms, genau wie im MQL-Skript.
  • Die Portfoliowertprüfung basiert auf Portfolio.CurrentValue. Wenn die Handelsumgebung diesen Wert nicht liefert, wird die Prüfung automatisch bestanden, was die Strategie für simulierte Konten nutzbar hält.
  • Der Zeichnungsbereich im Chart stellt Kerzen, den MACD-Indikator und Handelsmarkierungen dar, wenn ein Chartbereich in der Host-Anwendung verfügbar ist.
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;
	}
}