Ver en GitHub

Brecha de Tuyul fin de semana

Descripción general

Tuyul Gap End Of Week transfiere el asesor experto MetaTrader 5 TuyulGAP a StockSharp. La estrategia se prepara para la apertura semanal del mercado escaneando un número configurable de velas recientes el viernes por la noche, colocando un par de órdenes stop de ruptura alrededor del máximo más alto y el mínimo más bajo. Sólo se permite una sesión de negociación por semana; Una vez que se organizan las órdenes, la estrategia espera a que el precio supere cualquiera de los niveles. Cualquier posición abierta que alcance un objetivo de ganancias seguro en la moneda de la cuenta se cierra inmediatamente y todas las órdenes pendientes restantes se cancelan el lunes para restablecer el flujo de trabajo para la próxima semana.

Lógica estratégica

  • Activador de sesión semanal: la configuración se ejecuta en un día laborable configurable (viernes de forma predeterminada) cuando el reloj del intercambio llega a la hora configurada. Durante la ventana de minutos (de 23:00 a 23:15 de forma predeterminada), la estrategia prepara los niveles de ruptura una vez por sesión.
  • Niveles de ruptura dinámica: el máximo más alto y el mínimo más bajo de las velas terminadas Lookback Bars anteriores definen los precios de activación. Buy Stop se coloca un tic por encima del máximo, Sell Stop un tic por debajo del mínimo, imitando el desplazamiento del punto MetaTrader.
  • Higiene de orden pendiente: si ya existe una orden suspendida para la semana, no se vuelve a crear. La orden pendiente opuesta permanece activa después de que se activa un lado, por lo que la estrategia puede operar en cualquier dirección de la brecha.
  • Salida segura de ganancias: las posiciones abiertas se monitorean en cada vela terminada. Cuando el beneficio no realizado de una posición alcanza el objetivo de beneficio seguro (en la moneda de la cartera), se estabiliza en el mercado independientemente de la dirección.
  • Restablecimiento semanal: en la primera vela del lunes, la estrategia cancela cualquier orden pendiente aún activa y vuelve a armar el indicador de sesión para que se pueda organizar la configuración del próximo viernes.

Parámetros

  • Volumen: volumen de órdenes para las órdenes stop de ruptura.
  • Stop Loss (puntos) – distancia desde el precio de entrada, expresada en puntos del instrumento, utilizada para colocar un stop de protección después de la apertura de una posición. Establezca en 0 para desactivar la parada.
  • Barras retrospectivas: número de velas terminadas inspeccionadas para calcular los niveles máximos y mínimos semanales.
  • Configuración del día de la semana: índice del día (0=domingo… 6=sábado) que activa la configuración semanal. El valor predeterminado de 5 mantiene el comportamiento original del viernes.
  • Hora de configuración: hora de intercambio utilizada como ancla para organizar las órdenes de ruptura.
  • Ventana de minutos de configuración: número de minutos después de Setup Hour cuando la configuración sigue siendo válida. Con el valor predeterminado 15 la estrategia se ejecuta entre las 23:00 y las 23:15 inclusive.
  • Objetivo de beneficio seguro: beneficio mínimo no realizado por posición (en la moneda de la cartera) que desencadena una salida inmediata del mercado.
  • Tipo de vela: período de tiempo utilizado para el escaneo alto/bajo y el bucle de monitoreo.

Notas adicionales

  • La orden de stop loss se envía solo después de que se abre una posición, porque StockSharp no admite adjuntar un stop de protección directamente a una orden de stop pendiente.
  • Los niveles de volumen, precio y parada se normalizan utilizando la información de precisión y pasos del valor que proporciona StockSharp.
  • No existe una traducción de Python para esta estrategia; En este paquete solo se incluye la implementación de C#.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader 5 expert advisor TuyulGAP.
/// Places weekly breakout stop orders around the recent high/low range and closes positions once secure profit is reached.
/// </summary>
public class TuyulGapEndOfWeekStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _lookbackBars;
	private readonly StrategyParam<int> _setupDayOfWeek;
	private readonly StrategyParam<int> _setupHour;
	private readonly StrategyParam<int> _setupMinuteWindow;
	private readonly StrategyParam<decimal> _secureProfitTarget;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highestHigh;
	private Lowest _lowestLow;

	private decimal _tickSize;
	private decimal _entryPrice;
	private decimal? _virtualStopPrice;
	private decimal _prevHighest;
	private decimal _prevLowest;

	/// <summary>
	/// Initializes a new instance of the <see cref="TuyulGapEndOfWeekStrategy"/> class.
	/// </summary>
	public TuyulGapEndOfWeekStrategy()
	{

		_stopLossPoints = Param(nameof(StopLossPoints), 60)
		.SetRange(0, 5000)
		.SetDisplay("Stop Loss (points)", "Distance from entry used for protective stops", "Risk");

		_lookbackBars = Param(nameof(LookbackBars), 12)
		.SetRange(2, 500)
		.SetDisplay("Lookback Bars", "Number of finished candles inspected for highs/lows", "Setup");

		_setupDayOfWeek = Param(nameof(SetupDayOfWeek), 5)
		.SetRange(0, 6)
		.SetDisplay("Setup Day Of Week", "Day index (0=Sunday) that stages the weekly orders", "Setup");

		_setupHour = Param(nameof(SetupHour), 23)
		.SetRange(0, 23)
		.SetDisplay("Setup Hour", "Exchange hour when the weekly setup is evaluated", "Setup");

		_setupMinuteWindow = Param(nameof(SetupMinuteWindow), 15)
		.SetRange(0, 59)
		.SetDisplay("Setup Minute Window", "Minutes after the setup hour when staging is allowed", "Setup");

		_secureProfitTarget = Param(nameof(SecureProfitTarget), 5m)
		.SetRange(0m, 100000m)
		.SetDisplay("Secure Profit Target", "Unrealized profit per position that triggers an immediate exit", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for the high/low scan and monitoring", "Data");
	}


	/// <summary>
	/// Distance from entry used for protective stops, measured in instrument points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Number of finished candles inspected for highs and lows.
	/// </summary>
	public int LookbackBars
	{
		get => _lookbackBars.Value;
		set => _lookbackBars.Value = value;
	}

	/// <summary>
	/// Day index (0=Sunday … 6=Saturday) that stages the weekly setup.
	/// </summary>
	public int SetupDayOfWeek
	{
		get => _setupDayOfWeek.Value;
		set => _setupDayOfWeek.Value = value;
	}

	/// <summary>
	/// Exchange hour when the weekly setup is evaluated.
	/// </summary>
	public int SetupHour
	{
		get => _setupHour.Value;
		set => _setupHour.Value = value;
	}

	/// <summary>
	/// Minutes after the setup hour when staging is allowed.
	/// </summary>
	public int SetupMinuteWindow
	{
		get => _setupMinuteWindow.Value;
		set => _setupMinuteWindow.Value = value;
	}

	/// <summary>
	/// Unrealized profit per position that triggers an immediate exit.
	/// </summary>
	public decimal SecureProfitTarget
	{
		get => _secureProfitTarget.Value;
		set => _secureProfitTarget.Value = value;
	}

	/// <summary>
	/// Timeframe used for the high/low scan and monitoring.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_highestHigh = null;
		_lowestLow = null;
		_tickSize = 0m;
		_entryPrice = 0m;
		_virtualStopPrice = null;
		_prevHighest = 0m;
		_prevLowest = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_tickSize = Security?.PriceStep ?? 0m;

		_highestHigh = new Highest
		{
			Length = Math.Max(2, LookbackBars)
		};

		_lowestLow = new Lowest
		{
			Length = Math.Max(2, LookbackBars)
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_highestHigh, _lowestLow, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (!_highestHigh.IsFormed || !_lowestLow.IsFormed)
			return;

		// Check virtual stop
		if (Position > 0m && _virtualStopPrice.HasValue && candle.LowPrice <= _virtualStopPrice.Value)
		{
			SellMarket(Math.Abs(Position));
			_virtualStopPrice = null;
			_entryPrice = 0m;
			return;
		}
		if (Position < 0m && _virtualStopPrice.HasValue && candle.HighPrice >= _virtualStopPrice.Value)
		{
			BuyMarket(Math.Abs(Position));
			_virtualStopPrice = null;
			_entryPrice = 0m;
			return;
		}

		// Close on profit
		if (Position != 0m && SecureProfitTarget > 0m && PnL >= SecureProfitTarget)
		{
			if (Position > 0m)
				SellMarket(Math.Abs(Position));
			else
				BuyMarket(Math.Abs(Position));
			_virtualStopPrice = null;
			_entryPrice = 0m;
			return;
		}

		if (Position == 0m && _prevHighest > 0m && _prevLowest > 0m)
		{
			// Breakout above previous highest
			if (candle.ClosePrice > _prevHighest)
			{
				BuyMarket(Volume);
				_entryPrice = candle.ClosePrice;
				var stopDist = GetStopLossDistance();
				if (stopDist > 0m)
					_virtualStopPrice = _entryPrice - stopDist;
			}
			// Breakout below previous lowest
			else if (candle.ClosePrice < _prevLowest)
			{
				SellMarket(Volume);
				_entryPrice = candle.ClosePrice;
				var stopDist = GetStopLossDistance();
				if (stopDist > 0m)
					_virtualStopPrice = _entryPrice + stopDist;
			}
		}

		_prevHighest = highestValue;
		_prevLowest = lowestValue;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	private decimal GetStopLossDistance()
	{
		var tick = _tickSize > 0m ? _tickSize : Security?.PriceStep ?? 0m;
		if (tick <= 0m)
		return 0m;

		return StopLossPoints > 0 ? StopLossPoints * tick : 0m;
	}

	private decimal NormalizePrice(decimal price)
	{
		var tick = _tickSize > 0m ? _tickSize : Security?.PriceStep ?? 0m;
		if (tick <= 0m)
		return price;

		return Math.Round(price / tick, MidpointRounding.AwayFromZero) * tick;
	}

	private decimal NormalizeVolume(decimal volume)
	{
		var security = Security;
		if (security == null)
		return volume;

		if (security.VolumeStep is { } volumeStep && volumeStep > 0m)
		volume = Math.Round(volume / volumeStep, MidpointRounding.AwayFromZero) * volumeStep;

		if (security.MinVolume is { } minVolume && minVolume > 0m && volume < minVolume)
		volume = minVolume;

		if (security.MaxVolume is { } maxVolume && maxVolume > 0m && volume > maxVolume)
		volume = maxVolume;

		return volume;
	}

	private DayOfWeek GetSetupDay()
	{
		var day = SetupDayOfWeek % 7;
		if (day < 0)
		day += 7;

		return (DayOfWeek)day;
	}
}