Auf GitHub ansehen

Strategie Gleitender-Durchschnitt-Crossover mit Glättung

Überblick

Die Strategie Gleitender-Durchschnitt-Crossover mit Glättung repliziert die Logik des originalen MQL5-Experten Smoothing Average (barabashkakvn's edition). Sie kombiniert einen konfigurierbaren gleitenden Durchschnitt mit einem in Pips gemessenen Preisabstandsfilter. Wenn sich der Markt weit genug vom geglätteten Durchschnitt entfernt, öffnet die Strategie eine Position in Richtung der Bewegung (oder auf der entgegengesetzten Seite, wenn der Umkehrmodus aktiviert ist). Positionen werden geschlossen, sobald der Preis durch einen erweiterten Kanal um den gleitenden Durchschnitt umkehrt.

Handelslogik

Standardmodus (ReverseSignals = false)

  • Long einsteigen: der Schlusskurs steigt über den gleitenden Durchschnitt minus Entry Delta (pips).
  • Short einsteigen: der Schlusskurs fällt unter den gleitenden Durchschnitt plus Entry Delta (pips).
  • Short schließen: der Schlusskurs steigt über den gleitenden Durchschnitt plus Entry Delta (pips) × Close Delta Coefficient.
  • Long schließen: der Schlusskurs fällt unter den gleitenden Durchschnitt minus Entry Delta (pips) × Close Delta Coefficient.

Umkehrmodus (ReverseSignals = true)

  • Long einsteigen: der Schlusskurs fällt unter den gleitenden Durchschnitt plus Entry Delta (pips).
  • Short einsteigen: der Schlusskurs steigt über den gleitenden Durchschnitt minus Entry Delta (pips).
  • Long schließen: der Schlusskurs fällt unter den gleitenden Durchschnitt minus Entry Delta (pips) × Close Delta Coefficient.
  • Short schließen: der Schlusskurs steigt über den gleitenden Durchschnitt plus Entry Delta (pips) × Close Delta Coefficient.

Der gleitende Durchschnitt kann um mehrere Kerzen nach vorne verschoben werden. Die Strategie emuliert dieses Verhalten, indem sie einen kleinen Puffer der neuesten Indikatorwerte hält und den Wert von MaShift Balken zurück verwendet. Dies entspricht der verschobenen Linie der originalen MetaTrader-Implementierung.

Parameter

  • Candle Type – Datenreihe für Berechnungen.
  • MA Length – Periode des Glättungsdurchschnitts.
  • MA Shift – Anzahl der Balken, um die der gleitende Durchschnitt nach vorne verschoben ist.
  • MA Type – Methode des gleitenden Durchschnitts (einfach, exponentiell, geglättet oder linear gewichtet).
  • Price Source – in den gleitenden Durchschnitt eingespeister Kerzenkurs (Standard: typischer Kurs).
  • Entry Delta (pips) – Abstand vom gleitenden Durchschnitt zum Auslösen von Einstiegen. Wird über die Instrument-Pip-Größe in Preis umgerechnet.
  • Close Delta Coefficient – Multiplikator, der beim Prüfen von Ausstiegsbedingungen auf das Einstiegs-Delta angewendet wird.
  • Reverse Signals – kehrt die Long/Short-Einstiegslogik um.
  • Trade Volume – Ordergröße für Long- und Short-Einstiege.

Risikomanagement

  • Orders werden mit dem festen Parameter Trade Volume gesendet. Die Strategie skaliert nicht, während eine Position offen ist.
  • Alle Ausstiege sind regelbasiert. Es werden keine Hard-Stop-Loss- oder Take-Profit-Orders eingereicht, aber StartProtection() wird aufgerufen, um das Sicherheitsnetz auf Plattformebene zu aktivieren.
  • Der Umkehrmodus ist für gegenläufiges Verhalten verfügbar, ohne andere Einstellungen zu ändern.

Implementierungshinweise

  • Die Pip-Größe wird aus Security.PriceStep abgeleitet. Drei- oder fünfstellige FX-Symbole erhalten die gleiche 10×-Anpassung wie im MQL5-Code.
  • Der gleitende Durchschnitt verwendet die Price Source-Auswahl, sodass typische, mediane oder andere Kerzenpreise auf die Original-EA-Einstellungen abgestimmt werden können.
  • Einstiegs- und Ausstiegsvergleiche verwenden den Kerzenschluss als stabilen Proxy für Bid/Ask-Prüfungen im Quell-Experten.
  • Alle Kommentare im C#-Code sind auf Englisch, wie von den Konvertierungsrichtlinien gefordert.
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>
/// Smoothing Average strategy converted from MQL5.
/// Opens trades when price moves away from the moving average by a configurable delta.
/// Supports reversing the signals and shifting the moving average output.
/// </summary>
public class SmoothingAverageCrossoverStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<int> _maShift;
	private readonly StrategyParam<MovingAverageKinds> _maType;
	private readonly StrategyParam<CandlePrices> _priceSource;
	private readonly StrategyParam<decimal> _entryDeltaPips;
	private readonly StrategyParam<decimal> _closeDeltaCoefficient;
	private readonly StrategyParam<bool> _reverseSignals;
	private readonly StrategyParam<decimal> _tradeVolume;

	private readonly Queue<decimal> _maShiftBuffer = new();

	private decimal _entryDelta;
	private decimal _closeDelta;

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
        public SmoothingAverageCrossoverStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for calculations", "General");

		_maLength = Param(nameof(MaLength), 60)
			.SetGreaterThanZero()
			.SetDisplay("MA Length", "Period of the smoothing average", "Moving Average");

		_maShift = Param(nameof(MaShift), 3)
			.SetNotNegative()
			.SetDisplay("MA Shift", "Horizontal shift applied to the average", "Moving Average");

		_maType = Param(nameof(MaType), MovingAverageKinds.Simple)
			.SetDisplay("MA Type", "Type of smoothing applied", "Moving Average");

		_priceSource = Param(nameof(PriceSource), CandlePrices.Typical)
			.SetDisplay("Price Source", "Price used for the moving average", "Moving Average");

		_entryDeltaPips = Param(nameof(EntryDeltaPips), 60m)
			.SetNotNegative()
			.SetDisplay("Entry Delta (pips)", "Distance from MA to trigger entries", "Trading Rules");

		_closeDeltaCoefficient = Param(nameof(CloseDeltaCoefficient), 1.0m)
			.SetGreaterThanZero()
			.SetDisplay("Close Delta Coefficient", "Multiplier applied to entry delta for exits", "Trading Rules");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse Signals", "Invert long and short logic", "Trading Rules");

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume for each entry", "Risk");
	}

	/// <summary>
	/// Primary candle series used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Moving average period.
	/// </summary>
	public int MaLength
	{
		get => _maLength.Value;
		set => _maLength.Value = value;
	}

	/// <summary>
	/// Number of candles used to shift the moving average output.
	/// </summary>
	public int MaShift
	{
		get => _maShift.Value;
		set => _maShift.Value = value;
	}

	/// <summary>
	/// Moving average type.
	/// </summary>
	public MovingAverageKinds MaType
	{
		get => _maType.Value;
		set => _maType.Value = value;
	}

	/// <summary>
	/// Candle price source for the moving average.
	/// </summary>
	public CandlePrices PriceSource
	{
		get => _priceSource.Value;
		set => _priceSource.Value = value;
	}

	/// <summary>
	/// Delta in pip units used to open new positions.
	/// </summary>
	public decimal EntryDeltaPips
	{
		get => _entryDeltaPips.Value;
		set => _entryDeltaPips.Value = value;
	}

	/// <summary>
	/// Multiplier applied to the entry delta when evaluating exits.
	/// </summary>
	public decimal CloseDeltaCoefficient
	{
		get => _closeDeltaCoefficient.Value;
		set => _closeDeltaCoefficient.Value = value;
	}

	/// <summary>
	/// If true, swaps long and short signals.
	/// </summary>
	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	/// <summary>
	/// Volume sent with market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

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

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

		_maShiftBuffer.Clear();
		_entryDelta = 0m;
		_closeDelta = 0m;
	}

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

		// Sync the base strategy volume with the parameter value.
		Volume = TradeVolume;

		// Calculate pip-based offsets once at the start to avoid repeated computations.
		_entryDelta = CalculateEntryDelta();
		_closeDelta = _entryDelta * CloseDeltaCoefficient;

		var movingAverage = CreateMovingAverage(MaType, MaLength);
		//movingAverage.CandlePrice = PriceSource;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(movingAverage, ProcessCandle)
			.Start();

		// Enable built-in protection helpers (no additional parameters required).
		StartProtection(null, null);
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var shiftedMa = ApplyShift(maValue);

		// Use candle close as a proxy for bid/ask checks from the original Expert Advisor.
		var askPrice = candle.ClosePrice;
		var bidPrice = candle.ClosePrice;

		var entryUpper = shiftedMa + _entryDelta;
		var entryLower = shiftedMa - _entryDelta;
		var closeUpper = shiftedMa + _closeDelta;
		var closeLower = shiftedMa - _closeDelta;

		if (Position == 0m)
		{
			if (!ReverseSignals)
			{
				if (askPrice > entryUpper)
				{
					OpenLong();
					return;
				}

				if (bidPrice < entryLower)
				{
					OpenShort();
					return;
				}
			}
			else
			{
				if (askPrice > entryUpper)
				{
					OpenShort();
					return;
				}

				if (bidPrice < entryLower)
				{
					OpenLong();
					return;
				}
			}
		}
		else
		{
			if (!ReverseSignals)
			{
				if (Position < 0m && bidPrice > closeUpper)
					CloseShort();

				if (Position > 0m && askPrice < closeLower)
					CloseLong();
			}
			else
			{
				if (Position > 0m && askPrice < closeLower)
					CloseLong();

				if (Position < 0m && bidPrice > closeUpper)
					CloseShort();
			}
		}
	}

	private decimal ApplyShift(decimal currentValue)
	{
		if (MaShift <= 0)
			return currentValue;

		var shifted = _maShiftBuffer.Count < MaShift ? currentValue : _maShiftBuffer.Peek();

		_maShiftBuffer.Enqueue(currentValue);

		if (_maShiftBuffer.Count > MaShift)
			_maShiftBuffer.Dequeue();

		return shifted;
	}

	private decimal CalculateEntryDelta()
	{
		var pip = CalculatePipSize();
		return pip * EntryDeltaPips;
	}

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

		var digits = (int)Math.Round(Math.Log10((double)(1m / step)));
		return digits == 3 || digits == 5 ? step * 10m : step;
	}

	private static DecimalLengthIndicator CreateMovingAverage(MovingAverageKinds type, int length)
	{
		return type switch
		{
			MovingAverageKinds.Simple => new SMA { Length = length },
			MovingAverageKinds.Exponential => new EMA { Length = length },
			MovingAverageKinds.Smoothed => new SmoothedMovingAverage { Length = length },
			MovingAverageKinds.LinearWeighted => new WeightedMovingAverage { Length = length },
			_ => new SMA { Length = length }
		};
	}

	private void OpenLong()
	{
		var volume = TradeVolume + Math.Max(0m, -Position);
		if (volume <= 0m)
			return;

		BuyMarket(volume);
	}

	private void OpenShort()
	{
		var volume = TradeVolume + Math.Max(0m, Position);
		if (volume <= 0m)
			return;

		SellMarket(volume);
	}

	private void CloseLong()
	{
		if (Position <= 0m)
			return;

		SellMarket(Position);
	}

	private void CloseShort()
	{
		if (Position >= 0m)
			return;

		BuyMarket(Math.Abs(Position));
	}

	/// <summary>
	/// Supported moving average types replicating the MQL5 enumeration.
	/// </summary>
	public enum MovingAverageKinds
	{
		Simple,
		Exponential,
		Smoothed,
		LinearWeighted
	}

	public enum CandlePrices
	{
		Open,
		Close,
		High,
		Low,
		Median,
		Typical,
		Weighted
	}
}