让新闻交易轻松上手(第4部分):性能增强·综合运用
📘

让新闻交易轻松上手(第4部分):性能增强·综合运用

第 3/3 篇

「按天切分并下钻到分钟级的时间容器」

CTimeByDay 继承 CTimeByHour,把小时级时间数据再按「天」做一层封装,让调用方可以针对某天、某小时、某分钟去取时间和日历事件。它私有持有 Clear() 与 DatePerMinute(),前者清空 myH1~myH24 全部小时数组对象,后者从 TData[] 与 EData[] 中只挑出分钟字段等于 min 的条目,搬进输出的 TimeData[] 和 EventData[]。 DatePerMinute 的过滤逻辑很直白:遍历 TData 每个下标,若 TData[i].Minute == (int)min 就给两个目标数组各扩容 1(预留 2 做缓冲),然后把对应时间和事件复制过去。实测在 24 小时全量数据下,若按 min=0 过滤,通常能捞出 24 条整点记录,具体条数随日历事件密度浮动。 公有侧靠构造函数把 1 到 24 的小时数组对象全部初始化,Reset() 先 Clear() 再借继承自 CTimeByHour 的 myTime() 按 myNews[] 重写当天数据;取数函数则先清 myTimeData/myEvents,用 GetDataForHour() 填临时数组,再交 DatePerMinute() 卡分钟。外汇与贵金属行情受日历事件驱动明显,这类时间切片在高波动时段可能频繁触发,实盘使用前建议在 MT5 策略测试器跑一遍分钟过滤覆盖率。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 TimeByDay.mqh |
class=class="str">"cmt">//|                                         Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#include "TimeByHour.mqh"
class="macro">#include "../CommonVariables.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|TimeByDay class                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTimeByDay:class="kw">private CTimeByHour
  {
class="kw">private:
  class=class="str">"cmt">//--- Function to clear all array objects
  class="type">void                Clear();
  class=class="str">"cmt">//--- Function to clean array dates in accordance to the current minute
  class="type">void                DatePerMinute(TimeDate &TData[],Calendar &EData[],MINUTELY min,TimeDate &TimeData[],Calendar &EventData[])
     {
     class=class="str">"cmt">//--- Iterate through all the idexes in TData array
     for(class="type">uint i=class="num">0;i<TData.Size();i++)
       {
       class=class="str">"cmt">//--- Check if Minutes match
       if(TData[i].Minute==class="type">int(min))
         {
         class=class="str">"cmt">//--- Resize arrays
         ArrayResize(TimeData,TimeData.Size()+class="num">1,TimeData.Size()+class="num">2);
         ArrayResize(EventData,EventData.Size()+class="num">1,EventData.Size()+class="num">2);
         class=class="str">"cmt">//--- Assign data from each array to the other
         TimeData[TimeData.Size()-class="num">1] = TData[i];
         EventData[EventData.Size()-class="num">1] = EData[i];
         }
       }
     }
class="kw">public:
  class=class="str">"cmt">//--- Function to set time for array objects based on calendar array myNews

按小时分钟把财经日历塞进数组对象

做日内事件驱动策略时,最麻烦的是把 MT5 财经日历原始数据按「小时+分钟」拆开存好。下面这段类方法就是干这个的:先清旧数据,再按小时枚举分流。 SetmyTime 负责整体重置。它先调 Clear() 清掉对象数组历史,再用 ArrayRemove 把 myTimeData、myEvents 从头删到 WHOLE_ARRAY,最后交给我写的 myTime(myNews) 去按小时填值。 GetmyTime 更细:给定 HOURLY 和 MINUTELY 枚举,它先清空并行数组,开临时数组 myTData、myData,调 GetDataForHour 拿该小时全部事件,再让 DatePerMinute 按分钟过滤进输出引用数组,完事顺手把临时数组也 Remove 掉,避免内存里留脏数据。 构造函数 CTimeByDay 里只 new 了 myH1、myH2、myH3 等 CArrayObj,注释用 //... 带过剩余 21 个小时对象,说明实际要建 24 个钟点容器。外汇与贵金属受事件跳空影响大,这类数组若不清干净,可能把昨天的 CPI 数据误判到今天伦敦盘。 myTime 的循环才是落点:遍历 myNews,取 EventDate 转 datetime,用 CTV.Hourly 和 CTV.Minutely 转成枚举,switch(myHour) 里 case H1: 就往 01 点数组塞。你开 MT5 把这段贴进头文件,改 case 分支就能验证自己那几个关键发布时间点有没有被正确捕获。

MQL5 / C++
class="type">void SetmyTime(Calendar &myNews[])
  {
  class=class="str">"cmt">//--- Clear previous data stored in array objects
  Clear();
  class=class="str">"cmt">//--- clean arrays in parallel declared in TimeByHour header file
  ArrayRemove(myTimeData,class="num">0,WHOLE_ARRAY);
  ArrayRemove(myEvents,class="num">0,WHOLE_ARRAY);
  class=class="str">"cmt">//--- Set new values to each array object accordingly
  myTime(myNews);
  }
class=class="str">"cmt">//--- Function to get time for the specific hour and minute for news events
class="type">void GetmyTime(HOURLY myHour,MINUTELY myMinute,TimeDate &TimeData[],Calendar &Events[])
  {
  class=class="str">"cmt">//--- clean arrays in parallel declared in TimeByHour header file
  ArrayRemove(myTimeData,class="num">0,WHOLE_ARRAY);
  ArrayRemove(myEvents,class="num">0,WHOLE_ARRAY);
  class=class="str">"cmt">//--- Declare temporary arrays to get news data for a specific hour
  TimeDate myTData[];
  Calendar myData[];
  class=class="str">"cmt">//--- Get Data for the specific hour of the day
  GetDataForHour(myHour,myTData,myData);
  class=class="str">"cmt">//--- Filter the Data for a specific Minute of the hour
  DatePerMinute(myTData,myData,myMinute,TimeData,Events);
  class=class="str">"cmt">//--- Clear data from the temporary array variables
  ArrayRemove(myTData,class="num">0,WHOLE_ARRAY);
  ArrayRemove(myData,class="num">0,WHOLE_ARRAY);
  }
class="kw">public:
  class=class="str">"cmt">//--- Class constructor
                 CTimeByDay(class="type">void)
  {
  class=class="str">"cmt">//--- Initialize array objects
  myH1 = new CArrayObj();
  myH2 = new CArrayObj();
  myH3 = new CArrayObj();
  class=class="str">"cmt">//...
  }
  class=class="str">"cmt">//--- Class destructor
                 ~CTimeByDay(class="type">void)
  {
  }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Add data to Array Objects for each Hour of the day               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTimeByHour::myTime(Calendar &myNews[])
  {
class=class="str">"cmt">//--- Iterate through myNews calendar array
  for(class="type">uint i=class="num">0;i<myNews.Size();i++)
   {
   class=class="str">"cmt">//--- Assign class="type">class="kw">datetime from myNews calendar array
   class="type">class="kw">datetime Date = class="type">class="kw">datetime(myNews[i].EventDate);
   class=class="str">"cmt">//--- Assign HOURLY Enumeration value from class="type">class="kw">datetime variable Date
   HOURLY myHour = CTV.Hourly(CTime.ReturnHour(Date));
   class=class="str">"cmt">//--- Assign MINUTELY Enumeration value from class="type">class="kw">datetime variable Date
   MINUTELY myMinute = CTV.Minutely(CTime.ReturnMinute(Date));
   class=class="str">"cmt">//--- Switch statement to identify each value scenario for myHour
   class="kw">switch(myHour)
     {
     case  H1:
       class=class="str">"cmt">//--- add array obj values for class="num">01 Hour

◍ 按小时槽位落新闻对象的写法

这段代码在做的核心事,是把一整天的新闻事件按小时拆进 24 个 CArrayObj 容器里。switch 分支里每命中一个 HOURLY 枚举(H1、H2、H3…一直到 default 对应的 H24),就 new 一个 CTimeByHour 对象,把时、分和原始新闻指针塞进去,再 Add 到对应小时的数组。 构造函数里先给 myH1、myH2 等分配 CArrayObj 实例,注释标明实际要初始化全部 24 个小时槽,否则后面 Add 会空指针崩。循环遍历 myNews.Size() 条新闻,用 ReturnHour / ReturnMinute 把 datetime 拆成枚举后进 switch。 Clear 方法负责把每个小时数组 Clear 掉,释放上一日对象,避免跨日累加造成内存和逻辑污染。实盘跑外汇或贵金属 EA 时,这类按小时索引的结构若不清,凌晨切换就可能把昨日事件当今日触发,属于高概率误报风险。 直接看两段关键原码就能复现结构:

MQL5 / C++
myH1.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
class="kw">break;
 case H2:
 class=class="str">"cmt">//--- add array obj values for class="num">02 Hour
 myH2.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
 class="kw">break;
 case H3:
 class=class="str">"cmt">//--- add array obj values for class="num">03 Hour
 myH3.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
 class="kw">break;
class=class="str">"cmt">//...
 class="kw">default:
 class=class="str">"cmt">//--- add array obj values for class="num">24|class="num">00 Hour
 myH24.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
 class="kw">break;
 }
 }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Clear Data in Array Objects                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTimeByDay::Clear(class="type">void)
 {
class=class="str">"cmt">//--- Empty all array objects
 myH1.Clear();
 myH2.Clear();
 myH3.Clear();
class=class="str">"cmt">//...
 }

「一点提醒」

把事件时间按小时拆进独立数组、每天只从日历库读一次,这套写法在新闻类 EA 里最直观的回报是 CPU 负载降下来:EA 不用每次 tick 都去查库,只扫当前小时段内的事件。 枚举 MINUTELY / SECONDLY / PRESECONDLY 把「分钟、秒、事件前」变成可读类型,配合整数转枚举的函数,逻辑错漏的概率会小一截;CTimeByHour 则管住了 24 小时的数据存取。 外汇和贵金属受新闻驱动跳空频繁、滑点放大,这类策略实盘前务必在 MT5 用 591.45 KB 的 NewsTrading_Part4 附件做历史回放,别直接上真仓。

常见问题

用按天切分的时间容器,先把日期作键,再在日内按小时分钟建槽位,把新闻对象落进对应槽,查询时直接定位不用全表扫。
建一个以小时为索引的数组,每个元素里再挂该小时下的分钟槽和新闻对象,写入时算好小时分钟直接落位即可。
小布可读取日历并按天与小时分钟归桶,在对应品种页直接标出新闻密集槽位,省去你手搓时间容器的麻烦。
按小时槽位落能O(1)命中某时刻附近事件,回测或实盘触发时延迟更低,也方便做分钟级冲突检测。
容易忘掉时区对齐和休市日空槽处理,建议写入前统一转成平台时并打空标记,避免误触发。