Стратегия повторяет логику советника MetaTrader «OpenTime». Она выставляет рыночные заявки в заданный момент времени, при необходимости закрывает позиции в отдельном окне выхода и применяет простые правила управления риском: фиксированный стоп-лосс, тейк-профит и трейлинг-стоп. Портирование выполнено на высокоуровневом API StockSharp, что упрощает использование стратегии совместно с другими компонентами платформы.
Принцип работы
После подписки на свечи выбранного таймфрейма стратегия обрабатывает только закрытые свечи.
Если текущее время попадает в торговое окно, отправляются рыночные заявки для всех разрешённых направлений:
При включённом одном направлении стратегия увеличивает существующую позицию или разворачивается, пока не будет достигнут целевой объём.
При включённых покупках и продажах заявки отправляются последовательно в рамках одного окна. В Netting-модели StockSharp вторая заявка сначала закрывает противоположную позицию, а затем открывает новую.
Во время окна закрытия вызывается ClosePosition() — это гарантирует однократное принудительное закрытие текущего объёма.
Значения стоп-лосса, тейк-профита и трейлинг-стопа передаются в StartProtection, которая управляет защитными заявками с помощью рыночных выходов.
Параметры
Enable Close Window – аналог флага TimeClose. Определяет, нужно ли закрывать позиции в отдельном временном интервале.
Close Position Time – время начала окна закрытия (по умолчанию 20:50).
Trading Time – время начала окна открытия позиций (по умолчанию 18:50).
Window Length – длительность как торгового, так и закрывающего окон (по умолчанию 5 минут; соответствует параметру Duration).
Allow Sell Entries – включает короткие позиции, соответствует параметру Sell (по умолчанию включено).
Allow Buy Entries – включает длинные позиции, соответствует параметру Buy (по умолчанию выключено).
Order Volume – целевой нетто-объём каждой новой сделки (по умолчанию 0.1 лота). При смене направления добавляется модуль текущей позиции, чтобы выполнить разворот одной сделкой.
Stop-Loss Points – расстояние до стоп-лосса в пунктах (0 отключает стоп).
Take-Profit Points – расстояние до тейк-профита в пунктах (0 отключает цель).
Use Trailing Stop – включает логику трейлинг-стопа из функции SimpleTrailing оригинального эксперта.
Trailing Stop Points – базовое расстояние трейлинг-стопа в пунктах (по умолчанию 300).
Trailing Step Points – дополнительное движение цены, необходимое для сдвига трейлинга (по умолчанию 3).
Candle Type – таймфрейм свечей, по которым выполняются проверки времени (по умолчанию одна минута).
Особенности
Размер пункта вычисляется по минимальному шагу цены инструмента. Для инструментов с тремя и пятью десятичными знаками шаг дополнительно умножается на 10, повторяя обработку пипсов в MQL-скрипте.
StartProtection запускается только при наличии положительных дистанций. Если включён только трейлинг-стоп, его значение используется как стартовый защитный уровень.
Попытки повторной отправки заявок и ручная обработка ошибок в портированной версии не нужны: StockSharp сам управляет повторной регистрацией рыночных заявок.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Open Time Daily Window: trades during a specific time window using
/// EMA direction for entry. Closes position at the end of window.
/// </summary>
public class OpenTimeDailyWindowStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _tradeHour;
private readonly StrategyParam<int> _windowMinutes;
private readonly StrategyParam<int> _closeHour;
private decimal _prevEma;
private decimal _entryPrice;
public OpenTimeDailyWindowStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "EMA period for direction.", "Indicators");
_tradeHour = Param(nameof(TradeHour), 10)
.SetDisplay("Trade Hour", "Hour when trading window opens (UTC).", "Schedule");
_windowMinutes = Param(nameof(WindowMinutes), 120)
.SetDisplay("Window Minutes", "Duration of trading window.", "Schedule");
_closeHour = Param(nameof(CloseHour), 20)
.SetDisplay("Close Hour", "Hour to close positions (UTC).", "Schedule");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int TradeHour
{
get => _tradeHour.Value;
set => _tradeHour.Value = value;
}
public int WindowMinutes
{
get => _windowMinutes.Value;
set => _windowMinutes.Value = value;
}
public int CloseHour
{
get => _closeHour.Value;
set => _closeHour.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevEma = 0;
_entryPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.OpenTime.Hour;
var minute = candle.OpenTime.Minute;
var totalMinutes = hour * 60 + minute;
var tradeStart = TradeHour * 60;
var tradeEnd = tradeStart + WindowMinutes;
var closeStart = CloseHour * 60;
var close = candle.ClosePrice;
// Close position at close hour
if (Position != 0 && totalMinutes >= closeStart && totalMinutes < closeStart + 30)
{
if (Position > 0)
SellMarket();
else
BuyMarket();
_entryPrice = 0;
}
if (_prevEma == 0)
{
_prevEma = emaVal;
return;
}
// Trade within window
var inWindow = totalMinutes >= tradeStart && totalMinutes < tradeEnd;
if (Position == 0 && inWindow)
{
var emaRising = emaVal > _prevEma;
var emaFalling = emaVal < _prevEma;
if (emaRising && close > emaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (emaFalling && close < emaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevEma = emaVal;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
class open_time_daily_window_strategy(Strategy):
def __init__(self):
super(open_time_daily_window_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA period for direction", "Indicators")
self._trade_hour = self.Param("TradeHour", 10) \
.SetDisplay("Trade Hour", "Hour when trading window opens", "Schedule")
self._window_minutes = self.Param("WindowMinutes", 120) \
.SetDisplay("Window Minutes", "Duration of trading window", "Schedule")
self._close_hour = self.Param("CloseHour", 20) \
.SetDisplay("Close Hour", "Hour to close positions", "Schedule")
self._prev_ema = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def TradeHour(self):
return self._trade_hour.Value
@property
def WindowMinutes(self):
return self._window_minutes.Value
@property
def CloseHour(self):
return self._close_hour.Value
def OnStarted2(self, time):
super(open_time_daily_window_strategy, self).OnStarted2(time)
self._prev_ema = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
hour = candle.OpenTime.Hour
minute = candle.OpenTime.Minute
total_minutes = hour * 60 + minute
trade_start = self.TradeHour * 60
trade_end = trade_start + self.WindowMinutes
close_start = self.CloseHour * 60
close = float(candle.ClosePrice)
# Close position at close hour
if self.Position != 0 and total_minutes >= close_start and total_minutes < close_start + 30:
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = 0.0
if self._prev_ema == 0:
self._prev_ema = ev
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema = ev
return
# Trade within window
in_window = total_minutes >= trade_start and total_minutes < trade_end
if self.Position == 0 and in_window:
ema_rising = ev > self._prev_ema
ema_falling = ev < self._prev_ema
if ema_rising and close > ev:
self._entry_price = close
self.BuyMarket()
elif ema_falling and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_ema = ev
def OnReseted(self):
super(open_time_daily_window_strategy, self).OnReseted()
self._prev_ema = 0.0
self._entry_price = 0.0
def CreateClone(self):
return open_time_daily_window_strategy()