Auf GitHub ansehen

Doubler Hedge-Trailing-Strategie

Übersicht

Die Doubler Hedge-Trailing-Strategie ist eine direkte StockSharp High-Level-API-Konvertierung des MetaTrader 5 Expert Advisors Doubler.mq5. Der Algorithmus öffnet sofort eine symmetrische Long- und Short-Market-Position, wann immer kein Exposure besteht, und verwaltet dann beide Legs mit unabhängigen Stop-Loss-, Take-Profit- und Trailing-Stop-Regeln. Die Konvertierung bewahrt das Hedging-Verhalten des ursprünglichen MQL-Programms und passt das Risikomanagement an StockSharp-Primitive an (Market-Orders, Level1-Subscriptions und Strategieparameter).

Im Gegensatz zu direktionalen Strategien hält das System beide Richtungen aktiv, bis jedes Leg durch seine eigene Schutzlogik aussteigt. Sobald beide Legs flach sind, recreiert die Strategie den Hedge und hält kontinuierlich gepaarte Exposure aufrecht.

Hauptmerkmale

  • Automatisches Hedging – öffnet eine Kauf- und eine Verkaufsorder mit demselben Volumen, wann immer die Strategie keine aktiven Positionen hat.
  • Pip-basierte Risikokontrollen – Stop-Loss, Take-Profit und Trailing-Offsets werden in Pips konfiguriert und intern durch Inspektion des Wertpapierpreisschritts und der Dezimalgenauigkeit in Preisschritte umgewandelt (3/5-Dezimal-Instrumente werden automatisch um den Faktor 10 skaliert).
  • Unabhängiges Trailing pro Leg – jedes Leg verfolgt das aktuelle beste Bid/Ask. Wenn sich der Preis um mehr als TrailingStopPips + TrailingStepPips günstig bewegt, wird das Stop-Level um TrailingStopPips vorgerückt und dabei die Trailing-Schritt-Bedingung eingehalten, was die ursprüngliche EA-Logik exakt widerspiegelt.
  • Volumenvalidierung – das Ordervolumen wird gegen MinVolume, MaxVolume und VolumeStep validiert, und es wird eine Ausnahme ausgelöst, wenn die angeforderte Größe die Börsenbeschränkungen verletzt.
  • Optionale Diagnose – das LogTradeDetails-Flag aktiviert detaillierte Informationsmeldungen (Einstiege, Ausstiege, Trailing-Anpassungen), die beim Testen oder Live-Monitoring helfen.

Parameter

Parameter Beschreibung Standard Hinweise
OrderVolume Volumen jedes Hedge-Legs (Kauf- und Verkaufsorders). 1 Muss Börsenvolumengrenzen einhalten; normalisiert auf den nächsten VolumeStep.
StopLossPips Stop-Loss-Distanz in Pips. 150 0 deaktiviert den Stop-Loss.
TakeProfitPips Take-Profit-Distanz in Pips. 300 0 deaktiviert den Take-Profit.
TrailingStopPips Trailing-Stop-Distanz in Pips. 5 Wenn größer als null, muss TrailingStepPips ebenfalls positiv sein.
TrailingStepPips Minimaler zusätzlicher Move bevor der Trailing Stop vorrückt. 5 Schutzgeländer, das verhindert, dass sich der Stop zu häufig bewegt.
LogTradeDetails Aktiviert ausführliches Logging von Ausführungen und Trailing-Updates. false Auf true setzen für Debugging-Läufe.

Handelslogik

Einstieg

  1. Level1-Updates abonnieren (bestes Bid/Ask).
  2. Wenn sowohl _longPosition als auch _shortPosition null sind und keine Einstiegsorders ausstehen, zwei Market-Orders registrieren: eine Kauf- und eine Verkaufsorder mit jeweils OrderVolume.
  3. Nach Bestätigung der Ausführungen zeichnet die Strategie Einstiegspreise auf, setzt initiale Stop/Take-Levels und setzt Trailing-Tracker zurück.

Risikomanagement

  • Stop-Loss – für jedes Leg wird der anfängliche Stop StopLossPips weit vom Eintrittspreis entfernt platziert. Eine Stop-Distanz von 0 deaktiviert den Schutz-Stop vollständig.
  • Take-Profit – symmetrischer Take-Profit bei TakeProfitPips. Ein Wert von 0 deaktiviert Gewinnziele.
  • Erzwungene Schließung – wenn NormalizeVolume eine ungültige Größe erkennt (zu klein/groß oder nicht mit VolumeStep übereinstimmend), wirft die Strategie eine Ausnahme, um das Senden einer ungültigen Order zu verhindern.

Trailing-Stop-Verhalten

  1. Wenn sich der Preis günstig um mindestens TrailingStopPips + TrailingStepPips bewegt, wird der Stop zu currentPrice ± TrailingStopPips vorgerückt.
  2. Die Trailing-Schritt-Prüfung reproduziert die MQL-Bedingung: Der Stop bewegt sich nur, wenn das neue Level mindestens TrailingStepPips näher am Preis liegt als der bestehende Stop, oder wenn noch kein Stop existiert.
  3. Für Long-Positionen wird das beste Bid als Referenzpreis verwendet; für Short-Positionen das beste Ask, damit Ausstiegslevel realistische Ausführungspreise widerspiegeln.

Ausstieg

  • Jedes Leg steigt unabhängig aus, wann immer seine Stop-Loss-, Trailing-Stop- oder Take-Profit-Bedingung erfüllt ist. Ausstiegsorders werden als Market-Orders registriert, und sobald ein Leg flach ist, wird sein interner Zustand geleert.
  • Nachdem beide Legs geschlossen wurden, löst das nächste Level1-Update ein brandneues gehedgtes Paar aus.

Datenanforderungen

  • Level1 (bestes Bid/Ask) – erforderlich für Eintrittspreis-Snapshots, Trailing-Berechnungen und Ausstiegs-Trigger.
  • Keine Kerzen- oder Trade-Subscription notwendig; die Strategie reagiert ausschließlich auf Level1-Updates.

Konvertierungshinweise

  • Pip-Abstände werden in absolute Preis-Offsets umgerechnet, indem mit dem Wertpapier-PriceStep multipliziert wird. Instrumente mit 3 oder 5 Dezimalstellen erhalten automatisch eine ×10-Anpassung, was der im MetaTrader-Expert verwendeten Pip-Definition entspricht.
  • Die Strategie basiert auf den High-Level-Strategy-Methoden von StockSharp (RegisterOrder, StartProtection, SubscribeLevel1) und vermeidet Low-Level-Connector-Operationen.
  • Hedging wird durch interne PositionState-Objekte implementiert, damit Long- und Short-Legs verfolgt werden, auch wenn der Broker/das Portfolio Netto-Positionen verwendet.
  • Die Konvertierung ist in sich geschlossen und modifiziert oder erfordert kein Test-Harness aus dem Repository.
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>
/// Doubler strategy using double EMA confirmation with trailing stop management.
/// Enters long when both fast and medium EMAs are above slow EMA.
/// Enters short when both fast and medium EMAs are below slow EMA.
/// </summary>
public class DoublerStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _medPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _med;
	private ExponentialMovingAverage _slow;

	private decimal _entryPrice;
	private int _cooldown;

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

	/// <summary>
	/// Medium EMA period.
	/// </summary>
	public int MedPeriod
	{
		get => _medPeriod.Value;
		set => _medPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA 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 strategy parameters.
	/// </summary>
	public DoublerStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_medPeriod = Param(nameof(MedPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Medium Period", "Medium EMA period", "Indicator");

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

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

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance 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;
		_med = null;
		_slow = null;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_med = new ExponentialMovingAverage { Length = MedPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };

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

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

		if (!_fast.IsFormed || !_med.IsFormed || !_slow.IsFormed)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			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 = 100;
				return;
			}

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

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 100;
				return;
			}
		}

		// Double confirmation: both fast and med above slow for long
		if (fastValue > slowValue && medValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 100;
		}
		// Double confirmation: both fast and med below slow for short
		else if (fastValue < slowValue && medValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

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