Ver en GitHub

Estrategia de Cobertura EES Hedger

Descripción general

La estrategia EES Hedger replica el comportamiento del asesor experto clásico de MetaTrader que cubre automáticamente posiciones creadas por otro sistema de trading o por traders manuales. Siempre que la cuenta monitorizada abre una posición que coincide con el filtro configurado, la estrategia abre inmediatamente una posición opuesta con sus propios parámetros. De este modo neutraliza la exposición direccional permitiendo que la operación original continúe.

El algoritmo está construido sobre la API de alto nivel de StockSharp. Escucha las operaciones de la cuenta, abre posiciones de cobertura y gestiona órdenes protectoras a través de lógica de stop-loss, take-profit y trailing stop. La gestión del trailing sigue de cerca la implementación original, avanzando el stop solo cuando el movimiento del precio supera tanto la distancia del stop como el incremento del trailing.

Parámetros

Nombre Descripción
HedgeVolume Volumen fijo para la orden de cobertura. No depende del tamaño de la operación externa.
StopLossPips Distancia en pips para el stop-loss de protección de la cobertura. Establecer en cero para omitir el stop inicial.
TakeProfitPips Distancia en pips para la orden de take-profit. Establecer en cero para omitir el objetivo.
TrailingStopPips Distancia en pips utilizada para el trailing una vez que el precio se mueve favorablemente.
TrailingStepPips Movimiento mínimo en pips requerido antes de mover nuevamente el trailing stop. Debe ser positivo cuando el trailing está activo.
OriginalOrderComment Filtro de comentario opcional. Solo se cubrirán las operaciones cuyo comentario coincida con este valor (sin distinción de mayúsculas). Dejar vacío para reaccionar a todas las operaciones.
HedgerOrderComment Comentario opcional usado para reconocer las propias operaciones de cobertura de la estrategia. Cuando se suministra, las operaciones con el mismo comentario se ignoran para evitar re-cobertura.

Comportamiento

  1. Detección de operaciones – la estrategia se suscribe a los eventos NewMyTrade del conector. Cada operación que proviene del instrumento seleccionado y pasa los filtros de comentario se trata como una señal de entrada externa.
  2. Ejecución de cobertura – tan pronto como se ve una operación calificada, la estrategia envía una orden de mercado en dirección opuesta usando HedgeVolume.
  3. Configuración de protección – después de cada ejecución propia, el algoritmo cancela las órdenes protectoras existentes y registra nuevas de stop-loss y take-profit de acuerdo con el precio promedio de posición actual.
  4. Trailing stop – cada tick de operación entrante se usa para evaluar las reglas de trailing. Una vez que el precio se ha movido al menos TrailingStopPips + TrailingStepPips a favor de la cobertura, el stop se acerca al precio. Para posiciones largas el stop va por debajo del mercado, para cortos por encima.
  5. Restablecimiento de posición – cuando la posición de cobertura está totalmente cerrada (por ejemplo, por stop o por objetivo), la estrategia cancela automáticamente las órdenes protectoras restantes y espera la siguiente operación externa.

Notas de uso

  • La estrategia asume que el conector de cuenta reporta todas las operaciones de la cuenta, incluyendo las generadas por otros sistemas.
  • El cálculo de pips se adapta al paso de precio del instrumento y multiplica por diez para cotizaciones de 3 o 5 dígitos, imitando el ajuste de punto MQL.
  • Establecer OriginalOrderComment para que coincida con el comentario del sistema primario si solo se deben reflejar operaciones específicas. Al cubrir operaciones manuales, dejarlo vacío.
  • Asegurarse de que TrailingStepPips sea mayor que cero siempre que el trailing esté habilitado para evitar la terminación prematura al inicio.
  • Dado que el cobertor siempre usa un volumen fijo, puede ser conveniente ajustar HedgeVolume para que la cobertura cubra la exposición promedio generada por el sistema principal.
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;
	}
}