跨平台智能交易系统: 时间过滤器(基础篇)
📘

跨平台智能交易系统: 时间过滤器(基础篇)

第 1/3 篇

「用时间过滤器管住EA的开仓窗口」

在 MT5 里跑跨平台智能交易系统,最容易被忽略的是「什么时候允许下单」。不少策略回测漂亮,实盘却在流动性极差的非主流时段连续吃滑点,根源就是没给 EA 加时间约束。 MQL5 标准库提供 CTimeFilter 作为时间过滤器基类,开发者可派生子类实现具体时段逻辑,再用容器类统一管理多个子过滤器。一个过滤器返回 false,EA 就该停止开仓。 官方示例文章《跨平台智能交易系统: 时间过滤器》发布于 2017-08-28 07:48,截至统计已获 7071 次查看、7 条评论,说明这是实盘 EA 基建里高频被翻出来的老文。外汇与贵金属杠杆高、时段波动分化大,错误时段触发信号可能放大亏损风险。 打开 MT5 的 MetaEditor,搜 CTimeFilter 能看到标准库实现;先确认你的 EA 是否在任何 Hour 都发单,再决定要挂几个子过滤器。

给EA装上时间开关

时间过滤的本质,是让智能交易系统先判断“当前时刻是否落在你定义的时间段内”,再决定某些功能开不开。它不依赖行情,只依赖时钟,所以适合做周期性的启用/停用:比如横盘或高波动时段直接规避,给订单设一个到期时刻到点离场,或者交易周结束前统一平仓。 实际用得最多的就是三类:避坑特定时段、给持仓挂“到点退场”、周末清仓。当然变体很多,核心逻辑只有一个——先判定时间,再放行动作。 这套思路在 MQL4 和 MQL5 里都能落地,不挑平台。写好一个时间过滤器,你的 EA 就能轻松叠加多个时段规则,而不是把所有逻辑写死在全时段跑。外汇与贵金属杠杆高、跳空频繁,用时间过滤砍掉高风险窗口,是性价比极高的第一道防线。

◍ 时间过滤器的基类骨架

在 MT5 智能交易系统里,所有时间过滤器对象都从同一个基类派生。这里把 CTimeBase 作为统一入口,它继承自 CObject,后续具体的时段、周期间过滤类只要覆写 Evaluate 就能挂进同一套调度逻辑。 基类内部用 m_active 控制对象是否参与判断,设成 false 就直接跳过;m_reverse 负责把输出取反,比如原逻辑在某时段返回 false,打开反转后同一时段会返回 true。这两个布尔成员让你在 OnInit 里就能静态配置行为,不用改继承类的代码。 下面这段声明值得直接抄进你的 include 头文件。注意 Evaluate 是纯虚函数(=0),意味着基类本身不可实例化,强制子类给出具体的 datetime 判断实现;而 Type 固定返回 CLASS_TYPE_TIME,方便事件聚合器在运行时做类型识别。 别把 m_active 当摆设 很多自建 EA 把开关写死在代码里,其实留一个运行时可写的 Active() 接口,能在不重编译的情况下用外部脚本禁用某类过滤器,排查「为什么今天没下单」会快很多。外汇与贵金属杠杆高,逻辑误判可能瞬间放大亏损,开关留痕比硬改代码稳。

MQL5 / C++
class CTimeBase : class="kw">public CObject
  {
class="kw">protected:
   class="type">bool              m_active;
   class="type">bool              m_reverse;
   CSymbolManager   *m_symbol_man;
   CEventAggregator *m_event_man;
   CObject          *m_container;
class="kw">public:
                     CTimeBase(class="type">void);
                    ~CTimeBase(class="type">void);
   class="kw">virtual class="type">int       Type(class="type">void) const {class="kw">return CLASS_TYPE_TIME;}
   class=class="str">"cmt">//--- 初始化
   class="kw">virtual class="type">bool      Init(CSymbolManager*,CEventAggregator*);
   class="kw">virtual CObject *GetContainer(class="type">void);
   class="kw">virtual class="type">void      SetContainer(CObject*);
   class="kw">virtual class="type">bool      Validate(class="type">void);
   class=class="str">"cmt">//--- 赋值与取值
   class="type">bool              Active(class="type">void) const;
   class="type">void              Active(const class="type">bool);
   class="type">bool              Reverse(class="type">void);
   class="type">void              Reverse(const class="type">bool);
   class=class="str">"cmt">//--- 检查
   class="kw">virtual class="type">bool      Evaluate(class="type">class="kw">datetime)=class="num">0;
   };

「EA 里四种时间过滤的落地写法」

做价格行为策略时,时间窗口往往比信号本身更决定回撤。MT5 里把时间过滤拆成四类封装,能直接拿去卡掉不该交易的那段行情:固定日期区间、周内某几天、计时器到期、日内小时表。 按日期区间过滤最简单,CTimeRange 在构造时吃进开始和结束 datetime,Evaluate 不传参就用当前时间比大小,落在中间返回 true。注意若起止为 0 或未设,方法自动退回到当前时间,容易在回测里悄悄放行,实盘前要在 OnTesterInit 打一行 Print 确认 m_begin/m_end 不为 0。 周内过滤推荐用 MqlDateTime.day_of_week 做位标志,而不是自己写 TimeDayOfWeek(MQL5 没有)。CTimeDays 用 long 成员存 7 天标志,Monday() 这类方法单独翻转某一天,避免一次性 Set 七位写错。典型诉求如“周五不出单”,就是把 FRIDAY 位清零。 CTimer 是拿 UNIX 纪元(1970-01-01 00:00 GMT)做基准的到期锁。构造传入年/月/日/时/分/秒换算出的 m_total 秒数,Evaluate 算“当前 - 开始时间”是否超 m_total。它和 EventSetTimer 不同,后者全 EA 只能挂一个事件,所以 CTimer 只在需要额外倒计时(比如二元式到期平仓)时才用。 日内表 CTimeFilter 最常用也最坑:比较不含结束那一秒,设 08:00–17:00 实际最后成交停在 16:59。跨天情形(如 17:00–08:00)要判开始>结束,把结束+1天或开始-1天。若起止都设 05:00,代码视其为跨全天 24 小时全放行。外汇和贵金属杠杆高,错误时间窗会在低流动性时段放单,滑点可能翻倍,务必在策略测试器用 2023 年 1 月数据跑一遍跨天分支。

MQL5 / C++
class CTimeRangeBase : class="kw">public CTime
  {
class="kw">protected:
   class="type">class="kw">datetime                m_begin;
   class="type">class="kw">datetime                m_end;
class="kw">public:
                      CTimeRangeBase(class="type">void);
                      CTimeRangeBase(class="type">class="kw">datetime,class="type">class="kw">datetime);
                      ~CTimeRangeBase(class="type">void);
   class=class="str">"cmt">//--- 初始化                class="type">class="kw">datetime,class="type">class="kw">datetime
   class="kw">virtual class="type">bool      Set(class="type">class="kw">datetime,class="type">class="kw">datetime);
   class="kw">virtual class="type">bool      Validate(class="type">void);
   class=class="str">"cmt">//--- 赋值与取值
class="type">class="kw">datetime                Begin(class="type">void) const;
   class="type">void                Begin(const class="type">class="kw">datetime);
class="type">class="kw">datetime                End(class="type">void) const;
   class="type">void                End(const class="type">class="kw">datetime);
   class=class="str">"cmt">//--- 处理

用位标志圈定可交易星期

CTimeDaysBase 在 CTime 之上扩展出按星期过滤的能力,核心是一个 long 类型的 m_day_flags,用每一位记录周日到周六是否允许交易。构造函数默认值已经把周末剔除:sun=false、sat=false,周一到周五为 true,这和外盘贵金属、外汇在周六周日停盘的实际节奏一致。 Evaluate(datetime) 会先取当前时间(默认参数 current=0 时调用 TimeCurrent()),再换算出对应星期,去 m_day_flags 里查那一位是否为 1。如果你做伦敦金或欧美盘自动化,直接用这个默认配置就能避开无流动性的周末跳空,但遇上周五尾盘提前休市,仍要自己补一层时间区间判断。 下面这段是时间区间基类的 Evaluate 实现,能看清「失效即放行」和「反向区间」两个坑: virtual bool Evaluate(datetime); }; bool CTimeRangeBase::Evaluate(datetime current=0) { if(!Active()) return true; if(current==0) current=TimeCurrent(); bool result=current>=m_begin && current<m_end; return Reverse()?!result:result; } 逐行拆解:Active() 返回 false 时直接 return true,意味着过滤器未激活就不拦截任何 tick;current 为 0 则取服务器当前时间;result 是「时间落在 [m_begin, m_end) 半开区间」的真值;Reverse() 为 true 时取反,用来做「仅在该区间外交易」的反向逻辑。外汇和贵金属杠杆高,这类时间过滤器写错一个符号,可能在非农夜误触发,务必在策略测试器里用 2023 年 1 月至今的实盘数据跑一遍。

MQL5 / C++
class="kw">virtual class="type">bool Evaluate(class="type">class="kw">datetime);
};
class="type">bool CTimeRangeBase::Evaluate(class="type">class="kw">datetime current=class="num">0)
{
 if(!Active())
   class="kw">return true;
 if(current==class="num">0)
   current=TimeCurrent();
 class="type">bool result=current>=m_begin && current<m_end;
 class="kw">return Reverse()?!result:result;
}

◍ 用位掩码把交易日过滤写进类里

上面这段把「星期几允许交易」做成了位标志枚举,每位代表一天,Sunday 占 bit0、Monday 占 bit1,一直到 Saturday 占 bit6。默认 Set() 的参数是周一到周五为 true、周日周六为 false,也就是常见的工作日交易窗口。 Monday(const bool set) 里用 m_day_flags|=TIME_DAY_FLAG_MON 置位、用 m_day_flags &=~TIME_DAY_FLAG_MON 清位,这种写法比用七个 bool 变量省内存,也方便一次按位或判断。 Evaluate() 是核心:若 Active() 为假直接返回 true(不过滤),否则把当前时间(默认 TimeCurrent())拆成 MqlDateTime,用 day_of_week 做 switch 查当天是否被允许。Reverse() 为真时把结果取反,相当于「只允许周末跑」的反向模式。外汇和贵金属周末流动性极差、点差可能飙升,这种开关在高风险品种上要谨慎验证。 CTimerBase 继承自 CTime,底下挂了 m_years 到 m_minutes 五个 uint 成员,看名字是给「每隔 N 年/月/日/时/分触发」用的底层计数器。你在 MT5 里建个 EA 把这段贴进去,改 Set() 默认值就能快速切换交易日程。

MQL5 / C++
class="type">void                Friday(const class="type">bool);
 class="type">bool                Saturday(class="type">void) const;
 class="type">void                Saturday(const class="type">bool);
};
enum ENUM_TIME_DAY_FLAGS
 {
 TIME_DAY_FLAG_SUN=class="num">1<<class="num">0,
 TIME_DAY_FLAG_MON=class="num">1<<class="num">1,
 TIME_DAY_FLAG_TUE=class="num">1<<class="num">2,
 TIME_DAY_FLAG_WED=class="num">1<<class="num">3,
 TIME_DAY_FLAG_THU=class="num">1<<class="num">4,
 TIME_DAY_FLAG_FRI=class="num">1<<class="num">5,
 TIME_DAY_FLAG_SAT=class="num">1<<class="num">6
 };
class="type">void CTimeDaysBase::Set(const class="type">bool sun=false,const class="type">bool mon=true,const class="type">bool tue=true,const class="type">bool wed=true,
const class="type">bool thu=true,const class="type">bool fri=true,const class="type">bool sat=false)
 {
 Sunday(sun);
 Monday(mon);
 Tuesday(tue);
 Wednesday(wed);
 Thursday(thu);
 Friday(fri);
 Saturday(sat);
}

class="type">void CTimeDaysBase::Monday(const class="type">bool set)
 {
 if(set)
    m_day_flags|=TIME_DAY_FLAG_MON;
 else
    m_day_flags &=~TIME_DAY_FLAG_MON;
 }
class="type">bool CTimeDaysBase::Evaluate(class="type">class="kw">datetime current=class="num">0)
 {
 if(!Active())
   class="kw">return true;
 class="type">bool result=false;
 class="type">MqlDateTime time;
 if(current==class="num">0)
   current=TimeCurrent();
 TimeToStruct(current,time);
 class="kw">switch(time.day_of_week)
   {
    case class="num">0: result=Sunday();     break;
    case class="num">1: result=Monday();     break;
    case class="num">2: result=Tuesday();   break;
    case class="num">3: result=Wednesday(); break;
    case class="num">4: result=Thursday();   break;
    case class="num">5: result=Friday();     break;
    case class="num">6: result=Saturday();   break;
   }
 class="kw">return Reverse()?!result:result;
 }
class CTimerBase : class="kw">public CTime
 {
class="kw">protected:
 class="type">uint                m_years;
 class="type">uint                m_months;
 class="type">uint                m_days;
 class="type">uint                m_hours;
 class="type">uint                m_minutes;

常见问题

在EA初始化时设定允许交易的起止小时,只在白盘指定区间返回true,其余时段直接跳过OrderSend逻辑即可。
先写一个时间过滤基类,提供虚方法检查当前时间是否合规,再让具体过滤器继承并重写判断条件。
可以,把EA代码贴给小布,它能指出时间窗口冲突、时区误用等隐患,并给出修改建议。
给星期一到日各分配1/2/4/8/16/32/64的位值,用按位或组合目标日,判断时拿当前星期位与掩码做按位与。
若派生类忘了重写,基类默认可能放行全部时间,导致EA在所有时段都可能触发开仓,失去过滤意义。