Auf GitHub ansehen

MA Cross Method PriceMode-Strategie

Überblick

Die MA Cross Method PriceMode-Strategie ist eine direkte StockSharp-Portierung des MetaTrader 4-Experten „MA_cross_Method_PriceMode“. Es kombiniert zwei konfigurierbare gleitende Durchschnitte und reagiert immer dann, wenn der schnelle Durchschnitt den langsamen Durchschnitt kreuzt. Beide Zeilen stellen die ursprünglichen MetaTrader-Eingaben dar: Zeitraum, Glättungsmethode (SMA, EMA, SMMA, LWMA), angewandter Preis (Schlusskurs, Eröffnungskurs, Höchstkurs, Tiefstkurs, Medianwert, typisch, gewichtet) und horizontale Verschiebung. Die Strategie funktioniert mit jedem Instrument, das regelmäßig zeitbasierte Kerzen bereitstellt.

Indikatoren

  • Fast Moving Average – konfigurierbare Länge, Methode und Preisquelle. Der Verschiebungsparameter MetaTrader wird reproduziert, indem die abgeschlossenen Indikatorwerte gepuffert und der Wert FirstShift Balken zurückgelesen werden.
  • Langsamer gleitender Durchschnitt – konfigurierbare Länge, Methode und Preisquelle mit derselben Shift-Emulation über Pufferung.

Handelslogik

  1. Die Strategie abonniert den ausgewählten Kerzentyp und verarbeitet nur fertige Kerzen, um ein Neulackieren innerhalb des Balkens zu vermeiden.
  2. Für jeden geschlossenen Balken werden beide gleitenden Durchschnitte mit den jeweils angewendeten Preisen gefüttert.
  3. Wenn beide Durchschnittswerte Endwerte ergeben, wertet die Strategie zwei Bedingungen aus:
    • Bullish Cross – der schnelle MA lag unter oder gleich dem langsamen MA des vorherigen Balkens und bewegt sich auf dem aktuellen Balken darüber.
    • Bearish Cross – der schnelle MA lag über oder gleich dem langsamen MA des vorherigen Balkens und bewegt sich unter diesem Wert des aktuellen Balkens.
  4. Bei einem bullischen Cross kauft die Strategie OrderVolume Kontrakte. Wenn eine Short-Position offen ist, wird die Ordergröße automatisch erhöht, um sowohl die Short-Position abzudecken als auch das neue Long-Engagement aufzubauen.
  5. Bei einem rückläufigen Cross verkauft die Strategie OrderVolume Kontrakte. Wenn eine Long-Position offen ist, wird die Ordergröße erhöht, um sie zu schließen, bevor die Short-Position eingerichtet wird.
  6. StartProtection() wird aufgerufen, damit bei Bedarf StockSharp Schutzmodule hinzugefügt werden können (z. B. Stop-Loss- oder Break-Even-Assistenten).

Parameter

Name Beschreibung Standard
FirstPeriod Periode des schnellen gleitenden Durchschnitts. 3
SecondPeriod Periode des langsamen gleitenden Durchschnitts. 13
FirstMethod Glättungsmethode für den schnellen gleitenden Durchschnitt (Simple, Exponential, Smoothed, LinearWeighted). Simple
SecondMethod Glättungsmethode für den langsamen gleitenden Durchschnitt. LinearWeighted
FirstPriceMode Angewendeter Preis für den schnell gleitenden Durchschnitt (Close, Open, High, Low, Median, Typical, Weighted). Close
SecondPriceMode Angewandter Preis für den langsam gleitenden Durchschnitt. Median
FirstShift Horizontale Verschiebung (in Balken), angewendet auf den sich schnell bewegenden Durchschnitt. 0
SecondShift Horizontale Verschiebung (in Balken), angewendet auf den langsam gleitenden Durchschnitt. 0
OrderVolume Basisauftragsvolumen, das für neue Positionen verwendet wird. 0.1
CandleType Von der Strategie verarbeiteter Kerzentyp/Zeitrahmen. 5-Minuten-Kerzen

Unterschiede im Vergleich zur MQL-Version

  • Die MetaTrader-Auftragsiteration (OrdersTotal, OrderSelect, OrderClose) wird durch die direkte Verwendung der Eigenschaft StockSharp Strategy.Position und Marktaufträge ersetzt, deren Größe bei Bedarf so dimensioniert ist, dass das Risiko umgekehrt wird.
  • Das Flag „Neuer Balken“ MetaTrader ist nicht erforderlich: ProcessCandle wird genau einmal pro fertiger Kerze ausgeführt, wodurch das gleiche Verhalten einmal pro Balken ohne Abfragen auf Tick-Ebene sichergestellt wird.
  • Die Handhabung der MA-Verschiebung wird mit kompakten Puffern implementiert, die die letzten shift + 2-Werte für jeden Durchschnitt enthalten. Dies spiegelt die Indikatorverschiebung wider, ohne sich auf verbotene Indikator-Rückverweise (GetValue) zu verlassen.
  • Die Strategie ist maklerunabhängig; Risikomanagement-Helfer können über StartProtection() anstelle der festen MetaTrader Stopp-/Limit-Argumente angehängt werden.

Nutzungshinweise

  • Wählen Sie eine Kerzendauer, die dem ursprünglichen Zeitrahmen entspricht (z. B. M5 oder H1). Benutzerdefinierte Zeitrahmen können durch Bearbeiten von CandleType in den Strategieparametern bereitgestellt werden.
  • Das Setzen von FirstShift oder SecondShift auf einen positiven Wert verzögert den effektiven Crossover um entsprechend viele abgeschlossene Balken, genau wie die horizontale Verschiebungseingabe in MetaTrader.
  • Der Preismodus Weighted reproduziert die (High + Low + 2 * Close) / 4-Formel von MetaTrader. Der Median- und der typische Modus folgen den Standarddefinitionen (High + Low) / 2 und (High + Low + Close) / 3.
  • Da es sich bei jeder Order um eine Marktorder handelt, stellen Sie sicher, dass die Kontokonfiguration das angeforderte Volumen und die angeforderte Slippage toleriert.
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>
/// Moving average crossover strategy converted from the MetaTrader script "MA_cross_Method_PriceMode".
/// Allows selecting the smoothing method, applied price and horizontal shift for each average.
/// </summary>
public class MaCrossMethodPriceModeStrategy : Strategy
{
	private readonly StrategyParam<int> _firstPeriod;
	private readonly StrategyParam<int> _secondPeriod;
	private readonly StrategyParam<MaMethods> _firstMethod;
	private readonly StrategyParam<MaMethods> _secondMethod;
	private readonly StrategyParam<AppliedPriceModes> _firstPriceMode;
	private readonly StrategyParam<AppliedPriceModes> _secondPriceMode;
	private readonly StrategyParam<int> _firstShift;
	private readonly StrategyParam<int> _secondShift;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<DataType> _candleType;

	private DecimalLengthIndicator _firstMa = null!;
	private DecimalLengthIndicator _secondMa = null!;

	private readonly List<decimal> _firstValues = new();
	private readonly List<decimal> _secondValues = new();

	/// <summary>
	/// Initializes a new instance of <see cref="MaCrossMethodPriceModeStrategy"/>.
	/// </summary>
	public MaCrossMethodPriceModeStrategy()
	{
		_firstPeriod = Param(nameof(FirstPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "Length of the first moving average.", "Indicators")
			
			.SetOptimize(2, 50, 1);

		_secondPeriod = Param(nameof(SecondPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Length of the second moving average.", "Indicators")
			
			.SetOptimize(5, 100, 1);

		_firstMethod = Param(nameof(FirstMethod), MaMethods.Simple)
			.SetDisplay("Fast MA Method", "Smoothing method applied to the first moving average.", "Indicators")
			;

		_secondMethod = Param(nameof(SecondMethod), MaMethods.LinearWeighted)
			.SetDisplay("Slow MA Method", "Smoothing method applied to the second moving average.", "Indicators")
			;

		_firstPriceMode = Param(nameof(FirstPriceMode), AppliedPriceModes.Close)
			.SetDisplay("Fast MA Price", "Price source used for the first moving average.", "Indicators")
			;

		_secondPriceMode = Param(nameof(SecondPriceMode), AppliedPriceModes.Median)
			.SetDisplay("Slow MA Price", "Price source used for the second moving average.", "Indicators")
			;

		_firstShift = Param(nameof(FirstShift), 0)
			.SetNotNegative()
			.SetDisplay("Fast MA Shift", "Horizontal shift (in bars) applied to the first moving average.", "Indicators");

		_secondShift = Param(nameof(SecondShift), 0)
			.SetNotNegative()
			.SetDisplay("Slow MA Shift", "Horizontal shift (in bars) applied to the second moving average.", "Indicators");

		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Base order volume used for new entries.", "Trading")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for price processing.", "General");
	}

	/// <summary>
	/// Period of the first moving average.
	/// </summary>
	public int FirstPeriod
	{
		get => _firstPeriod.Value;
		set => _firstPeriod.Value = value;
	}

	/// <summary>
	/// Period of the second moving average.
	/// </summary>
	public int SecondPeriod
	{
		get => _secondPeriod.Value;
		set => _secondPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing method applied to the first moving average.
	/// </summary>
	public MaMethods FirstMethod
	{
		get => _firstMethod.Value;
		set => _firstMethod.Value = value;
	}

	/// <summary>
	/// Smoothing method applied to the second moving average.
	/// </summary>
	public MaMethods SecondMethod
	{
		get => _secondMethod.Value;
		set => _secondMethod.Value = value;
	}

	/// <summary>
	/// Applied price mode for the first moving average.
	/// </summary>
	public AppliedPriceModes FirstPriceMode
	{
		get => _firstPriceMode.Value;
		set => _firstPriceMode.Value = value;
	}

	/// <summary>
	/// Applied price mode for the second moving average.
	/// </summary>
	public AppliedPriceModes SecondPriceMode
	{
		get => _secondPriceMode.Value;
		set => _secondPriceMode.Value = value;
	}

	/// <summary>
	/// Shift (in bars) applied to the first moving average values.
	/// </summary>
	public int FirstShift
	{
		get => _firstShift.Value;
		set => _firstShift.Value = value;
	}

	/// <summary>
	/// Shift (in bars) applied to the second moving average values.
	/// </summary>
	public int SecondShift
	{
		get => _secondShift.Value;
		set => _secondShift.Value = value;
	}

	/// <summary>
	/// Base order volume used for new positions.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Candle type (timeframe) processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

		_firstMa = null!;
		_secondMa = null!;
		_firstValues.Clear();
		_secondValues.Clear();
	}

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

		_firstMa = CreateMovingAverage(FirstMethod, FirstPeriod);
		_secondMa = CreateMovingAverage(SecondMethod, SecondPeriod);

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _firstMa);
			DrawIndicator(area, _secondMa);
			DrawOwnTrades(area);
		}

		StartProtection(null, null);
	}

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

		UpdateBuffer(_firstValues, firstDecimal, FirstShift);
		UpdateBuffer(_secondValues, secondDecimal, SecondShift);

		if (!TryGetShiftedValues(_firstValues, FirstShift, out var firstCurrent, out var firstPrevious))
			return;

		if (!TryGetShiftedValues(_secondValues, SecondShift, out var secondCurrent, out _))
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var bullishCross = IsBullishCross(firstPrevious, firstCurrent, secondCurrent);
		var bearishCross = IsBearishCross(firstPrevious, firstCurrent, secondCurrent);

		if (bullishCross && OrderVolume > 0m && Position <= 0m)
		{
			var volumeToBuy = OrderVolume + (Position < 0m ? Math.Abs(Position) : 0m);
			BuyMarket(volumeToBuy);
		}
		else if (bearishCross && OrderVolume > 0m && Position >= 0m)
		{
			var volumeToSell = OrderVolume + (Position > 0m ? Position : 0m);
			SellMarket(volumeToSell);
		}
	}

	private static void UpdateBuffer(List<decimal> buffer, decimal value, int shift)
	{
		buffer.Add(value);

		var maxCount = Math.Max(shift + 2, 2);
		while (buffer.Count > maxCount)
		{
			buffer.RemoveAt(0);
		}
	}

	private static bool TryGetShiftedValues(IReadOnlyList<decimal> buffer, int shift, out decimal current, out decimal previous)
	{
		var currentIndex = buffer.Count - 1 - shift;
		var previousIndex = buffer.Count - 2 - shift;

		if (previousIndex < 0 || currentIndex < 0 || currentIndex >= buffer.Count)
		{
			current = default;
			previous = default;
			return false;
		}

		current = buffer[currentIndex];
		previous = buffer[previousIndex];
		return true;
	}

	private static bool IsBullishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
	{
		return (previousFast <= currentSlow && currentFast > currentSlow)
			|| (previousFast < currentSlow && currentFast >= currentSlow);
	}

	private static bool IsBearishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
	{
		return (previousFast >= currentSlow && currentFast < currentSlow)
			|| (previousFast > currentSlow && currentFast <= currentSlow);
	}

	private static decimal SelectPrice(ICandleMessage candle, AppliedPriceModes mode)
	{
		return mode switch
		{
			AppliedPriceModes.Close => candle.ClosePrice,
			AppliedPriceModes.Open => candle.OpenPrice,
			AppliedPriceModes.High => candle.HighPrice,
			AppliedPriceModes.Low => candle.LowPrice,
			AppliedPriceModes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPriceModes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPriceModes.Weighted => (candle.HighPrice + candle.LowPrice + (2m * candle.ClosePrice)) / 4m,
			_ => candle.ClosePrice
		};
	}

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

	/// <summary>
	/// Moving average smoothing methods that mirror the MetaTrader inputs.
	/// </summary>
	public enum MaMethods
	{
		Simple,
		Exponential,
		Smoothed,
		LinearWeighted
	}

	/// <summary>
	/// Applied price options equivalent to the MetaTrader constants.
	/// </summary>
	public enum AppliedPriceModes
	{
		Close,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted
	}
}