Auf GitHub ansehen

FiftyFiveMedianSlopeStrategie

Herkunft

  • Konvertiert vom MetaTrader 4 Expert Advisor 55_MA_med_FIN.mq4.
  • Konzentriert sich auf die Steigung eines gleitenden 55-Perioden-Durchschnitts, der anhand der mittleren Kerzenpreise berechnet wird.

Handelslogik

  • Abonniert die konfigurierte Kerzenserie (Standard: 1-stündiger Zeitrahmen) und verarbeitet nur abgeschlossene Kerzen.
  • Berechnet einen gleitenden Durchschnitt des Medianpreises (((Hoch + Tief) / 2)) unter Verwendung der ausgewählten Methode (SMA, EMA, SMMA oder LWMA).
  • Speichert die neuesten gleitenden Durchschnittswerte in einem Ringpuffer, um den Wert vor einem Balken mit dem Wert vor MaShift Balken zu vergleichen.
  • Wenn der Wert vor einem Balken größer ist als der Wert vor MaShift Balken, gilt folgende Strategie:
    • Schließt zuerst jede kurze Belichtung.
    • Eröffnet eine Long-Position, wenn das Limit von MaxOrders nicht erreicht wurde.
  • Wenn der Wert vor einem Balken kleiner ist als der Wert vor MaShift Balken, spiegelt dies das Verhalten bei Short-Positionen wider.
  • Die Signale werden über interne Flags abgewechselt, sodass die Strategie auf einen entgegengesetzten Übergang wartet, bevor sie in die gleiche Richtung wieder eintritt.
  • Der Handel ist nur zulässig, solange die Kerzenöffnungsstunde StartHour < hour < EndHour erfüllt. Die Grenzen gelten ausschließlich für die Übereinstimmung mit der ursprünglichen MQL-Implementierung.

Positionsgrößenbestimmung und Risikomanagement

  • FixedVolume definiert die Losgröße pro Market-Order. Wenn der Wert auf Null gesetzt ist, wechselt die Strategie zur risikobasierten Dimensionierung unter Verwendung von RiskPercentage und dem aktuellen Wert des Portfolios.
  • MaxOrders begrenzt, wie oft das Basisvolumen in die gleiche Richtung gestapelt werden kann. Bei einem Wert von Null wird die Obergrenze entfernt.
  • Optional StopLossPoints und TakeProfitPoints stellen die MT4-Stop-Loss- und Take-Profit-Distanzen über StartProtection unter Verwendung von Preisschritten wieder her.

Parameter

  • FixedVolume – primäre Losgröße. Auf Null setzen, um die prozentuale Größenanpassung zu aktivieren.
  • RiskPercentage – Bruchteil des zugeteilten Portfolios, wenn FixedVolume gleich Null ist.
  • TakeProfitPoints / StopLossPoints – Schutzabstände ausgedrückt in Preisschritten.
  • MaPeriod – Länge des mittleren gleitenden Durchschnitts (Standard 55).
  • MaShift – Anzahl der Balken zwischen den aktuellen und historischen Snapshots des gleitenden Durchschnitts (Standard 13).
  • MaMethod – Berechnungstyp des gleitenden Durchschnitts (einfach, exponentiell, geglättet, linear gewichtet).
  • StartHour / EndHour – exklusives Handelsfenster in der Plattformzeit (0–23 Stunden).
  • MaxOrders – maximale gleichzeitige Einträge pro Richtung.
  • CandleType – Zeitrahmen, der für die Signalkerzen verwendet wird.

Nutzungshinweise

  • Stellen Sie sicher, dass das abonnierte Instrument einen Wert ungleich Null PriceStep und Volumenmetadaten bereitstellt, sodass die Volumenausrichtung den Austauschanforderungen entspricht.
  • Bei der risikobasierten Größenbestimmung werden der aktuelle Wert des Portfolios und der letzte Schlusskurs verwendet. Wenn eines davon nicht verfügbar ist, fällt die Strategie auf das Volumen Null zurück (kein Handel).
  • Die Strategie bricht das entgegengesetzte Engagement ab, bevor eine neue Position eröffnet wird, und emuliert so das ursprüngliche MT4-Verhalten beim Schließen gegensätzlicher Orders.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Fifty Five Median Slope: EMA slope direction with ATR stops.
/// </summary>
public class FiftyFiveMedianSlopeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<int> _slopeShift;

	private decimal _entryPrice;
	private decimal _prevEma;
	private int _barCount;
	private readonly decimal[] _emaHistory = new decimal[20];

	public FiftyFiveMedianSlopeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_emaLength = Param(nameof(EmaLength), 55)
			.SetDisplay("EMA Length", "Moving average period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");

		_slopeShift = Param(nameof(SlopeShift), 13)
			.SetDisplay("Slope Shift", "Bars between slope comparison.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public int SlopeShift
	{
		get => _slopeShift.Value;
		set => _slopeShift.Value = value;
	}

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

		_entryPrice = 0;
		_prevEma = 0;
		_barCount = 0;
		Array.Clear(_emaHistory, 0, _emaHistory.Length);
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_prevEma = 0;
		_barCount = 0;

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		var len = Math.Min(SlopeShift + 1, _emaHistory.Length);
		var idx = _barCount % len;
		_emaHistory[idx] = emaVal;
		_barCount++;

		if (_barCount < len || atrVal <= 0)
			return;

		var shiftIdx = (_barCount - SlopeShift) % len;
		if (shiftIdx < 0) shiftIdx += len;
		var shiftedEma = _emaHistory[shiftIdx];

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 1.5m || emaVal < shiftedEma)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 1.5m || emaVal > shiftedEma)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (emaVal > shiftedEma && _prevEma <= shiftedEma)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (emaVal < shiftedEma && _prevEma >= shiftedEma)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevEma = emaVal;
	}
}