Auf GitHub ansehen

FineTuning MA Candle Duplex-Strategie

Überblick

  • C#-Port des MetaTrader 5-Expertenberaters Exp_FineTuningMACandle_Duplex.
  • Repliziert den FineTuningMA-Kerzenindikator in zwei unabhängigen Strömen, damit Long- und Short-Logik separat konfiguriert werden kann.
  • Entwickelt für die High-Level-Strategie-API von StockSharp: Subscriptions, Indikatoren, Risikomanagement und Chart-Zeichnung werden alle automatisch vom Framework verwaltet.

FineTuningMA-Kerzenmodell

  • Der ursprüngliche Indikator erstellt eine synthetische Kerze durch Anwendung von drei gewichteten Exponenten (Rank1Rank3) und entsprechenden Verschiebungskoeffizienten auf die letzten Length Bars.
  • Die resultierenden gewichteten Eröffnungs- und Schlusswerte werden verglichen, um einen Farbcode zu generieren: 2 für bullisch, 1 für neutral, 0 für bearisch.
  • Wenn der reale Kerzenkörper kleiner als der konfigurierbare Gap ist, wird der synthetische Eröffnungskurs auf den vorherigen synthetischen Schlusskurs gesetzt. Dies reproduziert die "Flat-Body"-Logik der MQL5-Version.
  • Der Indikator in diesem Port emittiert nur den Farbstrom (Dezimalwerte 0/1/2), da die Handelsregeln ausschließlich von Farbübergängen abhängen.

Handelslogik

  1. Abonniert zwei Kerzendatenfeeds (LongCandleType und ShortCandleType). Diese können auf denselben oder verschiedene Zeitrahmen zeigen.
  2. Für jeden Feed wird eine dedizierte FineTuningMA-Indikatorinstanz mit eigenen Gewichtungsparametern und Signal-Offset (SignalBar) erstellt.
  3. Abgeschlossene Kerzenereignisse werden mit folgenden Regeln verarbeitet:
    • Long-Exit – wenn die vorherige Farbe gleich 0 ist, wird die bestehende Long-Position geschlossen.
    • Long-Einstieg – wenn die vorherige Farbe gleich 2 ist und die aktuelle Farbe sich von 2 weg veränderte, wird eine Kauforder gesendet (nach Schließen eines eventuellen Shorts).
    • Short-Exit – wenn die vorherige Farbe gleich 2 ist, wird die bestehende Short-Position geschlossen.
    • Short-Einstieg – wenn die vorherige Farbe gleich 0 ist und die aktuelle Farbe sich von 0 weg veränderte, wird eine Verkaufsorder gesendet (nach Schließen eines eventuellen Longs).
  4. Das Ordervolumen wird durch OrderVolume gesteuert. Wenn eine Umkehr erforderlich ist, addiert die Strategie automatisch die absolute aktuelle Position, damit die Position in einer einzigen Marktorder gedreht wird.
  5. Optionale Schutzbarrieren (TakeProfitPoints, StopLossPoints) werden in Preispunkte übersetzt und über StartProtection angewendet.

Parameter

Long-Strom

  • LongCandleType – Kerzendatentyp (Zeitrahmen) für den Long-Indikatorstrom.
  • LongLength – Anzahl der Bars in der gewichteten Berechnung.
  • LongRank1, LongRank2, LongRank3 – Exponentkoeffizienten, die die Gewichtskurve über das Lookback-Fenster formen.
  • LongShift1, LongShift2, LongShift3 – Zusätzliche Modifikatoren (0…1), die die Gewichte zum Anfang oder Ende des Fensters hin verschieben.
  • LongGap – Maximale Größe des realen Kerzenkörpers, der den synthetischen Eröffnungspreis gleich dem vorherigen synthetischen Schlusskurs hält.
  • LongSignalBar – Wie viele abgeschlossene Kerzen vor dem Lesen des Signals übersprungen werden (0 wertet die letzte geschlossene Kerze aus, 1 die vorherige usw.).
  • EnableLongEntries – Schaltet Long-Einstiege ein.
  • EnableLongExits – Schaltet automatische Long-Exits ein.

Short-Strom

  • ShortCandleType – Kerzendatentyp für den Short-Indikatorstrom.
  • ShortLength, ShortRank1, ShortRank2, ShortRank3, ShortShift1, ShortShift2, ShortShift3, ShortGap, ShortSignalBar – Identisch mit ihren Long-seitigen Gegenstücken, aber auf den Short-Strom angewendet.
  • EnableShortEntries – Schaltet Short-Einstiege ein.
  • EnableShortExits – Schaltet automatische Short-Exits ein.

Handel

  • OrderVolume – Basismenge für neue Positionen. Umkehrungen addieren automatisch die absolute aktuelle Position zu diesem Wert.
  • TakeProfitPoints – Optionaler Take-Profit-Abstand in Preispunkten (0 deaktiviert ihn).
  • StopLossPoints – Optionaler Stop-Loss-Abstand in Preispunkten (0 deaktiviert ihn).

Hinweise

  • Der ursprüngliche Expertenberater enthielt Geldmanagement-Modi basierend auf Balance oder Margin. Der Port exponiert einen einfacheren festen OrderVolume-Parameter. Passen Sie ihn auf das gewünschte Positionsgrößen-Sizing an.
  • StartProtection wird nur aufgerufen, wenn das Instrument einen gültigen Preisschritt (Security.Step > 0) hat.
  • Keine Python-Version ist absichtlich vorgesehen.
  • Chart-Bereiche werden automatisch erstellt: wenn Long- und Short-Kerzenfeeds abweichen, werden zwei separate Panels angezeigt; andernfalls wird nur eines gezeigt.
  • Die Strategie beruht auf abgeschlossenen Kerzen; sie reagiert nicht auf Intrabar-Updates.
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>
/// FineTuning MA Candle Duplex strategy using WMA crossover.
/// Buys when fast WMA crosses above slow WMA, sells on reverse.
/// </summary>
public class FineTuningMaCandleDuplexStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private WeightedMovingAverage _fast;
	private WeightedMovingAverage _slow;

	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>
	/// 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 a new instance of the <see cref="FineTuningMaCandleDuplexStrategy"/> class.
	/// </summary>
	public FineTuningMaCandleDuplexStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast WMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 85)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow WMA 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;
		_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 };

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

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

		if (!_fast.IsFormed || !_slow.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
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

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

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

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}