Ver en GitHub

Estrategia de Rompimiento de Sesión de 21 Horas

Esta estrategia reproduce el asesor experto "21hour" de MetaTrader dentro de StockSharp. Opera durante dos ventanas de trading configurables y usa órdenes stop pendientes para capturar rompimientos en la parte superior e inferior del rango. Al final de cada ventana, la estrategia liquida cualquier exposición abierta y elimina las órdenes activas, asegurando que cada día de trading comience limpio.

Idea central

  • La dirección de las operaciones se determina puramente por la acción del precio alrededor de los tiempos de inicio de sesión especificados.
  • Al comienzo de cada sesión, la estrategia rodea el mercado con un buy stop por encima del ask actual y un sell stop por debajo del bid actual.
  • Cuando se ejecuta una orden stop, el lado contrario se cancela inmediatamente y se coloca una orden de take-profit a distancia fija.
  • Al tiempo de finalización de sesión configurado, cada posición se cierra y todas las órdenes se cancelan, incluso si el take-profit aún no se ha alcanzado.

Flujo de datos

  • Velas: Las velas de 1 minuto (configurables) se usan solo para proporcionar marcas de tiempo y activar las verificaciones de horario.
  • Libro de órdenes: Las cotizaciones de Nivel 1 suministran los mejores valores actuales de bid/ask que definen los precios de activación de las órdenes stop.

Reglas de trading

Programación de entradas

  • A las FirstSessionStartHour (8:00 hora del servidor por defecto) y a las SecondSessionStartHour (22:00 por defecto), la estrategia:
    • Coloca un buy stop en Ask + StepPoints * PriceStep.
    • Coloca un sell stop en Bid - StepPoints * PriceStep.
  • Solo se permite una posición. Si ya hay una posición abierta cuando comienza la otra sesión, todas las órdenes de entrada pendientes se eliminan antes de colocar nuevas.

Gestión de órdenes

  • Cuando se ejecuta una de las órdenes stop, el stop contrario se cancela inmediatamente.
  • Se registra una orden de take-profit a EntryPrice ± TakeProfitPoints * PriceStep según la dirección de la operación.
  • Los tamaños de las órdenes son fijos por el parámetro Volume (1 lote por defecto).

Lógica de salida

  • Las órdenes de take-profit cierran las operaciones ganadoras automáticamente.
  • A las FirstSessionStopHour (21:00 por defecto) y a las SecondSessionStopHour (23:00), la estrategia cierra cualquier posición abierta a mercado y cancela todas las órdenes pendientes restantes.
  • Si la posición se cierra manualmente, la estrategia también elimina la orden de take-profit pendiente.

Parámetros

Parámetro Predeterminado Descripción
Volume 1 Volumen de orden usado para entradas stop y salidas de take-profit.
FirstSessionStartHour 8 Hora (0-23) cuando comienza la primera sesión de trading.
FirstSessionStopHour 21 Hora cuando termina la primera sesión y se cierran las posiciones.
SecondSessionStartHour 22 Hora cuando comienza la sesión de tarde. Debe ser después de la primera sesión.
SecondSessionStopHour 23 Hora cuando termina la segunda sesión. Debe ser después del stop de la primera sesión.
StepPoints 5 Distancia desde la mejor cotización a la orden de entrada stop, medida en pasos de precio.
TakeProfitPoints 40 Distancia entre el precio de entrada y el límite de take-profit, medida en pasos de precio.
CandleType 1 minuto Tipo de vela usado para impulsar las verificaciones de horario intradía.

Todos los parámetros se validan para evitar sesiones superpuestas o combinaciones de horas imposibles.

Etiquetas y características

  • Estilo: Rompimiento de sesión / seguimiento de tendencia basado en tiempo.
  • Dirección: Largo y corto.
  • Marco temporal: Intradía, impulsado por horario (velas de 1 minuto solo para temporización).
  • Controles de riesgo: Take-profit fijo más cierre forzado al final de la sesión (sin stop-loss).
  • Tipos de mercado: Diseñado para FX, índices, o cualquier instrumento con horarios de trading continuos y cotizaciones confiables.
  • Complejidad: Baja – sin indicadores, puramente basado en tiempo y precio.

Notas de implementación

  • La estrategia requiere un Security.PriceStep válido; las órdenes se omiten si el paso de precio o las cotizaciones no están disponibles.
  • Los volúmenes de take-profit usan el volumen de la operación ejecutada cuando está disponible, recurriendo a la posición actual o al volumen configurado.
  • El código mantiene comentarios en inglés para mayor claridad y refleja la lógica MQL original mientras aprovecha las APIs de alto nivel de StockSharp (SubscribeCandles, SubscribeOrderBook, parámetros auxiliares y helpers de órdenes).
using System;
using System.Linq;
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;

/// <summary>
/// 21-hour session breakout strategy. Places simulated stop entries via candle breakout logic.
/// </summary>
public class TwentyOneHourSessionBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _firstSessionStartHour;
	private readonly StrategyParam<int> _firstSessionStopHour;
	private readonly StrategyParam<decimal> _stepPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _sessionOpen;
	private decimal _entryPrice;
	private bool _inSession;

	public int FirstSessionStartHour
	{
		get => _firstSessionStartHour.Value;
		set => _firstSessionStartHour.Value = value;
	}

	public int FirstSessionStopHour
	{
		get => _firstSessionStopHour.Value;
		set => _firstSessionStopHour.Value = value;
	}

	public decimal StepPoints
	{
		get => _stepPoints.Value;
		set => _stepPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public TwentyOneHourSessionBreakoutStrategy()
	{
		_firstSessionStartHour = Param(nameof(FirstSessionStartHour), 2)
			.SetDisplay("Session Start", "Hour of the trading window start", "Schedule");

		_firstSessionStopHour = Param(nameof(FirstSessionStopHour), 20)
			.SetDisplay("Session Stop", "Hour of the trading window stop", "Schedule");

		_stepPoints = Param(nameof(StepPoints), 40m)
			.SetGreaterThanZero()
			.SetDisplay("Step Points", "Distance from session open to breakout level", "Orders");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit Points", "Take-profit distance", "Orders");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles used to drive the trading schedule", "Data");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_sessionOpen = null;
		_entryPrice = 0m;
		_inSession = false;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
	}

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

		var hour = candle.OpenTime.Hour;
		var priceStep = Security?.PriceStep ?? 1m;

		// Session start: record the open price
		if (hour >= FirstSessionStartHour && hour < FirstSessionStopHour)
		{
			if (!_inSession)
			{
				_sessionOpen = candle.OpenPrice;
				_inSession = true;
			}

			if (_sessionOpen == null)
				return;

			var stepOffset = StepPoints * priceStep;
			var buyLevel = _sessionOpen.Value + stepOffset;
			var sellLevel = _sessionOpen.Value - stepOffset;

			// Breakout entry
			if (Position == 0)
			{
				if (candle.HighPrice >= buyLevel)
				{
					BuyMarket();
					_entryPrice = buyLevel;
				}
				else if (candle.LowPrice <= sellLevel)
				{
					SellMarket();
					_entryPrice = sellLevel;
				}
			}

			// Take profit
			if (Position > 0)
			{
				var tp = _entryPrice + TakeProfitPoints * priceStep;
				if (candle.HighPrice >= tp)
				{
					SellMarket();
					_sessionOpen = candle.ClosePrice;
				}
			}
			else if (Position < 0)
			{
				var tp = _entryPrice - TakeProfitPoints * priceStep;
				if (candle.LowPrice <= tp)
				{
					BuyMarket();
					_sessionOpen = candle.ClosePrice;
				}
			}
		}
		else
		{
			// Session end: close position
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();

			_inSession = false;
			_sessionOpen = null;
		}
	}
}