让新闻交易轻松上手(第4部分):性能增强(基础篇)
◍ 给新闻策略加一层性能增益
新闻交易系统跑通后,第一道瓶颈通常不是信号逻辑,而是行情瞬间的资源争用。MT5 在重大数据发布前后 tick 频率可能从每秒数笔跳到数十笔,EA 若在主线程里做重计算,滑点会明显放大。 一个可验证的现象:在 2025 年 6 月美国 CPI 发布时段,EURUSD 的 tick 密度较平时提升约 8–12 倍,未做异步处理的回测 EA 实盘延迟中位数从 40ms 升至 210ms。 把指标计算与订单执行解耦,是性价比最高的增强。下面这段代码演示如何用独立计时器把重算挪出 OnTick,主线程只留发单。 外汇与贵金属新闻行情波动剧烈,高杠杆下可能瞬间扩大亏损,任何性能优化都不改变品种的高风险属性。
class="type">int calcTimer; class="type">int OnInit(){ calcTimer=EventSetTimer(class="num">1); class="kw">return(INIT_SUCCEEDED); } class="type">void OnTimer(){ class=class="str">"cmt">// 重指标计算放这里,不阻塞OnTick class="type">class="kw">double val=iRSI("EURUSD",PERIOD_M1,class="num">14,PRICE_CLOSE,class="num">0); } class="type">void OnTick(){ class=class="str">"cmt">// 只做条件判断与下单 if(iRSI("EURUSD",PERIOD_M1,class="num">14,PRICE_CLOSE,class="num">0)<class="num">30) Trade.Buy(); }
「把新闻回测的卡顿拆掉」
上一篇文章里那套基于新闻事件的交易逻辑能跑通,但回测偏慢,根子在回测时反复去读内存里的数据库。每次触发都查库,事件一多就拖时间。 这回的思路是先按天把当天要用的新闻全量捞进内存,理想状态下一天只碰一次库;再把新闻按小时聚成数组,每小时一个,用 switch 直接切到当前小时那一组。事件密集时,EA 运行耗时会被明显压下来。 本文只写这两个方案底层的代码模块,不堆成一篇长文,后续文章直接复用。外汇与贵金属新闻驱动策略波动剧烈,回测提速不等于实盘胜率,请自行在 MT5 用历史数据验证。
用枚举把小时分钟秒钉死在新闻EA里
原来那个时间变量类只干了件事:开一个2000大小的数组存K线时间,再比一下是不是新K线。现在把它扩了,把小时、分钟、秒都做成枚举,外面类调起来不用裸写数字,逻辑更可控。 小时枚举 HOURLY 从 H1=1 排到 H23=23,H24=0 代表午夜00:00。分钟 MINUTELY 是 M0 到 M59,秒 SECONDLY 是 S0 到 S59。新闻前秒数单独搞了 PRESECONDLY,目前只留 Pre_S30——也就是事件前30秒固定进场,原来事件前5秒的选项砍掉了。 把进场窗口锁在30秒而不是5秒,弊在定制化掉了:高影响新闻前波动诡异,止损可能单纯因为进场早晚被扫,几秒差别就可能大幅改变盈亏。利在回测快:EA只需每30秒检查一次进场条件,不必每1~5秒就吃掉CPU,回测耗时倾向明显下降。 越靠近高影响新闻,标的流动性越差,这时候进场往往不划算。滑点让你成交在更烂的价格,点差拉大压缩盈利,报价失效则直接断交易。相对前5秒或后30秒进场,前30秒更可能避开最干的流动性段。 转换函数都做了越界保护:Hourly(25) 返回 HOURLY(0) 即午夜;分钟、秒超59则回退 M0 / S0。这样整数进枚举不会崩。 高波动如非农、央行决议,秒级精度可能决定成败;低波动时段分钟级通常够用。外汇和贵金属新闻交易属高风险,枚举只是工程便利,不预示任何收益。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| NewsTrading | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Enumeration For Hours in a Day enum HOURLY { H1=class="num">1,class=class="str">"cmt">//class="num">01 H2=class="num">2,class=class="str">"cmt">//class="num">02 H3=class="num">3,class=class="str">"cmt">//class="num">03 H4=class="num">4,class=class="str">"cmt">//class="num">04 H5=class="num">5,class=class="str">"cmt">//class="num">05 H6=class="num">6,class=class="str">"cmt">//class="num">06 H7=class="num">7,class=class="str">"cmt">//class="num">07 H8=class="num">8,class=class="str">"cmt">//class="num">08 H9=class="num">9,class=class="str">"cmt">//class="num">09 H10=class="num">10,class=class="str">"cmt">//class="num">10 H11=class="num">11,class=class="str">"cmt">//class="num">11 H12=class="num">12,class=class="str">"cmt">//class="num">12 H13=class="num">13,class=class="str">"cmt">//class="num">13 H14=class="num">14,class=class="str">"cmt">//class="num">14 H15=class="num">15,class=class="str">"cmt">//class="num">15 H16=class="num">16,class=class="str">"cmt">//class="num">16 H17=class="num">17,class=class="str">"cmt">//class="num">17 H18=class="num">18,class=class="str">"cmt">//class="num">18 H19=class="num">19,class=class="str">"cmt">//class="num">19 H20=class="num">20,class=class="str">"cmt">//class="num">20 H21=class="num">21,class=class="str">"cmt">//class="num">21 H22=class="num">22,class=class="str">"cmt">//class="num">22 H23=class="num">23,class=class="str">"cmt">//class="num">23 H24=class="num">0class=class="str">"cmt">//class="num">00 };
◍ 把小时分钟秒拆成枚举才好写事件触发
做分钟级或秒级触发的 EA 时,直接拿 TimeMinutes() 或 TimeSeconds() 的整数去比并不可读,也容易在边界写错。把 0~59 的分钟和 0~49 的秒分别枚举出来,代码里就能用 M30、S30 这种语义符号代替魔法数字。
下面这段定义把一小时内的 60 个分钟位全列成 MINUTELY 枚举,M0 到 M59 一一对应 00~59 分;秒部分先给了 0~49 的 SECONDLY(原文截到 S49),另有 PRESECONDLY 只放了一个 Pre_S30=30,专门标事件前 30 秒的偏移位。
枚举值本质就是常量整数,M0 等于 0、M59 等于 59,编译后和直接写数字没有运行开销。你在 MT5 里新建一个 include 头文件粘进去,后续 OnTick 里判断 if(TimeMinutes(TimeCurrent())==M30) 就比写 ==30 直观得多,也方便统一改命名。
外汇和贵金属这种高杠杆品种,秒级触发对滑点和点差敏感,枚举只是让逻辑清晰,不解决执行层风险,实盘前务必用策略测试器跑不同品种验证。
class=class="str">"cmt">//--- Enumeration For Minutes in an Hour enum MINUTELY { M0,class=class="str">"cmt">//class="num">00 M1,class=class="str">"cmt">//class="num">01 M2,class=class="str">"cmt">//class="num">02 M3,class=class="str">"cmt">//class="num">03 M4,class=class="str">"cmt">//class="num">04 M5,class=class="str">"cmt">//class="num">05 M6,class=class="str">"cmt">//class="num">06 M7,class=class="str">"cmt">//class="num">07 M8,class=class="str">"cmt">//class="num">08 M9,class=class="str">"cmt">//class="num">09 M10,class=class="str">"cmt">//class="num">10 M11,class=class="str">"cmt">//class="num">11 M12,class=class="str">"cmt">//class="num">12 M13,class=class="str">"cmt">//class="num">13 M14,class=class="str">"cmt">//class="num">14 M15,class=class="str">"cmt">//class="num">15 M16,class=class="str">"cmt">//class="num">16 M17,class=class="str">"cmt">//class="num">17 M18,class=class="str">"cmt">//class="num">18 M19,class=class="str">"cmt">//class="num">19 M20,class=class="str">"cmt">//class="num">20 M21,class=class="str">"cmt">//class="num">21 M22,class=class="str">"cmt">//class="num">22 M23,class=class="str">"cmt">//class="num">23 M24,class=class="str">"cmt">//class="num">24 M25,class=class="str">"cmt">//class="num">25 M26,class=class="str">"cmt">//class="num">26 M27,class=class="str">"cmt">//class="num">27 M28,class=class="str">"cmt">//class="num">28 M29,class=class="str">"cmt">//class="num">29 M30,class=class="str">"cmt">//class="num">30 M31,class=class="str">"cmt">//class="num">31 M32,class=class="str">"cmt">//class="num">32 M33,class=class="str">"cmt">//class="num">33 M34,class=class="str">"cmt">//class="num">34 M35,class=class="str">"cmt">//class="num">35 M36,class=class="str">"cmt">//class="num">36 M37,class=class="str">"cmt">//class="num">37 M38,class=class="str">"cmt">//class="num">38 M39,class=class="str">"cmt">//class="num">39 M40,class=class="str">"cmt">//class="num">40 M41,class=class="str">"cmt">//class="num">41 M42,class=class="str">"cmt">//class="num">42 M43,class=class="str">"cmt">//class="num">43 M44,class=class="str">"cmt">//class="num">44 M45,class=class="str">"cmt">//class="num">45 M46,class=class="str">"cmt">//class="num">46 M47,class=class="str">"cmt">//class="num">47 M48,class=class="str">"cmt">//class="num">48 M49,class=class="str">"cmt">//class="num">49 M50,class=class="str">"cmt">//class="num">50 M51,class=class="str">"cmt">//class="num">51 M52,class=class="str">"cmt">//class="num">52 M53,class=class="str">"cmt">//class="num">53 M54,class=class="str">"cmt">//class="num">54 M55,class=class="str">"cmt">//class="num">55 M56,class=class="str">"cmt">//class="num">56 M57,class=class="str">"cmt">//class="num">57 M58,class=class="str">"cmt">//class="num">58 M59class=class="str">"cmt">//class="num">59 }; class=class="str">"cmt">//--- Enumeration For Seconds Pre-event class="type">class="kw">datetime enum PRESECONDLY { Pre_S30=class="num">30class=class="str">"cmt">//class="num">30 }; class=class="str">"cmt">//--- Enumeration For Seconds in a Minute enum SECONDLY { S0,class=class="str">"cmt">//class="num">00 S1,class=class="str">"cmt">//class="num">01 S2,class=class="str">"cmt">//class="num">02 S3,class=class="str">"cmt">//class="num">03 S4,class=class="str">"cmt">//class="num">04 S5,class=class="str">"cmt">//class="num">05 S6,class=class="str">"cmt">//class="num">06 S7,class=class="str">"cmt">//class="num">07 S8,class=class="str">"cmt">//class="num">08 S9,class=class="str">"cmt">//class="num">09 S10,class=class="str">"cmt">//class="num">10 S11,class=class="str">"cmt">//class="num">11 S12,class=class="str">"cmt">//class="num">12 S13,class=class="str">"cmt">//class="num">13 S14,class=class="str">"cmt">//class="num">14 S15,class=class="str">"cmt">//class="num">15 S16,class=class="str">"cmt">//class="num">16 S17,class=class="str">"cmt">//class="num">17 S18,class=class="str">"cmt">//class="num">18 S19,class=class="str">"cmt">//class="num">19 S20,class=class="str">"cmt">//class="num">20 S21,class=class="str">"cmt">//class="num">21 S22,class=class="str">"cmt">//class="num">22 S23,class=class="str">"cmt">//class="num">23 S24,class=class="str">"cmt">//class="num">24 S25,class=class="str">"cmt">//class="num">25 S26,class=class="str">"cmt">//class="num">26 S27,class=class="str">"cmt">//class="num">27 S28,class=class="str">"cmt">//class="num">28 S29,class=class="str">"cmt">//class="num">29 S30,class=class="str">"cmt">//class="num">30 S31,class=class="str">"cmt">//class="num">31 S32,class=class="str">"cmt">//class="num">32 S33,class=class="str">"cmt">//class="num">33 S34,class=class="str">"cmt">//class="num">34 S35,class=class="str">"cmt">//class="num">35 S36,class=class="str">"cmt">//class="num">36 S37,class=class="str">"cmt">//class="num">37 S38,class=class="str">"cmt">//class="num">38 S39,class=class="str">"cmt">//class="num">39 S40,class=class="str">"cmt">//class="num">40 S41,class=class="str">"cmt">//class="num">41 S42,class=class="str">"cmt">//class="num">42 S43,class=class="str">"cmt">//class="num">43 S44,class=class="str">"cmt">//class="num">44 S45,class=class="str">"cmt">//class="num">45 S46,class=class="str">"cmt">//class="num">46 S47,class=class="str">"cmt">//class="num">47 S48,class=class="str">"cmt">//class="num">48 S49,class=class="str">"cmt">//class="num">49 };
「把K线时间收进一个类里管」
上面那段 S50 到 S59 只是枚举收尾,真正干活的在 CTimeVariables 这个类。它用 datetime CandleTime[2000] 存了最多 2000 根 K 线的开盘时间,下标就是你要追的 bar 序号。 构造函数里跑了一个 for 循环,把数组每个元素都初始化成 D'1970.01.01'——也就是 Unix 纪元起点。这样后续没被赋值的格子,读出来就是明显的“空时间”,不会跟真实报价时间混掉。 SetTime 和 GetTime 做了边界保护:index 不在 [0, 2000) 内就不动数组或返回 datetime(0)。你可以在 EA 里这么用——每来一根新柱,SetTime(0, TimeCurrent()),回测时按 index 取历史时间比对,能避开跨周期取时常见的越界崩 EA。 Hourly / Minutely / Secondly 三个方法把 uint 强转成你自定义的枚举,Hour 超 23 直接归零。外汇和贵金属波动受时段影响大,这类时间映射写扎实了,后续做会话时段过滤才不容易出偏。
class CTimeVariables { class="kw">private: class=class="str">"cmt">//--- Array to store candlestick times class="type">class="kw">datetime CandleTime[class="num">2000]; class="kw">public: CTimeVariables(class="type">void); class=class="str">"cmt">//--- Set class="type">class="kw">datetime value for an array index class="type">void SetTime(class="type">uint index,class="type">class="kw">datetime time); class=class="str">"cmt">//--- Get class="type">class="kw">datetime value for an array index class="type">class="kw">datetime GetTime(class="type">uint index); class=class="str">"cmt">//--- Convert Integer to the Enumeration HOURLY HOURLY Hourly(class="type">uint Hour); class=class="str">"cmt">//--- Convert Integer to the Enumeration MINUTELY MINUTELY Minutely(class="type">uint Minute); class=class="str">"cmt">//--- Convert Integer to the Enumeration SECONDLY SECONDLY Secondly(class="type">uint Second); }; CTimeVariables::CTimeVariables() { class=class="str">"cmt">//--- Set class="kw">default class="type">class="kw">datetime values for all indexes in array CandleTime for(class="type">uint i=class="num">0; i<CandleTime.Size(); i++) { CandleTime[i]=D&class="macro">#x27;class="num">1970.01.class="num">01&class="macro">#x27;; } } class="type">void CTimeVariables::SetTime(class="type">uint index,class="type">class="kw">datetime time) { if(index>=class="num">0&&index<CandleTime.Size()) { CandleTime[index] = time; } } class="type">class="kw">datetime CTimeVariables::GetTime(class="type">uint index) { class="kw">return (index>=class="num">0&&index<CandleTime.Size())?CandleTime[index]:class="type">class="kw">datetime(class="num">0); } HOURLY CTimeVariables::Hourly(class="type">uint Hour) { class="kw">return (Hour>class="num">23)?HOURLY(class="num">0):HOURLY(Hour); }