Esta estratégia recria o clássico expert MetaTrader "Pending orders by time" para StockSharp. Funciona com uma agenda discreta: todos os dias coloca ordens stop simétricas ao redor do mercado quando uma nova hora de sessão começa, e limpa todas as ordens mais as posições abertas em uma hora de fechamento especificada. A implementação mantém as entradas originais baseadas em pips, converte-as em unidades de preço nativas e usa a API de alto nível para gerenciar o risco.
Como funciona
Gatilho baseado em tempo – Quando uma vela que termina na hora de abertura configurada é recebida, a estratégia envia um buy stop acima do ask e um sell stop abaixo do bid. Ambas as ordens são deslocadas pelo parâmetro Distance (pips) convertido em unidades de preço.
Ordens protetoras – StartProtection anexa automaticamente proteção de stop-loss e take-profit usando as distâncias em pips definidas nos parâmetros. ManageRisk também atua como salvaguarda, fechando qualquer posição residual se uma vela completada mostrar que os limites foram cruzados.
Encerramento de sessão – Quando a hora de fechamento chega, a estratégia cancela quaisquer ordens pendentes restantes e sai forcosamente de operações abertas independentemente do lucro ou perda. Isso reproduz o comportamento do expert original de redefinir no final da sessão.
Tamanho de pip com reconhecimento de dígitos – O multiplicador de pip emula a implementação MetaTrader multiplicando o passo de preço por dez para símbolos cotados com três ou cinco casas decimais (p. ex., JPY ou pares FX de 5 dígitos). Isso mantém as entradas legadas consistentes entre brokers.
O tipo de vela padrão são barras de 30 minutos para permanecer abaixo da restrição original de períodos menores que H1. Qualquer outro período pode ser usado, desde que os registros de hora resultantes correspondam às horas de sessão desejadas.
Parâmetros
Nome
Descrição
Padrão
Opening Hour
Hora (0-23) quando a estratégia colocará o par de ordens stop.
9
Closing Hour
Hora (0-23) quando todas as ordens são canceladas e posições são fechadas.
2
Distance (pips)
Deslocamento, em pips, entre o preço atual e as entradas stop pendentes.
20
Stop Loss (pips)
Distância em pips para o stop protetor assim que uma posição estiver aberta.
20
Take Profit (pips)
Distância em pips para o alvo de lucro assim que uma posição estiver aberta.
500
Order Volume
Quantidade usada ao colocar cada ordem stop pendente.
0.1
Candle Type
Período que impulsiona a agenda horária.
Período de 30 minutos
Todos os parâmetros podem ser otimizados. As entradas baseadas em pips são convertidas internamente usando o passo de preço do instrumento para que permaneçam portáteis entre símbolos FX com diferente precisão decimal.
Fluxo de trabalho diário
A cada fechamento de vela a estratégia verifica se a distância de stop-loss ou take-profit foi atingida. Se sim, fecha a posição ativa a mercado.
Quando a hora de fechamento é alcançada cancela quaisquer ordens pendentes não preenchidas e sai da posição, garantindo que o livro esteja plano antes da próxima sessão.
Quando a hora de abertura é alcançada (e a estratégia está plana) cancela ordens antigas por precaução e envia um novo sell stop abaixo do bid e um buy stop acima do ask. As ordens são espelhadas ao redor do spread para que qualquer rompimento possa ser capturado.
Ao longo da sessão a proteção a nível de plataforma criada por StartProtection mantém um stop-loss e take-profit anexados, agindo imediatamente se a ação do preço intrabar atingir os limites.
Notas de uso
Use instrumentos cujo tamanho de tick representa um único "ponto" para que o ajuste de pip reflita o expert original. Tamanhos de tick exóticos podem exigir ajuste manual dos parâmetros de distância.
A lógica assume um ciclo de trading por dia. Se usar dados intradiários com múltiplas correspondências de abertura/fechamento, ajuste as horas de acordo.
Como todas as ações ocorrem no fechamento da vela, selecione um tamanho de vela que corresponda à frequência com que deseja avaliar a agenda. Por exemplo, velas horárias fornecem a mesma cadência que a versão MetaTrader.
A estratégia só coloca novas ordens pendentes quando a posição está plana, evitando sobreexposição se uma operação de rompimento ainda estiver ativa durante a próxima hora de abertura.
Diferenças da versão MQL
As saídas protetoras são tratadas via StartProtection mais verificações explícitas, aproveitando a API de alto nível do StockSharp em vez da atribuição direta de stop-loss no ticket da ordem pendente.
Os preços bid/ask são lidos de Security.BestBid e Security.BestAsk. Se essas cotações estiverem indisponíveis, o fechamento da vela é usado como referência de fallback.
As ordens de mercado são usadas para liquidar posições na hora de fechamento por simplicidade e para evitar comportamentos específicos do broker.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Places simulated symmetric stop entries at scheduled hours and manages them with daily resets.
/// </summary>
public class PendingOrdersByTimeStrategy : Strategy
{
private readonly StrategyParam<int> _openingHour;
private readonly StrategyParam<int> _closingHour;
private readonly StrategyParam<decimal> _distancePips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<DataType> _candleType;
private decimal _pipSize;
private decimal? _pendingBuyPrice;
private decimal? _pendingSellPrice;
private decimal? _entryPrice;
public int OpeningHour
{
get => _openingHour.Value;
set => _openingHour.Value = value;
}
public int ClosingHour
{
get => _closingHour.Value;
set => _closingHour.Value = value;
}
public decimal DistancePips
{
get => _distancePips.Value;
set => _distancePips.Value = value;
}
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public PendingOrdersByTimeStrategy()
{
_openingHour = Param(nameof(OpeningHour), 2)
.SetDisplay("Opening Hour", "Hour to activate pending orders", "Schedule")
.SetRange(0, 23);
_closingHour = Param(nameof(ClosingHour), 22)
.SetDisplay("Closing Hour", "Hour to cancel orders and flat positions", "Schedule")
.SetRange(0, 23);
_distancePips = Param(nameof(DistancePips), 500m)
.SetDisplay("Distance (pips)", "Offset for entry stop orders", "Orders")
.SetGreaterThanZero();
_stopLossPips = Param(nameof(StopLossPips), 500m)
.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Risk")
.SetGreaterThanZero();
_takeProfitPips = Param(nameof(TakeProfitPips), 2000m)
.SetDisplay("Take Profit (pips)", "Profit target distance", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Working timeframe for the schedule", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_pendingBuyPrice = null;
_pendingSellPrice = null;
_entryPrice = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0.01m;
if (step <= 0m)
return 0.01m;
return step;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.OpenTime.Hour;
// Check pending stop entries
CheckPendingEntries(candle);
// Manage existing position
ManageRisk(candle);
if (hour == ClosingHour)
{
// Closing hour: cancel pending and exit any open trades.
_pendingBuyPrice = null;
_pendingSellPrice = null;
ExitPosition();
}
if (hour == OpeningHour && hour != ClosingHour && Position == 0m && !_pendingBuyPrice.HasValue)
{
// Opening hour: set up new pending entries.
SetupPendingEntries(candle.ClosePrice);
}
}
private void CheckPendingEntries(ICandleMessage candle)
{
if (_pendingBuyPrice is decimal buyPrice && candle.HighPrice >= buyPrice && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = buyPrice;
_pendingBuyPrice = null;
_pendingSellPrice = null;
return;
}
if (_pendingSellPrice is decimal sellPrice && candle.LowPrice <= sellPrice && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = sellPrice;
_pendingBuyPrice = null;
_pendingSellPrice = null;
}
}
private void ManageRisk(ICandleMessage candle)
{
if (_pipSize <= 0m || _entryPrice is not decimal entry)
return;
var takeProfitDistance = TakeProfitPips * _pipSize;
var stopLossDistance = StopLossPips * _pipSize;
if (Position > 0m)
{
if (takeProfitDistance > 0m && candle.HighPrice - entry >= takeProfitDistance)
{
SellMarket();
_entryPrice = null;
return;
}
if (stopLossDistance > 0m && entry - candle.LowPrice >= stopLossDistance)
{
SellMarket();
_entryPrice = null;
}
}
else if (Position < 0m)
{
if (takeProfitDistance > 0m && entry - candle.LowPrice >= takeProfitDistance)
{
BuyMarket();
_entryPrice = null;
return;
}
if (stopLossDistance > 0m && candle.HighPrice - entry >= stopLossDistance)
{
BuyMarket();
_entryPrice = null;
}
}
}
private void ExitPosition()
{
if (Position > 0m)
SellMarket();
else if (Position < 0m)
BuyMarket();
_entryPrice = null;
}
private void SetupPendingEntries(decimal referencePrice)
{
if (_pipSize <= 0m)
return;
var distance = DistancePips * _pipSize;
if (distance <= 0m)
return;
_pendingBuyPrice = referencePrice + distance;
_pendingSellPrice = referencePrice - distance;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class pending_orders_by_time_strategy(Strategy):
"""Places symmetric stop entries at scheduled hours with daily resets and SL/TP management."""
def __init__(self):
super(pending_orders_by_time_strategy, self).__init__()
self._opening_hour = self.Param("OpeningHour", 2) \
.SetDisplay("Opening Hour", "Hour to activate pending orders", "Schedule")
self._closing_hour = self.Param("ClosingHour", 22) \
.SetDisplay("Closing Hour", "Hour to cancel orders and flat positions", "Schedule")
self._distance_pips = self.Param("DistancePips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Distance (pips)", "Offset for entry stop orders", "Orders")
self._stop_loss_pips = self.Param("StopLossPips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 2000.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pips)", "Profit target distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Working timeframe for the schedule", "General")
self._pip_size = 0.0
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = None
@property
def OpeningHour(self):
return int(self._opening_hour.Value)
@property
def ClosingHour(self):
return int(self._closing_hour.Value)
@property
def DistancePips(self):
return float(self._distance_pips.Value)
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _calculate_pip_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.01
if step <= 0:
return 0.01
return step
def OnStarted2(self, time):
super(pending_orders_by_time_strategy, self).OnStarted2(time)
self._pip_size = self._calculate_pip_size()
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
# Check pending stop entries
self._check_pending_entries(candle)
# Manage existing position
self._manage_risk(candle)
if hour == self.ClosingHour:
# Closing hour: cancel pending and exit any open trades
self._pending_buy_price = None
self._pending_sell_price = None
self._exit_position()
if hour == self.OpeningHour and hour != self.ClosingHour and self.Position == 0 and self._pending_buy_price is None:
# Opening hour: set up new pending entries
self._setup_pending_entries(float(candle.ClosePrice))
def _check_pending_entries(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self._pending_buy_price is not None and h >= self._pending_buy_price and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = self._pending_buy_price
self._pending_buy_price = None
self._pending_sell_price = None
return
if self._pending_sell_price is not None and lo <= self._pending_sell_price and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = self._pending_sell_price
self._pending_buy_price = None
self._pending_sell_price = None
def _manage_risk(self, candle):
if self._pip_size <= 0 or self._entry_price is None:
return
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
entry = self._entry_price
tp_dist = self.TakeProfitPips * self._pip_size
sl_dist = self.StopLossPips * self._pip_size
if self.Position > 0:
if tp_dist > 0 and h - entry >= tp_dist:
self.SellMarket()
self._entry_price = None
return
if sl_dist > 0 and entry - lo >= sl_dist:
self.SellMarket()
self._entry_price = None
elif self.Position < 0:
if tp_dist > 0 and entry - lo >= tp_dist:
self.BuyMarket()
self._entry_price = None
return
if sl_dist > 0 and h - entry >= sl_dist:
self.BuyMarket()
self._entry_price = None
def _exit_position(self):
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_price = None
def _setup_pending_entries(self, reference_price):
if self._pip_size <= 0:
return
distance = self.DistancePips * self._pip_size
if distance <= 0:
return
self._pending_buy_price = reference_price + distance
self._pending_sell_price = reference_price - distance
def OnReseted(self):
super(pending_orders_by_time_strategy, self).OnReseted()
self._pip_size = 0.0
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = None
def CreateClone(self):
return pending_orders_by_time_strategy()