在 GitHub 上查看
AutoTrading Scheduler 策略
概述
AutoTrading Scheduler 策略移植自 EarnForex 的 MetaTrader 智能交易系统,用于自动控制平台的 “AutoTrading” 开关。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 则表示整天允许交易。
- 当结束时间小于开始时间时,区间自动跨越午夜,这与原始 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"。
使用方法
- 将策略附加到目标证券或投资组合。
- 为需要交易的日期填写一个或多个时间段;留空表示当天禁止自动交易。
- 把
SchedulerEnabled 设为 true 以启动调度器。
- 根据需要调整
ClosePositionsBeforeDisable,决定是否在禁用时自动平仓。
- 观察日志输出:每次状态变化都会记录原因(进入或离开时间窗口)。
当时间位于允许区间时,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;
}
}
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.Indicators import Momentum
from StockSharp.Algo.Strategies import Strategy
class auto_trading_scheduler_strategy(Strategy):
def __init__(self):
super(auto_trading_scheduler_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._momentum_period = self.Param("MomentumPeriod", 20)
self._momentum_level = self.Param("MomentumLevel", 101.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._prev_mom = 0.0
self._candles_since_trade = 4
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MomentumPeriod(self):
return self._momentum_period.Value
@MomentumPeriod.setter
def MomentumPeriod(self, value):
self._momentum_period.Value = value
@property
def MomentumLevel(self):
return self._momentum_level.Value
@MomentumLevel.setter
def MomentumLevel(self, value):
self._momentum_level.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(auto_trading_scheduler_strategy, self).OnReseted()
self._prev_mom = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
def OnStarted2(self, time):
super(auto_trading_scheduler_strategy, self).OnStarted2(time)
self._prev_mom = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
momentum = Momentum()
momentum.Length = self.MomentumPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(momentum, self._process_candle).Start()
def _process_candle(self, candle, mom_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
mom_val = float(mom_value)
if self._has_prev:
if self._prev_mom < self.MomentumLevel and mom_val >= self.MomentumLevel and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif self._prev_mom > (200.0 - self.MomentumLevel) and mom_val <= (200.0 - self.MomentumLevel) and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
self._prev_mom = mom_val
self._has_prev = True
def CreateClone(self):
return auto_trading_scheduler_strategy()