Открыть на GitHub

Стратегия Open Time

Обзор

Стратегия повторяет логику советника MetaTrader «OpenTime». Она выставляет рыночные заявки в заданный момент времени, при необходимости закрывает позиции в отдельном окне выхода и применяет простые правила управления риском: фиксированный стоп-лосс, тейк-профит и трейлинг-стоп. Портирование выполнено на высокоуровневом API StockSharp, что упрощает использование стратегии совместно с другими компонентами платформы.

Принцип работы

  1. После подписки на свечи выбранного таймфрейма стратегия обрабатывает только закрытые свечи.
  2. Если текущее время попадает в торговое окно, отправляются рыночные заявки для всех разрешённых направлений:
    • При включённом одном направлении стратегия увеличивает существующую позицию или разворачивается, пока не будет достигнут целевой объём.
    • При включённых покупках и продажах заявки отправляются последовательно в рамках одного окна. В Netting-модели StockSharp вторая заявка сначала закрывает противоположную позицию, а затем открывает новую.
  3. Во время окна закрытия вызывается ClosePosition() — это гарантирует однократное принудительное закрытие текущего объёма.
  4. Значения стоп-лосса, тейк-профита и трейлинг-стопа передаются в 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;
	}
}