Esta estratégia StockSharp é uma conversão C# fiel do consultor especialista MetaTrader 4 Strategy_of_Regularities_of_Exchange_Rates.mq4. O sistema foi projetado como um breakout straddle diário: ele coloca o mercado entre ordens de stop quando chega uma hora específica e mantém essas ordens ativas até a hora de fechamento noturno. Qualquer posição preenchida é supervisionada por um stop-loss do lado da corretora e um watchdog intradiário de take-profit para que as negociações não se prolonguem além da sessão de negociação definida.
Ao contrário dos sistemas baseados em indicadores, a lógica centra-se apenas no tempo e na distância. Quando o cronograma diz que o mercado deve estar pronto, a estratégia mede um deslocamento fixo em pontos de corretagem (pips) da oferta atual e da oferta de venda e coloca um par de ordens de stop simétricas. O código adapta automaticamente o cálculo do ponto para símbolos com aspas de 3 ou 5 dígitos, correspondendo ao comportamento da versão original MQL.
Lógica de negociação
Horário de abertura – assim que uma vela concluída reporta OpeningHour, a estratégia cancela quaisquer ordens pendentes restantes e envia um stop de compra acima do pedido atual e um stop de venda abaixo do lance atual. A distância é EntryOffsetPoints * point, onde o valor point é derivado do instrumento PriceStep e ajustado para cotações fracionárias.
Ordens de proteção – imediatamente após a inicialização a estratégia habilita StartProtection com o StopLossPoints configurado. Qualquer negociação executada recebe, portanto, um stop loss do lado da corretora idêntico ao EA original.
Supervisão de lucro – em cada vela concluída, o algoritmo verifica se o lucro atual excede TakeProfitPoints * point. Nesse caso, fecha a posição no mercado. Isso reflete o loop OrderClose original que saiu quando o lucro atingiu o limite.
Hora de fechamento – quando o relógio chega a ClosingHour, a estratégia fecha à força todas as posições abertas e cancela as ordens de stop, garantindo que o livro esteja estável para a próxima sessão.
Reinicialização diária – um novo lote de ordens pendentes é enviado apenas uma vez por dia de negociação, evitando duplicatas e ainda respeitando a intenção original de uma única configuração por sessão.
Parâmetros
Parâmetro
Padrão
Descrição
OpeningHour
9
Hora (0–23) em que o par de ordens stop é colocado.
ClosingHour
2
Hora (0–23) em que as ordens pendentes são removidas e quaisquer negociações abertas são estabilizadas.
EntryOffsetPoints
20
Distância em pontos da corretora desde o bid/ask atual até as ordens stop.
TakeProfitPoints
20
Meta de lucro em pontos de corretagem que desencadeia uma saída do mercado. Defina como 0 para desativar o lucro manual.
StopLossPoints
500
Distância em pontos intermediários para a parada de proteção anexada via StartProtection.
OrderVolume
0.1
Volume de cada ordem de parada.
CandleType
30 minute time frame
Série de velas usada para avaliar o cronograma. Qualquer período ≤ 1 hora mantém o comportamento consistente com o script MQL.
Notas de conversão
O consultor especialista original trabalhou em eventos de tick e referenciou Hour() diretamente. Em StockSharp a estratégia escuta as velas finalizadas e usa seu horário de abertura, o que preserva a lógica de uma vez por hora enquanto permanece dentro das diretrizes do repositório sobre os estados das velas.
As ordens pendentes são normalizadas com Security.ShrinkPrice para que os preços gerados sempre correspondam ao tamanho do tick do instrumento.
O gerenciamento de parada delega para StartProtection, recriando o stop-loss gerado pela plataforma que MetaTrader anexou durante OrderSend.
O código rastreia a última data de negociação para evitar o reenvio da mesma faixa várias vezes no mesmo dia, algo que poderia acontecer em períodos inferiores a uma hora no EA original.
Extensos comentários embutidos esclarecem cada etapa do fluxo de trabalho para manutenção ou experimentação futura.
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()