Auf GitHub ansehen

EES Hedger-Strategie

Übersicht

Die EES Hedger-Strategie spiegelt das Verhalten des klassischen MetaTrader Expert Advisors, der automatisch Positionen absichert, die von einem anderen Handelssystem oder von manuellen Tradern eröffnet wurden. Sobald das überwachte Konto eine Position eröffnet, die dem konfigurierten Filter entspricht, eröffnet die Strategie sofort eine Gegenposition mit eigenen Parametern. Dadurch wird das gerichtete Exposure neutralisiert, während der ursprüngliche Trade weiterläuft.

Der Algorithmus basiert auf der High-Level-StockSharp-API. Er überwacht Kontotrades, eröffnet Hedge-Positionen und verwaltet Schutzorders durch Stop-Loss-, Take-Profit- und Trailing-Stop-Logik. Das Trailing-Management folgt der Originalimplementierung eng und rückt den Stop nur dann vor, wenn die Preisbewegung sowohl die Stop-Distanz als auch das Trailing-Inkrement überschreitet.

Parameter

Name Beschreibung
HedgeVolume Fixes Volumen für die Hedge-Order. Hängt nicht von der externen Tradegröße ab.
StopLossPips Abstand in Pips für den Schutz-Stop-Loss des Hedges. Auf null setzen, um den anfänglichen Stop zu überspringen.
TakeProfitPips Abstand in Pips für die Take-Profit-Order. Auf null setzen, um das Ziel wegzulassen.
TrailingStopPips Abstand in Pips, der für das Trailing verwendet wird, sobald sich der Preis günstig bewegt.
TrailingStepPips Minimale Pip-Bewegung, die erforderlich ist, bevor der Trailing Stop erneut bewegt wird. Muss positiv sein, wenn Trailing aktiv ist.
OriginalOrderComment Optionaler Kommentarfilter. Nur Trades, deren Kommentar mit diesem Wert übereinstimmt (Groß-/Kleinschreibung wird ignoriert), werden abgesichert. Leer lassen, um auf jeden Trade zu reagieren.
HedgerOrderComment Optionaler Kommentar zur Identifizierung der eigenen Hedge-Trades der Strategie. Wenn angegeben, werden Trades mit demselben Kommentar ignoriert, um erneutes Hedging zu vermeiden.

Verhalten

  1. Trade-Erkennung – die Strategie abonniert NewMyTrade-Ereignisse des Connectors. Jeder Trade, der vom ausgewählten Instrument kommt und die Kommentarfilter passiert, wird als externes Einstiegssignal behandelt.
  2. Hedge-Ausführung – sobald ein qualifizierender Trade gesehen wird, sendet die Strategie eine Marktorder in der Gegenrichtung mit HedgeVolume.
  3. Schutz-Setup – nach jeder eigenen Füllung storniert der Algorithmus bestehende Schutzorders und registriert neue Stop-Loss- und Take-Profit-Orders gemäß dem aktuellen durchschnittlichen Positionspreis.
  4. Trailing Stop – jedes eingehende Trade-Tick wird zur Auswertung der Trailing-Regeln verwendet. Sobald sich der Preis um mindestens TrailingStopPips + TrailingStepPips zugunsten des Hedges bewegt hat, wird der Stop näher an den Preis gerückt. Bei Long-Positionen folgt der Stop unter dem Markt, bei Shorts über ihm.
  5. Positions-Reset – wenn die Hedge-Position vollständig geschlossen ist (z. B. durch Stop oder Ziel), storniert die Strategie automatisch die verbleibenden Schutzorders und wartet auf den nächsten externen Trade.

Verwendungshinweise

  • Die Strategie setzt voraus, dass der Konto-Connector alle Kontotrades meldet, einschließlich der von anderen Systemen generierten.
  • Die Pip-Berechnung passt sich dem Instrument-Preisschritt an und multipliziert bei 3- oder 5-stelligen Kursen mit zehn, um die MQL-Punktanpassung nachzuahmen.
  • OriginalOrderComment so einstellen, dass er mit dem Kommentar des Primärsystems übereinstimmt, wenn nur bestimmte Trades gespiegelt werden sollen. Beim Absichern manueller Trades leer lassen.
  • Sicherstellen, dass TrailingStepPips größer als null bleibt, wann immer Trailing aktiviert ist, um vorzeitige Beendigung beim Start zu vermeiden.
  • Da der Hedger immer ein festes Volumen verwendet, empfiehlt es sich, HedgeVolume so anzupassen, dass der Hedge das durchschnittliche Exposure des Primärsystems abdeckt.
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;

using System.Globalization;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Mirrors trades by opening an opposite hedge position with trailing stop management.
/// Simplified from the EES Hedger expert advisor.
/// </summary>
public class EesHedgerStrategy : Strategy
{
	private readonly StrategyParam<decimal> _hedgeVolume;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal _pipSize;

	/// <summary>
	/// Hedge position volume.
	/// </summary>
	public decimal HedgeVolume
	{
		get => _hedgeVolume.Value;
		set => _hedgeVolume.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Minimum step between trailing stop updates in pips.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public EesHedgerStrategy()
	{
		_hedgeVolume = Param(nameof(HedgeVolume), 0.1m)
			.SetDisplay("Hedge Volume", "Volume used for hedge orders", "General");

		_stopLossPips = Param(nameof(StopLossPips), 50)
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance per hedge", "Risk Management");

		_takeProfitPips = Param(nameof(TakeProfitPips), 50)
			.SetDisplay("Take Profit (pips)", "Take-profit distance per hedge", "Risk Management");

		_trailingStopPips = Param(nameof(TrailingStopPips), 25)
			.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk Management");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetDisplay("Trailing Step (pips)", "Minimum trailing stop increment", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for processing", "General");
	}

	/// <summary>
	/// Candle type used for processing.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_stopPrice = null;
		_pipSize = 0m;
	}

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

		_pipSize = CalculatePipSize();

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

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

		var price = candle.ClosePrice;

		if (price <= 0m)
			return;

		// Entry: if no position, open based on tick direction
		if (Position == 0 && _entryPrice == 0m)
		{
			var volume = HedgeVolume > 0m ? HedgeVolume : Volume;
			if (volume <= 0m)
				return;

			BuyMarket();
			_entryPrice = price;
			_stopPrice = null;
			return;
		}

		if (Position != 0 && _entryPrice == 0m)
			_entryPrice = price;

		// Check stop loss
		if (Position != 0 && StopLossPips > 0 && _pipSize > 0m)
		{
			var stopDistance = StopLossPips * _pipSize;

			if (Position > 0 && price <= _entryPrice - stopDistance)
			{
				SellMarket();
				_entryPrice = 0m;
				_stopPrice = null;
				return;
			}
			else if (Position < 0 && price >= _entryPrice + stopDistance)
			{
				BuyMarket();
				_entryPrice = 0m;
				_stopPrice = null;
				return;
			}
		}

		// Check take profit
		if (Position != 0 && TakeProfitPips > 0 && _pipSize > 0m)
		{
			var takeDistance = TakeProfitPips * _pipSize;

			if (Position > 0 && price >= _entryPrice + takeDistance)
			{
				SellMarket();
				_entryPrice = 0m;
				_stopPrice = null;
				return;
			}
			else if (Position < 0 && price <= _entryPrice - takeDistance)
			{
				BuyMarket();
				_entryPrice = 0m;
				_stopPrice = null;
				return;
			}
		}

		// Trailing stop
		if (Position != 0 && TrailingStopPips > 0 && _pipSize > 0m)
		{
			var trailingDistance = TrailingStopPips * _pipSize;
			var trailingStep = TrailingStepPips * _pipSize;

			if (Position > 0)
			{
				var newStop = price - trailingDistance;
				if (newStop > _entryPrice && (!_stopPrice.HasValue || newStop > _stopPrice.Value + trailingStep))
					_stopPrice = newStop;

				if (_stopPrice.HasValue && price <= _stopPrice.Value)
				{
					SellMarket();
					_entryPrice = 0m;
					_stopPrice = null;
				}
			}
			else if (Position < 0)
			{
				var newStop = price + trailingDistance;
				if (newStop < _entryPrice && (!_stopPrice.HasValue || newStop < _stopPrice.Value - trailingStep))
					_stopPrice = newStop;

				if (_stopPrice.HasValue && price >= _stopPrice.Value)
				{
					BuyMarket();
					_entryPrice = 0m;
					_stopPrice = null;
				}
			}
		}
	}

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

		var decimals = GetDecimalPlaces(step);
		return decimals == 3 || decimals == 5 ? step * 10m : step;
	}

	private static int GetDecimalPlaces(decimal value)
	{
		var text = Math.Abs(value).ToString(CultureInfo.InvariantCulture);
		var index = text.IndexOf('.');
		return index >= 0 ? text.Length - index - 1 : 0;
	}
}