Открыть на GitHub

Стратегия AutoTrading Scheduler

Обзор

AutoTrading Scheduler — это порт советника EarnForex, управляющего переключателем «AutoTrading» в MetaTrader. Версия для StockSharp контролирует, чтобы торговля велась только в разрешённые промежутки времени. Как только текущее время выходит за допустимые границы, стратегия отключает автоторговлю, отменяет активные заявки, при необходимости закрывает позиции и фиксирует событие через AddInfoLog.

Стратегия не генерирует торговые сигналы. Её задача — следить за состоянием автоторговли и синхронизировать его с недельным расписанием, заданным пользователем. Для каждого дня недели используется отдельная строка с интервалами.

Логика оригинала

  • Загружает расписание с несколькими диапазонами времени на каждый день недели.
  • Поддерживает выбор между локальным временем компьютера и временем сервера брокера.
  • Каждую секунду проверяет, входит ли текущее время в разрешённые интервалы.
  • Если время попадает вне всех диапазонов текущего дня, выключает AutoTrading и может попытаться закрыть все позиции и отложенные ордера.
  • Вновь включает AutoTrading, когда время возвращается в любой из допустимых интервалов.

Особенности реализации

  • Порт для StockSharp хранит разобранное расписание в памяти и пересобирает его при каждом изменении текстовых параметров.
  • Интервалы допускают разные форматы: 9-12, 09:30-16:00, 21.15-23.45. Минуты можно опустить — тогда берётся 00. Несколько диапазонов разделяются запятыми.
  • Если конец диапазона равен 00:00, то разрешение действует до полуночи (например, 22-0 означает 22:00:00–23:59:59). Запись 0-0 оставляет автоторговлю включённой на весь день.
  • Если конец меньше начала, диапазон автоматически переносится через полночь, что соответствует поведению оригинала.
  • Таймер срабатывает каждые пять секунд — этого достаточно для оперативности и экономии ресурсов.

Параметры

Имя Тип Значение по умолчанию Описание
SchedulerEnabled bool false Главный переключатель расписания. При false стратегия не вмешивается в торговлю.
ReferenceClock TimeReference Local Выбор часовой базы: локальное время или серверное (биржевое).
ClosePositionsBeforeDisable bool true Перед отключением автоторговли отменяет активные заявки и закрывает позицию.
MondaySchedule string "" Интервалы для понедельника.
TuesdaySchedule string "" Интервалы для вторника.
WednesdaySchedule string "" Интервалы для среды.
ThursdaySchedule string "" Интервалы для четверга.
FridaySchedule string "" Интервалы для пятницы.
SaturdaySchedule string "" Интервалы для субботы.
SundaySchedule string "" Интервалы для воскресенья.

Формат расписания единый для всех дней. Пример: "09-12, 13:30-17:45, 22-0".

Как использовать

  1. Привяжите стратегию к нужному инструменту или портфелю.
  2. Заполните интервалы для тех дней, когда автоторговля должна быть включена. Пустая строка означает полный запрет на торговлю в этот день.
  3. Включите расписание (SchedulerEnabled = true).
  4. Настройте поведение при остановке через ClosePositionsBeforeDisable.
  5. Следите за логом: каждое переключение сопровождается записью с указанием причины («окно открылось» или «окно закрылось»).

Когда текущее время входит в разрешённый диапазон, свойство IsAutoTradingEnabled равно true. Вне допустимых интервалов свойство переходит в false, стратегия отменяет заявки, при необходимости закрывает позицию и протоколирует действие.

Ограничения

  • Стратегия управляет только тем инструментом, к которому она привязана. Для мульти-символьных портфелей потребуется несколько экземпляров или координирующий модуль.
  • Интервал таймера (TimeSpan.FromSeconds(5)) можно изменить в исходном коде при необходимости более точной реактивности.
  • Расписание не сохраняется на диск — используйте механизмы сохранения параметров в вашем приложении, если требуется постоянство.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// AutoTrading Scheduler strategy: Momentum indicator crossover.
/// Buys when Momentum crosses above 100, sells when crosses below 100.
/// </summary>
public class AutoTradingSchedulerStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<decimal> _momentumLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevMom;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public decimal MomentumLevel { get => _momentumLevel.Value; set => _momentumLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public AutoTradingSchedulerStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_momentumPeriod = Param(nameof(MomentumPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Momentum period", "Indicators");
		_momentumLevel = Param(nameof(MomentumLevel), 101m)
			.SetDisplay("Momentum Level", "Momentum threshold", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMom = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevMom = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var momentum = new Momentum { Length = MomentumPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(momentum, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal momValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevMom < MomentumLevel && momValue >= MomentumLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevMom > 200m - MomentumLevel && momValue <= 200m - MomentumLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevMom = momValue;
		_hasPrev = true;
	}
}