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;
}
}
}
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()