Ver en GitHub

Estrategia de Hedger Drawdown

Port de StockSharp del asesor experto de MetaTrader 5 hedger.mq5 (MQL #23511). El sistema original abre una cobertura protectora en la dirección opuesta cuando una posición existente cae un número determinado de pips. Una vez que el precio retrocede en una cantidad menor, la cobertura se cierra incluso con pérdida, permitiendo que la operación original se recupere. Esta conversión reproduce el comportamiento con la API de alto nivel de StockSharp y adapta la mecánica al modelo de posición neta de la plataforma.

Lógica de trading

  1. La estrategia monitorea el cierre de cada vela del marco temporal configurado.
  2. Para cada posición long que no sea de cobertura, comprueba si la distancia entre el precio de entrada y el cierre actual es mayor o igual que DrawdownOpenPips. Si no hay cobertura short activa, abre una con el mismo volumen.
  3. Para cada posición short que no sea de cobertura, aplica la regla simétrica, abriendo una cobertura long después de que la pérdida alcanza el umbral de apertura.
  4. Las coberturas activas se cierran cuando su pérdida flotante alcanza DrawdownClosePips, reflejando la lógica de MetaTrader de liberar la protección después de una recuperación parcial.
  5. Cuando la cuenta está plana y StartWithLong está habilitado, el algoritmo abre una posición long inicial para iniciar el ciclo.

Dado que StockSharp rastrea posiciones netas, la estrategia mantiene registros internos de entradas long y short (incluidas las que son coberturas). Cada orden de mercado actualiza los registros para que las coberturas puedan abrirse y cerrarse independientemente, incluso si el broker colapsa las posiciones.

Parámetros

Parámetro Descripción
DrawdownOpenPips Drawdown en pips que activa la apertura de la cobertura opuesta.
DrawdownClosePips Drawdown en pips que fuerza el cierre de la cobertura.
InitialVolume Volumen de la operación inicial al iniciar el ciclo.
StartWithLong Si está habilitado, abre la posición long inicial cuando la cuenta está plana.
EnableVerboseLogging Escribe las acciones de cobertura en el registro de la estrategia para depuración.
CandleType Serie de velas utilizada para monitorear los drawdowns.

Diferencias con la versión de MetaTrader

  • El asesor experto dependía de comentarios en los tickets (hedge_buy / hedge_sell) para distinguir las posiciones de cobertura. La conversión almacena este estado en memoria porque StockSharp usa netting.
  • Las verificaciones de margen y configuraciones de slippage se omiten; la colocación de órdenes usa los helpers de alto nivel BuyMarket / SellMarket.
  • La estrategia expone rangos de optimización para los umbrales de pips y el volumen para que puedan ajustarse con los optimizadores de StockSharp.

Notas de uso

  1. Adjunte la estrategia al símbolo y portfolio deseados.
  2. Ajuste los umbrales de pips para que coincidan con la volatilidad del instrumento.
  3. Habilite el registro detallado al validar la conversión: el registro registra cada creación y eliminación de cobertura con estadísticas de pips.
  4. Implemente en marcos temporales que entreguen cierres de velas significativos (por ejemplo, M15 a H1) para evitar el sobretrading.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class HedgerDrawdownStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public HedgerDrawdownStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}