Esta estrategia StockSharp es una conversión fiel de C# del asesor experto MetaTrader 4 Strategy_of_Regularities_of_Exchange_Rates.mq4. El sistema fue diseñado como un sistema de ruptura diaria: coloca entre paréntesis el mercado con órdenes de parada cuando llega una hora específica y mantiene esas órdenes activas hasta la hora de cierre nocturno. Cualquier posición ocupada es supervisada tanto por un stop-loss del corredor como por un organismo de control de toma de ganancias intradiario para que las operaciones no se prolonguen más allá de la sesión de negociación definida.
A diferencia de los sistemas basados en indicadores, la lógica se centra únicamente en el tiempo y la distancia. Cuando el cronograma dice que el mercado debería estar listo, la estrategia mide una compensación fija en puntos de corredor (pips) de la oferta y demanda actual y coloca un par de órdenes stop simétricas. El código adapta automáticamente el cálculo de puntos a símbolos con comillas de 3 o 5 dígitos, coincidiendo con el comportamiento de la versión original MQL.
Lógica de trading
Hora de apertura: una vez que una vela terminada informa OpeningHour, la estrategia cancela cualquier orden pendiente sobrante y envía un stop de compra por encima de la demanda actual y un stop de venta por debajo de la oferta actual. La distancia es EntryOffsetPoints * point, donde el valor point se deriva del instrumento PriceStep y se ajusta para cotizaciones fraccionarias.
Órdenes de protección: inmediatamente después del inicio, la estrategia habilita StartProtection con el StopLossPoints configurado. Por lo tanto, cualquier operación ejecutada recibe un stop-loss del lado del corredor idéntico al EA original.
Supervisión de obtención de beneficios: en cada vela completa, el algoritmo comprueba si el beneficio actual supera TakeProfitPoints * point. Si es así, cierra la posición al mercado. Esto refleja el bucle OrderClose original que salió cuando las ganancias alcanzaron el umbral.
Hora de cierre: cuando el reloj llega a ClosingHour, la estrategia cierra con fuerza cualquier posición abierta y cancela las órdenes stop, asegurando que el libro se mantenga plano para la siguiente sesión.
Reinicio diario: se envía un nuevo lote de órdenes pendientes solo una vez por día de negociación, lo que evita duplicados y al mismo tiempo respeta la intención original de una única configuración por sesión.
Parámetros
Parámetro
Predeterminado
Descripción
OpeningHour
9
Hora (0–23) en la que se coloca el par de órdenes stop.
ClosingHour
2
Hora (0–23) en la que se eliminan las órdenes pendientes y se aplanan las operaciones abiertas.
EntryOffsetPoints
20
Distancia en puntos del corredor desde la oferta/demanda actual hasta las órdenes de parada.
TakeProfitPoints
20
Objetivo de beneficio en puntos del corredor que desencadena una salida del mercado. Establezca en 0 para deshabilitar la obtención de ganancias manual.
StopLossPoints
500
Distancia en puntos de intermediario para la parada de protección conectada a través de StartProtection.
OrderVolume
0.1
Volumen de cada orden stop.
CandleType
30 minute time frame
Serie de velas utilizadas para evaluar el cronograma. Cualquier período de tiempo ≤ 1 hora mantiene el comportamiento coherente con el script MQL.
Notas de conversión
El asesor experto original trabajó en eventos de ticks y hizo referencia a Hour() directamente. En StockSharp la estrategia escucha las velas terminadas y usa su hora de apertura, lo que preserva la lógica de una vez por hora mientras se mantiene dentro de las pautas del repositorio sobre los estados de las velas.
Las órdenes pendientes se normalizan con Security.ShrinkPrice para que los precios generados siempre coincidan con el tamaño del tick del instrumento.
Detenga la administración de delegados a StartProtection, recreando el stop-loss generado por la plataforma que MetaTrader adjuntó durante OrderSend.
El código rastrea la última fecha de negociación para evitar volver a enviar el mismo grupo varias veces dentro del mismo día, algo que podría suceder en períodos de tiempo inferiores a una hora en el EA original.
Amplios comentarios en línea aclaran cada paso del flujo de trabajo para futuros mantenimiento o experimentación.
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>
/// Time-based breakout strategy converted from the "Strategy of Regularities of Exchange Rates" MQL expert advisor.
/// At a scheduled hour captures reference price, then enters on breakout above/below offset levels.
/// Exits at a closing hour or on take-profit/stop-loss hit.
/// </summary>
public class RegularitiesOfExchangeRatesStrategy : Strategy
{
private readonly StrategyParam<int> _openingHour;
private readonly StrategyParam<int> _closingHour;
private readonly StrategyParam<decimal> _entryOffsetPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _dummySma;
private decimal _pointSize;
private DateTime? _lastEntryDate;
private decimal _referencePrice;
private decimal _entryPrice;
private bool _waitingForBreakout;
public int OpeningHour
{
get => _openingHour.Value;
set => _openingHour.Value = value;
}
public int ClosingHour
{
get => _closingHour.Value;
set => _closingHour.Value = value;
}
public decimal EntryOffsetPoints
{
get => _entryOffsetPoints.Value;
set => _entryOffsetPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public RegularitiesOfExchangeRatesStrategy()
{
_openingHour = Param(nameof(OpeningHour), 9)
.SetDisplay("Opening Hour", "Hour (0-23) when breakout levels are set", "Schedule")
.SetRange(0, 23);
_closingHour = Param(nameof(ClosingHour), 2)
.SetDisplay("Closing Hour", "Hour (0-23) when the strategy exits", "Schedule")
.SetRange(0, 23);
_entryOffsetPoints = Param(nameof(EntryOffsetPoints), 20m)
.SetDisplay("Entry Offset (points)", "Distance from reference price for breakout", "Orders")
.SetGreaterThanZero();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
.SetDisplay("Take Profit (points)", "Profit target distance in points", "Risk")
.SetNotNegative();
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetDisplay("Stop Loss (points)", "Stop-loss distance in points", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to evaluate trading hours", "General");
}
protected override void OnReseted()
{
base.OnReseted();
_pointSize = 0m;
_lastEntryDate = null;
_referencePrice = 0m;
_entryPrice = 0m;
_waitingForBreakout = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointSize = Security?.PriceStep ?? 0.01m;
if (_pointSize <= 0m)
_pointSize = 0.01m;
_dummySma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_dummySma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.OpenTime.Hour;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
// At closing hour: flatten position and cancel breakout watch
if (hour == ClosingHour)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_waitingForBreakout = false;
_entryPrice = 0m;
}
// Manage take-profit and stop-loss for existing position
if (Position != 0 && _entryPrice > 0m)
{
var tp = TakeProfitPoints * _pointSize;
var sl = StopLossPoints * _pointSize;
if (Position > 0)
{
if ((tp > 0m && close - _entryPrice >= tp) || (sl > 0m && _entryPrice - close >= sl))
{
SellMarket(Position);
_entryPrice = 0m;
_waitingForBreakout = false;
}
}
else if (Position < 0)
{
if ((tp > 0m && _entryPrice - close >= tp) || (sl > 0m && close - _entryPrice >= sl))
{
BuyMarket(-Position);
_entryPrice = 0m;
_waitingForBreakout = false;
}
}
}
// At opening hour: set reference price for breakout
if (hour == OpeningHour && Position == 0)
{
var date = candle.OpenTime.Date;
if (!_lastEntryDate.HasValue || _lastEntryDate.Value != date)
{
_referencePrice = close;
_waitingForBreakout = true;
_lastEntryDate = date;
}
}
// Check for breakout entry
if (_waitingForBreakout && Position == 0 && _referencePrice > 0m)
{
var offset = EntryOffsetPoints * _pointSize;
var buyLevel = _referencePrice + offset;
var sellLevel = _referencePrice - offset;
if (high >= buyLevel)
{
BuyMarket();
_entryPrice = close;
_waitingForBreakout = false;
}
else if (low <= sellLevel)
{
SellMarket();
_entryPrice = close;
_waitingForBreakout = false;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class regularities_of_exchange_rates_strategy(Strategy):
def __init__(self):
super(regularities_of_exchange_rates_strategy, self).__init__()
self._opening_hour = self.Param("OpeningHour", 9) \
.SetDisplay("Opening Hour", "Hour (0-23) when breakout levels are set", "Schedule")
self._closing_hour = self.Param("ClosingHour", 2) \
.SetDisplay("Closing Hour", "Hour (0-23) when the strategy exits", "Schedule")
self._entry_offset_points = self.Param("EntryOffsetPoints", 20.0) \
.SetDisplay("Entry Offset (points)", "Distance from reference price for breakout", "Orders")
self._take_profit_points = self.Param("TakeProfitPoints", 20.0) \
.SetDisplay("Take Profit (points)", "Profit target distance in points", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 500.0) \
.SetDisplay("Stop Loss (points)", "Stop-loss distance in points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe used to evaluate trading hours", "General")
self._point_size = 0.01
self._last_entry_date = None
self._reference_price = 0.0
self._entry_price = 0.0
self._waiting_for_breakout = False
@property
def OpeningHour(self):
return self._opening_hour.Value
@property
def ClosingHour(self):
return self._closing_hour.Value
@property
def EntryOffsetPoints(self):
return self._entry_offset_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(regularities_of_exchange_rates_strategy, self).OnStarted2(time)
self._point_size = 0.01
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps > 0:
self._point_size = ps
self._dummy_sma = SimpleMovingAverage()
self._dummy_sma.Length = 2
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._dummy_sma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# At closing hour: flatten position and cancel breakout watch
if hour == self.ClosingHour:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._waiting_for_breakout = False
self._entry_price = 0.0
# Manage take-profit and stop-loss for existing position
if self.Position != 0 and self._entry_price > 0:
tp = float(self.TakeProfitPoints) * self._point_size
sl = float(self.StopLossPoints) * self._point_size
if self.Position > 0:
if (tp > 0 and close - self._entry_price >= tp) or (sl > 0 and self._entry_price - close >= sl):
self.SellMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._waiting_for_breakout = False
elif self.Position < 0:
if (tp > 0 and self._entry_price - close >= tp) or (sl > 0 and close - self._entry_price >= sl):
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._waiting_for_breakout = False
# At opening hour: set reference price for breakout
if hour == self.OpeningHour and self.Position == 0:
candle_date = candle.OpenTime.Date
if self._last_entry_date is None or self._last_entry_date != candle_date:
self._reference_price = close
self._waiting_for_breakout = True
self._last_entry_date = candle_date
# Check for breakout entry
if self._waiting_for_breakout and self.Position == 0 and self._reference_price > 0:
offset = float(self.EntryOffsetPoints) * self._point_size
buy_level = self._reference_price + offset
sell_level = self._reference_price - offset
if high >= buy_level:
self.BuyMarket()
self._entry_price = close
self._waiting_for_breakout = False
elif low <= sell_level:
self.SellMarket()
self._entry_price = close
self._waiting_for_breakout = False
def OnReseted(self):
super(regularities_of_exchange_rates_strategy, self).OnReseted()
self._point_size = 0.01
self._last_entry_date = None
self._reference_price = 0.0
self._entry_price = 0.0
self._waiting_for_breakout = False
def CreateClone(self):
return regularities_of_exchange_rates_strategy()