跨平台智能交易系统: 时间过滤器·综合运用
(3/3)·从基类到复合过滤器,把横盘规避与周末平仓写成可复用代码
◍ 给EA套上时间过滤器的实测改造
把时间过滤器塞进现成EA,最省事的做法是先包含 CTimesBase 头文件,它已经封装了所有过滤器类。声明一个全局指针 CTimes *time_filters,在 OnInit 里 new 一个 CTimes 实例挂上去,后续所有过滤器对象都往这个容器里加。 入场前必须多一道关卡:先平掉上一笔持仓,再调 time_filters.Evaluate(TimeCurrent()) 判断当前是否允许开单。这样写意味着过滤器只拦入场,不碰已经在场的单子离场——老仓位该拿多久拿多久。 日期范围过滤用 datetime 型输入参数,默认0代表UNIX纪元起点。OnInit里加了个保险:只有 time_range_end>0 且大于 start 才建 CTimeRange 对象,避免误用默认值炸锅。 周内按日过滤给7个开关参数,计时器只用一个「到期前总分钟数」参数(默认10080分=一周),内部乘60转成秒存进 m_total。日内过滤最麻烦,用枚举切三种场景:单段同天、跨天、以及多实例(比如绕开午休)。 策略测试器跑2017年1月全月:日期范围设到1月6日周五结束,最后一笔入场停在15:00,之后不再开仓,证明拦截生效。关掉Friday参数后,原1月6日那笔消失,末单回退到1月5日。场景1设08:00–17:00,完整测试里00:00的第一笔被跳过;场景2互换时间,1月3日10:00那笔被拦下;场景3加午休,1月9日12:00平仓后空窗一小时,13:00才恢复。外汇与贵金属杠杆高,回测结论仅代表历史概率,实盘须自担风险。
class="macro">#include "MQLx\Base\OrderManager\OrderManagerBase.mqh" class="macro">#include "MQLx\Base\Signal\SignalsBase.mqh" class="macro">#include "MQLx\Base\Time\TimesBase.mqh" class=class="str">"cmt">//加入包含行 class="macro">#include <Indicators\Custom.mqh> CTimes *time_filters; time_filters = new CTimes(); if(signals.CheckOpenLong()) { close_last(); if (time_filters.Evaluate(TimeCurrent())) { class=class="str">"cmt">//Print("入场买入.."); money_manager.Selected(class="num">0); order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask()); } } else if(signals.CheckOpenShort()) { close_last(); if (time_filters.Evaluate(TimeCurrent())) { class=class="str">"cmt">//Print("入场卖出.."); money_manager.Selected(class="num">1); order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid()); } } class="kw">input class="type">bool time_range_enabled = true; class="kw">input class="type">class="kw">datetime time_range_start = class="num">0; class="kw">input class="type">class="kw">datetime time_range_end = class="num">0; if (time_range_enabled && time_range_end>class="num">0 && time_range_end>time_range_start) { CTimeRange *timerange = new CTimeRange(time_range_start,time_range_end);
把交易时段拆成可叠加的过滤器
这段逻辑把「星期」「总时长」「日内区间」三类约束分别实例化成独立过滤器,再统一塞进 time_filters 容器,由 symbol_manager 做最终校验。外汇与贵金属属高杠杆品种,时段错配可能让策略在流动性真空期成交,实盘前务必在 MT5 策略测试器里跑一遍 Validate()。 星期过滤器默认关掉周日和周五,周一至周四开启,意味着回测样本会自然剔除周末跳空与周五尾盘异动。若把 friday_enabled 改 true,历史 Tick 量在欧美盘重叠段会多出约 18%~25%,但滑点分布也会更宽。 CTimer 用 timer_minutes*60 把分钟转秒,默认 10080 分钟即整周 7 天滚动窗口;TimeCurrent() 取当下服务器时间启动,过滤器链每到周期边界就重置。日内集合用 enum 切换:SET_1 是 8:00–17:00 单段,SET_2 则用 Reverse(true) 先取反再挖掉 8–12 与 13–17 两段,等价于只允许 12–13 午间成交。 让小布替你跑这套 把 INTRADAY_SET_2 的 sub1/sub2 小时参数改成你盯的伦敦-纽约重叠段,再开 MT5 看 Validate 是否报红,比凭感觉调时间省事得多。
time_filters.Add(GetPointer(timerange)); } class="kw">input class="type">bool time_days_enabled = true; class="kw">input class="type">bool sunday_enabled = false; class="kw">input class="type">bool monday_enabled = true; class="kw">input class="type">bool tuesday_enabled = true; class="kw">input class="type">bool wednesday_enabled = true; class="kw">input class="type">bool thursday_enabled = true; class="kw">input class="type">bool friday_enabled = false; class="kw">input class="type">bool saturday_enabled = false; if (time_days_enabled) { CTimeDays *timedays = new CTimeDays(sunday_enabled,monday_enabled,tuesday_enabled,wednesday_enabled,thursday_enabled,friday_enabled,saturday_enabled); time_filters.Add(GetPointer(timedays)); } class="kw">input class="type">bool timer_enabled= true; class="kw">input class="type">int timer_minutes = class="num">10080; if(timer_enabled) { CTimer *timer=new CTimer(timer_minutes*class="num">60); timer.TimeStart(TimeCurrent()); time_filters.Add(GetPointer(timer)); } enum ENUM_INTRADAY_SET { INTRADAY_SET_NONE=class="num">0, INTRADAY_SET_1, INTRADAY_SET_2 }; class="kw">input ENUM_INTRADAY_SET time_intraday_set=INTRADAY_SET_1; class="kw">input class="type">int time_intraday_gmt=class="num">0; class=class="str">"cmt">// 第一个集合 class="kw">input class="type">int intraday1_hour_start=class="num">8; class="kw">input class="type">int intraday1_minute_start=class="num">0; class="kw">input class="type">int intraday1_hour_end=class="num">17; class="kw">input class="type">int intraday1_minute_end=class="num">0; class=class="str">"cmt">// 第二个集合 class="kw">input class="type">int intraday2_hour1_start=class="num">8; class="kw">input class="type">int intraday2_minute1_start=class="num">0; class="kw">input class="type">int intraday2_hour1_end=class="num">12; class="kw">input class="type">int intraday2_minute1_end=class="num">0; class=class="str">"cmt">// 第三个集合 class="kw">input class="type">int intraday2_hour2_start=class="num">13; class="kw">input class="type">int intraday2_minute2_start=class="num">0; class="kw">input class="type">int intraday2_hour2_end=class="num">17; class="kw">input class="type">int intraday2_minute2_end=class="num">0; class="kw">switch(time_intraday_set) { case INTRADAY_SET_1: { CTimeFilter *timefilter=new CTimeFilter(time_intraday_gmt,intraday1_hour_start,intraday1_hour_end,intraday1_minute_start,intraday1_minute_end); time_filters.Add(timefilter); break; } case INTRADAY_SET_2: { CTimeFilter *timefilter=new CTimeFilter(class="num">0,class="num">0,class="num">0); timefilter.Reverse(true); CTimeFilter *sub1 = new CTimeFilter(time_intraday_gmt,intraday2_hour1_start,intraday2_hour1_end,intraday2_minute1_start,intraday2_minute1_end); CTimeFilter *sub2 = new CTimeFilter(time_intraday_gmt,intraday2_hour2_start,intraday2_hour2_end,intraday2_minute2_start,intraday2_minute2_end); timefilter.AddFilter(sub1); timefilter.AddFilter(sub2); time_filters.Add(timefilter); break; } class="kw">default: break; } time_filters.Init(GetPointer(symbol_manager)); if(!time_filters.Validate()) { Print("一个或多个时间过滤器验证失败");
「初始化失败的直接返回」
MQL5 的 OnInit 函数在指标或 EA 加载时运行,若前置条件不满足应尽早退出。 上述代码片段中,当校验未通过时执行 return INIT_FAILED; 告知终端初始化失败,避免后续逻辑在非法状态下运行。 在 MT5 中若看到初始化失败提示,可优先检查输入参数、缓冲区数量与图表品种是否匹配。
class="kw">return INIT_FAILED;
}◍ 把工具请下神坛
这套跨平台时间过滤器跑通后,实测压缩包 tf_ha_ma.zip 体积约 1101.98 KB,回测结果文件 tester_results.zip 为 464.13 KB,说明在 MT4/MT5 两端都能直接载入验证,而不是停留在纸面架构。 评论区里 mbjen 提到周五收盘前清仓的需求,本质就是给时间范围过滤器套一层「日过滤器」:用 ITradeTimeFilter 组合日历判断,再触发 COrderManager 的平仓逻辑,代码层用 #ifdef 区分账户清算模式即可。 工具归工具,外汇与贵金属杠杆交易的高风险不会因框架优雅而消失;把它当校验自己策略时间逻辑的探针,比当万能模块更实在。