Auf GitHub ansehen

TradingLabs beste MACD-Strategie

Diese Strategie repliziert den MetaTrader-Expertenberater „TradingLab_Best_MACD_Strategy“ unter Verwendung des übergeordneten API von StockSharp. Es kombiniert eine gleitende Durchschnittsstruktur, MACD-Crossovers und dynamische Unterstützungs-/Widerstandsprüfungen, um direktionale Trades zu eröffnen, die sich an der Dynamik und den jüngsten Preisreaktionen orientieren.

Kernlogik

  • Kerzenquelle – Verwendet den konfigurierbaren Parameter CandleType, um fertige Kerzen zu abonnieren. Nur abgeschlossene Kerzen generieren Handelsentscheidungen.
  • Trendfilter – Ein einfacher gleitender Durchschnitt über 200 Perioden definiert den vorherrschenden Trend. Bei Long-Trades muss der Schlusskurs über dem Durchschnitt bleiben, bei Short-Trades muss der Schlusskurs unter diesem Durchschnitt bleiben.
  • Unterstützungs- und Widerstandsbox – Ein 20-Perioden-Höchst-/Tiefstfenster emuliert den benutzerdefinierten „Box“-Indikator. Durch Berühren des vorherigen Widerstands- oder Unterstützungsniveaus werden kurze oder lange Setups für eine begrenzte Anzahl von Kerzen aktiviert, die von SignalValidity gesteuert werden.
  • MACD Crossovers – Ein Standard-MACD (standardmäßig 12, 26, 9) muss seine Signallinie auf der vorherigen Kerze kreuzen und auf der erforderlichen Seite der Nulllinie bleiben. Jeder gültige Crossover hält sein Signal für SignalValidity Kerzen am Leben und spiegelt die Countdown-Logik der Quelle EA wider.
  • Einstiegszeitpunkt – Eine Position wird eröffnet, wenn sowohl der MACD als auch die entsprechende Unterstützungs-/Widerstandsberührung noch gültig sind und mindestens einer von ihnen bei der aktuellen Kerze ausgelöst wurde.
  • Exit-Logik – Beim Einstieg werden dynamische Stop-Loss- und Take-Profit-Ziele im Verhältnis zur gleitenden Durchschnittsentfernung berechnet. Die Take-Profit-Distanz beträgt das RiskRewardMultiplier-fache der angepassten Distanz, die für den Stopp verwendet wird. Schutzausgänge überwachen nachfolgende Kerzen und rufen ClosePosition() auf, sobald der Preis die gespeicherten Niveaus überschreitet.

Parameter

Parameter Beschreibung
OrderVolume Bei jeder Marktorder wird ein festes Volumen gesendet.
SignalValidity Anzahl der Kerzen, die MACD und Unterstützungs-/Widerstandsauslöser aktiv halten.
MaLength Zeitraum des einfachen Trendfilters für gleitende Durchschnitte.
BoxPeriod Lookback-Länge für das höchste/tiefste Feld, das den aktuellen Widerstand und die Unterstützung verfolgt.
MacdFastLength, MacdSlowLength, MacdSignalLength MACD schnelle, langsame und Signalperioden.
StopDistancePoints Abstand vom gleitenden Durchschnitt zum Stop-Loss, ausgedrückt in Punkten im MetaTrader-Stil (multipliziert mit dem Symbolpreisschritt).
RiskRewardMultiplier Der Multiplikator wird auf den angepassten MA-Abstand angewendet, um das Take-Profit-Ziel zu ermitteln.
CandleType Datentyp, der die zu abonnierende Kerzenserie beschreibt (Standard: 1-Stunden-Zeitrahmen).

Notizen

  • Die Unterstützungs- und Widerstandserkennung folgt der ursprünglichen Idee und beobachtet, ob die vorherige Kerze die höchsten/tiefsten 20-Perioden-Niveaus durchbricht. Bei jeder Berührung werden die Gültigkeitszähler neu gestartet.
  • Stopps und Ziele werden für jeden neuen Eintrag neu berechnet und mit dem Hoch/Tief jeder fertigen Kerze verglichen, um die Intrabar-Überwachung von MetaTrader auf deterministische Weise nachzuahmen.
  • Das Schutzmanagement basiert auf dem Instrument PriceStep. Wenn ein Instrument einen Nullschritt meldet, wird ein Fallback von 0,0001 verwendet.
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;

public class TradingLabBestMacdStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _signalValidity;
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<int> _boxPeriod;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _macdSignalLength;
	private readonly StrategyParam<decimal> _stopDistancePoints;
	private readonly StrategyParam<decimal> _riskRewardMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private Highest _highest;
	private Lowest _lowest;
	private MovingAverageConvergenceDivergenceSignal _macd;

	private int _resistanceCounter;
	private int _supportCounter;
	private int _macdDownCounter;
	private int _macdUpCounter;

	private decimal? _prevMacdMain;
	private decimal? _prevMacdSignal;

	private decimal? _plannedStop;
	private decimal? _plannedTake;
	private Sides? _plannedSide;

	private decimal? _activeStop;
	private decimal? _activeTake;
	private Sides? _activeSide;

	private decimal? _previousHigh;
	private decimal? _previousLow;
	private bool _hasPreviousCandle;

	/// <summary>
	/// Initializes a new instance of <see cref="TradingLabBestMacdStrategy"/>.
	/// </summary>
	public TradingLabBestMacdStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetDisplay("Order Volume", "Fixed volume sent with each market order", "Risk")
			;

		_signalValidity = Param(nameof(SignalValidity), 7)
			.SetGreaterThanZero()
			.SetDisplay("Signal Validity", "Number of candles a MACD or box trigger remains active", "Filters")
			;

		_maLength = Param(nameof(MaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Length", "Simple moving average period used as the trend filter", "Filters")
			;

		_boxPeriod = Param(nameof(BoxPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Box Period", "Lookback length for the support/resistance box", "Filters")
			;

		_macdFastLength = Param(nameof(MacdFastLength), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast Length", "Fast EMA length for MACD", "Indicators")
			;

		_macdSlowLength = Param(nameof(MacdSlowLength), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow Length", "Slow EMA length for MACD", "Indicators")
			;

		_macdSignalLength = Param(nameof(MacdSignalLength), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal Length", "Signal line length for MACD", "Indicators")
			;

		_stopDistancePoints = Param(nameof(StopDistancePoints), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Distance (points)", "Protective stop distance from the moving average expressed in points", "Risk")
			;

		_riskRewardMultiplier = Param(nameof(RiskRewardMultiplier), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Risk-Reward Multiplier", "Multiplier applied to derive the take-profit distance", "Risk")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Data type used to subscribe for candles", "General")
			;
	}

	/// <summary>
	/// Fixed volume sent with every market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Number of candles that keep MACD and support/resistance triggers active.
	/// </summary>
	public int SignalValidity
	{
		get => _signalValidity.Value;
		set => _signalValidity.Value = value;
	}

	/// <summary>
	/// Period for the simple moving average trend filter.
	/// </summary>
	public int MaLength
	{
		get => _maLength.Value;
		set => _maLength.Value = value;
	}

	/// <summary>
	/// Lookback length for the support/resistance box.
	/// </summary>
	public int BoxPeriod
	{
		get => _boxPeriod.Value;
		set => _boxPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Signal line length used by MACD.
	/// </summary>
	public int MacdSignalLength
	{
		get => _macdSignalLength.Value;
		set => _macdSignalLength.Value = value;
	}

	/// <summary>
	/// Protective stop distance measured in MetaTrader points.
	/// </summary>
	public decimal StopDistancePoints
	{
		get => _stopDistancePoints.Value;
		set => _stopDistancePoints.Value = value;
	}

	/// <summary>
	/// Multiplier applied to the adjusted moving-average distance to build the take-profit target.
	/// </summary>
	public decimal RiskRewardMultiplier
	{
		get => _riskRewardMultiplier.Value;
		set => _riskRewardMultiplier.Value = value;
	}

	/// <summary>
	/// Candle data type used to subscribe for historical bars.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_sma = null;
		_highest = null;
		_lowest = null;
		_macd = null;

		_resistanceCounter = 0;
		_supportCounter = 0;
		_macdDownCounter = 0;
		_macdUpCounter = 0;

		_prevMacdMain = null;
		_prevMacdSignal = null;

		_plannedStop = null;
		_plannedTake = null;
		_plannedSide = null;

		_activeStop = null;
		_activeTake = null;
		_activeSide = null;

		_previousHigh = null;
		_previousLow = null;
		_hasPreviousCandle = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Configure the indicators that replicate the MetaTrader calculations.
		_sma = new SimpleMovingAverage { Length = MaLength };
		_highest = new Highest { Length = BoxPeriod };
		_lowest = new Lowest { Length = BoxPeriod };
		_macd = new MovingAverageConvergenceDivergenceSignal { Macd = { ShortMa = { Length = MacdFastLength }, LongMa = { Length = MacdSlowLength } }, SignalMa = { Length = MacdSignalLength } };

		// Subscribe to the configured candle stream and bind indicator outputs to the handler.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(new IIndicator[] { _sma, _highest, _lowest, _macd }, ProcessCandle)
			.Start();

		// removed StartProtection
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Manage trailing exits before analysing new signals.
		CheckProtectiveLevels(candle);

		if (values.Length != 4)
		{
			UpdatePreviousCandle(candle, null, null);
			return;
		}

		var smaValue = values[0].ToDecimal();
		var resistanceValue = values[1].ToDecimal();
		var supportValue = values[2].ToDecimal();

		if (values[3] is not IMovingAverageConvergenceDivergenceSignalValue macdValue)
		{
			UpdatePreviousCandle(candle, null, null);
			return;
		}

		var macdMain = macdValue.Macd;
		var macdSignal = macdValue.Signal;

		if (!_sma.IsFormed || !_highest.IsFormed || !_lowest.IsFormed || !_macd.IsFormed)
		{
			UpdatePreviousCandle(candle, macdMain, macdSignal);
			return;
		}

		// Decrease counters that track how many candles each signal remains active.
		if (_resistanceCounter > 0)
			_resistanceCounter--;
		if (_supportCounter > 0)
			_supportCounter--;
		if (_macdDownCounter > 0)
			_macdDownCounter--;
		if (_macdUpCounter > 0)
			_macdUpCounter--;

		var point = GetPointValue();

		if (_hasPreviousCandle)
		{
			// Detect fresh touches of the synthetic resistance/support levels.
			if (_previousHigh.HasValue && resistanceValue > 0m && _previousHigh.Value > resistanceValue)
			{
				_resistanceCounter = SignalValidity;
			}

			if (_previousLow.HasValue && supportValue > 0m && _previousLow.Value < supportValue)
			{
				_supportCounter = SignalValidity;
			}
		}

		if (_prevMacdMain.HasValue && _prevMacdSignal.HasValue)
		{
			// Track MACD crossovers relative to the zero line.
			if (macdMain < macdSignal && _prevMacdMain.Value > _prevMacdSignal.Value && macdMain > 0m)
			{
				_macdDownCounter = SignalValidity;
			}

			if (macdMain > macdSignal && _prevMacdMain.Value < _prevMacdSignal.Value && macdMain < 0m)
			{
				_macdUpCounter = SignalValidity;
			}
		}

		var volume = OrderVolume;

		if (volume > 0m)
		{
			// Evaluate entry conditions once both the MACD and box counters are armed.
			var longSignalActive = _macdUpCounter > 0 && candle.ClosePrice > smaValue;
			var longTriggeredNow = true;
			if (longSignalActive && longTriggeredNow && Position <= 0)
			{
				var stopOffset = StopDistancePoints * point;
				var adjustedDistance = candle.ClosePrice - smaValue + stopOffset;
				if (adjustedDistance > 0m)
				{
					_plannedStop = smaValue - stopOffset;
					_plannedTake = candle.ClosePrice + adjustedDistance * RiskRewardMultiplier;
					_plannedSide = Sides.Buy;
					BuyMarket(volume);
				}
			}

			var shortSignalActive = _macdDownCounter > 0 && candle.ClosePrice < smaValue;
			var shortTriggeredNow = true;
			if (shortSignalActive && shortTriggeredNow && Position >= 0)
			{
				var stopOffset = StopDistancePoints * point;
				var adjustedDistance = smaValue - candle.ClosePrice + stopOffset;
				if (adjustedDistance > 0m)
				{
					_plannedStop = smaValue + stopOffset;
					_plannedTake = candle.ClosePrice - adjustedDistance * RiskRewardMultiplier;
					_plannedSide = Sides.Sell;
					SellMarket(volume);
				}
			}
		}

		UpdatePreviousCandle(candle, macdMain, macdSignal);
	}

	private void CheckProtectiveLevels(ICandleMessage candle)
	{
		if (_activeSide == null || Position == 0)
			return;

		// Close the position if price violates the stored stop-loss or take-profit.
		if (_activeSide == Sides.Buy && Position > 0)
		{
			if (_activeStop.HasValue && candle.LowPrice <= _activeStop.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
				return;
			}

			if (_activeTake.HasValue && candle.HighPrice >= _activeTake.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
				return;
			}
		}
		else if (_activeSide == Sides.Sell && Position < 0)
		{
			if (_activeStop.HasValue && candle.HighPrice >= _activeStop.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
				return;
			}

			if (_activeTake.HasValue && candle.LowPrice <= _activeTake.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
			}
		}
	}

	private void UpdatePreviousCandle(ICandleMessage candle, decimal? macdMain, decimal? macdSignal)
	{
		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;
		_hasPreviousCandle = true;
		_prevMacdMain = macdMain;
		_prevMacdSignal = macdSignal;
	}

	private decimal GetPointValue()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 0.0001m;

		return step;
	}

	private void ClosePosition()
	{
		if (Position > 0)
			SellMarket(Position);
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));
	}

	private void ClearPlannedLevels()
	{
		_plannedStop = null;
		_plannedTake = null;
		_plannedSide = null;
	}

	private void ClearActiveLevels()
	{
		_activeStop = null;
		_activeTake = null;
		_activeSide = null;
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (trade.Order?.Security != Security)
			return;

		if (Position == 0)
		{
			ClearActiveLevels();
			ClearPlannedLevels();
			return;
		}

		if (Position > 0)
		{
			if (_plannedSide == Sides.Buy)
			{
				_activeStop = _plannedStop;
				_activeTake = _plannedTake;
				_activeSide = Sides.Buy;
				ClearPlannedLevels();
			}
			else
			{
				_activeSide = Sides.Buy;
			}
		}
		else if (Position < 0)
		{
			if (_plannedSide == Sides.Sell)
			{
				_activeStop = _plannedStop;
				_activeTake = _plannedTake;
				_activeSide = Sides.Sell;
				ClearPlannedLevels();
			}
			else
			{
				_activeSide = Sides.Sell;
			}
		}
	}
}