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;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class twenty_one_hour_session_breakout_strategy(Strategy):
def __init__(self):
super(twenty_one_hour_session_breakout_strategy, self).__init__()
self._first_session_start_hour = self.Param("FirstSessionStartHour", 2)
self._first_session_stop_hour = self.Param("FirstSessionStopHour", 20)
self._step_points = self.Param("StepPoints", 40.0)
self._take_profit_points = self.Param("TakeProfitPoints", 200.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._session_open = None
self._entry_price = 0.0
self._in_session = False
@property
def FirstSessionStartHour(self):
return self._first_session_start_hour.Value
@FirstSessionStartHour.setter
def FirstSessionStartHour(self, value):
self._first_session_start_hour.Value = value
@property
def FirstSessionStopHour(self):
return self._first_session_stop_hour.Value
@FirstSessionStopHour.setter
def FirstSessionStopHour(self, value):
self._first_session_stop_hour.Value = value
@property
def StepPoints(self):
return self._step_points.Value
@StepPoints.setter
def StepPoints(self, value):
self._step_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(twenty_one_hour_session_breakout_strategy, self).OnStarted2(time)
self._session_open = None
self._entry_price = 0.0
self._in_session = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if price_step <= 0.0:
price_step = 1.0
start_hour = int(self.FirstSessionStartHour)
stop_hour = int(self.FirstSessionStopHour)
if hour >= start_hour and hour < stop_hour:
if not self._in_session:
self._session_open = float(candle.OpenPrice)
self._in_session = True
if self._session_open is None:
return
step_offset = float(self.StepPoints) * price_step
buy_level = self._session_open + step_offset
sell_level = self._session_open - step_offset
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position == 0:
if high >= buy_level:
self.BuyMarket()
self._entry_price = buy_level
elif low <= sell_level:
self.SellMarket()
self._entry_price = sell_level
if self.Position > 0:
tp = self._entry_price + float(self.TakeProfitPoints) * price_step
if high >= tp:
self.SellMarket()
self._session_open = close
elif self.Position < 0:
tp = self._entry_price - float(self.TakeProfitPoints) * price_step
if low <= tp:
self.BuyMarket()
self._session_open = close
else:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._in_session = False
self._session_open = None
def OnReseted(self):
super(twenty_one_hour_session_breakout_strategy, self).OnReseted()
self._session_open = None
self._entry_price = 0.0
self._in_session = False
def CreateClone(self):
return twenty_one_hour_session_breakout_strategy()