GitHub で見る

EES ヘッジャー戦略

概要

EES Hedger戦略は、別のトレードシステムや手動トレーダーが作成したポジションを自動的にヘッジするクラシックなMetaTraderエキスパートアドバイザーの動作を再現します。監視されているアカウントが設定されたフィルターに一致するポジションを開くたびに、この戦略は即座に独自のパラメーターで反対方向のポジションを開きます。これにより、元のトレードを継続させながら方向性エクスポージャーを中立化します。

アルゴリズムはStockSharpの高レベルAPIをベースに構築されています。アカウントのトレードを監視し、ヘッジポジションを開き、ストップロス・テイクプロフィット・トレーリングストップのロジックによって保護注文を管理します。トレーリング管理はオリジナルの実装に忠実に従い、価格変動がストップ距離とトレーリングインクリメントの両方を超えた場合にのみストップを前進させます。

パラメーター

名前 説明
HedgeVolume ヘッジ注文の固定数量。外部トレードのサイズに依存しない。
StopLossPips ヘッジの保護ストップロスのpips距離。最初のストップをスキップするにはゼロに設定。
TakeProfitPips テイクプロフィット注文のpips距離。ターゲットを省略するにはゼロに設定。
TrailingStopPips 価格が有利に動いた後のトレーリングに使用するpips距離。
TrailingStepPips トレーリングストップを再び移動させる前に必要な最小pip変動。トレーリングが有効の場合は正でなければならない。
OriginalOrderComment オプションのコメントフィルター。このコメントに一致する(大文字小文字を区別しない)コメントを持つトレードのみがヘッジされる。すべてのトレードに反応するには空白のままにする。
HedgerOrderComment 戦略自身のヘッジトレードを識別するためのオプションコメント。指定すると、同じコメントを持つトレードは再ヘッジを防ぐために無視される。

動作

  1. トレード検出 – 戦略はコネクターのNewMyTradeイベントにサブスクライブします。選択された銘柄からのコメントフィルターを通過した各トレードは外部エントリーシグナルとして扱われます。
  2. ヘッジ実行 – 条件を満たすトレードが検出されると、戦略はHedgeVolumeを使用して反対方向に即座に成行注文を送信します。
  3. 保護セットアップ – 各自己約定後、アルゴリズムは既存の保護注文をキャンセルし、現在の平均ポジション価格に基づいて新しいストップロスとテイクプロフィット注文を登録します。
  4. トレーリングストップ – 入ってくる各トレードティックはトレーリングルールの評価に使用されます。価格がヘッジに有利な方向に少なくともTrailingStopPips + TrailingStepPips動いた後、ストップが価格に近づきます。ロングポジションではストップは市場価格の下に、ショートでは上に付きます。
  5. ポジションリセット – ヘッジポジションが完全に閉じられた(例:ストップやターゲットによる)場合、戦略は残りの保護注文を自動的にキャンセルし、次の外部トレードを待ちます。

使用上の注意

  • 戦略は、アカウントコネクターが他のシステムによって生成されたものを含むすべてのアカウントトレードを報告することを前提としています。
  • Pip計算は銘柄の価格刻みに適応し、3桁または5桁の相場では10を掛けて、MQLのポイント調整を模倣します。
  • 特定のトレードのみをミラーリングすべき場合は、OriginalOrderCommentをプライマリシステムのコメントに一致するように設定してください。手動トレードをヘッジする際は空白のままにしてください。
  • トレーリングが有効な場合は常にTrailingStepPipsがゼロより大きいままであることを確認し、起動時の早期終了を避けてください。
  • ヘッジャーは常に固定数量を使用するため、プライマリシステムが生成する平均エクスポージャーをカバーするようにHedgeVolumeを調整することをお勧めします。
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;
	}
}