Открыть на GitHub

Стратегия EES Hedger

Описание

Стратегия EES Hedger повторяет логику одноимённого советника для MetaTrader и автоматически хеджирует позиции, открытые другим алгоритмом или вручную. Когда на счёте появляется сделка, удовлетворяющая заданному фильтру, стратегия немедленно открывает противоположную позицию со своими параметрами. Это позволяет свести рыночную экспозицию к нулю, сохраняя исходную сделку в рынке.

Реализация использует высокоуровневый API StockSharp: стратегия отслеживает сделки счёта, выставляет ордера для хеджа и управляет защитой через стоп-лосс, тейк-профит и трейлинг-стоп. Алгоритм трейлинга копирует оригинал: стоп переносится только после того, как цена прошла расстояние, превышающее сумму TrailingStopPips и TrailingStepPips.

Параметры

Параметр Назначение
HedgeVolume Фиксированный объём встречного ордера. Не зависит от объёма исходной сделки.
StopLossPips Расстояние до начального стоп-лосса. Значение 0 отключает стартовый стоп.
TakeProfitPips Расстояние до тейк-профита. Значение 0 означает отсутствие цели.
TrailingStopPips Расстояние трейлинг-стопа от текущей цены.
TrailingStepPips Минимальный шаг для очередного переноса трейлинг-стопа. При включённом трейлинге должен быть больше нуля.
OriginalOrderComment Необязательный фильтр по комментарию. Хеджируются только сделки с совпадающим комментарием (без учёта регистра). Пустое значение — все сделки.
HedgerOrderComment Необязательный комментарий для идентификации собственных хеджевых сделок. Совпадающие комментарии игнорируются, чтобы исключить повторный хедж.

Логика работы

  1. Отслеживание сделок. Стратегия подписывается на событие NewMyTrade коннектора и анализирует каждую сделку по выбранному инструменту.
  2. Выставление хеджа. При появлении подходящей сделки открывается рыночный ордер противоположного направления объёмом HedgeVolume.
  3. Настройка защиты. После каждого собственного исполнения отменяются старые защитные ордера и выставляются новые стоп-лосс и тейк-профит, рассчитанные от средней цены позиции.
  4. Трейлинг-стоп. Каждый новый тик проверяет условия трейлинга. Когда цена прошла в прибыльную сторону не меньше TrailingStopPips + TrailingStepPips, стоп подтягивается ближе к рынку (под цену для лонга и над ценой для шорта).
  5. Завершение цикла. При полном закрытии хеджевой позиции оставшиеся ордера отменяются, и стратегия ожидает следующую исходную сделку.

Рекомендации по использованию

  • Убедитесь, что коннектор передаёт все сделки счёта, включая операции других систем.
  • Расчёт величины пункта выполняется на основе PriceStep; для инструментов с 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;
	}
}