Auf GitHub ansehen

MA MACD Positionsmittelungs-Strategie v2

Übersicht

Die MA MACD Positionsmittelungs-Strategie v2 ist eine direkte Übersetzung des MetaTrader Expert Advisors von Vladimir Karputov. Sie kombiniert einen gewichteten gleitenden Durchschnittsfilter, einen MACD-Bestätigungsblock und ein Mittelungsmodul, das die Exposition erhöht, wenn bestehende Trades gegen die Position laufen. Die StockSharp-Version behält die ursprüngliche Signalhierarchie bei, verarbeitet Indikatoren auf fertigen Kerzen und verwaltet Schutzlogik (Stop-Loss, Take-Profit, Trailing) im Code, um das brokerseitige Verhalten aus MQL zu reproduzieren.

Handelslogik

  1. Indikatorvorbereitung
    • Ein konfigurierbarer gleitender Durchschnitt berechnet auf dem ausgewählten Kerzentyp und Preiskomponenten. Der Parameter MaShift emuliert MetaTraders Vorwärtsverschiebung durch Lesen von Werten aus älteren Kerzen, während BarOffset die Bewertung der aktuellen oder einer vorherigen Bar ermöglicht.
    • Ein MACD-Signalindikator produziert die Haupt- und Signallinie unter Verwendung anpassbarer schneller, langsamer und Signal-Perioden und eines angewendeten Preises, der dem ursprünglichen Expert Advisor entspricht.
  2. Signalvalidierung
    • Long-Setups erfordern, dass beide MACD-Linien negativ sind, der Preis über dem verschobenen gleitenden Durchschnitt liegt und der Preisabstand zum Durchschnitt MaIndentPips überschreitet (umgerechnet in absoluten Preis unter Verwendung der Pip-Größe des Instruments).
    • Short-Setups spiegeln die Bedingungen: Beide MACD-Linien müssen positiv sein, der Preis muss unter dem verschobenen gleitenden Durchschnitt bleiben und die Lücke zum Durchschnitt muss mindestens MaIndentPips betragen.
    • Der Verhältnisfilter MacdRatio erzwingt MACD_main / MACD_signal >= MacdRatio (unter Verwendung absoluter Dezimaldivision), bevor ein Trade erlaubt ist.
    • Wenn ReverseSignals = true, wird die Richtung der Marktorder invertiert, nachdem alle Bedingungen erfüllt sind.
  3. Positions-Lebenszyklus
    • Wenn keine Position besteht, öffnet die Strategie eine Marktorder mit dem konfigurierten OrderVolume (gerundet nach dem Instrument-Volumenschritt) in der berechneten Richtung. Stop-Loss- und Take-Profit-Niveaus werden sofort gemäß StopLossPips und TakeProfitPips angewendet.
    • Wenn bereits eine Exposition besteht, öffnet die Strategie nie die Gegenseite. Stattdessen:
      • Schließt alles, wenn Longs und Shorts gleichzeitig erkannt werden (Sicherheitsnetz, das die MQL-Prüfung spiegelt), oder
      • Ruft den Mittelungsblock für die aktuelle Seite auf.
  4. Mittelungsmodul
    • Für Longs findet der Algorithmus das günstigste offene Leg, dessen nicht realisierter Verlust StepLossPips überschreitet. Für Shorts wählt er das verlustreichste Leg mit dem höchsten Preis.
    • Sobald ein Kandidat gefunden ist, wird eine neue Marktorder mit Volumen CandidateVolume × LotCoefficient gesendet (nach Anpassung an den erlaubten Volumenschritt/Min/Max). Dies reproduziert die geometrische Progression des ursprünglichen Experten.
    • Neue Legs erben dieselben Stop-Loss- und Take-Profit-Abstände und werden für Trailing-Updates geeignet.
  5. Risikokontrollen
    • Ein Trailing Stop aktiviert sich nur, wenn sowohl TrailingStopPips als auch TrailingStepPips größer als null sind. Für Longs bewegt sich der Stop auf Close - TrailingStopPips, sobald der Gewinn TrailingStopPips + TrailingStepPips überschreitet; Shorts verhalten sich symmetrisch.
    • Manuelle Stop-Loss- und Take-Profit-Prüfungen werden bei jeder fertigen Kerze durchgeführt. Wenn ausgelöst, schließt eine Marktorder das genaue Leg und entfernt es aus der Mittelungsliste.

Parameter

Parameter Beschreibung
OrderVolume Basisvolumen für den allerersten Trade in einem Zyklus.
StopLossPips Stop-Loss-Abstand in Pips. Auf null setzen, um den Stop zu deaktivieren.
TakeProfitPips Take-Profit-Abstand in Pips. Auf null setzen, um das Ziel zu deaktivieren.
TrailingStopPips Abstand zwischen Preis und Trailing Stop. Funktioniert zusammen mit TrailingStepPips.
TrailingStepPips Zusätzliche günstige Bewegung, die erforderlich ist, bevor der Trailing Stop aktualisiert wird.
StepLossPips Mindestverlust (in Pips), bevor ein Mittelungs-Leg hinzugefügt wird.
LotCoefficient Multiplikator, der auf das ausgewählte verlustsreiche Leg-Volumen bei der Mittelung angewendet wird.
BarOffset Anzahl der Bars zurück zum Lesen von Indikatorwerten (0 = aktuelle fertige Bar).
ReverseSignals Invertiert die Long/Short-Ausführung unter Beibehaltung derselben Filter.
MaPeriod Periode des gleitenden Durchschnitts.
MaShift Vorwärtsverschiebung des gleitenden Durchschnitts (MetaTrader-Stil).
MaMethod Glättungsmethode des gleitenden Durchschnitts (Einfach, Exponentiell, Geglättet, Gewichtet).
MaPrice Kerzenpreiskomponente für den gleitenden Durchschnitt.
MaIndentPips Mindestpreisabstand vom gleitenden Durchschnitt vor dem Einstieg.
MacdFastPeriod Schnelle EMA-Periode für MACD.
MacdSlowPeriod Langsame EMA-Periode für MACD.
MacdSignalPeriod Signal-EMA-Periode für MACD.
MacdPrice Angewendeter Preis in der MACD-Berechnung.
MacdRatio Mindestverhältnis zwischen MACD-Haupt- und Signallinie.
CandleType Kerzenreihe für alle Berechnungen.

Implementierungshinweise

  • Die Pip-Größe wird aus dem Preisschritt des Instruments berechnet, was die 3/5-stellige Anpassung aus der MQL-Version reproduziert. Dies hält pip-basierte Abstände über Forex-Symbole hinweg identisch.
  • Alle Indikator-Buffer verwenden Warteschlangen, um MetaTraders ma_shift- und bar-Indizierung zu emulieren, ohne historische Suchmethoden aufzurufen, die durch die Projektregeln verboten sind.
  • Volumenanpassungen respektieren Security.VolumeStep, Security.MinVolume und Security.MaxVolume, um ungültige Ordergrößen zu verhindern, wenn LotCoefficient die Exposition multipliziert.
  • Schutzlogik (Stops, Takes, Trailing) läuft vollständig in der Strategieschicht, sodass keine Abhängigkeit von brokerseitigen Positionsmodifikations-APIs besteht.
  • Die Klasse befindet sich im Namespace StockSharp.Samples.Strategies und folgt der Repository-Anforderung, Tab-Einrückung und ausschließlich englische Kommentare zu verwenden.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MA MACD Position Averaging v2 strategy using WMA crossover with EMA trend filter.
/// Buys when fast WMA crosses above slow WMA and price above EMA.
/// Sells on reverse conditions.
/// </summary>
public class MaMacdPositionAveragingV2Strategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private WeightedMovingAverage _fast;
	private WeightedMovingAverage _slow;
	private ExponentialMovingAverage _ema;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast WMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow WMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// EMA trend filter period.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public MaMacdPositionAveragingV2Strategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast WMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow WMA period", "Indicator");

		_emaPeriod = Param(nameof(EmaPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fast = null;
		_slow = null;
		_ema = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new WeightedMovingAverage { Length = FastPeriod };
		_slow = new WeightedMovingAverage { Length = SlowPeriod };
		_ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, _ema, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_fast.IsFormed || !_slow.IsFormed || !_ema.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// WMA crossover with EMA trend filter
		if (_prevFast <= _prevSlow && fastValue > slowValue && close > emaValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && close < emaValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}