Ver no GitHub

Estratégia de Hedge EES Hedger

Visão geral

A estratégia EES Hedger espelha o comportamento do assessor especializado clássico do MetaTrader que cobre automaticamente posições criadas por outro sistema de trading ou por traders manuais. Sempre que a conta monitorada abre uma posição que corresponde ao filtro configurado, a estratégia abre imediatamente uma posição oposta com seus próprios parâmetros. Dessa forma, neutraliza a exposição direcional enquanto ainda permite que a operação original continue.

O algoritmo é construído sobre a API de alto nível do StockSharp. Ele escuta as operações da conta, abre posições de hedge e gerencia ordens de proteção por meio de lógica de stop-loss, take-profit e trailing stop. O gerenciamento do trailing segue de perto a implementação original, avançando o stop somente quando o movimento do preço supera tanto a distância do stop quanto o incremento do trailing.

Parâmetros

Nome Descrição
HedgeVolume Volume fixo para a ordem de hedge. Não depende do tamanho da operação externa.
StopLossPips Distância em pips para o stop-loss de proteção do hedge. Definir como zero para pular o stop inicial.
TakeProfitPips Distância em pips para a ordem de take-profit. Definir como zero para omitir o alvo.
TrailingStopPips Distância em pips usada para o trailing assim que o preço se move favoravelmente.
TrailingStepPips Movimento mínimo em pips necessário antes de mover novamente o trailing stop. Deve ser positivo quando o trailing estiver ativo.
OriginalOrderComment Filtro de comentário opcional. Apenas operações cujo comentário corresponda a este valor (sem distinção de maiúsculas/minúsculas) serão cobertas. Deixar vazio para reagir a todas as operações.
HedgerOrderComment Comentário opcional usado para reconhecer as próprias operações de hedge da estratégia. Quando fornecido, operações com o mesmo comentário são ignoradas para evitar novo hedge.

Comportamento

  1. Detecção de operações – a estratégia se subscreve aos eventos NewMyTrade do conector. Cada operação que vem do instrumento selecionado e passa pelos filtros de comentário é tratada como um sinal de entrada externo.
  2. Execução do hedge – assim que uma operação qualificada é vista, a estratégia envia uma ordem de mercado na direção oposta usando HedgeVolume.
  3. Configuração de proteção – após cada preenchimento próprio, o algoritmo cancela as ordens de proteção existentes e registra novas ordens de stop-loss e take-profit de acordo com o preço médio da posição atual.
  4. Trailing stop – cada tick de operação recebido é usado para avaliar as regras de trailing. Assim que o preço se moveu pelo menos TrailingStopPips + TrailingStepPips em favor do hedge, o stop é aproximado do preço. Para posições compradas o stop segue abaixo do mercado, para vendidas acima.
  5. Redefinição de posição – quando a posição de hedge está totalmente fechada (por exemplo, por stop ou alvo), a estratégia cancela automaticamente as ordens de proteção restantes e aguarda a próxima operação externa.

Notas de uso

  • A estratégia assume que o conector de conta reporta todas as operações da conta, incluindo as geradas por outros sistemas.
  • O cálculo de pips se adapta ao passo de preço do instrumento e multiplica por dez para cotações de 3 ou 5 dígitos, imitando o ajuste de ponto MQL.
  • Definir OriginalOrderComment para corresponder ao comentário do sistema primário se apenas operações específicas devem ser espelhadas. Ao cobrir operações manuais, deixar vazio.
  • Garantir que TrailingStepPips permaneça maior que zero sempre que o trailing estiver habilitado para evitar encerramento prematuro na inicialização.
  • Como o hedge sempre usa um volume fixo, pode ser conveniente ajustar HedgeVolume para que o hedge cubra a exposição média gerada pelo sistema primário.
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;
	}
}