在 GitHub 上查看

AutoTrading Scheduler 策略

概述

AutoTrading Scheduler 策略移植自 EarnForex 的 MetaTrader 智能交易系统,用于自动控制平台的 “AutoTrading” 开关。StockSharp 版本根据用户定义的周计划决定何时允许交易:当时间处于开放窗口内时保持自动交易开启,离开窗口则禁止交易、撤销所有挂单,并根据设置平掉当前仓位,同时通过 AddInfoLog 记录操作。

策略本身不会生成买卖信号,而是作为调度器与其他交易逻辑配合使用。每个交易日对应一个文本参数,里面可以写多个允许交易的时间段。

原始逻辑

  • 从配置文件读取带有多个时间段的周计划。
  • 支持在本地时间与服务器(交易所)时间之间切换。
  • 使用定时器每秒检查一次当前时间是否仍在允许区间内。
  • 如果当前时间不属于当天的任何区间,则关闭 AutoTrading,并可选地关闭所有仓位与挂单。
  • 当时间重新进入允许区间时再次打开 AutoTrading。

实现细节

  • StockSharp 版本把解析后的时间段缓存在内存中,一旦用户修改任意文本参数就重新解析。
  • 时间段支持多种写法:9-1209:30-16:0021.15-23.45 等。分钟部分可省略,默认按 00 处理。多个时间段之间使用逗号分隔。
  • 如果结束时间写成 00:00,则该区间持续到午夜(例如 22-0 表示 22:00:00 至 23:59:59)。写成 0-0 则表示整天允许交易。
  • 当结束时间小于开始时间时,区间自动跨越午夜,这与原始 EA 的行为保持一致。
  • 定时器每 5 秒触发一次,兼顾响应速度和资源消耗。

参数

名称 类型 默认值 说明
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;
	}
}