Ver no GitHub

Estratégia de Rompimento de Sessão de 21 Horas

Esta estratégia reproduz o consultor especializado "21hour" do MetaTrader dentro do StockSharp. Ela opera durante duas janelas de negociação configuráveis e usa ordens stop pendentes para capturar rompimentos no topo e na base do intervalo. No final de cada janela, a estratégia liquida qualquer exposição aberta e remove as ordens ativas, garantindo que cada dia de negociação comece zerado.

Ideia central

  • A direção das operações é determinada puramente pela ação do preço em torno dos horários de início de sessão especificados.
  • No início de cada sessão, a estratégia cerca o mercado com um buy stop acima do ask atual e um sell stop abaixo do bid atual.
  • Quando uma ordem stop é executada, o lado contrário é cancelado imediatamente e uma ordem de take-profit a distância fixa é colocada.
  • No horário de término de sessão configurado, cada posição é fechada e todas as ordens são canceladas, mesmo que o take-profit ainda não tenha sido atingido.

Fluxo de dados

  • Velas: As velas de 1 minuto (configuráveis) são usadas apenas para fornecer marcas de tempo e acionar as verificações de horário.
  • Livro de ordens: As cotações de Nível 1 fornecem os melhores valores atuais de bid/ask que definem os preços de ativação das ordens stop.

Regras de trading

Programação de entradas

  • Às FirstSessionStartHour (8:00 hora do servidor por padrão) e às SecondSessionStartHour (22:00 por padrão), a estratégia:
    • Coloca um buy stop em Ask + StepPoints * PriceStep.
    • Coloca um sell stop em Bid - StepPoints * PriceStep.
  • Apenas uma posição é permitida. Se já houver uma posição aberta quando a outra sessão começa, todas as ordens de entrada pendentes são removidas antes de novas serem colocadas.

Gerenciamento de ordens

  • Quando uma das ordens stop é executada, o stop contrário é cancelado imediatamente.
  • Uma ordem de take-profit é registrada em EntryPrice ± TakeProfitPoints * PriceStep dependendo da direção da operação.
  • Os tamanhos das ordens são fixos pelo parâmetro Volume (1 lote por padrão).

Lógica de saída

  • As ordens de take-profit fecham as operações vencedoras automaticamente.
  • Às FirstSessionStopHour (21:00 por padrão) e SecondSessionStopHour (23:00), a estratégia fecha qualquer posição aberta a mercado e cancela todas as ordens pendentes restantes.
  • Se a posição for fechada manualmente, a estratégia também remove a ordem de take-profit pendente.

Parâmetros

Parâmetro Padrão Descrição
Volume 1 Volume de ordem usado para entradas stop e saídas de take-profit.
FirstSessionStartHour 8 Hora (0-23) quando a primeira sessão de negociação começa.
FirstSessionStopHour 21 Hora quando a primeira sessão termina e as posições são fechadas.
SecondSessionStartHour 22 Hora quando a sessão da tarde começa. Deve ser após a primeira sessão.
SecondSessionStopHour 23 Hora quando a segunda sessão termina. Deve ser após o stop da primeira sessão.
StepPoints 5 Distância da melhor cotação à ordem de entrada stop, medida em passos de preço.
TakeProfitPoints 40 Distância entre o preço de entrada e o limite de take-profit, medida em passos de preço.
CandleType 1 minuto Tipo de vela usado para acionar as verificações de horário intradiário.

Todos os parâmetros são validados para evitar sessões sobrepostas ou combinações de horas impossíveis.

Tags e características

  • Estilo: Rompimento de sessão / seguidor de tendência baseado em tempo.
  • Direção: Comprado e vendido.
  • Período: Intradiário, impulsado por horário (velas de 1 minuto apenas para temporização).
  • Controles de risco: Take-profit fixo mais fechamento forçado ao final da sessão (sem stop-loss).
  • Tipos de mercado: Projetado para FX, índices, ou qualquer instrumento com horários de negociação contínuos e cotizações confiáveis.
  • Complexidade: Baixa – sem indicadores, puramente baseado em tempo e preço.

Notas de implementação

  • A estratégia requer um Security.PriceStep válido; as ordens são ignoradas se o passo de preço ou as cotações não estiverem disponíveis.
  • Os volumes de take-profit usam o volume da operação executada quando disponível, recorrendo à posição atual ou ao volume configurado.
  • O código mantém comentários em inglês para clareza e reflete a lógica MQL original enquanto aproveita as APIs de alto nível do StockSharp (SubscribeCandles, SubscribeOrderBook, parâmetros auxiliares e helpers de ordens).
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;
		}
	}
}