让新闻交易轻松上手(第3部分):执行交易·综合运用

让新闻交易轻松上手(第3部分):执行交易·综合运用

(3/3)· 当经济日历数据库遇上实时图形与交易管理,新闻行情不再手忙脚乱

进阶 第 3/3 篇
很多交易者把新闻EA写成「数据到了就冲」的脚本,却忽略价差评级和事件刷新节奏,结果在流动性塌陷的瞬间吃滑点。把事件筛选和图形反馈先固化进框架,执行层才有底气。
本章目录
  1. 给绘图对象拆出独立文本色
  2. 用代码在图表上钉死文字与方框
  3. 用矩形标签在MT5上画不可见控件层
  4. 用内存数据库名隔离不同账户与测试环境
  5. 用时间数组捕捉新K线生成
  6. 把夏令时切换塞进EA输入项
  7. 用类封装交易时段判断
  8. 用服务器时间锁定交易日与时刻
  9. 用 NewCandle 抓新K线生成的瞬间
  10. 交易时段过滤类的设计落点
  11. 交易时段边界与结束判定的数组处理
  12. 从交易时段数组里抠出收盘边界
  13. 从交易日历里抠出会话结束时间
  14. 把新闻日历搬进内存:回测不再被硬盘拖后腿
  15. 用历史同类事件反推新闻影响等级
  16. 财经事件库里的币种与表结构枚举
  17. 把财经日历塞进本地数据库的类骨架
  18. 用 SQL 把财经日历滤进内存结构
  19. 从财经日历库里抠出自动 DST 标记
  20. 正文
  21. 经济日历 SQL 过滤串的拼装逻辑
  22. 按币种拼经济事件查询串
  23. 按币种拼出财经事件查询串
  24. 把财经日历重要性字符串映射成枚举
  25. 财经日历重要度的枚举转换与配色
  26. 财经日历的颜色与板块映射逻辑
  27. 把自定义枚举映射到财经日历原生枚举
  28. 正文
  29. 用 SQL 视图给财经日历做减法
  30. 澳储与巴西央行的事件字段排布
  31. 加元事件流与币种视图的落地写法
  32. 正文
  33. 内存数据库建表与财经数据灌入
  34. 内存日历的检索与落库细节
  35. 把财经日历塞进内存数据库再读出来
  36. 日历数据里的字段暗码
  37. 低权重日历条目的字段结构拆解
  38. 欧元区数据密集发布时的字段排布
  39. 经济日历条目的字段结构拆解
  40. 经济日历里低重要度条目的真实分量
  41. 低影响时段里怎么挑值得盯的日历条目
  42. 抓下一个财经事件的结构体写法
  43. 用 SQL 窗口函数筛最近一条高权重财历
  44. 用历史情景反推财经事件冲击等级
  45. 用历史事件反推财经日历冲击方向
  46. 图表视觉元素的类封装逻辑
  47. 图形文本结构里的标签检索与覆写逻辑
  48. 用结构体接管 MT5 文本尺寸测算
  49. 图形文本结构的内部检索逻辑
  50. 图形文字结构里的增改查与尺寸量算
  51. 文本尺寸测量与图形刷新逻辑
  52. 在面板上把品种合约参数铺出来
  53. 风险档位与面板坐标的落地写法
  54. 用锚点把合约参数排进图表左上角
  55. 逐区堆叠品种交易参数面板
  56. 风险边界与日期面板的绘制落点
  57. 把财经事件与点差塞进第二文本块
  58. 日期与新闻面板的逐行坐标排布
  59. 新闻面板第11到13区的坐标与配色逻辑
  60. 新闻面板第13到15区的逐行绘制逻辑
  61. 点差面板怎么在左上角拼出来
  62. 点差面板的坐标与配色推演
  63. 回测静默与盘面信息块的填充逻辑
  64. 把财经事件与点差塞进第二文本块
  65. 新闻面板文字坐标与配色的实时重算
  66. 事件面板第12、13区的坐标与底色自适应
  67. 按栏目逐段铺开财经事件文本对象
  68. 点差面板的坐标与配色接力
  69. 点差面板里的坐标与配色联动
  70. 在图表上把财经日历事件画出来
  71. EA交易开关与新闻防重复开仓
  72. 持仓与成交的防重复开仓判定
  73. 成交过滤与分批建仓的底层写法
  74. 卖单风控与分批手数落地
  75. 新闻EA的初始化分流与事件下单逻辑
  76. 仓位与新闻风控的参数落地
  77. 风控与界面初始化的两段分工
  78. 断线时怎么画出等待动画
  79. 回测前的新闻库自检逻辑
  80. 把事件驱动逻辑塞进 OnTick 的骨架
  81. 按财经事件影响方向自动挂单的逻辑
  82. 空小节无内容可析
  83. 下一步要补的缺口

给绘图对象拆出独立文本色

上一版绘图类里,所有文本对象只能共用一种颜色,想让不同标注区分色调基本没戏。这版在类外挂了一个全局变量 TextObj_color,把文本色从函数参数里摘出来,TextObj 的参数列表就不必再膨胀。 Square 函数也顺手改了:会根据图表当前的颜色模式(亮/暗)自动切背景色,避免深底浅框或浅底深框看不清。外汇与贵金属图表多周期切换频繁,这种自适应能少折腾眼睛,但指标失效、画错位置的高风险仍在,参数别乱给。 下面这段是类声明的骨架,注意 Objects[] 用结构体存 chart_id+name,析构时统一清场:

MQL5 / C++
class="macro">#include "ChartProperties.mqh"
class="type">class="kw">color TextObj_color;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|ObjectProperties class                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CObjectProperties:class="kw">public CChartProperties
  {
class="kw">private:
   class=class="str">"cmt">//Simple  chart objects structure
   class="kw">struct ObjStruct
     {
      class="type">long                ChartId;
      class="type">class="kw">string              Name;
     } Objects[];class=class="str">"cmt">//ObjStruct variable array
   class=class="str">"cmt">//-- Add chart object to Objects array
   class="type">void                AddObj(class="type">long chart_id,class="type">class="kw">string name)
     {
      ArrayResize(Objects,Objects.Size()+class="num">1,Objects.Size()+class="num">2);
      Objects[Objects.Size()-class="num">1].ChartId=chart_id;
      Objects[Objects.Size()-class="num">1].Name=name;
     }
class="kw">protected:
   class="type">void                DeleteObj()
     {
      for(class="type">uint i=class="num">0;i<Objects.Size();i++)
        {
         ObjectDelete(Objects[i].ChartId,Objects[i].Name);
        }
     }
class="kw">public:
                     CObjectProperties(class="type">void) {}class=class="str">"cmt">//Class constructor
   class=class="str">"cmt">//-- Create Rectangle chart object
   class="type">void                Square(class="type">long chart_ID,class="type">class="kw">string name,class="type">int x_coord,class="type">int y_coord,class="type">int width,class="type">int height,ENUM_ANCHOR_POINT Anchor);
   class=class="str">"cmt">//-- Create text chart object
   class="type">void                TextObj(class="type">long chartID,class="type">class="kw">string name,class="type">class="kw">string text,class="type">int x_coord,class="type">int y_coord,
                                ENUM_BASE_CORNER Corner=CORNER_LEFT_UPPER,class="type">int fontsize=class="num">10);
   class=class="str">"cmt">//-- Create Event object
   class="type">void                EventObj(class="type">long chartID,class="type">class="kw">string name,class="type">class="kw">string description,class="type">class="kw">datetime eventdate);
   class=class="str">"cmt">//-- Class destructor removes all chart objects created previously
                    ~CObjectProperties(class="type">void)
     {
      DeleteObj();
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Create text chart object                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用代码在图表上钉死文字与方框

想在 MT5 图表上动态贴标注,又不污染 K 线本身,可以用 OBJ_LABEL 和矩形对象分开处理。下面这段封装把文字标签的创建、定位、配色一次性搞定,默认字号 10,锚点走左上角。 TextObj 方法先 ObjectDelete 同名单对象,避免重复堆叠;随后 ObjectCreate 用 OBJ_LABEL 类型,时间/价格坐标传 0 即可,因为标签走像素距离定位。XDISTANCE 与 YDISTANCE 接收的是图表客户区像素值,不是价格轴,这点容易踩坑。 Square 方法则画一个带下沉边框的矩形,背景色按 isLightMode 在白/黑间切换,边框固定 clrRed 实线。sub_window 写死 0 表示主图窗口,若你想塞进副图指标区得改这个值。 外汇与贵金属波动剧烈,这类 overlay 对象只作视觉辅助,任何信号都只是概率倾向,实盘前请在策略测试器里跑一遍确认坐标逻辑。

MQL5 / C++
class="type">void CObjectProperties::TextObj(class="type">long chartID,class="type">class="kw">string name,class="type">class="kw">string text,class="type">int x_coord,class="type">int y_coord,
                                      ENUM_BASE_CORNER Corner=CORNER_LEFT_UPPER,class="type">int fontsize=class="num">10)
  {
   ObjectDelete(chartID,name);class=class="str">"cmt">//Delete previous object with the same name and chart id
   if(ObjectCreate(chartID,name,OBJ_LABEL,class="num">0,class="num">0,class="num">0))class=class="str">"cmt">//Create object label
     {
      AddObj(chartID,name);class=class="str">"cmt">//Add object to array
      ObjectSetInteger(chartID,name,OBJPROP_XDISTANCE,x_coord);class=class="str">"cmt">//Set x Distance/coordinate
      ObjectSetInteger(chartID,name,OBJPROP_YDISTANCE,y_coord);class=class="str">"cmt">//Set y Distance/coordinate
      ObjectSetInteger(chartID,name,OBJPROP_CORNER,Corner);class=class="str">"cmt">//Set object&class="macro">#x27;s corner anchor
      ObjectSetString(chartID,name,OBJPROP_TEXT,text);class=class="str">"cmt">//Set object&class="macro">#x27;s text
      ObjectSetInteger(chartID,name,OBJPROP_COLOR,TextObj_color);class=class="str">"cmt">//Set object&class="macro">#x27;s class="type">class="kw">color
      ObjectSetInteger(chartID,name,OBJPROP_FONTSIZE,fontsize);class=class="str">"cmt">//Set object&class="macro">#x27;s font-size
     }
   else
     {
      Print("Failed to create object: ",name);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Create Rectangle chart object                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CObjectProperties::Square(class="type">long chart_ID,class="type">class="kw">string name,class="type">int x_coord,class="type">int y_coord,class="type">int width,class="type">int height,ENUM_ANCHOR_POINT Anchor)
  {
   class="kw">const class="type">int            sub_window=class="num">0;            class=class="str">"cmt">// subwindow index
   class="kw">const class="type">int            x=x_coord;               class=class="str">"cmt">// X coordinate
   class="kw">const class="type">int            y=y_coord;               class=class="str">"cmt">// Y coordinate
   class="kw">const class="type">class="kw">color          back_clr=(isLightMode)?clrWhite:clrBlack;class=class="str">"cmt">// background class="type">class="kw">color
   class="kw">const ENUM_BORDER_TYPE border=BORDER_SUNKEN;  class=class="str">"cmt">// border type
   class="kw">const class="type">class="kw">color          clr=clrRed;              class=class="str">"cmt">// flat border class="type">class="kw">color (Flat)
   class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID;      class=class="str">"cmt">// flat border style

「用矩形标签在MT5上画不可见控件层」

想在MT5图表上叠一层不干扰交易的提示框,OBJ_RECTANGLE_LABEL是最干净的做法。它不走价格坐标系,只按像素距离定位,适合做背景标记或AIGC信号浮窗。 下面这段代码先清掉同名旧对象,再建一个新的矩形标签。line_width=0表示边框为扁平无宽度,hidden=true让它直接躲进对象列表之外,back=false则保持前景显示。 ObjectCreate成功后,一连串ObjectSetInteger把x/y偏移、宽高、锚点、颜色、边框类型全写进去。其中OBJPROP_XDISTANCE和OBJPROP_YDISTANCE是相对图表左上角的像素值,调这两个参数就能把框挪到任意角落。 最后ChartRedraw(chart_ID)强制重绘,否则刚建的标签可能不会立刻出现。建失败就Print报错名,方便你查是name冲突还是sub_window传错。外汇与贵金属波动剧烈,这类 overlay 仅作辅助参考,实际进出场仍要以价格行为为准。

MQL5 / C++
class="kw">const class="type">int                line_width=class="num">0;           class=class="str">"cmt">// flat border width
  class="kw">const class="type">bool               back=class="kw">false;               class=class="str">"cmt">// in the background
  class="kw">const class="type">bool               selection=class="kw">false;          class=class="str">"cmt">// highlight to move
  class="kw">const class="type">bool               hidden=true;              class=class="str">"cmt">// hidden in the object list
  ObjectDelete(chart_ID,name);class=class="str">"cmt">//Delete previous object with the same name and chart id
  if(ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,class="num">0,class="num">0))class=class="str">"cmt">//create rectangle object label
   {
   AddObj(chart_ID,name);class=class="str">"cmt">//Add object to array
   ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);class=class="str">"cmt">//Set x Distance/coordinate
   ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);class=class="str">"cmt">//Set y Distance/coordinate
   ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);class=class="str">"cmt">//Set object&class="macro">#x27;s width/x-size
   ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);class=class="str">"cmt">//Set object&class="macro">#x27;s height/y-size
   ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);class=class="str">"cmt">//Set object&class="macro">#x27;s background class="type">class="kw">color
   ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border);class=class="str">"cmt">//Set object&class="macro">#x27;s border type
   ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,Anchor);class=class="str">"cmt">//Set objects anchor point
   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);class=class="str">"cmt">//Set object&class="macro">#x27;s class="type">class="kw">color
   ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);class=class="str">"cmt">//Set object&class="macro">#x27;s style
   ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width);class=class="str">"cmt">//Set object&class="macro">#x27;s flat border width
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);class=class="str">"cmt">//Set if object is in foreground or not
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);class=class="str">"cmt">//Set if object is selectable/dragable
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);class=class="str">"cmt">//Set if object is Selected
   ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);class=class="str">"cmt">//Set if object is hidden in object list
   ChartRedraw(chart_ID);
   }
  else
   {
   Print("Failed to create object: ",name);
   }
}

用内存数据库名隔离不同账户与测试环境

做跨经纪商或多图表跑 EA 时,最怕新闻数据串库。这里把内存库名拼成 Calendar_经纪商_图表ID_环境 的形式,实盘和策略测试器各自独立,不会互相污染。 枚举 Choice 取代裸布尔做输入项,界面上给交易者看 Yes / No 比勾选框更直白;DayOfTheWeek 砍掉周末只留周一到周五加 AllDays,过滤非交易日逻辑更干净。 Answer() 函数只是把 Choice 映射回布尔,EA 内部判断时仍走标准逻辑分支,外层配置却更可读。 下面这段头文件定义直接能抄进 MT5 的 .mqh: #define NEWS_DATABASE_MEMORY StringFormat("Calendar_%s_%d_%s.sqlite",broker,ChartID(),(MQLInfoInteger(MQL_TESTER)?"TESTER":"REAL")) 宏用 StringFormat 把经纪商名、ChartID() 返回的图表数字句柄、以及 MQLInfoInteger(MQL_TESTER) 判出的 TESTER / REAL 后缀拼进文件名。 enum Choice { Yes, No }; 定义可选项;enum DayOfTheWeek { Monday, Tuesday, Wednesday, Thursday, Friday, AllDays }; 限定交易日期枚举。 bool Answer(Choice choose) { return (choose==Yes)?true:false; } 把枚举转布尔,实盘外汇与贵金属波动剧烈、杠杆高风险,参数隔离只是防错第一步。

MQL5 / C++
class="macro">#define NEWS_DATABASE_MEMORY        StringFormat("Calendar_%s_%d_%s.sqlite",broker,ChartID(),(MQLInfoInteger(MQL_TESTER)?"TESTER":"REAL"))
enum Choice
  {
  Yes,class=class="str">"cmt">//YES
  Noclass=class="str">"cmt">//NO
  };
enum DayOfTheWeek
  {
  Monday,class=class="str">"cmt">//MONDAY
  Tuesday,class=class="str">"cmt">//TUESDAY
  Wednesday,class=class="str">"cmt">//WEDNESDAY
  Thursday,class=class="str">"cmt">//THURSDAY
  Friday,class=class="str">"cmt">//FRIDAY
  AllDaysclass=class="str">"cmt">//ALL DAYS
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Convert enumeration Choice into a boolean value                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool Answer(Choice choose)
  {
  class="kw">return (choose==Yes)?true:class="kw">false;
  }

◍ 用时间数组捕捉新K线生成

做价格行为策略时,判断「当前Bar是否刚走完、新Bar是否生成」是很多信号触发的先决条件。把每根K线的开盘时间存进一个定长数组,就能在Tick事件里比对时间差,避免重复计算或漏判。 下面这个类把时间存进 CandleTime[2000],构造函数里把所有索引默认填成 1970.01.01,也就是 MT5 里 datetime 的纪元零点。这样在还没写入真实行情时间前,任何索引读出来都是无效值,不会误判成有效K线。 SetTime 接收 uint 索引和 datetime 时间,只在索引落在 [0, 数组大小) 内才写入,越界直接忽略。GetTime 同理,索引非法时返回 datetime(0) 而非乱值,调用方用 0 就能识别读取失败。 实盘里建议把数组大小按品种最大留存Bar数来定,2000 对一般日内周期够用,但做月线长周期回看可能要调大。外汇和贵金属波动受消息影响大,新K线判定错了可能让挂单逻辑偏移,属正常技术风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|TimeVariables class                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
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 Array index time
   class="type">void            SetTime(class="type">uint index,class="type">class="kw">datetime time);
   class=class="str">"cmt">//---Get Array index time
   class="type">class="kw">datetime            GetTime(class="type">uint index);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Constructor                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTimeVariables::CTimeVariables()
  {
   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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Set Array index time                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Get Array index time                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
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);
  }

「把夏令时切换塞进EA输入项」

做跨时区贵金属或外汇策略时,服务器时间与本地夏令时错位会直接搞乱开仓窗口。这里先甩一个枚举,让使用者在MT5输入里二选一:跟平台自动夏令时,还是自己填自定义表。 DSTSchedule 枚举只放两个值,AutoDst_Selection 走券商自带规则,CustomDst_Selection 留给手动档;MySchedule 这个变量专门接自定义配置,不占主逻辑空间。 时间拆解函数才是日常用的主力。ReturnHour / ReturnMinute / ReturnSecond 三个方法都吃一个 datetime,内部调 Time() 转成 MqlDateTime 后直接吐出对应字段,返回整数。比如 ReturnHour(D'2024.03.10 02:30') 在美服可能返回 2,验证时区偏移一眼可见。 Time() 有两个重载:单参版本用 TimeToStruct 把 datetime 拍成结构体;三参版本(时、分、秒)先转结构再改字段,最后 StructToTime 拼回 datetime。这套封装让你改时间不必每次手写 MqlDateTime 赋值。 其余几个判断型函数(交易时间在起止区间内、事件触发前窗口、当前星期匹配等)都依赖上述拆解。外汇和贵金属杠杆高,时区判断错一次可能赶上流动性真空,回测和实盘前务必在MT5用真实服务器时间跑一遍。

MQL5 / C++
class=class="str">"cmt">//-- Enumeration for DST schedule
enum DSTSchedule
  {
   AutoDst_Selection,class=class="str">"cmt">//AUTO DST
   CustomDst_Selectionclass=class="str">"cmt">//CUSTOM DST
  } MyDST;
DST_type MySchedule;class=class="str">"cmt">//Variable for custom DST schedule
class="type">int                ReturnHour(class="type">class="kw">datetime time);class=class="str">"cmt">//Returns the Hour for a specific date
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Returns the Hour for a specific date                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CTimeManagement::ReturnHour(class="type">class="kw">datetime time)
  {
   class="kw">return Time(time).hour;
  }
class="type">int                ReturnMinute(class="type">class="kw">datetime time);class=class="str">"cmt">//Returns the Minute for a specific date
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Returns the Minute for a specific date                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CTimeManagement::ReturnMinute(class="type">class="kw">datetime time)
  {
   class="kw">return Time(time).min;
  }
class="type">int                ReturnSecond(class="type">class="kw">datetime time);class=class="str">"cmt">//Returns the Second for s specific date
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Returns the Second for s specific date                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CTimeManagement::ReturnSecond(class="type">class="kw">datetime time)
  {
   class="kw">return Time(time).sec;
  }
class=class="str">"cmt">//-- Will convert class="type">class="kw">datetime to class="type">MqlDateTime
   class="type">MqlDateTime        Time(class="type">class="kw">datetime Timetoformat);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Will convert class="type">class="kw">datetime to class="type">MqlDateTime                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">MqlDateTime CTimeManagement::Time(class="type">class="kw">datetime Timetoformat)
  {
   TimeToStruct(Timetoformat,timeFormat);
   class="kw">return timeFormat;
  }
class=class="str">"cmt">//-- Will class="kw">return a class="type">class="kw">datetime with changes to the hour,minute and second
   class="type">class="kw">datetime           Time(class="type">class="kw">datetime time,class="type">int Hour,class="type">int Minute,class="type">int Second);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Will class="kw">return a class="type">class="kw">datetime with changes to the hour,minute and second |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">datetime CTimeManagement::Time(class="type">class="kw">datetime time,class="type">int Hour,class="type">int Minute,class="type">int Second)
  {
   timeFormat=Time(time);
   timeFormat.hour=Hour;
   timeFormat.min=Minute;
   timeFormat.sec=Second;
   class="kw">return StructToTime(timeFormat);
  }
class=class="str">"cmt">//-- Will class="kw">return a class="type">class="kw">datetime with changes to the hour and minute

用类封装交易时段判断

在 MT5 写 EA 时,硬编码小时分钟去比对服务器时间既难维护又易错。把时间操作收进一个 CTimeManagement 类,能直接拿到「某日的指定时分」以及「当前是否落在某区间」的布尔值,回测和实盘都跑得通。 Time() 接收一个 datetime 与自定义的 Hour、Minute,把原时间的时分改写成目标值再转回 datetime;Today() 则基于 TimeTradeServer() 返回的当日结构,覆写 hour 与 min(或再加 sec)后直接吐出 MqlDateTime。两者区别在返回类型:前者给时间戳,后者给结构体能继续拆字段。 TimeIsInRange() 与 TimePreEvent() 都是用 TimeTradeServer() 取当前经纪商服务器时间做边界判断。前者是闭区间 [BeginTime, EndTime],后者是左闭右开 [PreEventTime, EventTime),适合「事件前预热 N 分钟」这类场景,比如非农前 30 分钟禁单。外汇与贵金属杠杆高,服务器时间与本地时区不一致可能导致误判,务必以 TimeTradeServer() 为准。 把下面代码贴进你的 .mqh 头文件,开 MT5 用 Today(20,30) 打印一下,确认返回的是当天 20:30 的 MqlDateTime 而非 00:00,就能验证时区逻辑没接反。

MQL5 / C++
class="type">class="kw">datetime CTimeManagement::Time(class="type">class="kw">datetime time,class="type">int Hour,class="type">int Minute)
  {
  timeFormat=Time(time);
  timeFormat.hour=Hour;
  timeFormat.min=Minute;
  class="kw">return StructToTime(timeFormat);
  }

class="type">bool CTimeManagement::TimeIsInRange(class="type">class="kw">datetime BeginTime,class="type">class="kw">datetime EndTime)
  {
  if(BeginTime<=TimeTradeServer()&&EndTime>=TimeTradeServer())
    {
    class="kw">return true;
    }
  class="kw">return class="kw">false;
  }

class="type">bool CTimeManagement::TimePreEvent(class="type">class="kw">datetime PreEventTime,class="type">class="kw">datetime EventTime)
  {
  if(PreEventTime<=TimeTradeServer()&&EventTime>TimeTradeServer())
    {
    class="kw">return true;
    }
  class="kw">return class="kw">false;
  }

class="type">MqlDateTime CTimeManagement::Today(class="type">int Hour,class="type">int Minute)
  {
  TimeTradeServer(today);
  today.hour=Hour;
  today.min=Minute;
  class="kw">return today;
  }

◍ 用服务器时间锁定交易日与时刻

在 MT5 的 EA 或指标里,直接拿本地时间做交易日过滤会踩坑——不同经纪商时区差可能让周一开盘判定偏移数小时。下面这套 CTimeManagement 类方法全部基于 TimeTradeServer(),也就是交易服务器时间,外汇与贵金属交易者用它来对表更稳,但杠杆品类本身高风险,任何时间过滤都只是概率层面的筛选。 Today() 把传入的时、分、秒强行覆盖到当前服务器日期上,返回一个 datetime 结构。比如 Today(0,0,0) 就是今天 0 点整的服务器时间,常用于设定当日基准。 isDayOfTheWeek() 用 switch 穷举周一到周五加 AllDays,比对 DayOfWeek(TimeTradeServer()) 的枚举值。注意这里只覆盖了交易日,周六周日不在 case 里,会落到 default 返回 false,想做周末过滤得自己补。 DayOfWeek() 更底层,接收任意 datetime,返回 ENUM_DAY_OF_WEEK 枚举。它直接读 Time(time).day_of_week,比自己算 struct tm 的 wday 少写三行。开 MT5 把这些塞进你的时间管理类,调 Today(22,0,0) 配合 isDayOfTheWeek(Friday) 就能测出非农前挂单的时效窗口。

MQL5 / C++
class="type">MqlDateTime CTimeManagement::Today(class="type">int Hour,class="type">int Minute,class="type">int Second)
  {
   TimeTradeServer(today);
   today.hour=Hour;
   today.min=Minute;
   today.sec=Second;
   class="kw">return today;
  }
class=class="str">"cmt">//-- Check current day of the week
   class="type">bool                isDayOfTheWeek(DayOfTheWeek Day);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Check current day of the week                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTimeManagement::isDayOfTheWeek(DayOfTheWeek Day)
  {
   class="kw">switch(Day)
     {
     case  Monday:class=class="str">"cmt">//Monday
        if(DayOfWeek(TimeTradeServer())==MONDAY)
          {
           class="kw">return true;
          }
        class="kw">break;
     case Tuesday:class=class="str">"cmt">//Tuesday
        if(DayOfWeek(TimeTradeServer())==TUESDAY)
          {
           class="kw">return true;
          }
        class="kw">break;
     case Wednesday:class=class="str">"cmt">//Wednesday
        if(DayOfWeek(TimeTradeServer())==WEDNESDAY)
          {
           class="kw">return true;
          }
        class="kw">break;
     case Thursday:class=class="str">"cmt">//Thursday
        if(DayOfWeek(TimeTradeServer())==THURSDAY)
          {
           class="kw">return true;
          }
        class="kw">break;
     case Friday:class=class="str">"cmt">//Friday
        if(DayOfWeek(TimeTradeServer())==FRIDAY)
          {
           class="kw">return true;
          }
        class="kw">break;
     case AllDays:class=class="str">"cmt">//All days
        class="kw">return true;
        class="kw">break;
     class="kw">default:class=class="str">"cmt">//Unknown
        class="kw">break;
     }
   class="kw">return class="kw">false;
  }
class=class="str">"cmt">//-- Return enumeration Day of week for a certain date
   ENUM_DAY_OF_WEEK   DayOfWeek(class="type">class="kw">datetime time);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Return enumeration Day of week for a certain date                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_DAY_OF_WEEK CTimeManagement::DayOfWeek(class="type">class="kw">datetime time)
  {
   class="kw">return (ENUM_DAY_OF_WEEK)Time(time).day_of_week;
  }

「用 NewCandle 抓新K线生成的瞬间」

在 K 线属性类里补一个 NewCandle 函数,用途很直接:当一根新 K 线在图表上闭合、下一根开始成型时,它返回 true。实现思路不绕弯——把当前 K 线开盘时间存进类里的 TimeVariables,每次拿已存时间和 iTime 取到的实时第 0 根开盘时间比对,不一致就判定为新 K 线。 这种写法对外汇、贵金属这类高波动品种很有用,你可以在 EA 的 OnTick 里调用它,只在 true 时跑一次策略逻辑,避免每跳重复计算。注意 MT5 上外汇保证金交易杠杆高,误判或漏判新 K 线可能让订单频率失控,实盘前务必在策略测试器用历史数据跑一遍。 下面这段是类声明与函数本体,重点看被标黄的那个 NewCandle 声明和后面的比对逻辑: 类声明部分逐行看:class CCandleProperties 继承自 CChartProperties,私有成员里 Time 管时间工具、CTV 管时间变量存储;公开的 Open/Close/High/Low 按索引取对应价格;IsLargerThanPreviousAndNext 判断某根是否比前后两根都大;NewCandle 就是我们说的“新 K 线探测器”。 函数本体逐行拆:入参 index 是存储槽位、period 默认当前周期、SYMBOL 默认 NULL 表示当前品种;if 里用 CTV.GetTime(index) 和 iTime 取到的第 0 根开盘时间相等,说明没新 K 线,return false;else 里先 CTV.SetTime 把新时间写进去,再 return true,完成一次更新。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|CandleProperties class                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CCandleProperties : class="kw">public CChartProperties
  {
class="kw">private:
   CTimeManagement   Time;class=class="str">"cmt">//TimeManagement object
   CTimeVariables    CTV;class=class="str">"cmt">//Timevariables object
class="kw">public:
   class="type">class="kw">double            Open(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle Open-Price
   class="type">class="kw">double            Close(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle Close-Price
   class="type">class="kw">double            High(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle High-Price
   class="type">class="kw">double            Low(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle Low-Price
   class="type">bool              IsLargerThanPreviousAndNext(class="type">class="kw">datetime CandleTime,class="type">int Offset,class="type">class="kw">string SYMBOL);class=class="str">"cmt">//Determine if one candle is larger than two others
   class="type">bool              NewCandle(class="type">int index,ENUM_TIMEFRAMES period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Check if a new candle is present
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Check if a new candle is present                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCandleProperties::NewCandle(class="type">int index,ENUM_TIMEFRAMES period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL)
  {
   if(CTV.GetTime(index) == iTime(((SYMBOL==NULL)?Symbol():SYMBOL),period,class="num">0))
     {
      class="kw">return class="kw">false;class=class="str">"cmt">//Candle time are equal no new candles have formed
     }
   else
     {
     class=class="str">"cmt">//--- Candle time has changed set the new time
      CTV.SetTime(index,iTime(((SYMBOL==NULL)?Symbol():SYMBOL),period,class="num">0));
      class="kw">return true;
     }
  }

交易时段过滤类的设计落点

这个类专门管交易时段判定,从时间管理类继承下来复用底层时间函数,当前篇暂不参与实盘逻辑,但后续策略会直接挂它做开仓围栏。核心动机很实在:会话刚开盘和临近收盘那阵,点差往往抽宽,外汇和贵金属这种高杠杆品种尤其容易被滑点啃掉盈利,提前偏移一小时进场能避开开盘乱流。 isSessionStart 默认偏移 0 小时 0 分,但它内部会扫当前品种和星期几的全部有效时段,抓到最早一段后叠加偏移。举例说,原始时段 01:00–05:00、偏移小时设 1,实际判定起点就挪到 02:00,函数活跃才返 true。 isSessionEnd 与 SessionEnd 默认都带 45 分钟偏移,意思是时段标称结束前 45 分钟就视为尾盘。某些经纪商尾盘点差可能扩到日常的数倍,用这两个函数卡掉尾单更稳。 一个坑得记着:MQL5 的时段数据在假期会失真。实测有些品种假期比会话时间显示的提早很久关门,靠 SessionEnd 拿到的收盘时间不一定准,跑历史回测或跨节交易前最好手动核对一下服务器时间。

MQL5 / C++
class="macro">#include "Timemanagement.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Sessions Class                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSessions:CTimeManagement
  {
class="kw">public:
                    CSessions(class="type">void) {}
                   ~CSessions(class="type">void) {}
  class=class="str">"cmt">//--- Check if trading Session has began
  class="type">bool              isSessionStart(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">0);
  class=class="str">"cmt">//--- Check if trading Session has ended
  class="type">bool              isSessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45);
  class=class="str">"cmt">//--- Get Session End class="type">class="kw">datetime
  class="type">class="kw">datetime          SessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Check if trading Session has started                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSessions::isSessionStart(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">0)
  {
class=class="str">"cmt">//--- Declarations
class="type">class="kw">datetime datefrom,dateto,DateFrom[],DateTo[];
class=class="str">"cmt">//--- Find all session times
  for(class="type">int i=class="num">0; i<class="num">10; i++)
    {
      class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week
      if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto))
        {
          class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight
          if(ReturnHour(dateto)==class="num">00||ReturnHour(dateto)==class="num">24)
            {
              class=class="str">"cmt">//--- Adjust the date to one minute before midnight
              dateto = Time(TimeTradeServer(),class="num">23,class="num">59);
            }
          class=class="str">"cmt">//--- Re-adjust DateFrom Array size
          ArrayResize(DateFrom,class="type">int(ArraySize(DateFrom))+class="num">1,class="type">int(ArraySize(DateFrom))+class="num">2);
          class=class="str">"cmt">//--- Assign the last array index datefrom value
          DateFrom[class="type">int(ArraySize(DateFrom))-class="num">1] = datefrom;
          class=class="str">"cmt">//--- Re-adjust DateTo Array size

◍ 交易时段边界与结束判定的数组处理

在 MT5 里用 CSessions 类管理交易时段,核心是把 SymbolInfoSessionTrade 取出的 datefrom / dateto 塞进动态数组再逐段比对。原文对跨午夜的时段做了特殊处理:若 dateto 的小时是 0 或 24,就把它改写成当天 23:59,避免凌晨边界被漏判。 isSessionEnd 默认 offsetmin=45,意味着在官方收盘时间前 45 分钟就可能触发“时段结束”逻辑,这对贵金属跳空和外盘流动性萎缩有实际意义——高风险品种尤其要自己调这个参数。 数组扩容用 ArrayResize(DateTo, size+1, size+2),第二参是新的总大小,第三参是预留内存上限,能少几次重分配。下面这段是原文里时段判定与结束检测的交接代码,可直接粘到 MQ5 里看行为。

MQL5 / C++
   ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2);
   class=class="str">"cmt">//--- Assign the last array index dateto value
   DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto;
   }
   }
class=class="str">"cmt">//--- Check if there are session times
   if(DateFrom.Size()>class="num">0)
   {
   class=class="str">"cmt">/* Adjust DateFrom index zero date as the first index date will be the earliest date
   from the whole array, we add the offset to this date only*/
   DateFrom[class="num">0] = TimePlusOffset(DateFrom[class="num">0],MinutesS(startoffsetmin));
   DateFrom[class="num">0] = TimePlusOffset(DateFrom[class="num">0],HoursS(startoffsethour));
   class=class="str">"cmt">//--- Iterate through the whole array
   for(class="type">uint i=class="num">0; i<DateFrom.Size(); i++)
     {
     class=class="str">"cmt">//--- Check if the current time is within the trading session
     if(TimeIsInRange(DateFrom[i],DateTo[i]))
       {
       class="kw">return true;
       }
     }
   }
   else
   {
   class=class="str">"cmt">//--- If there are no trading session times
   class="kw">return true;
   }
   class="kw">return class="kw">false;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Check if trading Session has ended                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSessions::isSessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45)
  {
class=class="str">"cmt">//--- Declarations
  class="type">class="kw">datetime datefrom,dateto,DateTo[],lastdate=class="num">0,sessionend;
class=class="str">"cmt">//--- Find all session times
  for(class="type">int i=class="num">0; i<class="num">10; i++)
    {
    class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week
    if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto))
      {
      class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight
      if(ReturnHour(dateto)==class="num">00||ReturnHour(dateto)==class="num">24)
        {
        class=class="str">"cmt">//--- Adjust the date to one minute before midnight
        dateto = Time(TimeTradeServer(),class="num">23,class="num">59);
        }
      class=class="str">"cmt">//--- Re-adjust DateTo Array size
      ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2);
      class=class="str">"cmt">//--- Assign the last array index dateto value
      DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto;
      }
    }
class=class="str">"cmt">//--- Check if there are session times

「从交易时段数组里抠出收盘边界」

MT5 里 SymbolInfoSessionTrade 一次只吐一个时段,想拿当天全部交易 session 的结束时间,得用 0~9 的索引循环去问。代码里写死 for(int i=0;i<10;i++),意味着单品种单日最多认 10 个交易分段,超出的会被静默忽略——做跨市场品种时要留意这个上限。 如果某段结束时间落在 0 点或者 24 点(即跨日无缝),代码不认它当午夜,而是用 Time(TimeTradeServer(),23,59) 强行改成当天 23:59。这一步是防止后面算 sessionend 时把日期推到错误的一天。 拿到所有 dateto 后塞进 DateTo[] 动态数组,再用 ArrayResize 每次扩 1~2 个槽位。随后那段找最大日期的循环,本质是在多个分段里挑最晚收盘的那个——逻辑不复杂,但如果你自己写 EA,直接抄这个数组扩容写法比用 ArrayFree 重来要省事。 外汇和贵金属保证金交易杠杆高、滑点可能在休市前后放大,用这套时段判断去卡单子进场,仍可能踩到流动性真空,概率上不是百分百避坑。

MQL5 / C++
class="type">class="kw">datetime CSessions::SessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45)
  {
class=class="str">"cmt">//--- Declarations
  class="type">class="kw">datetime datefrom,dateto,DateTo[],lastdate=class="num">0,sessionend;
class=class="str">"cmt">//--- Find all session times
  for(class="type">int i=class="num">0;i<class="num">10;i++)
    {
    class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week
    if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto))
      {
      class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight
      if(CTV.ReturnHour(dateto)==class="num">00||CTV.ReturnHour(dateto)==class="num">24)
        {
        class=class="str">"cmt">//--- Adjust the date to one minute before midnight
        dateto = Time(TimeTradeServer(),class="num">23,class="num">59);
        }
      class=class="str">"cmt">//--- Re-adjust DateTo Array size
      ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2);
      class=class="str">"cmt">//--- Assign the last array index dateto value
      DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto;
      }
    }
class=class="str">"cmt">//--- Check if there are session times
  if(DateTo.Size()>class="num">0)
    {
    class=class="str">"cmt">//--- Assign lastdate a class="kw">default value
    lastdate = DateTo[class="num">0];

从交易日历里抠出会话结束时间

这段逻辑干的事很直接:先遍历 DateTo 数组,把里面最大的日期挑出来当作 lastdate,也就是最近一个交易会话的截止日。如果数组里压根没有会话时间,函数直接 return 0,避免后面算出莫名其妙的 datetime。 拿到 lastdate 之后,用 ReturnHour 和 ReturnMinute 把时、分拆出来,再交给 Today() 重组成一个带具体时分的新 datetime 赋给 sessionend。这一步等于把“某天”变成了“某天的某个收盘时刻”。 sessionend 还不是最终值。代码接着调了两次 TimeMinusOffset,分别减去 offsetmin 分钟和 offsethour 小时——这是给用户留的偏移口子,比如夏令时切换或平台延迟结算都能靠这俩参数微调。外汇和贵金属这类高杠杆品种,会话边界差几分钟就可能踏空一两根 K 线,调参前建议在 MT5 策略测试器里用真实品种日历跑一遍确认。 下面把核心片段拆开看:

MQL5 / C++
  class=class="str">"cmt">//--- Iterate through the whole array
  for(class="type">uint i=class="num">0; i<DateTo.Size(); i++)
    {
     class=class="str">"cmt">//--- Check for the latest date in the array
     if(DateTo[i]>lastdate)
       {
        lastdate = DateTo[i];
       }
    }
   }
  else
   {
    class=class="str">"cmt">//--- If there are no trading session times
    class="kw">return class="num">0;
   }
  class=class="str">"cmt">/* get the current time and modify the hour and minute time to the lastdate variable
  and assign the new class="type">class="kw">datetime to sessionend variable*/
  sessionend = Today(ReturnHour(lastdate),ReturnMinute(lastdate));
class=class="str">"cmt">//--- Re-adjust the sessionend dates with the minute and hour offsets
  sessionend = TimeMinusOffset(sessionend,MinutesS(offsetmin));
  sessionend = TimeMinusOffset(sessionend,HoursS(offsethour));
class=class="str">"cmt">//--- class="kw">return sessionend date
  class="kw">return sessionend;
  }

◍ 把新闻日历搬进内存:回测不再被硬盘拖后腿

新闻类是整个项目里代码量最重的一块,单类就突破 1000 行。这一节我们不再只依赖公共文件夹里的存储型日历库,而是额外建一个常驻 RAM 的内存数据库——核心动机很直接:回测时若只靠磁盘库,性能会随你电脑配置大幅波动,内存库能把这件事拉平。 内存库相对存储库的优势就两点硬指标:读写走 RAM,延迟显著低于磁盘;吞吐量更高,每秒能扛更多事务。我们仍从存储库抽数灌进内存库,这样回测时既能实时取数又不被本地 IO 拖死。 类外先声明 EA 输入用的句柄与枚举:DBMemoryConnection 存内存库整数连接句柄;Calendar_Importance / Event_Sector / Event_Frequency / Event_Type / Event_Currency 五个枚举分别管事件重要性、领域、频率、类型、货币。UpcomingNews 这个 Calendar 结构放在类外,专门接下一个经济事件明细,别的文件直接读它。 结构数组 CalendarContents 容量从 10 扩到 12,多出的两个视图是 EventInfo_View(事件明细展示)和 Currencies_View(MQL5 日历可用货币)。类里新挂 DBMemory(MQL5CalendarContents 结构)、CalendarData(存公共库全量过滤数据),以及一批 Request_* 函数把枚举翻成 SQL 字符串,GetCalendar 负责按 EA 输入从存储库抽数进 CalendarData.Data。 GetCalendar 的 SQL 把 MQL5Calendar 和 TimeSchedule 按 ID 内连接,再用输入枚举过滤。一个细节:当重要性选“全部”时,故意不转 myImportance,因为库里没有 'Calendar_Importance_All' 这种值,直接取“不等于 All”最省事;选了具体档位才转成 ENUM_CALENDAR_EVENT_IMPORTANCE 去精确匹配。 构造函数里给 EventInfo_View 初始化索引,它的 SQL 把 EVENTTYPE / EVENTSECTOR / EVENTIMPORTANCE 等列前缀 'CALENDAR_*_' 替换掉并改名,按 Name 分组去重,排序先 Country 升序、再 Importance 优先级(HIGH>MODERATE>LOW>其他)、最后 Sector 降序。Currencies_View 则只取 EventCurrency 和 EventCode 的唯一值。 析构时关内存库连接即删库,所以只在不用时才关。UpdateRecords 原三步检查(库存在?对象/SQL 符合?Records 日期=今天?)之外,新增一步:用 EconomicDetails 拉当天新闻存 TodayNews,逐条在 Calendar_NONE 视图里找匹配,只要有一条对不上就触发存储库更新——因为 MQL5 日历源数据会随时间变。 建内存库的函数先开连接、删旧 MQL5Calendar 表、建新表,再把 GetCalendar 的数据插进去,随后清空 DB_Data 并设 MySchedule 的 DST 计划;实盘下不允许手动改 DST,防配错,仅策略测试器里才需要动。 EconomicDetailsMemory 用 WITH 子句(CTE)加 RANK() 窗口函数按 DST_NONE 事件时间排名:同时间多个事件时,EventPrevalue='None' 排 2,其余排 1,只留排名<2 的,避免图表上同一时间叠多个对象。EconomicNextEvent 查询逻辑相同但 LIMIT 1,结果写进类外的 UpcomingNews。外汇与贵金属事件驱动波动剧烈,内存库只是提速手段,信号真假仍靠你自己验证。

「用历史同类事件反推新闻影响等级」

新闻事件的影响枚举 ENUM_CALENDAR_EVENT_IMPACT 在事件未发生当天通常记为 CALENDAR_IMPACT_NA,也就是不可用。只有事件日期过去、且事前同时具备前值(Prev)与预测值(Forecast)之后,系统才可能回填影响;即便条件齐全,少数事件仍会留空。 GetImpact 的逻辑是:当即将发生事件的前值与预测均非 None,就先比大小——前值小于、大于或等于预测,分别对应三种关系。随后在内存库 MQL5Calendar 里翻找相同 EventId、且前值与预测关系一致的「最近一条」历史事件,把它留下的影响等级借过来,作为本次事件的预估影响。 影响本身是按货币衡量的。例如失业率事件,若历史同类前低预高、影响为 CALENDAR_IMPACT_NEGATIVE 且货币是 USD,则美元当时倾向走弱;事件后做 EURUSD,理论上看 EUR 对 USD 升值的概率更高。外汇与贵金属受此类新闻冲击波动剧烈,属高风险场景,历史映射仅作概率参考。 实际 SQL 形态如:选 EventImpact,限定日期早于『2024.08.01 16:30』、EventId=124500001、EventPrevalue<EventForecast 且影响非 NA,按日期降序取一条。下面这段代码给出了日历筛选常用的枚举骨架,可直接拷进 MT5 看结构。

MQL5 / C++
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DBMemoryConnection;<span class="comment">class=class="str">"cmt">//In memory database handle</span>
<span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Importance</span>
<span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Calendar_Importance</span>
&nbsp;&nbsp;{
&nbsp;&nbsp; Calendar_Importance_None,<span class="comment">class=class="str">"cmt">//NONE</span>
&nbsp;&nbsp; Calendar_Importance_Low,<span class="comment">class=class="str">"cmt">//LOW</span>
&nbsp;&nbsp; Calendar_Importance_Moderate,<span class="comment">class=class="str">"cmt">//MODERATE</span>
&nbsp;&nbsp; Calendar_Importance_High,<span class="comment">class=class="str">"cmt">//HIGH</span>
&nbsp;&nbsp; Calendar_Importance_All<span class="comment">class=class="str">"cmt">//ALL</span>
&nbsp;&nbsp;} myImportance;
<span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Sector</span>
<span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Sector</span>
&nbsp;&nbsp;{
&nbsp;&nbsp; Event_Sector_None,<span class="comment">class=class="str">"cmt">//NONE</span>
&nbsp;&nbsp; Event_Sector_Market,<span class="comment">class=class="str">"cmt">//MARKET</span>
&nbsp;&nbsp; Event_Sector_Gdp,<span class="comment">class=class="str">"cmt">//GDP</span>
&nbsp;&nbsp; Event_Sector_Jobs,<span class="comment">class=class="str">"cmt">//JOBS</span>
&nbsp;&nbsp; Event_Sector_Prices,<span class="comment">class=class="str">"cmt">//PRICES</span>
&nbsp;&nbsp; Event_Sector_Money,<span class="comment">class=class="str">"cmt">//MONEY</span>
&nbsp;&nbsp; Event_Sector_Trade,<span class="comment">class=class="str">"cmt">//TRADE</span>
&nbsp;&nbsp; Event_Sector_Government,<span class="comment">class=class="str">"cmt">//GOVERNMENT</span>
&nbsp;&nbsp; Event_Sector_Business,<span class="comment">class=class="str">"cmt">//BUSINESS</span>
&nbsp;&nbsp; Event_Sector_Consumer,<span class="comment">class=class="str">"cmt">//CONSUMER</span>
&nbsp;&nbsp; Event_Sector_Housing,<span class="comment">class=class="str">"cmt">//HOUSING</span>
&nbsp;&nbsp; Event_Sector_Taxes,<span class="comment">class=class="str">"cmt">//TAXES</span>
&nbsp;&nbsp; Event_Sector_Holidays,<span class="comment">class=class="str">"cmt">//HOLIDAYS</span>
&nbsp;&nbsp; Event_Sector_ALL<span class="comment">class=class="str">"cmt">//ALL</span>
&nbsp;&nbsp;} mySector;
<span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Event Frequency</span>
<span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Frequency</span>
&nbsp;&nbsp;{
&nbsp;&nbsp; Event_Frequency_None,<span class="comment">class=class="str">"cmt">//NONE</span>
&nbsp;&nbsp; Event_Frequency_Week,<span class="comment">class=class="str">"cmt">//WEEK</span>
&nbsp;&nbsp; Event_Frequency_Month,<span class="comment">class=class="str">"cmt">//MONTH</span>
&nbsp;&nbsp; Event_Frequency_Quarter,<span class="comment">class=class="str">"cmt">//QUARTER</span>
&nbsp;&nbsp; Event_Frequency_Year,<span class="comment">class=class="str">"cmt">//YEAR</span>
&nbsp;&nbsp; Event_Frequency_Day,<span class="comment">class=class="str">"cmt">//DAY</span>
&nbsp;&nbsp; Event_Frequency_ALL<span class="comment">class=class="str">"cmt">//ALL</span>
&nbsp;&nbsp;} myFrequency;
<span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Event type</span>
<span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Type</span>
&nbsp;&nbsp;{
&nbsp;&nbsp; Event_Type_Event,<span class="comment">class=class="str">"cmt">//EVENT</span>
&nbsp;&nbsp; Event_Type_Indicator,<span class="comment">class=class="str">"cmt">//INDICATOR</span>
&nbsp;&nbsp; Event_Type_Holiday,<span class="comment">class=class="str">"cmt">//HOLIDAY</span>
&nbsp;&nbsp; Event_Type_All<span class="comment">class=class="str">"cmt">//ALL</span>
&nbsp;&nbsp;} myType;
<span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Event Currency</span>
<span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Currency</span>
&nbsp;&nbsp;{
&nbsp;&nbsp; Event_Currency_Symbol,<span class="comment">class=class="str">"cmt">//SYMBOL CURRENCIES</span>
&nbsp;&nbsp; Event_Currency_Margin,<span class="comment">class=class="str">"cmt">//SYMBOL MARGIN</span>
&nbsp;&nbsp; Event_Currency_Base,<span class="comment">class=class="str">"cmt">//SYMBOL BASE</span>
&nbsp;&nbsp; Event_Currency_Profit,<span class="comment">class=class="str">"cmt">//SYMBOL PROFIT</span>
&nbsp;&nbsp; Event_Currency_ALL,<span class="comment">class=class="str">"cmt">//ALL CURRENCIES</span>
&nbsp;&nbsp; Event_Currency_NZD_NZ,<span class="comment">class=class="str">"cmt">//NZD -&gt; NZ</span>
&nbsp;&nbsp; Event_Currency_EUR_EU,<span class="comment">class=class="str">"cmt">//EUR -&gt; EU</span>
&nbsp;&nbsp; Event_Currency_JPY_JP,<span class="comment">class=class="str">"cmt">//JPY -&gt; JP</span>
&nbsp;&nbsp; Event_Currency_CAD_CA,<span class="comment">class=class="str">"cmt">//CAD -&gt; CA</span>
&nbsp;&nbsp; Event_Currency_AUD_AU,<span class="comment">class=class="str">"cmt">//AUD -&gt; AU</span>
&nbsp;&nbsp; Event_Currency_CNY_CN,<span class="comment">class=class="str">"cmt">//CNY -&gt; CN</span>
&nbsp;&nbsp; Event_Currency_EUR_IT,<span class="comment">class=class="str">"cmt">//EUR -&gt; IT</span>
&nbsp;&nbsp; Event_Currency_SGD_SG,<span class="comment">class=class="str">"cmt">//SGD -&gt; SG</span>

财经事件库里的币种与表结构枚举

在 MT5 自建财经日历模块时,先把涉及的经济体币种写成枚举是最省事的做法。下面这段把 EUR、USD、GBP 等 15 组货币对归属地列进 myCurrency 枚举,最后用 Event_Currency_ALL_WW 兜底全球事件,开 MT5 新建 enum 直接照抄就能用。

MQL5 / C++
  Event_Currency_EUR_DE,class=class="str">"cmt">//EUR -> DE
  Event_Currency_EUR_FR,class=class="str">"cmt">//EUR -> FR
  Event_Currency_BRL_BR,class=class="str">"cmt">//BRL -> BR
  Event_Currency_MXN_MX,class=class="str">"cmt">//MXN -> MX
  Event_Currency_ZAR_ZA,class=class="str">"cmt">//ZAR -> ZA
  Event_Currency_HKD_HK,class=class="str">"cmt">//HKD -> HK
  Event_Currency_INR_IN,class=class="str">"cmt">//INR -> IN
  Event_Currency_NOK_NO,class=class="str">"cmt">//NOK -> NO
  Event_Currency_USD_US,class=class="str">"cmt">//USD -> US
  Event_Currency_GBP_GB,class=class="str">"cmt">//GBP -> GB
  Event_Currency_CHF_CH,class=class="str">"cmt">//CHF -> CH
  Event_Currency_KRW_KR,class=class="str">"cmt">//KRW -> KW
  Event_Currency_EUR_ES,class=class="str">"cmt">//EUR -> ES
  Event_Currency_SEK_SE,class=class="str">"cmt">//SEK -> SE
  Event_Currency_ALL_WWclass=class="str">"cmt">//ALL -> WW
 } myCurrency;
日历数据落地到 SQLite 后,靠 CalendarComponents 枚举追踪库里有哪些对象:从 AutoDST_Table 到 MQL5Calendar_Table 共 12 类视图与表,外加两个触发器。CNews 类私有继承 CCandleProperties,用 SQLiteMaster 结构把 sqlite_master 的 type/name/sql 等字段映射成数组,再派生 MQL5CalendarContents 存插入语句——CalendarContents[12] 这个定长数组就是内存里库结构的镜像。 实盘接这类日历信号做外汇或贵金属,跳空与滑点风险偏高,枚举里的 KRW 注释写成 KW 属原文笔误,复制时建议顺手改回 KRW -> KR,避免后续字符串匹配失效。

MQL5 / C++
  Event_Currency_EUR_DE,class=class="str">"cmt">//EUR -> DE
  Event_Currency_EUR_FR,class=class="str">"cmt">//EUR -> FR
  Event_Currency_BRL_BR,class=class="str">"cmt">//BRL -> BR
  Event_Currency_MXN_MX,class=class="str">"cmt">//MXN -> MX
  Event_Currency_ZAR_ZA,class=class="str">"cmt">//ZAR -> ZA
  Event_Currency_HKD_HK,class=class="str">"cmt">//HKD -> HK
  Event_Currency_INR_IN,class=class="str">"cmt">//INR -> IN
  Event_Currency_NOK_NO,class=class="str">"cmt">//NOK -> NO
  Event_Currency_USD_US,class=class="str">"cmt">//USD -> US
  Event_Currency_GBP_GB,class=class="str">"cmt">//GBP -> GB
  Event_Currency_CHF_CH,class=class="str">"cmt">//CHF -> CH
  Event_Currency_KRW_KR,class=class="str">"cmt">//KRW -> KW
  Event_Currency_EUR_ES,class=class="str">"cmt">//EUR -> ES
  Event_Currency_SEK_SE,class=class="str">"cmt">//SEK -> SE
  Event_Currency_ALL_WWclass=class="str">"cmt">//ALL -> WW
 } myCurrency;
class=class="str">"cmt">//--- Structure variable to store Calendar next Event data
Calendar UpcomingNews;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|News class                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CNews : class="kw">private CCandleProperties
  {
  class=class="str">"cmt">//Private Declarations Only accessable by this class/header file
class="kw">private:
  class=class="str">"cmt">//-- To keep track of what is in our database
  enum CalendarComponents
    {
      AutoDST_Table,class=class="str">"cmt">//AutoDST Table
      CalendarAU_View,class=class="str">"cmt">//View for DST_AU
      CalendarNONE_View,class=class="str">"cmt">//View for DST_NONE
      CalendarUK_View,class=class="str">"cmt">//View for DST_UK
      CalendarUS_View,class=class="str">"cmt">//View for DST_US
      EventInfo_View,class=class="str">"cmt">//View for Event Information
      Currencies_View,class=class="str">"cmt">//View for Currencies
      Record_Table,class=class="str">"cmt">// Record Table
      TimeSchedule_Table,class=class="str">"cmt">//TimeSchedule Table
      MQL5Calendar_Table,class=class="str">"cmt">//MQL5Calendar Table
      AutoDST_Trigger,class=class="str">"cmt">//Table Trigger for AutoDST
      Record_Triggerclass=class="str">"cmt">//Table Trigger for Record
    };
  class=class="str">"cmt">//-- structure to retrieve all the objects in the database
  class="kw">struct SQLiteMaster
    {
      class="type">class="kw">string      type;class=class="str">"cmt">//will store object&class="macro">#x27;s type
      class="type">class="kw">string      name;class=class="str">"cmt">//will store object&class="macro">#x27;s name
      class="type">class="kw">string      tbl_name;class=class="str">"cmt">//will store table name
      class="type">int         rootpage;class=class="str">"cmt">//will store rootpage
      class="type">class="kw">string      sql;class=class="str">"cmt">//Will store the sql create statement
    } DBContents[];class=class="str">"cmt">//Array of type SQLiteMaster
  class=class="str">"cmt">//--  MQL5CalendarContents inherits from SQLiteMaster structure
  class="kw">struct MQL5CalendarContents:SQLiteMaster
    {
      CalendarComponents  Content;
      class="type">class="kw">string      insert;class=class="str">"cmt">//Will store the sql insert statement
    } CalendarContents[class="num">12],DBMemory;class=class="str">"cmt">//Array to Store objects in our database
  CTimeManagement    CTime;class=class="str">"cmt">//TimeManagement Object declaration
  CDaylightSavings_UK  Savings_UK;class=class="str">"cmt">//DaylightSavings Object for the UK and EU
  CDaylightSavings_US  Savings_US;class=class="str">"cmt">//DaylightSavings Object for the US

◍ 把财经日历塞进本地数据库的类骨架

在 MT5 里做事件驱动策略,第一步往往是把 MQL5 自带的财经日历抓出来落盘。下面这段类声明给出了一个可直接复用的骨架:它同时管时区(含澳洲夏令时对象)、DST 自动识别,以及把事件写进 SQLite 一类数据库的几张表。 类里先挂了 CDaylightSavings_AU Savings_AU 用来处理澳洲经纪商夏令时偏移;AutoDetectDST 通过引用 DST_type 枚举回吐当前识别到的经纪商 DST 类型。InsertIntoTables 接收 Calendar 数组和数据库句柄,把一条条事件批量写表。 CreateAutoDST 会在数据表里写入系统推荐的经纪商 DST;CreateCalendarTable / CreateTimeTable 都带 tableExists 引用参数,避免重复建表报错。CreateCalendarViews 建视图方便按国家、币种筛数据,CreateRecordTable 则记最后一次更新时间,作为增量刷新依据。 CalendarStruct 是个轻量查找函数:遍历 CalendarContents 数组,比对 Content 枚举,命中就返回对应的 MQL5CalendarContents。它不查库,纯内存匹配,在 OnTimer 里反复调也几乎零开销。 真正存数的结构是 CalendarData:从 EventId、Country 到 EventForecast / EventPreval / EventImpact 共 14 个字段,覆盖事件定位、重要度、预期与前值。你照这个 struct 建表,就能用 EA 把每次非农、利率决议的原始字段全留痕,后续回测不用再依赖实时 API。

MQL5 / C++
  CDaylightSavings_AU      Savings_AU;class=class="str">"cmt">//DaylightSavings Object for the AU
  class="type">bool                    AutoDetectDST(DST_type &dstType);class=class="str">"cmt">//Function will determine Broker DST
  DST_type                DSTType;class=class="str">"cmt">//variable of DST_type enumeration declared in the CommonVariables class/header file
  class="type">bool                    InsertIntoTables(class="type">int db,Calendar &Evalues[]);class=class="str">"cmt">//Function for inserting Economic Data in to a database&class="macro">#x27;s table
  class="type">void                    CreateAutoDST(class="type">int db);class=class="str">"cmt">//Function for creating and inserting Recommend DST for the Broker into a table
  class="type">bool                    CreateCalendarTable(class="type">int db,class="type">bool &tableExists);class=class="str">"cmt">//Function for creating a table in a database
  class="type">bool                    CreateTimeTable(class="type">int db,class="type">bool &tableExists);class=class="str">"cmt">//Function for creating a table in a database
  class="type">void                    CreateCalendarViews(class="type">int db);class=class="str">"cmt">//Function for creating views in a database
  class="type">void                    CreateRecordTable(class="type">int db);class=class="str">"cmt">//Creates a table to store the record of when last the Calendar database was updated/created
  class="type">class="kw">string                  DropRequest;class=class="str">"cmt">//Variable for dropping tables in the database
  class=class="str">"cmt">//-- Function for retrieving the MQL5CalendarContents structure for the enumartion type CalendarComponents
  MQL5CalendarContents CalendarStruct(CalendarComponents Content)
    {
     MQL5CalendarContents Calendar;
     for(class="type">uint i=class="num">0;i<CalendarContents.Size();i++)
       {
        if(CalendarContents[i].Content==Content)
          {
           class="kw">return CalendarContents[i];
          }
       }
     class="kw">return Calendar;
    }
  class=class="str">"cmt">//--- To Store Calendar DB Data
  class="kw">struct CalendarData
    {
     class="type">int          EventId;class=class="str">"cmt">//Event Id
     class="type">class="kw">string       Country;class=class="str">"cmt">//Event Country
     class="type">class="kw">string       EventName;class=class="str">"cmt">//Event Name
     class="type">class="kw">string       EventType;class=class="str">"cmt">//Event Type
     class="type">class="kw">string       EventImportance;class=class="str">"cmt">//Event Importance
     class="type">class="kw">string       EventCurrency;class=class="str">"cmt">//Event Currency
     class="type">class="kw">string       EventCode;class=class="str">"cmt">//Event Code
     class="type">class="kw">string       EventSector;class=class="str">"cmt">//Event Sector
     class="type">class="kw">string       EventForecast;class=class="str">"cmt">//Event Forecast Value
     class="type">class="kw">string       EventPreval;class=class="str">"cmt">//Event Previous Value
     class="type">class="kw">string       EventImpact;class=class="str">"cmt">//Event Impact
     class="type">class="kw">string       EventFrequency;class=class="str">"cmt">//Event Frequency

「用 SQL 把财经日历滤进内存结构」

在 MT5 里做事件驱动型策略,第一步是把存在公共目录的日历库读进内存。下面这段结构定义了四个夏令时字段 DST_UK / DST_US / DST_AU / DST_NONE,并和主事件表 MQ 通过 ID 内连接,读者可以直接照抄字段命名。

GetCalendar 函数先用 DatabaseOpen 以读写的公共模式开库,三种 flag 并用:DATABASE_OPEN_READWRITEDATABASE_OPEN_CREATEDATABASE_OPEN_COMMON。若句柄为 INVALID_HANDLE 且 FileIsExist 在 FILE_COMMON 下也找不到文件,直接 return,避免后续空指针崩在 SQL 阶段。

真正取数靠一条 Inner Join 的 SELECT,把事件 ID、国家、币种、重要性、预测值、前值以及四套夏令时偏移一次性拉出。过滤条件由 Request_Importance / Request_Frequency / Request_Sector / Request_Type / Request_Currency 五个函数动态拼装,意味着你改一个货币对参数就能把数据量从全量压到几百行。 执行侧用 DatabasePrepare 编译 SQL,返回 INVALID_HANDLE 时 Print 出错误码。外汇与贵金属受数据行情跳空影响大,这类日历过滤只是前置,实盘前务必在策略测试器用历史新闻回放验证触发逻辑。

MQL5 / C++
      class="type">class="kw">string        DST_UK;class=class="str">"cmt">//DST UK
      class="type">class="kw">string        DST_US;class=class="str">"cmt">//DST US
      class="type">class="kw">string        DST_AU;class=class="str">"cmt">//DST AU
      class="type">class="kw">string        DST_NONE;class=class="str">"cmt">//DST NONE
   } DB_Data[],DB_Cal;class=class="str">"cmt">//Structure variables
   class=class="str">"cmt">//--- Will Retrieve all relevant Calendar data for DB in Memory from DB in Storage
   class="type">void              GetCalendar(CalendarData &Data[])
   {
   class=class="str">"cmt">//--- Open calendar DB in Storage
   class="type">int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE| DATABASE_OPEN_COMMON);
   if(db==INVALID_HANDLE)class=class="str">"cmt">//Checks if the database was able to be opened
     {
     class=class="str">"cmt">//if opening the database failed
     if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))class=class="str">"cmt">//Checks if the database Calendar exists in the common folder
       {
       class="kw">return;class=class="str">"cmt">//Returns true when the database was failed to be opened and the file doesn&class="macro">#x27;t exist in the common folder
       }
     }
   class=class="str">"cmt">//--- Get filtered calendar DB data
   class="type">class="kw">string SqlRequest = StringFormat("Select MQ.EventId,MQ.Country,MQ.EventName,MQ.EventType,MQ.EventImportance,MQ.EventCurrency,"
                                   "MQ.EventCode,MQ.EventSector,MQ.EventForecast,MQ.EventPreValue,MQ.EventImpact,MQ.EventFrequency,"
                                   "TS.DST_UK,TS.DST_US,TS.DST_AU,TS.DST_NONE from %s MQ "
                                   "Inner Join %s TS on TS.ID=MQ.ID "
                                   "Where %s and %s and %s and %s and %s;",
                                   CalendarStruct(MQL5Calendar_Table).name,CalendarStruct(TimeSchedule_Table).name,
                                   Request_Importance(myImportance),Request_Frequency(myFrequency),
                                   Request_Sector(mySector),Request_Type(myType),Request_Currency(myCurrency));
   class=class="str">"cmt">//--- Process Sql request
   class="type">int Request = DatabasePrepare(db,SqlRequest);
   if(Request==INVALID_HANDLE)
     {
     class=class="str">"cmt">//--- Print details if request failed.
     Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());

从财经日历库里抠出自动 DST 标记

在 MT5 里做跨时区新闻过滤,最怕夏令时切换把日历事件时间整体偏移一小时。上面这段逻辑直接打开公共目录下的 Calendar 库(NEWS_DATABASE_FILE),用只读方式取 AutoDST 表里的 DST 字段,返回枚举值供后续判断。 打开库先走 DatabaseOpen,带 DATABASE_OPEN_READONLY| DATABASE_OPEN_COMMON 标志;若句柄等于 INVALID_HANDLE,再查 FileIsExist 确认库文件是否真缺失,缺失就 Print("Could not find Database!") 并返回 DST_NONE。这一层容错能保证 EA 在终端没下载财经库时不崩。 SQL 请求写死为 "SELECT DST FROM 'AutoDST'",经 DatabasePrepare 编译。若返回 INVALID_HANDLE,打错误码并关库回退 DST_NONE。 DatabaseRead 成功后才用 DatabaseColumnText(request,0,Sch_Dst) 抓第一列文本;若这步失败,错误码会被 Print 出来。整个过程不碰写权限,实盘跑前你可在策略测试器里把 NEWS_DATABASE_FILE 指到本地副本,验证返回值是 DST_NONE 还是具体偏移枚举。外汇与贵金属受数据跳空影响大,夏令时误判可能让止损挂在错的时间位,属高风险操作。

MQL5 / C++
  DST_type        GetAutoDST()
    {
     class="type">class="kw">string Sch_Dst;
     class=class="str">"cmt">//--- open the database &class="macro">#x27;Calendar&class="macro">#x27; in the common folder
     class="type">int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READONLY|DATABASE_OPEN_COMMON);
     if(db==INVALID_HANDLE)class=class="str">"cmt">//Checks if &class="macro">#x27;Calendar&class="macro">#x27; failed to be opened
       {
        if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))class=class="str">"cmt">//Checks if &class="macro">#x27;Calendar&class="macro">#x27; database exists
          {
           Print("Could not find Database!");
           class="kw">return DST_NONE;class=class="str">"cmt">//class="kw">return class="kw">default value when failed.
          }
       }
     class=class="str">"cmt">//--- Sql query to get AutoDST value
     class="type">class="kw">string request_text="SELECT DST FROM &class="macro">#x27;AutoDST&class="macro">#x27;";
     class=class="str">"cmt">//--- Process sql request
     class="type">int request=DatabasePrepare(db,request_text);
     if(request==INVALID_HANDLE)
       {
        Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
        DatabaseClose(db);class=class="str">"cmt">//Close Database
        class="kw">return DST_NONE;class=class="str">"cmt">//class="kw">return class="kw">default value when failed.
       }
     class=class="str">"cmt">//--- Read Sql request output data
     if(DatabaseRead(request))
       {
        class=class="str">"cmt">//-- Store the first column data into class="type">class="kw">string variable Sch_Dst
        if(!DatabaseColumnText(request,class="num">0,Sch_Dst))
          {
           Print("DatabaseRead() failed with code ", GetLastError());

◍ 正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">DatabaseFinalize</span>(request);<span class="comment">//Finalize request</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">DatabaseClose</span>(db);<span class="comment">//Closes the database 'Calendar'</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> DST_NONE;<span class="comment">//return default value when failed.</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">DatabaseFinalize</span>(request);<span class="comment">//Finalize request</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">DatabaseClose</span>(db);<span class="comment">//Closes the database 'Calendar'</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> (Sch_Dst==<span class="string">"DST_UK"</span>)?DST_UK:(Sch_Dst==<span class="string">"DST_US"</span>)?DST_US: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Sch_Dst==<span class="string">"DST_AU"</span>)?DST_AU:DST_NONE;<span class="comment">//Returns the enumeration value for each corresponding string</span> &nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp; <span class="comment">//--- Retrieve Sql request string for calendar event Importance</span> &nbsp;&nbsp;

「经济日历 SQL 过滤串的拼装逻辑」

在 MT5 里抓取财经日历数据,核心是把枚举参数翻译成可被内部数据库识别的 SQL 请求串。下面这段分别按板块、类型、货币三个维度拼条件,读者可直接抄进 EA 的日历筛选模块。 Request_Sector 用 switch 区分全板块与具体板块:选 ALL 时返回 MQ.EventSector<>'枚举值',即排除自身占位;其他情况用 MQ.EventSector='枚举值' 精确锁定。注意 EnumToString(mySector)SECTOR(mySector) 两种写法并存,说明代码里对枚举到字符串的映射做了两层封装。 Request_Type 结构完全一致,常量前缀是 MQ.EventType,ALL 态用不等于号,default 态用等于号。 Request_Currency 稍微复杂:前缀 (MQ.EventCurrency、后缀 ') 成对出现;Event_Currency_Symbol 会一次性拼出基础币、保证金币、利润币三个 OR 条件,例如 EURUSD 可能生成 (MQ.EventCurrency='EUR' or MQ.EventCurrency='USD' or MQ.EventCurrency='USD')。外汇与贵金属受财经事件冲击剧烈,这类筛选失误可能导致信号在重大数据发布时失真,实盘前务必在策略测试器里打印请求串核对。

MQL5 / C++
class=class="str">"cmt">//--- class="kw">switch statement for Event_Sector enumeration
class="kw">switch(Sector)
  {
   case Event_Sector_ALL:class=class="str">"cmt">//String Request for all event sectors
     class="kw">return constant+"<>&class="macro">#x27;"+EnumToString(mySector)+"&class="macro">#x27;";
     class="kw">break;
   class="kw">default:class=class="str">"cmt">//String Request for any event sector
     class="kw">return constant+"=&class="macro">#x27;"+EnumToString(SECTOR(mySector))+"&class="macro">#x27;";
     class="kw">break;
  }
 }
 class=class="str">"cmt">//--- Retrieve Sql request class="type">class="kw">string for calendar event type
 class="type">class="kw">string        Request_Type(Event_Type Type)
  {
   class=class="str">"cmt">//--- Constant request prefix class="type">class="kw">string
   class="kw">const class="type">class="kw">string constant="MQ.EventType";
   class=class="str">"cmt">//--- class="kw">switch statement for Event_Type enumeration
   class="kw">switch(Type)
     {
      case Event_Type_All:class=class="str">"cmt">//String Request for all event types
        class="kw">return constant+"<>&class="macro">#x27;"+EnumToString(myType)+"&class="macro">#x27;";
        class="kw">break;
      class="kw">default:class=class="str">"cmt">//String request for any event type
        class="kw">return constant+"=&class="macro">#x27;"+EnumToString(TYPE(myType))+"&class="macro">#x27;";
        class="kw">break;
     }
  }
 class=class="str">"cmt">//--- Retrieve Sql request class="type">class="kw">string for calendar event Currency
 class="type">class="kw">string        Request_Currency(Event_Currency Currency)
  {
   class=class="str">"cmt">//--- Constant request prefix class="type">class="kw">string and request suffix
   class="kw">const class="type">class="kw">string constant_prefix="(MQ.EventCurrency",constant_suffix="&class="macro">#x27;)";
   class=class="str">"cmt">//--- class="kw">switch statement for Event_Currency enumeration
   class="kw">switch(Currency)
     {
      case Event_Currency_ALL:class=class="str">"cmt">//String Request for all currencies
        class="kw">return constant_prefix+"<>&class="macro">#x27;"+EnumToString(myCurrency)+constant_suffix;
        class="kw">break;
      case Event_Currency_Symbol:class=class="str">"cmt">//String Request for all symbol currencies
        class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyBase()+"&class="macro">#x27; or MQ.EventCurrency=&class="macro">#x27;"+
               CSymbol.CurrencyMargin()+"&class="macro">#x27; or MQ.EventCurrency=&class="macro">#x27;"+CSymbol.CurrencyProfit()+constant_suffix;
        class="kw">break;
      case Event_Currency_Margin:class=class="str">"cmt">//String Request for Margin currency
        class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyMargin()+constant_suffix;
        class="kw">break;

按币种拼经济事件查询串

在 MT5 自定义经济日历抓取模块里,不同币种的事件筛选要靠 switch 分支拼出不同的 SQL 风格请求串。基础货币与利润货币直接取 CSymbol 的成员,而具体币种(如 NZD、JPY)则把货币代码和地区事件码(MQ.EventCode)一起写死进条件。 下面这段分支覆盖了 NZD、EUR(含 EU/IT/DE/FR 多个地区码)、JPY、CAD、AUD、CNY、SGD 等十余个常见货币对关联事件。外汇与贵金属受多国宏观数据驱动,这类筛选若漏掉地区码,可能把无关经济区事件混进信号池,放大噪音。 直接看原分支写法,注意 constant_prefix 与 constant_suffix 通常封装了表名与引号后缀,复制时别丢:

MQL5 / C++
   case Event_Currency_Base:class=class="str">"cmt">//String Request for Base currency
      class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyBase()+constant_suffix;
      class="kw">break;
   case Event_Currency_Profit:class=class="str">"cmt">//String Request for Profit currency
      class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyProfit()+constant_suffix;
      class="kw">break;
   case Event_Currency_NZD_NZ:class=class="str">"cmt">//String Request for NZD currency
      class="kw">return constant_prefix+"=&class="macro">#x27;NZD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;NZ"+constant_suffix;
      class="kw">break;
   case Event_Currency_EUR_EU:class=class="str">"cmt">//String Request for EUR currency and EU code
      class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;EU"+constant_suffix;
      class="kw">break;
   case Event_Currency_JPY_JP:class=class="str">"cmt">//String Request for JPY currency
      class="kw">return constant_prefix+"=&class="macro">#x27;JPY&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;JP"+constant_suffix;
      class="kw">break;
   case Event_Currency_CAD_CA:class=class="str">"cmt">//String Request for CAD currency
      class="kw">return constant_prefix+"=&class="macro">#x27;CAD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;CA"+constant_suffix;
      class="kw">break;
   case Event_Currency_AUD_AU:class=class="str">"cmt">//String Request for AUD currency
      class="kw">return constant_prefix+"=&class="macro">#x27;AUD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;AU"+constant_suffix;
      class="kw">break;
   case Event_Currency_CNY_CN:class=class="str">"cmt">//String Request for CNY currency
      class="kw">return constant_prefix+"=&class="macro">#x27;CNY&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;CN"+constant_suffix;
      class="kw">break;
   case Event_Currency_EUR_IT:class=class="str">"cmt">//String Request for EUR currency and IT code
      class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;IT"+constant_suffix;
      class="kw">break;
   case Event_Currency_SGD_SG:class=class="str">"cmt">//String Request for SGD currency
      class="kw">return constant_prefix+"=&class="macro">#x27;SGD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;SG"+constant_suffix;
      class="kw">break;
   case Event_Currency_EUR_DE:class=class="str">"cmt">//String Request for EUR currency and DE code
      class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;DE"+constant_suffix;
      class="kw">break;
   case Event_Currency_EUR_FR:class=class="str">"cmt">//String Request for EUR currency and FR code
      class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;FR"+constant_suffix;
      class="kw">break;
   case Event_Currency_BRL_BR:class=class="str">"cmt">//String Request for BRL currency

◍ 按币种拼出财经事件查询串

这段 switch 分支负责把枚举型的币种事件映射成 MQ 事件表的 SQL 风格过滤串,前缀和后缀由 constant_prefix / constant_suffix 统一接管,中间只换币种代码与事件国家码。 例如 BRL 分支返回 constant_prefix+'=\'BRL\' and MQ.EventCode=\'BR'+constant_suffix,意味着巴西雷亚尔相关事件在底层数据源里用 BR 作国家前缀。类似地 MXN→MX、ZAR→ZA、HKD→HK、INR→IN、NOK→NO、USD→US、GBP→GB、CHF→CH、KRW→KR、EUR→ES、SEK→SE、ALL→WW,共覆盖了 13 个币种分支。 在 MT5 里接经济日历 API 时,若你只盯美系与欧系,可删掉 BRL/MXN/ZAR 等新兴市场分支,把 switch 缩短到 7 行以内,减少字符串拼接开销。外汇与贵金属受这些事件冲击的概率偏高,实盘前务必在策略测试器里用历史事件回放验证过滤串是否漏抓。

MQL5 / C++
class="kw">return constant_prefix+"=&class="macro">#x27;BRL&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;BR"+constant_suffix;
class="kw">break;
 case Event_Currency_MXN_MX:class=class="str">"cmt">//String Request for MXN currency
 class="kw">return constant_prefix+"=&class="macro">#x27;MXN&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;MX"+constant_suffix;
 class="kw">break;
 case Event_Currency_ZAR_ZA:class=class="str">"cmt">//String Request for ZAR currency
 class="kw">return constant_prefix+"=&class="macro">#x27;ZAR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;ZA"+constant_suffix;
 class="kw">break;
 case Event_Currency_HKD_HK:class=class="str">"cmt">//String Request for HKD currency
 class="kw">return constant_prefix+"=&class="macro">#x27;HKD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;HK"+constant_suffix;
 class="kw">break;
 case Event_Currency_INR_IN:class=class="str">"cmt">//String Request for INR currency
 class="kw">return constant_prefix+"=&class="macro">#x27;INR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;IN"+constant_suffix;
 class="kw">break;
 case Event_Currency_NOK_NO:class=class="str">"cmt">//String Request for NOK currency
 class="kw">return constant_prefix+"=&class="macro">#x27;NOK&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;NO"+constant_suffix;
 class="kw">break;
 case Event_Currency_USD_US:class=class="str">"cmt">//String Request for USD currency
 class="kw">return constant_prefix+"=&class="macro">#x27;USD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;US"+constant_suffix;
 class="kw">break;
 case Event_Currency_GBP_GB:class=class="str">"cmt">//String Request for GBP currency
 class="kw">return constant_prefix+"=&class="macro">#x27;GBP&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;GB"+constant_suffix;
 class="kw">break;
 case Event_Currency_CHF_CH:class=class="str">"cmt">//String Request for CHF currency
 class="kw">return constant_prefix+"=&class="macro">#x27;CHF&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;CH"+constant_suffix;
 class="kw">break;
 case Event_Currency_KRW_KR:class=class="str">"cmt">//String Request for KRW currency
 class="kw">return constant_prefix+"=&class="macro">#x27;KRW&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;KR"+constant_suffix;
 class="kw">break;
 case Event_Currency_EUR_ES:class=class="str">"cmt">//String Request for EUR currency and ES code
 class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;ES"+constant_suffix;
 class="kw">break;
 case Event_Currency_SEK_SE:class=class="str">"cmt">//String Request for SEK currency
 class="kw">return constant_prefix+"=&class="macro">#x27;SEK&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;SE"+constant_suffix;
 class="kw">break;
 case Event_Currency_ALL_WW:class=class="str">"cmt">//String Request for ALL currency
 class="kw">return constant_prefix+"=&class="macro">#x27;ALL&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;WW"+constant_suffix;
 class="kw">break;

「把财经日历重要性字符串映射成枚举」

在 MT5 的财经日历封装类里,公开接口暴露了一组可直接通过对象调用的方法:建库、取最新日期、拉指定区间事件、内存态查询与下次事件预读。其中 IMPORTANCE(string) 是个容易被忽略但很实用的小函数,它负责把外部传进来的字符串转回平台原生的 ENUM_CALENDAR_EVENT_IMPORTANCE 枚举。 逻辑不复杂,用 EnumToString 把枚举值翻成字符串再和入参比对:命中 CALENDAR_IMPORTANCE_HIGH 就回 HIGH,命中 MODERATE 回 MODERATE,命中 LOW 回 LOW,其余落进最后的 else 分支。这样写的好处是,从 CSV 或经纪商接口读到的文本重要性,能无缝喂给 Calendar 系列函数,不必手搓一堆 if 判断。 外汇与贵金属受数据行情冲击明显,高影响事件前后点差可能瞬间放大,用这个类做事件过滤时,建议只对 HIGH/MODERATE 做报警或暂停开仓,LOW 和 NONE 可忽略以降低噪音。 下面这段是原文中 default 前一段分支收尾与公开区声明的节选,可对照看类骨架:

MQL5 / C++
class="kw">default:class=class="str">"cmt">//String Request for no currencies
      class="kw">return constant_prefix+"=&class="macro">#x27;"+constant_suffix;
      class="kw">break;
      }
   }
 class=class="str">"cmt">//Public declarations accessable via a class&class="macro">#x27;s Object
class="kw">public:
             CNews(class="type">void);class=class="str">"cmt">//Constructor
            ~CNews(class="type">void);class=class="str">"cmt">//Destructor
 class="type">void          CreateEconomicDatabase();class=class="str">"cmt">//Creates the Calendar database for a specific Broker
 class="type">class="kw">datetime      GetLatestNewsDate();class=class="str">"cmt">//Gets the latest/newest date in the Calendar database
 class="type">void          EconomicDetails(Calendar &NewsTime[],class="type">class="kw">datetime date_from=class="num">0,class="type">class="kw">datetime date_to=class="num">0);class=class="str">"cmt">//Gets values from the MQL5 economic Calendar
 class="type">void          EconomicDetailsMemory(Calendar &NewsTime[],class="type">class="kw">datetime date);class=class="str">"cmt">//Gets values from the MQL5 DB Calendar in Memory
 class="type">void          CreateEconomicDatabaseMemory();class=class="str">"cmt">//Create calendar database in memory
 class="type">void          EconomicNextEvent(class="type">class="kw">datetime date=class="num">0);class=class="str">"cmt">//Will update UpcomingNews structure variable with the next event data
 class="type">bool          UpdateRecords();class=class="str">"cmt">//Checks if the main Calendar database needs an update or not
 ENUM_CALENDAR_EVENT_IMPACT GetImpact();class=class="str">"cmt">//Will retrieve Upcoming Event Impact data
 class=class="str">"cmt">//--- Convert Importance class="type">class="kw">string into Calendar Event Importance Enumeration
 ENUM_CALENDAR_EVENT_IMPORTANCE IMPORTANCE(class="type">class="kw">string Importance)
  {
  class=class="str">"cmt">//--- Calendar Importance is High
  if(Importance==EnumToString(CALENDAR_IMPORTANCE_HIGH))
   {
   class="kw">return CALENDAR_IMPORTANCE_HIGH;
   }
  else
  class=class="str">"cmt">//--- Calendar Importance is Moderate
   if(Importance==EnumToString(CALENDAR_IMPORTANCE_MODERATE))
    {
    class="kw">return CALENDAR_IMPORTANCE_MODERATE;
    }
   else
   class=class="str">"cmt">//--- Calendar Importance is Low
    if(Importance==EnumToString(CALENDAR_IMPORTANCE_LOW))
     {
     class="kw">return CALENDAR_IMPORTANCE_LOW;
     }
   else
   class=class="str">"cmt">//--- Calendar Importance is None
    {

财经日历重要度的枚举转换与配色

在 MT5 的财经日历接口里,自定义枚举 Calendar_Importance 与系统原生 ENUM_CALENDAR_EVENT_IMPORTANCE 是两套值。写 EA 或指标时若直接混用会编译报错,必须做一层映射。 下面这段函数把自定义的重要度转成系统枚举:None 对应 CALENDAR_IMPORTANCE_NONE,Low 对应 LOW,Moderate 对应 MODERATE,High 对应 HIGH,未匹配到的 default 也回落到 NONE,避免脏数据进图表。 转成字符串用 GetImportance(),返回 "HIGH" / "MODERATE" / "LOW" / "NONE" 四种大写标签,方便在 Comment() 或 OBJ_LABEL 里直接输出。 GetImportance_color() 则按重要度取颜色,高重要度事件在贵金属跳空时段可能更值得标红提醒——外汇与贵金属属高杠杆品种,事件行情波动剧烈,仅作参考而非方向暗示。

MQL5 / C++
ENUM_CALENDAR_EVENT_IMPORTANCE IMPORTANCE(Calendar_Importance Importance)
  {
   class="kw">switch(Importance)
     {
      case Calendar_Importance_None:
        class="kw">return CALENDAR_IMPORTANCE_NONE;
        class="kw">break;
      case Calendar_Importance_Low:
        class="kw">return CALENDAR_IMPORTANCE_LOW;
        class="kw">break;
      case Calendar_Importance_Moderate:
        class="kw">return CALENDAR_IMPORTANCE_MODERATE;
        class="kw">break;
      case Calendar_Importance_High:
        class="kw">return CALENDAR_IMPORTANCE_HIGH;
        class="kw">break;
      class="kw">default:
        class="kw">return CALENDAR_IMPORTANCE_NONE;
        class="kw">break;
     }
  }
class="type">class="kw">string GetImportance(ENUM_CALENDAR_EVENT_IMPORTANCE Importance)
  {
   class="kw">switch(Importance)
     {
      case  CALENDAR_IMPORTANCE_HIGH:
        class="kw">return "HIGH";
        class="kw">break;
      case CALENDAR_IMPORTANCE_MODERATE:
        class="kw">return "MODERATE";
        class="kw">break;
      case CALENDAR_IMPORTANCE_LOW:
        class="kw">return "LOW";
        class="kw">break;
      class="kw">default:
        class="kw">return "NONE";
        class="kw">break;
     }
  }
class="type">class="kw">color GetImportance_color(ENUM_CALENDAR_EVENT_IMPORTANCE Importance)
  {
   class="kw">switch(Importance)

◍ 财经日历的颜色与板块映射逻辑

在 MT5 的财经日历可视化里,事件重要度直接决定标记色:高影响返回红(clrRed),中影响返回橙(clrOrange),低影响则按界面明暗切换蓝或浅蓝。默认无重要度时,亮色模式给黑、暗色模式给小麦色,保证深色背景不掉色。 下面这段 switch 把自定义的 Event_Sector 枚举转成平台原生 ENUM_CALENDAR_EVENT_SECTOR。None 对 NONE,Market 对 MARKET,Gdp 对 GDP,Jobs 对 JOBS,Prices 对 PRICES,Money 对 MONEY,Trade 对 TRADE,Government 对 GOVERNMENT,Business 对 BUSINESS——九个分支一一对应,缺一个都会在日历面板上漏掉板块归类。 实盘接小布盯盘时,若你自写 EA 抓日历,复制这套映射能省掉 80% 的枚举错配 bug。外汇与贵金属受数据跳空影响大,高影响事件前后点差可能瞬间扩到日常 5 倍以上,映射准了才谈得上风控。

MQL5 / C++
{
 case CALENDAR_IMPORTANCE_HIGH:class=class="str">"cmt">//High
   class="kw">return clrRed;
   class="kw">break;
 case CALENDAR_IMPORTANCE_MODERATE:class=class="str">"cmt">//Moderate
   class="kw">return clrOrange;
   class="kw">break;
 case CALENDAR_IMPORTANCE_LOW:class=class="str">"cmt">//Low
   class="kw">return (isLightMode)?clrBlue:clrLightBlue;
   class="kw">break;
 class="kw">default:class=class="str">"cmt">//None
   class="kw">return (isLightMode)?clrBlack:clrWheat;
   class="kw">break;
}
 }
 class=class="str">"cmt">//--- Convert Event_Sector Enumeration into Calendar Event Sector Enumeration
 ENUM_CALENDAR_EVENT_SECTOR SECTOR(Event_Sector Sector)
  {
  class=class="str">"cmt">//--- class="kw">switch statement for Event_Sector enumeration
  class="kw">switch(Sector)
   {
   case Event_Sector_None:class=class="str">"cmt">//NONE
    class="kw">return CALENDAR_SECTOR_NONE;
    class="kw">break;
   case Event_Sector_Market:class=class="str">"cmt">//MARKET
    class="kw">return CALENDAR_SECTOR_MARKET;
    class="kw">break;
   case Event_Sector_Gdp:class=class="str">"cmt">//GDP
    class="kw">return CALENDAR_SECTOR_GDP;
    class="kw">break;
   case Event_Sector_Jobs:class=class="str">"cmt">//JOBS
    class="kw">return CALENDAR_SECTOR_JOBS;
    class="kw">break;
   case Event_Sector_Prices:class=class="str">"cmt">//PRICES
    class="kw">return CALENDAR_SECTOR_PRICES;
    class="kw">break;
   case Event_Sector_Money:class=class="str">"cmt">//MONEY
    class="kw">return CALENDAR_SECTOR_MONEY;
    class="kw">break;
   case Event_Sector_Trade:class=class="str">"cmt">//TRADE
    class="kw">return CALENDAR_SECTOR_TRADE;
    class="kw">break;
   case Event_Sector_Government:class=class="str">"cmt">//GOVERNMENT
    class="kw">return CALENDAR_SECTOR_GOVERNMENT;
    class="kw">break;
   case Event_Sector_Business:class=class="str">"cmt">//BUSINESS
    class="kw">return CALENDAR_SECTOR_BUSINESS;

「把自定义枚举映射到财经日历原生枚举」

在 MT5 里做日历事件过滤时,常会先定义一套自己的 Event_Sector、Event_Frequency、Event_Type 枚举,再需要转成平台原生的 ENUM_CALENDAR_EVENT_SECTOR / FREQUENCY / TYPE。上面这段就是两个映射函数的后半段,逻辑全是 switch-case 硬转。 SECTOR() 函数覆盖了 Consumer、Housing、Taxes、Holidays 四个分类,未匹配到的走 default 返回 CALENDAR_SECTOR_NONE;FREQUENCY() 则从 None 到 Year 共 7 档(含 Day/Week/Month/Quarter),同样用 default 兜底为 CALENDAR_FREQUENCY_NONE。 实操上,这种映射最易漏的是新增分类忘了加 case,导致事件被吞进 NONE。开 MT5 把这两段贴进 EA,故意传一个未定义的 Event_Sector 值,看返回是不是 CALENDAR_SECTOR_NONE 就能验证兜底是否生效。外汇与贵金属受宏观数据跳空影响明显,此类映射错误可能让策略漏掉高波动事件,属高风险环节。

MQL5 / C++
         class="kw">break;
       case Event_Sector_Consumer:class=class="str">"cmt">//CONSUMER
            class="kw">return CALENDAR_SECTOR_CONSUMER;
            class="kw">break;
       case Event_Sector_Housing:class=class="str">"cmt">//HOUSING
            class="kw">return CALENDAR_SECTOR_HOUSING;
            class="kw">break;
       case Event_Sector_Taxes:class=class="str">"cmt">//TAXES
            class="kw">return CALENDAR_SECTOR_TAXES;
            class="kw">break;
       case Event_Sector_Holidays:class=class="str">"cmt">//HOLIDAYS
            class="kw">return CALENDAR_SECTOR_HOLIDAYS;
            class="kw">break;
       class="kw">default:class=class="str">"cmt">//Unknown
            class="kw">return CALENDAR_SECTOR_NONE;
            class="kw">break;
       }
   }
  class=class="str">"cmt">//--- Convert Event_Frequency Enumeration into Calendar Event Frequency Enumeration
  ENUM_CALENDAR_EVENT_FREQUENCY FREQUENCY(Event_Frequency Frequency)
   {
   class=class="str">"cmt">//--- class="kw">switch statement for Event_Frequency enumeration
   class="kw">switch(Frequency)
     {
     case  Event_Frequency_None:class=class="str">"cmt">//NONE
          class="kw">return CALENDAR_FREQUENCY_NONE;
          class="kw">break;
     case Event_Frequency_Day:class=class="str">"cmt">//DAY
          class="kw">return CALENDAR_FREQUENCY_DAY;
          class="kw">break;
     case Event_Frequency_Week:class=class="str">"cmt">//WEEK
          class="kw">return CALENDAR_FREQUENCY_WEEK;
          class="kw">break;
     case Event_Frequency_Month:class=class="str">"cmt">//MONTH
          class="kw">return CALENDAR_FREQUENCY_MONTH;
          class="kw">break;
     case Event_Frequency_Quarter:class=class="str">"cmt">//QUARTER
          class="kw">return CALENDAR_FREQUENCY_QUARTER;
          class="kw">break;
     case Event_Frequency_Year:class=class="str">"cmt">//YEAR
          class="kw">return CALENDAR_FREQUENCY_YEAR;
          class="kw">break;
     class="kw">default:class=class="str">"cmt">//Unknown
          class="kw">return CALENDAR_FREQUENCY_NONE;
          class="kw">break;
     }
   }
  class=class="str">"cmt">//--- Convert Event_Type Enumeration into Calendar Event Type Enumeration
  ENUM_CALENDAR_EVENT_TYPE TYPE(Event_Type Type)
   {

正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- switch statement for Event_Type enumeration</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">switch</span>(Type) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">case</span> Event_Type_Event:<span class="comment">//EVENT</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="macro">CALENDAR_TYPE_EVENT</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">case</span> Event_Type_Indicator:<span class="comment">//INDICATOR</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="macro">CALENDAR_TYPE_INDICATOR</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">case</span> Event_Type_Holiday:<span class="comment">//HOLIDAY</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="macro">CALENDAR_TYPE_HOLIDAY</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

◍ 用 SQL 视图给财经日历做减法

MT5 内置的财经日历表字段冗长,直接查 MQL5Calendar 会带出一长串 CALENDAR_TYPE_、CALENDAR_SECTOR_ 前缀,读起来费劲。写一条 CREATE VIEW 把前缀 REPLACE 掉,再按国家升序、重要性权重、板块降序排,肉眼筛选效率高得多。 下面这段初始化把视图塞进 CalendarContents[5],视图名定为 Event Info,类型是 view。sql 字符串里 GROUP BY Name 是为了同一事件只留一行,ORDER BY 里的 CASE 把 HIGH 映射为 1、MODERATE 为 2、LOW 为 3,非这三类归 4,等于给重要性做了隐形排序键。

MQL5 / C++
class=class="str">"cmt">//--- initializing properties for the EventInfo view
  CalendarContents[class="num">5].Content = EventInfo_View;
  CalendarContents[class="num">5].name = "Event Info";
  CalendarContents[class="num">5].sql = "CREATE VIEW IF NOT EXISTS &class="macro">#x27;Event Info "
              "AS SELECT EVENTID as &class="macro">#x27;ID&class="macro">#x27;,COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,"
              "REPLACE(EVENTTYPE,&class="macro">#x27;CALENDAR_TYPE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Type&class="macro">#x27;,REPLACE(EVENTSECTOR,&class="macro">#x27;CALENDAR_SECTOR_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Sector&class="macro">#x27;,"
              "REPLACE(EVENTIMPORTANCE,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27;,EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27; "
              "FROM MQL5Calendar GROUP BY \"Name\" ORDER BY \"Country\" Asc,"
              "CASE \"Importance\" WHEN &class="macro">#x27;HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;LOW&class="macro">#x27; THEN class="num">3 ELSE class="num">4 END,\"Sector\" Desc;";
  CalendarContents[class="num">5].tbl_name = "Event Info";
  CalendarContents[class="num">5].type = "view";
实际跑出来的前几行里,澳大利亚的 RBA Governor Lowe Speech 和 RBA Interest Rate Decision 都是 AUD 货币、HIGH 重要性、MONEY 板块;PPI q/q 同为 AUD 但重要性只是 MODERATE、板块 PRICES。外汇和贵金属受这些 HIGH 级事件冲击的概率偏大,实盘前建议先确认本国 DST 与事件时区,避免错判公布时间。 另有一条带 Inner Join TimeSchedule 的查询,专门捞高重要性、月度频率、就业板块且非全货币的指标——也就是各国非农类数据。直接复制进 MT5 的 SQL 调试窗能验证返回行数,比翻网页日历快。

MQL5 / C++
class=class="str">"cmt">//--- initializing properties for the EventInfo view
  CalendarContents[class="num">5].Content = EventInfo_View;
  CalendarContents[class="num">5].name = "Event Info";
  CalendarContents[class="num">5].sql = "CREATE VIEW IF NOT EXISTS &class="macro">#x27;Event Info "
              "AS SELECT EVENTID as &class="macro">#x27;ID&class="macro">#x27;,COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;,"
              "REPLACE(EVENTTYPE,&class="macro">#x27;CALENDAR_TYPE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Type&class="macro">#x27;,REPLACE(EVENTSECTOR,&class="macro">#x27;CALENDAR_SECTOR_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Sector&class="macro">#x27;,"
              "REPLACE(EVENTIMPORTANCE,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27;,EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27; "
              "FROM MQL5Calendar GROUP BY \"Name\" ORDER BY \"Country\" Asc,"
              "CASE \"Importance\" WHEN &class="macro">#x27;HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;LOW&class="macro">#x27; THEN class="num">3 ELSE class="num">4 END,\"Sector\" Desc;";
  CalendarContents[class="num">5].tbl_name = "Event Info";
  CalendarContents[class="num">5].type = "view";

「澳储与巴西央行的事件字段排布」

经济日历的原始数据并非杂乱堆砌,而是按国家代码与事件 ID 顺序线性排列。上面这段截取里,澳大利亚部分从 ID 36030014 的 trimmed mean CPI 开始,连续 6 行覆盖物价类指标与三位助理行长讲话,影响等级统一标为 MODERATE,对应货币 AUD。 中间用「62 lines later...」标记跳过了 62 行其他地区的记录,直接落到巴西区块。巴西从 76020002 的 BCB 利率决议起头,该行影响等级是 HIGH,其余如通胀报告、焦点市场报告、Copom 纪要等多为 MODERATE,货币统一为 BRL。 这种排布说明:抓取日历时若按 ID 数值遍历,澳大利亚与巴西之间天然存在断层,写过滤器时要以国家字段做一级分组,别指望连续行号能贯穿全表。外汇与贵金属受此类央行事件冲击波动可能放大,属高风险博弈,实盘前应在 MT5 历史日历里核对具体发布时间。

加元事件流与币种视图的落地写法

日历数据里巴西服务业月率排在编码 76010014,往后数 98 行才出现加拿大央行行长 Macklem 讲话(124040017),中间夹着大量低权重条目。加元相关的高影响项集中且密集:从 124040003 的 Poloz 讲话、124040006 的利率决议,到 124010011 就业变动、124010021 GDP 月率,一连串 HIGH 级事件都挂在 CAD 下,外汇和贵金属交易者碰上这类重叠窗口要警惕双向扫单的高风险。 下面这段 MQL5 初始化代码直接建了一个 Currencies 视图,把日历表里出现过的币种和对应事件代码去重拉出来。跑完 SELECT 能看到实际落库的币种:NZD/NZ、EUR/EU、JPY/JP、CAD/CA、AUD/AU、CNY/CN、EUR/IT、SGD/SG、EUR/DE——注意 EUR 被意大利和德国的不同事件代码拆成了 IT 与 DE,做币种过滤时不能只按前缀硬匹配。 打开 MT5 把这段视图建好,你就能用一条 SQL 看清自己订阅的日历到底覆盖了哪些经济体,而不是被默认全量推送带着走。

MQL5 / C++
class=class="str">"cmt">//--- initializing properties for the Currencies view
   CalendarContents[class="num">6].Content = Currencies_View;
   CalendarContents[class="num">6].name = "Currencies";
   CalendarContents[class="num">6].sql = "CREATE VIEW IF NOT EXISTS Currencies AS "
                         "SELECT Distinct EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,EventCode as &class="macro">#x27;Code&class="macro">#x27; FROM &class="macro">#x27;MQL5Calendar&class="macro">#x27;;"
   CalendarContents[class="num">6].tbl_name = "Currencies";
   CalendarContents[class="num">6].type = "view";

◍ 正文

EUR&nbsp;&nbsp; FR BRL&nbsp;&nbsp; BR MXN&nbsp;&nbsp; MX ZAR&nbsp;&nbsp; ZA HKD&nbsp;&nbsp; HK INR&nbsp;&nbsp; IN NOK&nbsp;&nbsp; NO USD&nbsp;&nbsp; US GBP&nbsp;&nbsp; GB CHF&nbsp;&nbsp; CH KRW&nbsp;&nbsp; KR EUR&nbsp;&nbsp; ES SEK&nbsp;&nbsp; SE ALL&nbsp;&nbsp; WW <span class="comment">//-- initializing properties for the MQL5Calendar table for DB in System Memory</span> &nbsp;&nbsp; DBMemory.Content = MQL5Calendar_Table; &nbsp;&nbsp; DBMemory.name = <span class="string">"MQL5Calendar"</span>; &nbsp;&nbsp; DBMemory.sql = <span class="string">"CREATE TABLE IF NOT EXISTS MQL5Calendar(EVENTID&nbsp;&nbsp;INT&nbsp;&nbsp; NOT NULL,COUNTRY&nbsp;&nbsp;TEXT&nbsp;&nbsp; NOT NULL,"</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"EVENTNAME&nbsp;&nbsp; TEXT&nbsp;&nbsp; NOT NULL,EVENTTYPE&nbsp;&nbsp; TEXT&nbsp;&nbsp; NOT NULL,EVENTIMPORTANCE&nbsp;&nbsp; TEXT&nbsp;&nbsp; NOT NULL,"</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"EVENTCURRENCY&nbsp;&nbsp;TEXT&nbsp;&nbsp; NOT NULL,EVENTCODE&nbsp;&nbsp; TEXT&nbsp;&nbsp; NOT NULL,EVENTSECTOR TEXT&nbsp;&nbsp; NOT NULL,"</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"EVENTFORECAST&nbsp;&nbsp;TEXT&nbsp;&nbsp; NOT NULL,EVENTPREVALUE&nbsp;&nbsp;TEXT&nbsp;&nbsp; NOT NULL,E

「内存数据库建表与财经数据灌入」

在 MT5 里用 DatabaseOpen 配合 DATABASE_OPEN_MEMORY 标志,可以把经济日历直接建在内存里,避免每次读盘。句柄若返回 INVALID_HANDLE,说明内存库没开起来,这时打错误码并 return,后续插入逻辑全部跳过。 建表前先用 DatabaseExecute 跑一句 Drop table IF EXISTS,把同名旧表清掉,否则重复执行会撞结构。紧接着用 DBSql 模板建表,失败同样 Print 错误码退出。 表存在后,从持久化库 GetCalendar 拉全量数据,再 for 循环逐条拼 insert 语句灌进内存表。DB_Data.Size() 决定循环次数,数据量大的时候这一段的耗时倾向随条数线性增长,实盘前最好先打印 Size 估一下。 别把内存当永久存储 内存库随 EA 退出即释放,重启后要重新 CreateEconomicDatabaseMemory 灌数据;想跨周期留数据得落盘到文件库。

MQL5 / C++
class="type">void CNews::CreateEconomicDatabaseMemory()
  {
class=class="str">"cmt">//--- Open/create the database in memory
   DBMemoryConnection=DatabaseOpen(NEWS_DATABASE_MEMORY,DATABASE_OPEN_MEMORY);
   if(DBMemoryConnection==INVALID_HANDLE)class=class="str">"cmt">//Checks if the database failed to open/create
     {
       Print("DB: ",NEWS_DATABASE_MEMORY, " open failed with code ", GetLastError());
       class="kw">return;class=class="str">"cmt">//will terminate execution of the rest of the code below
     }
class=class="str">"cmt">//--- Drop the table if it already exists
   DatabaseExecute(DBMemoryConnection,StringFormat("Drop table IF EXISTS %s",DBMemory.name));
class=class="str">"cmt">//--- Attempt to create the table
   if(!DatabaseExecute(DBMemoryConnection,DBMemory.sql))
     {
       Print("DB: create the Calendar table failed with code ", GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- Check if the table exists
   if(DatabaseTableExists(DBMemoryConnection,DBMemory.tbl_name))
     {
       class=class="str">"cmt">//--- Get all news data and time from the database in storage
       GetCalendar(DB_Data);
       class=class="str">"cmt">//--- Insert all the news data and times into the table
       for(class="type">uint i=class="num">0;i<DB_Data.Size();i++)
         {
          class="type">class="kw">string request_text=StringFormat(DBMemory.insert,DB_Data[i].EventId,DB_Data[i].Country,
                                           DB_Data[i].EventName,DB_Data[i].EventType,DB_Data[i].EventImportance,
                                           DB_Data[i].EventCurrency,DB_Data[i].EventCode,DB_Data[i].EventSector,
                                           DB_Data[i].EventForecast,DB_Data[i].EventPreval,DB_Data[i].EventImpact,

内存日历的检索与落库细节

把新闻数据写进内存库之后,先调用 ArrayRemove 清空 DB_Data 数组,避免下一次循环把旧记录叠进去。这一行若漏掉,回测时可能出现同一事件被重复统计、事件频率字段翻倍的现象。 MySchedule 的赋值用了三元判断:若处在策略测试器环境且用户选了自动 DST,就走 GetAutoDST() 拿时区表,否则回落到 DST_NONE。外汇与贵金属对夏令时切换极敏感,时区错配会让新闻触发时间偏移 1 小时,实盘风险偏高。 EconomicDetailsMemory 用一段嵌套 SQL 从内存库按日期捞新闻。核心是用 RANK() 窗口函数按 Prevalue、Forecast 是否缺值以及重要性高低排优先级,再 where Ranking<2 只取每个时间点的最优先事件,Group by CTime 去重。 下面这段是 SQL 拼装开头,MySchedule 枚举会替换进 %s 占位符,DBMemory.name 指定内存表名,TimeToString(date) 把查询日传进去。开 MT5 把这段贴进 EA 的调试输出,能直接看到某天实际命中了几条高层级事件。

MQL5 / C++
class="type">class="kw">string request_text=StringFormat("WITH MySubQuery AS(SELECT EventId as &class="macro">#x27;Id&class="macro">#x27;,Country,EventName as &class="macro">#x27;Name&class="macro">#x27;,EventType as &class="macro">#x27;Type&class="macro">#x27;"
																															",EventImportance as &class="macro">#x27;Importance&class="macro">#x27;,%s as &class="macro">#x27;CTime&class="macro">#x27;,EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,EventCode as &class="macro">#x27;Code&class="macro">#x27;,"
																															"EventSector as &class="macro">#x27;Sector&class="macro">#x27;,EventForecast as &class="macro">#x27;Forecast&class="macro">#x27;,EventPrevalue as &class="macro">#x27;Prevalue&class="macro">#x27;,EventImpact as&class="macro">#x27;Impact&class="macro">#x27;,"
																															"EventFrequency as &class="macro">#x27;Freq&class="macro">#x27;,RANK() OVER(PARTITION BY %s Order BY CASE EventPrevalue WHEN &class="macro">#x27;None&class="macro">#x27; "
																															"THEN class="num">2 ELSE class="num">1 END,CASE EventForecast WHEN &class="macro">#x27;None&class="macro">#x27; THEN class="num">2 ELSE class="num">1 END,CASE EventImportance WHEN "
																															"&class="macro">#x27;CALENDAR_IMPORTANCE_HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_LOW&class="macro">#x27;"
																															" THEN class="num">3 ELSE class="num">4 END) Ranking FROM %s) SELECT Id,Country,Name,Type,Importance,CTime,Currency,Code,Sector,"
																															"Forecast,Prevalue,Impact,Freq FROM MySubQuery where Date(Replace(CTime,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))=Date(Replace(&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) and "
																															"Ranking<class="num">2 Group by CTime;",EnumToString(MySchedule),EnumToString(MySchedule),DBMemory.name,
																															TimeToString(date));

◍ 把财经日历塞进内存数据库再读出来

MT5 里用 DatabasePrepare 向内存连接提交 SQL 请求,拿到的是请求句柄而非结果集。句柄若等于 INVALID_HANDLE,说明 SQL 编译或连接出问题,这时候把 GetLastError 和原始 request_text 都打印出来,能直接定位是语法错还是表名错。

MQL5 / C++
class="type">int request=DatabasePrepare(DBMemoryConnection,request_text);class=class="str">"cmt">//Creates a handle of a request, which can then be executed class="kw">using DatabaseRead()
if(request==INVALID_HANDLE)class=class="str">"cmt">//Checks if the request failed to be completed
  {
    Print("DB: ",NEWS_DATABASE_MEMORY, " request failed with code ", GetLastError());
    PrintFormat(request_text);
  }
class=class="str">"cmt">//--- Calendar structure variable
Calendar ReadDB_Data;
class=class="str">"cmt">//--- Remove any data in the array
ArrayRemove(NewsTime,class="num">0,WHOLE_ARRAY);
for(class="type">int i=class="num">0; DatabaseReadBind(request,ReadDB_Data); i++)class=class="str">"cmt">//Will read all the results from the sql query/request
  {
    class=class="str">"cmt">//--- Resize array NewsTime
    ArrayResize(NewsTime,i+class="num">1,i+class="num">2);
    class=class="str">"cmt">//--- Assign calendar structure values into NewsTime array index
    NewsTime[i]  = ReadDB_Data;
  }
class=class="str">"cmt">//--- Removes a request created in DatabasePrepare()
DatabaseFinalize(request);
逐行拆一下:DatabasePrepare 第一个参数是内存库句柄,第二个是 SQL 文本,返回 request 供后续读取;INVALID_HANDLE 判断失败后,Print 打出库名加错误码,PrintFormat 原样回显 SQL。Calendar 结构 ReadDB_Data 用来接每行数据;ArrayRemove 先清空 NewsTime 数组避免旧数据污染;for 循环里 DatabaseReadBind 每调用一次绑一条记录,返回 false 即读完,循环内 ArrayResize 按 i+1 长度、i+2 预留量扩容,再把结构赋给 NewsTime[i];最后 DatabaseFinalize 释放请求句柄,不释放会漏内存。 SQL 侧用 CTE 做子查询,RANK() OVER 按时间分区,优先排有前值、有预测、重要性高的事件。实测 2024.07.30 这天,日本失业率(JPY,前值 2600000、预测 2500000)和澳洲建筑许可月率(AUD,前值 5500000、预测 -900000)都因 Ranking<2 被捞出,外汇与贵金属受此类数据扰动概率偏高,实操前建议在 MT5 策略测试器里先跑通这套读取逻辑。

MQL5 / C++
class="type">int request=DatabasePrepare(DBMemoryConnection,request_text);class=class="str">"cmt">//Creates a handle of a request, which can then be executed class="kw">using DatabaseRead()
if(request==INVALID_HANDLE)class=class="str">"cmt">//Checks if the request failed to be completed
  {
    Print("DB: ",NEWS_DATABASE_MEMORY, " request failed with code ", GetLastError());
    PrintFormat(request_text);
  }
class=class="str">"cmt">//--- Calendar structure variable
Calendar ReadDB_Data;
class=class="str">"cmt">//--- Remove any data in the array
ArrayRemove(NewsTime,class="num">0,WHOLE_ARRAY);
for(class="type">int i=class="num">0; DatabaseReadBind(request,ReadDB_Data); i++)class=class="str">"cmt">//Will read all the results from the sql query/request
  {
    class=class="str">"cmt">//--- Resize array NewsTime
    ArrayResize(NewsTime,i+class="num">1,i+class="num">2);
    class=class="str">"cmt">//--- Assign calendar structure values into NewsTime array index
    NewsTime[i]  = ReadDB_Data;
  }
class=class="str">"cmt">//--- Removes a request created in DatabasePrepare()
DatabaseFinalize(request);

「日历数据里的字段暗码」

上面这段是某日经济日历的原始条目流,覆盖了澳、法、瑞、西四个经济体,时间锚在 2024.07.30。每条记录用固定位宽拆分:事件 ID、国家、指标名、类型宏、重要度宏、发布时间、货币、板块宏、前值与预期值、影响标记、频率宏、末位序号。 看法国那几行:GDP q/q 标的是 HIGH 重要度,前值 200000、预期同数;GDP y/y 是 MODERATE,前值 1000000、预期 1100000;Consumer Spending m/m 也是 MODERATE,前值 800000、预期 1500000。这种前值预期差,是事件驱动挂单的初级过滤器。 西班牙的 CPI/HICP 同时间 09:00 放出,y/y 版本重要度压到 LOW,但末位序号是 5,比 m/m 的 1 高出一截——说明年度口径在渲染层可能被赋予更高权重。外汇与贵金属受此类数据跳空影响明显,属高风险事件,实操前先在 MT5 日历插件核对时区。 瑞士 KOF 经济晴雨表前值 101500000、预期 102700000,字段里没有单独的影响值占位,CALENDAR_IMPACT_NA 直接顶上。这种结构告诉你:不是每个指标都带冲击评级,低重要度条目常常留空。

低权重日历条目的字段结构拆解

上面这组 2024.07.30 的日历记录里,西班牙 GDP y/y 与瑞典五项信心类指标都被标了 CALENDAR_IMPORTANCE_LOW,发布时间集中在 09:00,频率分别为季度和月度。 以瑞典 Consumer Confidence 为例,前值 95300000、预测 93300000,CALENDAR_IMPACT_NA 说明系统未判定对 SEK 有即时冲击倾向;Business Confidence 前值 95500000、预测 97300000,差值方向偏正但权重等级仍为低。 德国 10:00 的 GDP q/q 是 CALENDAR_IMPORTANCE_HIGH(等级 1),前值 100000、预测 200000;同期的意大利 GDP q/q 为 MODERATE(等级 2)。这种同币种、不同时段的重要性梯度,是做 EUR 相关品种时过滤噪音的第一道筛子。 在 MT5 里打开财经日历接口对照这些宏,能看到 CALENDAR_FREQUENCY_QUARTER 与 MONTH 直接决定数据刷新节奏,低重要度条目默认不弹窗,但可手动写脚本拉全量做统计。外汇与贵金属受此类数据扰动的概率随重要性等级上升而增加,实盘需自担高风险。

◍ 欧元区数据密集发布时的字段排布

上面这一串是某日欧洲时段经济日历的原始记录片段,时间集中在 2024.07.30 的 10:00 到 11:30,全部挂 EUR 计价标的,属于典型的基本面事件窗口。 每条记录从左到右依次是:内部编号、经济体、指标名、类型宏、重要度宏、发布时间、货币、地区码、板块宏、前值、预期、影响宏、频率宏、排序序号。比如意大利 GDP y/y 编号为 380010021,重要度标为 LOW,前值 400000、预期 700000;欧盟 GDP q/q 编号 999030016,重要度 HIGH,前值 200000、预期 300000。 注意同一时间点(11:00)欧盟一口气出了 8 条:GDP 同比环比、工业/服务业/经济景气、消费者信心与价格预期等,板块覆盖 GDP、BUSINESS、CONSUMER。这种堆叠在 MT5 日历对象里往往共用同一时间戳,脚本读取时若不分板块过滤,容易把低重要度的信心指数和高重要度的 GDP 混在同一队列。 实操上,打开 MT5 终端的财经日历,把 2024.07.30 这一天调出来,对照上面编号和前值/预期数字逐条核对;若你自己在写 EA 抓日历,建议按 CALENDAR_SECTOR 宏先分桶,再按 CALENDAR_IMPORTANCE 做权重,避免被 LOW 级噪声干扰。外汇与贵金属受此类数据跳空影响明显,属于高风险品种,任何方向判断都只是概率倾向。

「经济日历条目的字段结构拆解」

MT5 内置经济日历每条记录由固定字段组成,从原始数据看,一行里依次排着事件 ID、国家、事件名、类型宏、重要度宏、时间、货币、国家码、板块宏、前值、预测、影响宏、频率宏和某种计数。 以 2024.07.30 14:00 德国 CPI m/m 为例:ID 为 276010020,重要度 CALENDAR_IMPORTANCE_MODERATE,前值 0、预测 100000,频率 CALENDAR_FREQUENCY_MONTH,行尾计数为 2。同日墨西哥 GDP q/q 重要度标为 CALENDAR_IMPORTANCE_HIGH,是那批低重要度拍卖数据里唯一的高影响事件。 灰色背景行(意大利 5 年期 BTP 拍卖、英国 10 年期 Gilt 拍卖)和白色背景行只是原文排版标记,在终端读取时不影响字段解析;写 EA 时直接用 CalendarValueHistory 或 CalendarEventByCountry 取对应宏即可。 开 MT5 按 F6 调出日历样例,对照上面字段把前值和预测打印到日志,能确认你本地宏定义和服务器推送是否一致。

经济日历里低重要度条目的真实分量

上面这组 2024.07.30 的日历快照里,德国 HICP y/y 与巴西 PPI y/y 都被标成 CALENDAR_IMPORTANCE_LOW,发布时间同为 14:00,但前者影响 EUR、后者影响 BRL,且巴西 PPI 前值 170000、预期 2200000,量级差了十倍出头。 美国时段 15:00 集中冒出 7 条住房类数据,其中 S&P/CS HPI Composite-20 y/y 是 MODERATE,其余全是 LOW;HPI 绝对值 427700000 对比前值 424300000,微增约 0.8%,这类低重要度条目在 MT5 日历里默认灰显,容易被一眼跳过。 16:00 的 JOLTS 职位空缺(7979000 vs 8140000)和 CB 消费者信心(108000000 vs 100400000)才是 HIGH,美元波动大概率由它们驱动。做欧元或贵金属时,别把前面的 LOW 条目当噪声全删——低风险事件也可能在 HIGH 数据前制造流动性真空。

◍ 低影响时段里怎么挑值得盯的日历条目

上面这组 2024.07.30 的财经日历原始记录,混了美、墨、日、澳、欧等多国事件,重要性从 LOW 到 HIGH 都有,但 Impact 字段清一色标着 CALENDAR_IMPACT_NA,说明按系统判定这些发布对盘面没有自动冲击标记。 具体看,德国 GDP q/q 预估 100000、前值 200000,法国 GDP q/q 预估与前值都是 200000,欧盟 GDP q/q 预估 200000、前值 300000——三个 HIGH 级 GDP 同在当天 07:30 到 11:00 之间出炉,可系统仍判为 NA 影响,这种「高重要度却无冲击标记」的矛盾,往往是流动性真空或非美盘交叉段的典型特征。 做价格行为的人,碰到这种日历别直接跳过。把 USD、EUR、JPY 相关条目的 CTime 拉出来排一遍:日本失业率 01:30、澳大利亚建筑许可 03:30、法国 GDP 07:30、德国 GDP 10:00、欧盟 GDP 11:00,中间美元区 Dallas Fed 两个 LOW 条目在 16:30 才来。亚欧时段那串 HIGH 如果实际走出了吞没或假突破,大概率不是日历算法预测的「无影响」,而是市场在消化预期差。 外汇和贵金属这类高杠杆品种,在这种 NA 标记日波动可能突然放大,开 MT5 把 2024.07.30 那天 EURUSD、XAUUSD 的 M15 调出来,对照上面时间轴看影线分布,比信日历的 Impact 字段更实在。

「抓下一个财经事件的结构体写法」

上面那串原始记录是某日财经日历的裸数据:2024.07.30 当天从西班牙商业信心(低重要度、前值 -4100000)到墨西哥财政余额(低重要度、前值 -900000、预期 -174071000),横跨 EUR、BRL、MXN、USD 多个币种,其中美国 JOLTS 职位空缺与墨西哥 GDP 季率被标为高重要度,发布时间分散在 12:00 至 22:30 服务器时间。 这类数据在 MT5 里通常不会裸读,而是先塞进一个 Calendar 结构体,再由函数挑出“下一个”事件。下面这段就是取即将到来事件的核心逻辑,跑通后你能直接挂到 EA 的 OnTick 前面做前置过滤。 别把结构体当万能缓存 UpcomingNews 在调用前必须清空,否则上一次残留的 EventId 可能让你的新闻屏蔽逻辑误判。代码里用 Empty 赋值而非手动逐项归零,是最省事的防脏读办法。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Will update UpcomingNews structure variable with the next event  |
class=class="str">"cmt">//|data                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNews::EconomicNextEvent(class="type">class="kw">datetime date=class="num">0)
  {
class=class="str">"cmt">//--- Declare unassigned Calendar structure variable Empty
   Calendar Empty;
class=class="str">"cmt">//--- assign empty values to Calendar structure variable UpcomingNews
   UpcomingNews = Empty;
class=class="str">"cmt">//--- If date variable is zero then assign current date
   date = (date==class="num">0)?TimeTradeServer():date;
class=class="str">"cmt">//--- Query to retrieve next upcoming event.
   class="type">class="kw">string request_text=StringFormat("WITH MySubQuery AS(SELECT EventId as &class="macro">#x27;Id&class="macro">#x27;,Country,EventName as &class="macro">#x27;Name&class="macro">#x27;,"

用 SQL 窗口函数筛最近一条高权重财历

这段逻辑把 MT5 内存财历库当成一个轻量 SQL 引擎来用:先用 CTE(MySubQuery)把事件字段改名映射,再用 RANK() OVER (PARTITION BY 时间字段 ORDER BY ...) 给同一时刻的多条事件排优先级。排序键先把无前值、无预测的事件压到后面(CASE 返回 2,否则 1),再按高/中/低重要性映射成 1/2/3,其余归 4。 外层只取 Ranking<2 且时间大于等于入参日期的那一条(LIMIT 1),等于每次只抓「离当前最近、且数据最完整」的那条宏观事件。外汇与贵金属受此类事件跳空影响明显,属于高风险场景,信号仅作概率参考。 代码里 DatabasePrepare 拿到请求句柄后必须判 INVALID_HANDLE,失败就打印错误码和整段 SQL 方便排查;DatabaseReadBind 把结果塞进 UpcomingNews 结构,最后 DatabaseFinalize 释放句柄,漏掉这一步会吃内存。 直接把下面这段 SQL 拼进你的 EA 调试面板,改掉时间字段和表名就能在 MT5 里跑通验证。

MQL5 / C++
WITH MySubQuery AS(SELECT EventId as &class="macro">#x27;Id&class="macro">#x27;,Country,EventName as &class="macro">#x27;Name&class="macro">#x27;,EventType as &class="macro">#x27;Type&class="macro">#x27;,EventImportance as &class="macro">#x27;Importance&class="macro">#x27;,
DST_NONE as &class="macro">#x27;CTime&class="macro">#x27;,EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,EventCode as &class="macro">#x27;Code&class="macro">#x27;,EventSector as &class="macro">#x27;Sector&class="macro">#x27;,EventForecast as &class="macro">#x27;Forecast&class="macro">#x27;,
EventPrevalue as &class="macro">#x27;Prevalue&class="macro">#x27;,EventImpact as &class="macro">#x27;Impact&class="macro">#x27;,EventFrequency as &class="macro">#x27;Freq&class="macro">#x27;,RANK() OVER(PARTITION BY DST_NONE
Order BY CASE EventPrevalue WHEN &class="macro">#x27;None&class="macro">#x27; THEN class="num">2 ELSE class="num">1 END,CASE EventForecast WHEN &class="macro">#x27;None&class="macro">#x27; THEN class="num">2 ELSE class="num">1 END,
CASE EventImportance WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_MODERATE&class="macro">#x27; THEN class="num">2
WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_LOW&class="macro">#x27; THEN class="num">3 ELSE class="num">4 END) Ranking FROM MQL5Calendar) SELECT Id,Country,Name,Type,
Importance,CTime,Currency,Code,Sector,Forecast,Prevalue,Impact,Freq FROM MySubQuery where

◍ 用历史情景反推财经事件冲击等级

想让 MT5 日历库告诉你「这次数据公布后市场可能怎么动」,不能只盯当前值,得把同一事件 ID 的历史影响拉出来比对。下面这段逻辑就是按 EventPrevalue 与 EventForecast 的大小关系,去筛出最近一条同情景的旧影响记录。 例如 2024.07.30 01:30 的日本失业率(JPY,JP 就业板块,前值 2600000、预测 2500000,Ranking=1),若前值大于预测,代码会限定查 EventPrevalue>EventForecast AND EventImpact<>'CALENDAR_IMPACT_NA' 的历史样本,倾向认为实际公布若延续该情景,冲击分类可能非空。 外汇与贵金属受此类数据跳变影响明显,杠杆品种高风险,历史情景只是概率参考而非方向保证。 核心函数 GetImpact() 先把筛选 SQL 拼出来,再 DatabasePrepare 拿句柄;若返回 INVALID_HANDLE 就打印错误码和原句,方便你直接在 MT5 专家日志里抓 SQL 毛病。

MQL5 / C++
ENUM_CALENDAR_EVENT_IMPACT CNews::GetImpact()
  {
class=class="str">"cmt">//--- Declaration of class="type">class="kw">string variable
   class="type">class="kw">string impact=NULL;
class=class="str">"cmt">//--- Query to get impact data from previous event with the same event id and matching EventPrevalue and EventForecast scenarios.
   class="type">class="kw">string request_text=StringFormat("SELECT EventImpact FROM %s where Replace(%s,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)<Replace(&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;) AND EventId=%d"
                                    " %s ORDER BY %s DESC LIMIT class="num">1;",DBMemory.name,EnumToString(MySchedule),UpcomingNews.EventDate,
                                    UpcomingNews.EventId,((UpcomingNews.EventPreval=="None"||UpcomingNews.EventForecast=="None")?
                                    "AND EventImpact=&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":(class="type">int(UpcomingNews.EventPreval)<class="type">int(UpcomingNews.EventForecast))?
                                    "AND EventPrevalue<EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":
                                    (class="type">int(UpcomingNews.EventPreval)>class="type">int(UpcomingNews.EventForecast))?
                                    "AND EventPrevalue>EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":
                                    "AND EventPrevalue=EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;"),
                                    EnumToString(MySchedule));
   class="type">int request=DatabasePrepare(DBMemoryConnection,request_text);class=class="str">"cmt">//Creates a handle of a request, which can then be executed class="kw">using DatabaseRead()
   if(request==INVALID_HANDLE)class=class="str">"cmt">//Checks if the request failed to be completed
     {
      Print("DB: ",NEWS_DATABASE_MEMORY, " request failed with code ", GetLastError());
      PrintFormat(request_text);
     }

「用历史事件反推财经日历冲击方向」

在 MT5 的 Database 体系里,单条记录读取后必须立刻释放请求句柄,否则内存表会持续占用。下面这段逻辑先尝试读取 'Record' 表里的唯一一行,把第一列文本塞进 impact 变量,随后调用 DatabaseFinalize 清掉 request,避免句柄泄漏。 读取到的 impact 字符串直接映射到 ENUM_CALENDAR_EVENT_IMPACT 枚举:命中 "CALENDAR_IMPACT_POSITIVE" 返回正向冲击,命中 "CALENDAR_IMPACT_NEGATIVE" 返回负向,其余一律归为 CALENDAR_IMPACT_NA。这样上层调度只需判断枚举,不必再处理字符串。 核心查询找的是「同一 EventId 下、日期早于当前事件、且前值/预期关系与本次一致」的最近一条历史冲击。例如实跑出的 SQL 为:SELECT EventImpact FROM MQL5Calendar where Replace(DST_NONE,'.','-')<Replace('2024.08.01 16:30','.','-') AND EventId=124500001 AND EventPrevalue<EventForecast AND EventImpact<>'CALENDAR_IMPACT_NA' ORDER BY DST_NONE DESC LIMIT 1; 只取 LIMIT 1,即紧邻的上一次同场景释放。 外汇与贵金属受此类数据冲击影响剧烈且跳空频繁,历史同场景冲击仅作概率参考,实际入场前应在 MT5 策略测试器或可视化回放中核对 EventId 与日期边界。

MQL5 / C++
if(DatabaseRead(request))class=class="str">"cmt">//Will read the one record in the &class="macro">#x27;Record&class="macro">#x27; table
    {
      class=class="str">"cmt">//--- assign first column result into impact variable
      DatabaseColumnText(request,class="num">0,impact);
    }
class=class="str">"cmt">//--- Removes a request created in DatabasePrepare()
   DatabaseFinalize(request);
class=class="str">"cmt">//--- Return equivalent Event impact in the enumeration ENUM_CALENDAR_EVENT_IMPACT
   if(impact=="CALENDAR_IMPACT_POSITIVE")
    {
       class="kw">return CALENDAR_IMPACT_POSITIVE;
    }
   else
     if(impact=="CALENDAR_IMPACT_NEGATIVE")
       {
         class="kw">return CALENDAR_IMPACT_NEGATIVE;
       }
      else
       {
         class="kw">return CALENDAR_IMPACT_NA;
       }
 }
class=class="str">"cmt">//--- Query to get impact data from previous event with the same event id and matching EventPrevalue and EventForecast scenarios.
   class="type">class="kw">string request_text=StringFormat("SELECT EventImpact FROM %s where Replace(%s,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)<Replace(&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;) AND EventId=%d"
                                   " %s ORDER BY %s DESC LIMIT class="num">1;",DBMemory.name,EnumToString(MySchedule),UpcomingNews.EventDate,
                                   UpcomingNews.EventId,((UpcomingNews.EventPreval=="None"||UpcomingNews.EventForecast=="None")?
                                   "AND EventImpact=&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":(class="type">int(UpcomingNews.EventPreval)<class="type">int(UpcomingNews.EventForecast))?
                                   "AND EventPrevalue<EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":
                                   (class="type">int(UpcomingNews.EventPreval)>class="type">int(UpcomingNews.EventForecast))?
                                   "AND EventPrevalue>EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":
                                   "AND EventPrevalue=EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;"),
                                   EnumToString(MySchedule));
SELECT EventImpact FROM MQL5Calendar where Replace(DST_NONE,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)<Replace(&class="macro">#x27;class="num">2024.08.class="num">01 class="num">16:class="num">30&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;) AND EventId=class="num">124500001
AND EventPrevalue<EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27; ORDER BY DST_NONE DESC LIMIT class="num">1;

图表视觉元素的类封装逻辑

通用图形类接管了专家在 MT5 图表上的一切视觉输出。相比第2部分,第3部分的面板信息密度明显更高,代码复杂度也跟着上来——这个类直接引用了新闻、风险管理和账户信息三个模块,头部用 include 把 ObjectProperties、RiskManagement、CommonVariables、News 全部挂进来。 GraphicText 不是普通结构体,它内嵌了子结构体、数组和函数,行为接近一个独立类。sub_text[] 存所有文本片段,FoundText() 按 label 在数组里检索,命中后通过继承自 subtextformat 的 found 返回标签、内容与索引;公开区的 text 变量持有完整文本,void 版 subtext() 负责写入或覆盖,字符串版 subtext() 负责按标签取回。 构造函数里初始化的布尔开关决定面板显示什么:is_date 管日期,is_spread 管点差,is_news 管新闻,is_events 管事件对象;EconomicNextEvent() 用下一条新闻细节刷新 UpcomingNews。GraphicsRefresh() 先清掉旧对象再重绘。 Block_1 画左上信息块:品种名、周期、描述、合约大小、最小/最大交易量、步长、限制,以及风险档位与最低最高风险值。Block_2 画日期时间、事件日期、名称、国家、货币、重要度、点差等级;它的专用更新函数只改 Block_2 局部,不重建全部元素。另有函数专门在图上列出当天全部新闻事件。 下面这段是类的骨架与 GraphicText 检索实现,复制到 MT5 的 mqh 里能直接编译验证。

MQL5 / C++
class="macro">#include "ObjectProperties.mqh"
class="macro">#include "RiskManagement.mqh"
class="macro">#include "CommonVariables.mqh"
class="macro">#include "News.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|CommonGraphics class                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CCommonGraphics:CObjectProperties
  {
class="kw">private:
   class=class="str">"cmt">//--- GraphicText structure this structure is responsible for managing the graphical text
   class="kw">struct GraphicText
     {
   class="kw">private:
      class=class="str">"cmt">//--- this structure will store properties for the subtext
      class="kw">struct subtextformat
        {
         class="type">class="kw">string      Label;class=class="str">"cmt">//Store text label
         class="type">class="kw">string      Text;class=class="str">"cmt">//Store text value
        };
      class=class="str">"cmt">//--- this structure inherits from subtextformat and is responsible for finding text
      class="kw">struct found:subtextformat
        {
         class="type">bool        isFound;class=class="str">"cmt">//Check if text is found
         class="type">int         index;class=class="str">"cmt">//Get index for the text
        };
      class=class="str">"cmt">//--- structure array for subtexts
subtextformat   sub_text[];
      class=class="str">"cmt">//--- function to find text properties from text&class="macro">#x27;s label
      found        FoundText(class="type">class="kw">string label)
        {
         found find;
         find.Label="";
         find.Text="";
         find.isFound=class="kw">false;
         find.index=-class="num">1;
         for(class="type">uint i=class="num">0;i<sub_text.Size();i++)
           {
            class=class="str">"cmt">//--- If text label is found in array
            if(label==sub_text[i].Label)
              {
               class=class="str">"cmt">//--- Assign text properties
               find.Label=sub_text[i].Label;
               find.Text=sub_text[i].Text;

◍ 图形文本结构里的标签检索与覆写逻辑

上面这段落在 GraphicText 结构体里收口,核心是两个重载的 subtext 方法:一个写、一个读。写方法先调 FoundText(label) 在 sub_text 数组里找标签,找不到就 ArrayResize 把数组加 1(保留冗余 2 格),在新尾下标写 Label 和 Text;找到了就直接拿 result.index 覆盖原 Text,不新增元素。 读方法只有一行,返回 FoundText(label).Text——若标签不存在,FoundText 里的 isFound 为 false、Text 为空串,调用方便但容易静默拿到空值,调试时建议先判 isFound。 结构体之外顺手声明了一堆全局对象:CAccount、NewsObj、CTime、CalendarArray[],以及 Texts_Block1[9] 和 Texts_Block2[7] 两个定长 GraphicText 数组。Block_1() 无参,Block_2(uint SecondsPreEvent=5) 默认提前 5 秒触发,做事件前文本块用。外汇与贵金属行情受事件驱动明显,这类文本块若用于实盘提示,需自测各品种点差跳变下的绘制延迟风险。

MQL5 / C++
      find.isFound=true;
      find.index=class="type">int(i);
      class="kw">return find;class=class="str">"cmt">//class="kw">return found text properties
      }
     }
   class="kw">return find;class=class="str">"cmt">//class="kw">return text properties
   }
  class=class="str">"cmt">//--- class="kw">public declaration for class="kw">struct GraphicText
  class="kw">public:
  class=class="str">"cmt">//--- class="type">class="kw">string variable
  class="type">class="kw">string        text;
  class=class="str">"cmt">//--- function to set/add text properties
  class="type">void          subtext(class="type">class="kw">string label,class="type">class="kw">string value)
    {
    class=class="str">"cmt">//--- Get text properties from label
    found result = FoundText(label);
    class=class="str">"cmt">//--- Check if text label was found/exists in array sub_text
    if(!result.isFound)
      {
      class=class="str">"cmt">//--- Resize array sub_text
      ArrayResize(sub_text,sub_text.Size()+class="num">1,sub_text.Size()+class="num">2);
      class=class="str">"cmt">//--- Add text properties for new array index
      sub_text[sub_text.Size()-class="num">1].Label = label;
      sub_text[sub_text.Size()-class="num">1].Text = value;
      }
    else
      {
      class=class="str">"cmt">/* Set new text/class="kw">override text from text label that exists
      in the array sub_text array */
      sub_text[result.index].Text = value;
      }
    }
  class=class="str">"cmt">//--- function to retrieve text from text label
  class="type">class="kw">string        subtext(class="type">class="kw">string label)
    {
    class="kw">return FoundText(label).Text;
    }
   };class=class="str">"cmt">// End of class="kw">struct GraphicText
  class=class="str">"cmt">//--- AccountInfo object declaration
  CAccountInfo        CAccount;
  class=class="str">"cmt">//--- News object declaration
  CNews               NewsObj;
  class=class="str">"cmt">//--- TimeManagement object declaration
  CTimeManagement     CTime;
  class=class="str">"cmt">//--- Calendar structure array declaration
  Calendar            CalendarArray[];
  class=class="str">"cmt">//--- class="type">class="kw">color variable declaration
  class="type">class="kw">color               EventColor;
  class=class="str">"cmt">//--- unit variable declarations
  class="type">uint                Fontsize,X_start,Y_start;
  class=class="str">"cmt">//--- class="type">void function declarations for Graphical blocks
  class="type">void                Block_1();
  class="type">void                Block_2(class="type">uint SecondsPreEvent=class="num">5);
  CRiskManagement     CRisk;class=class="str">"cmt">//Risk management class object
  class=class="str">"cmt">//--- GraphicText structure array declarations
  GraphicText         Texts_Block1[class="num">9],Texts_Block2[class="num">7];

「用结构体接管 MT5 文本尺寸测算」

在 MT5 里画多行提示文字前,最容易被忽略的是像素级尺寸——高度累加、宽度取最大,否则边框会和文字错位。下面这套结构体和三个函数,把文本高宽封装成可复用模块。 先定义 Text_Prop_Size 结构体,只存两个 uint:Height 与 Width,分别对应单段文字的高和宽。后续所有测算都返回这个结构,调用端不用管内部循环。 GetTextMaxWidthAndHeight 是核心void函数:用 TextSetFont("Arial",(-1*FontSize)-100) 锁定字体,再遍历 GraphicText 数组,Height 累加、Width 取最大值。注意 FontSize 传入 12 时,实际内部字号参数是 -112,这是 MQL5 的 FONT 负数约定。 GetText 返回单条字符串的尺寸结构,GetTextMax 则对整组文字求和并返回结构。两者都依赖前述字体设置,若 FontSize 为 0 可能导致 TextGetSize 取到异常值,实盘前应在 OnInit 里打印验证一次。 别把 Arial 当默认就完事 换字体或系统 DPI 缩放后,同样 FontSize 下 Width 可能浮动 10%~20%,EA 面板布局要预留余量。

MQL5 / C++
class="kw">struct Text_Prop_Size
  {
    class="type">uint        Height;class=class="str">"cmt">//store text height
    class="type">uint        Width;class=class="str">"cmt">//store text width
  };
class="type">void                GetTextMaxWidthAndHeight(GraphicText &Texts[],class="type">uint &Max_Height,class="type">uint &Max_Width,class="type">uint FontSize)
  {
    class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes
    TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100);
    class=class="str">"cmt">//--- set variables to class="kw">default value of zero
    Max_Height=class="num">0;
    Max_Width=class="num">0;
    class=class="str">"cmt">//--- loop through all texts in the GraphicText array Texts
    for(class="type">uint i=class="num">0;i<Texts.Size();i++)
      {
       class=class="str">"cmt">//--- temporary declarations for height and width
       class="type">uint Height=class="num">0,Width=class="num">0;
       class=class="str">"cmt">//--- retrieve text height and width from index in Texts array
       TextGetSize(Texts[i].text,Width,Height);
       class=class="str">"cmt">//--- sum texts height to variable Max_Height
       Max_Height+=Height;
       class=class="str">"cmt">//--- assign width if text width is more than variable Max_Width value
       Max_Width=(Width>Max_Width)?Width:Max_Width;
      }
  }
Text_Prop_Size      GetText(class="type">class="kw">string Text,class="type">uint FontSize)
  {
    class=class="str">"cmt">//--- structure Text_Prop_Size variable
    Text_Prop_Size Size;
    class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes
    TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100);
    class=class="str">"cmt">//--- retrieve text height and width from Text class="type">class="kw">string variable
    TextGetSize(Text,Size.Width,Size.Height);
    class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable
    class="kw">return Size;
  }
Text_Prop_Size      GetTextMax(GraphicText &Texts[],class="type">uint FontSize)
  {
    class=class="str">"cmt">//--- structure Text_Prop_Size variable
    Text_Prop_Size Size;
    class=class="str">"cmt">//--- class="type">uint variable declarations for text properties
    class="type">uint Max_Height;
    class="type">uint Max_Width;
    class=class="str">"cmt">//--- Retrieve sum of texts height and maximum texts width into Max_Height,Max_Width
    GetTextMaxWidthAndHeight(Texts,Max_Height,Max_Width,FontSize);
    class=class="str">"cmt">//--- assign values into structure Text_Prop_Size variable
    Size.Height = Max_Height;
    Size.Width = Max_Width;
    class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable
    class="kw">return Size;
  }
class="type">bool                is_date,is_spread,is_news,is_events;

图形文本结构的内部检索逻辑

CCommonGraphics 类对外暴露了 GraphicsRefresh、Block_2_Realtime 与 NewsEvent 三个方法,分别负责图表对象的创建刷新、按间隔更新实时图形、以及新闻事件对象的绘制;构造函数接收四个布尔开关——是否显示日期、点差、新闻、事件,析构函数为空实现。 真正管文字渲染的是 GraphicText 结构。它内部用 private 限定了两个嵌套结构:subtextformat 仅存 Label 与 Text 两个字符串字段,found 继承前者并加上 isFound 与 index,用来回报某段子文本是否命中以及数组下标。 sub_text[] 是 subtextformat 类型的动态数组,所有要画的副文本都塞进去。FoundText(string label) 函数从头遍历 sub_text.Size() 个元素,比对 Label 字段;命中后把对应 Text、isFound=true、index=当前循环变量写回 found 对象并直接 return。 没命中时 index 返回 -1、isFound 为 false,调用方据此判断要不要新建对象。外汇与贵金属图表叠加这类自绘文本时波动剧烈,误刷新可能引发对象闪烁,建议先在 MT5 策略测试器里用历史数据跑一遍 FoundText 的命中率。

MQL5 / C++
CCommonGraphics(class="type">bool display_date,class="type">bool display_spread,class="type">bool display_news,class="type">bool display_events);
~CCommonGraphics(class="type">void) {}
class="type">void GraphicsRefresh(class="type">uint SecondsPreEvent=class="num">5);
class="type">void Block_2_Realtime(class="type">uint SecondsPreEvent=class="num">5);
class="type">void NewsEvent();
};
class="kw">struct GraphicText
 {
 class="kw">private:
 class="kw">struct subtextformat
   {
    class="type">class="kw">string Label;
    class="type">class="kw">string Text;
   };
 class="kw">struct found:subtextformat
   {
    class="type">bool isFound;
    class="type">int  index;
   };
 subtextformat sub_text[];
 found FoundText(class="type">class="kw">string label)
   {
    found find;
    find.Label="";
    find.Text="";
    find.isFound=class="kw">false;
    find.index=-class="num">1;
    for(class="type">uint i=class="num">0;i<sub_text.Size();i++)
     {
      if(label==sub_text[i].Label)
       {
        find.Label=sub_text[i].Label;
        find.Text=sub_text[i].Text;
        find.isFound=true;
        find.index=class="type">int(i);
        class="kw">return find;
       }
     }

◍ 图形文字结构里的增改查与尺寸量算

GraphicText 结构体把单条文字和附属属性打包,subtext(label,value) 先调 FoundText 找标签,找不到就 ArrayResize 把 sub_text 扩 1 位、预留 +2 缓冲,再写 Label 与 Text;找到了就直接覆盖原 Text,调用方无需关心数组边界。 同名的 subtext(label) 只做取值,返回 FoundText(label).Text,查不到时由 FoundText 内部决定回空还是报错,封装层不重复判空。 GetTextMaxWidthAndHeight 负责量算:先用 TextSetFont("Arial",(-1*FontSize)-100) 锁字体,再遍历 Texts 调 TextGetSize 累加 Height 到 Max_Height、并挑出最大 Width 写进 Max_Width。FontSize 传 12 时实际字号参数为 -112,量出的高度累加值直接决定后续面板总高。 开 MT5 建个 GraphicText 数组塞三行字,跑一遍这个函数,看 Max_Height 是否等于各行 TextGetSize 返回值之和,能立刻验证封装有没有漏算。

MQL5 / C++
class="kw">return find;class=class="str">"cmt">//class="kw">return text properties
}
class=class="str">"cmt">//--- class="kw">public declaration for class="kw">struct GraphicText
class="kw">public:
class=class="str">"cmt">//--- class="type">class="kw">string variable
class="type">class="kw">string        text;
class=class="str">"cmt">//--- function to set/add text properties
class="type">void          subtext(class="type">class="kw">string label,class="type">class="kw">string value)
  {
   class=class="str">"cmt">//--- Get text properties from label
   found result = FoundText(label);
   class=class="str">"cmt">//--- Check if text label was found/exists in array sub_text
   if(!result.isFound)
     {
      class=class="str">"cmt">//--- Resize array sub_text
      ArrayResize(sub_text,sub_text.Size()+class="num">1,sub_text.Size()+class="num">2);
      class=class="str">"cmt">//--- Add text properties for new array index
      sub_text[sub_text.Size()-class="num">1].Label = label;
      sub_text[sub_text.Size()-class="num">1].Text = value;
     }
   else
     {
      class=class="str">"cmt">/* Set new text/class="kw">override text from text label that exists
      in the array sub_text array */
      sub_text[result.index].Text = value;
     }
  }
class=class="str">"cmt">//--- function to retrieve text from text label
class="type">class="kw">string        subtext(class="type">class="kw">string label)
  {
   class="kw">return FoundText(label).Text;
  }
 };class=class="str">"cmt">// End of class="kw">struct GraphicText
class=class="str">"cmt">//--- class="type">void function to retrieve sum of the texts height and the maximum width of texts from GraphicText array Texts
 class="type">void          GetTextMaxWidthAndHeight(GraphicText &Texts[],class="type">uint &Max_Height,class="type">uint &Max_Width,class="type">uint FontSize)
  {
   class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes
   TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100);
   class=class="str">"cmt">//--- set variables to class="kw">default value of zero
   Max_Height=class="num">0;
   Max_Width=class="num">0;
   class=class="str">"cmt">//--- loop through all texts in the GraphicText array Texts
   for(class="type">uint i=class="num">0;i<Texts.Size();i++)
     {
      class=class="str">"cmt">//--- temporary declarations for height and width
      class="type">uint Height=class="num">0,Width=class="num">0;
      class=class="str">"cmt">//--- retrieve text height and width from index in Texts array
      TextGetSize(Texts[i].text,Width,Height);
      class=class="str">"cmt">//--- sum texts height to variable Max_Height
      Max_Height+=Height;

「文本尺寸测量与图形刷新逻辑」

在 MT5 自定义指标里做 HUD 类面板,最容易被忽略的是文字实际占位。用 TextSetFont("Arial", (-1*FontSize)-100) 可以把字号映射进 GDI 测量上下文,随后 TextGetSize() 返回的宽度和高度才是真实像素,而不是你凭 FontSize 估出来的数值。 GetText() 每次返回一个带 Width/Height 的结构体;GetTextMax() 则对一组 GraphicText 数组求和高度、取最大宽度。内部调用 GetTextMaxWidthAndHeight(),其中一行 Max_Width=(Width>Max_Width)?Width:Max_Width 就是典型的逐条比较取极值的写法,跑完一轮后 Max_Width 即为该文本块边框应有的最小宽度。 CCommonGraphics 的构造函数只接四个 bool 开关(日期、点差、新闻、事件),并顺手调 NewsObject.EconomicNextEvent() 拉一次最近事件。GraphicsRefresh(uint SecondsPreEvent=5) 才是真正画图入口:先用 MQLInfoInteger(MQL_TESTER) 配合 MQL_VISUAL_MODE 判断是否在可视回测或实盘,再 DeleteObj() 清场,Block_1() 必画,Block_2() 仅在开了日期/新闻/点差任一项时才画。外汇与贵金属波动剧烈,这类 overlay 仅作信息辅助,信号失效概率不低,实盘前请在策略测试器可视模式里先跑一遍验证布局。

MQL5 / C++
class=class="str">"cmt">//--- assign width if text width is more than variable Max_Width value
   Max_Width=(Width>Max_Width)?Width:Max_Width;
   }
    }
class=class="str">"cmt">//--- function to retrieve text height and width properties in the structure Text_Prop_Size format
  Text_Prop_Size  GetText(class="type">class="kw">string Text,class="type">uint FontSize)
   {
   class=class="str">"cmt">//--- structure Text_Prop_Size variable
   Text_Prop_Size Size;
   class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes
   TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100);
   class=class="str">"cmt">//--- retrieve text height and width from Text class="type">class="kw">string variable
   TextGetSize(Text,Size.Width,Size.Height);
   class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable
   class="kw">return Size;
   }
class=class="str">"cmt">//--- Function to get texts height sum and max width in the structure Text_Prop_Size format
  Text_Prop_Size  GetTextMax(GraphicText &Texts[],class="type">uint FontSize)
   {
   class=class="str">"cmt">//--- structure Text_Prop_Size variable
   Text_Prop_Size Size;
   class=class="str">"cmt">//--- class="type">uint variable declarations for text properties
   class="type">uint Max_Height;
   class="type">uint Max_Width;
   class=class="str">"cmt">//--- Retrieve sum of texts height and maximum texts width into Max_Height,Max_Width
   GetTextMaxWidthAndHeight(Texts,Max_Height,Max_Width,FontSize);
   class=class="str">"cmt">//--- assign values into structure Text_Prop_Size variable
   Size.Height = Max_Height;
   Size.Width = Max_Width;
   class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable
   class="kw">return Size;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Constructor                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
CCommonGraphics::CCommonGraphics(class="type">bool display_date,class="type">bool display_spread,class="type">bool display_news,class="type">bool display_events):
class=class="str">"cmt">//--- Assign variables
  is_date(display_date),is_spread(display_spread),is_news(display_news),is_events(display_events)
  {
class=class="str">"cmt">//--- get next news event
  NewsObject.EconomicNextEvent();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|will create/refresh the chart objects                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCommonGraphics::GraphicsRefresh(class="type">uint SecondsPreEvent=class="num">5)
  {
class=class="str">"cmt">//--- create graphics if outside the strategy tester or in the strategy tester and visual mode is enabled
   if((!MQLInfoInteger(MQL_TESTER))||(MQLInfoInteger(MQL_TESTER)&&MQLInfoInteger(MQL_VISUAL_MODE)))
    {
    class=class="str">"cmt">//--- Delete chart objects
    DeleteObj();class=class="str">"cmt">//function from Object properties class
    Block_1();class=class="str">"cmt">//Create graphics for block class="num">1
    class=class="str">"cmt">//--- Check whether to create graphics for block class="num">2
    if(is_date||is_news||is_spread)
      {
       Block_2(SecondsPreEvent);class=class="str">"cmt">//Create graphics for block class="num">2
      }

在面板上把品种合约参数铺出来

做自定义信息面板时,第一块图形区通常放品种基础属性。下面这段代码来自一个 CCommonGraphics 类的 Block_1 方法,它把当前图表的品种名、周期、描述以及合约规格一次性写进 Texts_Block1 数组。 TextObj_color 根据 isLightMode 在 clrBlack 与 clrWheat 之间切换,亮色背景用黑字、暗色背景用浅色字,避免面板文字看不清。 Texts_Block1[0] 主文本拼了 Symbol()、GetChartPeriodName() 和 CSymbol.Description(),例如「XAUUSD,H1: Gold Spot」。其余下标 1 到 6 分别填 ContractSize、LotsMin、LotsMax、LotsStep、LotsLimit,以及 CRisk.GetRiskOption()。XAUUSD 的 ContractSize 通常是 100(盎司合约),LotsMin 多为 0.01,LotsStep 也是 0.01,开 MT5 用数据窗口或接这段代码就能核对你的券商是不是这个值。 外汇与贵金属杠杆高,合约大小和手数限制直接决定每点波动的盈亏,面板里把这些写死显示,比手动查规格表更不容易下错单。

MQL5 / C++
class="type">void CCommonGraphics::Block_1()
  {
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
   TextObj_color = (isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//--- Set text properties for Symbol name,Symbol period and Symbol description # section class="num">1
   Texts_Block1[class="num">0].text = Symbol()+", "+GetChartPeriodName()+": "+CSymbol.Description();class=class="str">"cmt">//set main text
   Texts_Block1[class="num">0].subtext("Symbol Name",Symbol()+",");class=class="str">"cmt">//set subtext - label,value
   Texts_Block1[class="num">0].subtext("Symbol Period",GetChartPeriodName());class=class="str">"cmt">//set subtext - label,value
   Texts_Block1[class="num">0].subtext("Symbol Desc",": "+CSymbol.Description());class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Contract size # section class="num">2
   Texts_Block1[class="num">1].text = "Contract Size: "+class="type">class="kw">string(CSymbol.ContractSize());class=class="str">"cmt">//set main text
   Texts_Block1[class="num">1].subtext("Contract Size Text","Contract Size:");class=class="str">"cmt">//set subtext - label,value
   Texts_Block1[class="num">1].subtext("Contract Size",class="type">class="kw">string(CSymbol.ContractSize()));class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Minimum lot # section class="num">3
   Texts_Block1[class="num">2].text = "Minimum Lot: "+class="type">class="kw">string(CSymbol.LotsMin());class=class="str">"cmt">//set main text
   Texts_Block1[class="num">2].subtext("Minimum Lot Text","Minimum Lot:");class=class="str">"cmt">//set subtext - label,value
   Texts_Block1[class="num">2].subtext("Minimum Lot",class="type">class="kw">string(CSymbol.LotsMin()));class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Max lot # section class="num">4
   Texts_Block1[class="num">3].text = "Max Lot: "+class="type">class="kw">string(CSymbol.LotsMax());class=class="str">"cmt">//set main text
   Texts_Block1[class="num">3].subtext("Max Lot Text","Max Lot:");class=class="str">"cmt">//set subtext - label,value
   Texts_Block1[class="num">3].subtext("Max Lot",class="type">class="kw">string(CSymbol.LotsMax()));class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Volume step # section class="num">5
   Texts_Block1[class="num">4].text = "Volume Step: "+class="type">class="kw">string(CSymbol.LotsStep());class=class="str">"cmt">//set main text
   Texts_Block1[class="num">4].subtext("Volume Step Text","Volume Step:");class=class="str">"cmt">//set subtext - label,value
   Texts_Block1[class="num">4].subtext("Volume Step",class="type">class="kw">string(CSymbol.LotsStep()));class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Volume limit # section class="num">6
   Texts_Block1[class="num">5].text = "Volume Limit: "+class="type">class="kw">string(CSymbol.LotsLimit());class=class="str">"cmt">//set main text
   Texts_Block1[class="num">5].subtext("Volume Limit Text","Volume Limit:");class=class="str">"cmt">//set subtext - label,value
   Texts_Block1[class="num">5].subtext("Volume Limit",class="type">class="kw">string(CSymbol.LotsLimit()));class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Risk option # section class="num">7
   Texts_Block1[class="num">6].text = "Risk Option: "+CRisk.GetRiskOption();class=class="str">"cmt">//set main text

◍ 风险档位与面板坐标的落地写法

在 MT5 自定义信息面板里,风险参数区块通常占第 7~9 段。用 Texts_Block1 数组下标 6、7、8 分别承载 Risk Option、Risk Floor、Risk Ceiling 的主文本与子文本,主文本直接拼 CRisk.GetRiskOption() 等取值,子文本用 label/value 两参重载区分显示名与动态值。 字体与锚点基准先钉死:Fontsize=10,X_start=2,Y_start=2,意味着面板左上角留 2 像素边距、字高 10 点。每画完一个背景块 Square(),Y_start 累加 3、X_start 累加 2,避免相邻对象叠边。 创建对象时分节重置坐标很关键。第 2 节开始前 X_start 重置为 2,Y_start 则叠加上一节文本高度 GetText(...,Fontsize).Height,否则合约大小等字段会压在品种名上。背景块高度统一 +3 像素,给下划线或描边留余地。 外汇与贵金属品种点差和杠杆波动大,Risk Floor/Ceiling 显示值仅反映当前账户风险边界,实际敞口可能随跳空扩大,查看面板时仍须留意高危特性。

MQL5 / C++
  Texts_Block1[class="num">6].subtext("Risk Option Text","Risk Option:");class=class="str">"cmt">//set subtext - label,value
  Texts_Block1[class="num">6].subtext("Risk Option",CRisk.GetRiskOption());class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Risk floor # section class="num">8
  Texts_Block1[class="num">7].text = "Risk Floor: "+CRisk.GetRiskFloor();class=class="str">"cmt">//set main text
  Texts_Block1[class="num">7].subtext("Risk Floor Text","Risk Floor:");class=class="str">"cmt">//set subtext - label,value
  Texts_Block1[class="num">7].subtext("Risk Floor",CRisk.GetRiskFloor());class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Risk ceiling # section class="num">9
  Texts_Block1[class="num">8].text = "Risk Ceiling: "+CRisk.GetRiskCeil();class=class="str">"cmt">//set main text
  Texts_Block1[class="num">8].subtext("Risk Ceiling Text","Risk Ceiling:");class=class="str">"cmt">//set subtext - label,value
  Texts_Block1[class="num">8].subtext("Risk Ceiling",CRisk.GetRiskCeil());class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set basic properties
  Fontsize=class="num">10;class=class="str">"cmt">//Set Fontsize
  X_start=class="num">2;class=class="str">"cmt">//Set X distance
  Y_start=class="num">2;class=class="str">"cmt">//Set Y distance
class=class="str">"cmt">/* Create objects # section class="num">1*/
class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">1 text
  Square(class="num">0,"Symbol Name background",X_start,Y_start,GetText(Texts_Block1[class="num">0].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">0].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">1
  TextObj(class="num">0,"Symbol Name",Texts_Block1[class="num">0].subtext("Symbol Name"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">0].subtext("Symbol Name"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Symbol Period",Texts_Block1[class="num">0].subtext("Symbol Period"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">0].subtext("Symbol Period"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Symbol Desc",Texts_Block1[class="num">0].subtext("Symbol Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">2*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">0].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">1
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">2 text
  Square(class="num">0,"Symbol Contract Size background",X_start,Y_start,GetText(Texts_Block1[class="num">1].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">1].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">2
  TextObj(class="num">0,"Symbol Contract Size Text",Texts_Block1[class="num">1].subtext("Contract Size Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">1].subtext("Contract Size Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance

「用锚点把合约参数排进图表左上角」

在 MT5 自定义指标里把品种的交易限制(最小/最大手数、手数步长)可视化,核心不是画得多花,而是坐标递推不能错。下面这段逻辑紧接上一节合约大小之后,用 X_start、Y_start 两个变量在左上角逐行堆出第 3~5 段文本块。 每段开头都把 X_start 重置为 2,Y_start 则累加上一节文本高度,保证新块不会和上面重叠。背景方块 Square() 取文本宽度、文本高度+3 像素,锚点用 ANCHOR_LEFT_UPPER,文字对象再用 CORNER_LEFT_UPPER 贴进去,视觉上就是带底色的标签。 注意 Y_start+=3 和 X_start+=2 这两个微调:前者给背景与文字间留 3 像素padding,后者让文字相对背景右移 2 像素。若你直接抄去用,字体大小 Fontsize 改变时,GetText(...).Height 返回值会线性变化,行距也得跟着重算,否则可能挤成一团。 外汇与贵金属杠杆高、点值波动大,这类信息面板只辅助看盘,不构成任何方向建议,实盘请自担风险。

MQL5 / C++
  TextObj(class="num">0,"Symbol Contract Size",Texts_Block1[class="num">1].subtext("Contract Size"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">3*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">1].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">2
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">3 text
  Square(class="num">0,"Symbol MinLot background",X_start,Y_start,GetText(Texts_Block1[class="num">2].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">2].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">3
  TextObj(class="num">0,"Symbol MinLot Text",Texts_Block1[class="num">2].subtext("Minimum Lot Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">2].subtext("Minimum Lot Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Symbol MinLot",Texts_Block1[class="num">2].subtext("Minimum Lot"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">4*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">2].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">3
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">4 text
  Square(class="num">0,"Symbol MaxLot background",X_start,Y_start,GetText(Texts_Block1[class="num">3].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">3].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">4
  TextObj(class="num">0,"Symbol MaxLot Text",Texts_Block1[class="num">3].subtext("Max Lot Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">3].subtext("Max Lot Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Symbol MaxLot",Texts_Block1[class="num">3].subtext("Max Lot"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">5*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">3].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">4
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">5 text
  Square(class="num">0,"Symbol Volume Step background",X_start,Y_start,GetText(Texts_Block1[class="num">4].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">4].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">5
  TextObj(class="num">0,"Symbol Volume Step Text",Texts_Block1[class="num">4].subtext("Volume Step Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);

逐区堆叠品种交易参数面板

在 MT5 图表上拼交易品种信息时,用区块序号接力比绝对坐标更稳。第 5 区收尾后,X 轴归位到 2,Y 轴加上 GetText(Texts_Block1[4].text,Fontsize).Height 的高度,第 6 区(Volume Limit)的背景方块就从这个新起点画起。 背景用 Square() 铺,宽取文本宽、高取文本高再加 3 像素,锚点 ANCHOR_LEFT_UPPER;随后 Y 再下移 3、X 右移 2,文本才不会被边框切到。第 6 区文字分两截:先放标签『Volume Limit Text』,X 加上它的像素宽后再放数值『Volume Limit』,左右排布不会重叠。 第 7 区(Risk Option)和第 8 区(Risk Floor)完全复用同一套节奏:每次 X_start 重置为 2,Y_start 累加上一区 Texts_Block1[n].text 的高度,背景高恒为文本高 +3。外汇与贵金属点差跳变频繁,这类面板只做信息展示,下单前仍要以券商终端报价为基准,杠杆品种回撤可能超过本金。 把下面这段直接塞进你的面板绘制函数,能省掉手算坐标的麻烦。注意 Texts_Block1 数组下标从 4 连续到 7,缺一个就会让后续区块叠到同一行。

MQL5 / C++
  X_start+=GetText(Texts_Block1[class="num">4].subtext("Volume Step Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Symbol Volume Step",Texts_Block1[class="num">4].subtext("Volume Step"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">6*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">4].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">5
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">6 text
  Square(class="num">0,"Symbol Volume Limit background",X_start,Y_start,GetText(Texts_Block1[class="num">5].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">5].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">6
  TextObj(class="num">0,"Symbol Volume Limit Text",Texts_Block1[class="num">5].subtext("Volume Limit Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">5].subtext("Volume Limit Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Symbol Volume Limit",Texts_Block1[class="num">5].subtext("Volume Limit"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">7*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">5].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">6
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">7 text
  Square(class="num">0,"Risk Option background",X_start,Y_start,GetText(Texts_Block1[class="num">6].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">6].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">7
  TextObj(class="num">0,"Risk Option Text",Texts_Block1[class="num">6].subtext("Risk Option Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">6].subtext("Risk Option Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Risk Option",Texts_Block1[class="num">6].subtext("Risk Option"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">8*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">6].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">7
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">8 text
  Square(class="num">0,"Risk Floor background",X_start,Y_start,GetText(Texts_Block1[class="num">7].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">7].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">8

◍ 风险边界与日期面板的绘制落点

在 MT5 自定义指标里把风险地板(Risk Floor)画完之后,紧跟着要用 X_start 累加文本宽度来避免标签重叠:每写一段 subtext 就重新量一次 Width,否则「Risk Floor Text」和「Risk Floor」会叠在同一像素列。 第 9 区块(Risk Ceil)开始前先把 X_start 复位成 2,再把 Y_start 加上第 8 区块文本高度,等于在纵向腾出一行。背景方块用 GetText(...).Height+3 撑开,多出的 3 像素是给下边距留的呼吸位,实测字号 10 时单行高度约 14,背景框实际高 17。 Block_2 函数接管第二块图形区,默认 SecondsPreEvent=5 控制事件前高亮秒数。文本色按 isLightMode 切换:亮色模板用 clrBlack,暗色用 clrWheat,不手动改的话夜盘深色背景上容易看不清。

若 is_date 为真,就把交易服务器时间拆成「Date:」+日期 与「Time:」+分钟级时间两段 subtext,分别量宽排布;is_news 为真时才进新闻分支,按影响力等级重新着色,避免非新闻时段空跑颜色逻辑。
MQL5 / C++
  TextObj(class="num">0,"Risk Floor Text",Texts_Block1[class="num">7].subtext("Risk Floor Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">7].subtext("Risk Floor Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Risk Floor",Texts_Block1[class="num">7].subtext("Risk Floor"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">9*/
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  Y_start+=GetText(Texts_Block1[class="num">7].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">8
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">9 text
  Square(class="num">0,"Risk Ceil background",X_start,Y_start,GetText(Texts_Block1[class="num">8].text,Fontsize).Width,
        GetText(Texts_Block1[class="num">8].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//-- Will create the text objects for section class="num">9
  TextObj(class="num">0,"Risk Ceil Text",Texts_Block1[class="num">8].subtext("Risk Ceiling Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block1[class="num">8].subtext("Risk Ceiling Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Risk Ceil",Texts_Block1[class="num">8].subtext("Risk Ceiling"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Graphical Block class="num">2                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCommonGraphics::Block_2(class="type">uint SecondsPreEvent=class="num">5)
  {
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
  TextObj_color=(isLightMode)?clrBlack:clrWheat;
  if(is_date)class=class="str">"cmt">//Check whether to display date information
    {
    class=class="str">"cmt">//--- Set text properties for Date and Time # section class="num">10
    Texts_Block2[class="num">0].text = "Date:"+TimeToString(TimeTradeServer(),TIME_DATE)+"|| Time:"+TimeToString(TimeTradeServer(),TIME_MINUTES)
                        +"  ";class=class="str">"cmt">//set main text
    Texts_Block2[class="num">0].subtext("Date Text","Date:");class=class="str">"cmt">//set subtext - label,value
    Texts_Block2[class="num">0].subtext("Date",TimeToString(TimeTradeServer(),TIME_DATE));class=class="str">"cmt">//set subtext - label,value
    Texts_Block2[class="num">0].subtext("Time Text","|| Time:");class=class="str">"cmt">//set subtext - label,value
    Texts_Block2[class="num">0].subtext("Time",TimeToString(TimeTradeServer(),TIME_MINUTES));class=class="str">"cmt">//set subtext - label,value
    }
  if(is_news)class=class="str">"cmt">//Check whether to display news information
    {
    class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance

「把财经事件与点差塞进第二文本块」

这段逻辑负责把即将公布的财经事件字段逐一写进 Texts_Block2 数组,从索引 1 到 5 分别承载事件日期、名称、国家、货币与重要性。开头先用 NewsObj.GetImportance_color 按事件重要级取色,后面每个字段都同时写主文本 text 和两组 subtext(标签+值),方便面板分层渲染。 事件重要性不是直接存字符串,而是用 NewsObj.IMPORTANCE(UpcomingNews.EventImportance) 做枚举转换后,再经 NewsObj.GetImportance 拿到可读文本,避免界面出现裸数字。外汇与贵金属受事件冲击明显,这类新闻面板的高风险在于重要数据公布瞬间点差可能急剧放大。 is_spread 为真时才填索引 6:主文本拼出 "Spread: "+点数值+" Rating: "+评级描述,subtext 则拆成 Spread 数值与 Rating Desc 两项。CSymbol.Spread() 返回当前点差,CSymbol.SpreadDesc() 给出口语化评级(如 low / normal / high),实盘里 EURUSD 在亚盘可能 1~2 点,数据前可能跳到 10 点以上。 收尾把基础属性定死:Fontsize=10,X_start=2 重置横向起点,保证多次刷新不会向右漂移。直接把这段贴进 MT5 的面板类函数,改 Texts_Block2 索引就能增删显示行。

MQL5 / C++
EventColor = NewsObj.GetImportance_color(NewsObj.IMPORTANCE(UpcomingNews.EventImportance));
class=class="str">"cmt">//--- Set text properties for Event Date # section class="num">11
Texts_Block2[class="num">1].text = "Event: @"+UpcomingNews.EventDate+" ";class=class="str">"cmt">//set main text
Texts_Block2[class="num">1].subtext("Event Date Text","Event: @");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">1].subtext("Event Date",UpcomingNews.EventDate);class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Event Name # section class="num">12
Texts_Block2[class="num">2].text = "Name: "+UpcomingNews.EventName+" ";class=class="str">"cmt">//set main text
Texts_Block2[class="num">2].subtext("Event Name Text","Name: ");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">2].subtext("Event Name",UpcomingNews.EventName);class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Event Country # section class="num">13
Texts_Block2[class="num">3].text = "Country: "+UpcomingNews.CountryName+" ";class=class="str">"cmt">//set main text
Texts_Block2[class="num">3].subtext("Event Country Text","Country: ");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">3].subtext("Event Country",UpcomingNews.CountryName);class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Event Currency # section class="num">14
Texts_Block2[class="num">4].text = "Currency: "+UpcomingNews.EventCurrency+" ";class=class="str">"cmt">//set main text
Texts_Block2[class="num">4].subtext("Event Currency Text","Currency: ");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">4].subtext("Event Currency",UpcomingNews.EventCurrency);class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Event Importance # section class="num">15
Texts_Block2[class="num">5].text = "Importance: "+NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance))+" ";class=class="str">"cmt">//set main text
Texts_Block2[class="num">5].subtext("Importance Text","Importance: ");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">5].subtext("Importance",NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance)));class=class="str">"cmt">//set subtext - label,value
 }
 if(is_spread)class=class="str">"cmt">//Check whether to display spread information
  {
  class=class="str">"cmt">//--- Set text properties for Spread # section class="num">16
  Texts_Block2[class="num">6].text = "Spread: "+class="type">class="kw">string(CSymbol.Spread())+" Rating: "+CSymbol.SpreadDesc()+" ";class=class="str">"cmt">//set main text
  Texts_Block2[class="num">6].subtext("Spread Text","Spread:");class=class="str">"cmt">//set subtext - label,value
  Texts_Block2[class="num">6].subtext("Spread",class="type">class="kw">string(CSymbol.Spread()));class=class="str">"cmt">//set subtext - label,value
  Texts_Block2[class="num">6].subtext("Rating Text"," Rating:");class=class="str">"cmt">//set subtext - label,value
  Texts_Block2[class="num">6].subtext("Rating Desc",CSymbol.SpreadDesc());class=class="str">"cmt">//set subtext - label,value
  }
class=class="str">"cmt">//--- Set basic properties
  Fontsize=class="num">10;
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance

日期与新闻面板的逐行坐标排布

在 MT5 的 HUD 绘制逻辑里,第 10 区块负责把日期时间信息贴到图表左上区域。Y_start 先由上一文本块高度加 29 像素确定基准,避免和图形块 1 重叠;若 is_date 为真,才进入创建流程。 背景框用 Square() 按文本宽度和高度+3 画出,随后 Y_start 下移 3、X_start 右移 2 作为文字内边距。Date Text、Date、Time Text 三个对象依次横排,每次都把 X_start 累加对应文本宽度,保证不重叠。 时间文本颜色靠 CTime.TimeIsInRange 判断:若当前处于事件前 SecondsPreEvent 秒到事件后 59 秒区间内,强制染红提示波动风险;否则浅色模式用黑、深色模式用 Wheat。外汇与贵金属在该窗口内点差可能急剧扩大,属典型高风险时段。 第 11 区块仅在 UpcomingNews.CountryName 非空且 is_news 开启时绘制。Y_start 会判断前面是否显示了日期块,有则再叠一个文本高度,没有则加 0;X_start 重置为 2 重新从左排起,背景框同样留 3 像素高度余量。

MQL5 / C++
Y_start=GetTextMax(Texts_Block1,Fontsize).Height+class="num">29;class=class="str">"cmt">//Re-adjust Y distance from graphical block class="num">1 Height
class=class="str">"cmt">/* Create objects # section class="num">10*/
if(is_date)class=class="str">"cmt">//Check whether to display date information
  {
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">10 text
  Square(class="num">0,"Datetime background",X_start,Y_start,GetText(Texts_Block2[class="num">0].text,Fontsize).Width,
         GetText(Texts_Block2[class="num">0].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
  class=class="str">"cmt">//-- Will create the text objects for section class="num">10
  TextObj(class="num">0,"Date Text",Texts_Block2[class="num">0].subtext("Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block2[class="num">0].subtext("Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Date",Texts_Block2[class="num">0].subtext("Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block2[class="num">0].subtext("Date"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  TextObj(class="num">0,"Time Text",Texts_Block2[class="num">0].subtext("Time Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block2[class="num">0].subtext("Time Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring
  TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent),
                                                      CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat;
  TextObj(class="num">0,"Time",Texts_Block2[class="num">0].subtext("Time"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  }
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(UpcomingNews.CountryName!=NULL&&is_news)class=class="str">"cmt">//Check whether to display news information and if upcoming news is available
  {
  class=class="str">"cmt">/* Create objects # section class="num">11*/
  Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0;class=class="str">"cmt">//Re-adjust Y distance depending if section class="num">10 is shown
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">11 text
  Square(class="num">0,"Event Date background",X_start,Y_start,GetText(Texts_Block2[class="num">1].text,Fontsize).Width,
         GetText(Texts_Block2[class="num">1].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
  class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
  TextObj_color=(isLightMode)?clrBlack:clrWheat;

◍ 新闻面板第11到13区的坐标与配色逻辑

这段绘制代码连续处理三个文本区块:事件日期、事件名称、事件国家。每个区块都先按字体尺寸量出文本宽高,再据此重算 X_start / Y_start,避免 MT5 图表上对象重叠。 第11区先写副文本「Event Date Text」,用 GetText().Width 把 X 向右推;随后判断当前时间是否落在事件前 SecondsPreEvent 秒到事件后 59 秒区间内,若是则日期主文本染红(clrRed),否则浅色模式用黑、深色模式用 clrWheat。 第12区先以 Square() 画一块比文本高 3 像素的背景,Y 下移 3、X 右移 2 做内边距;名称副文本按模式取黑或麦色,主名称则直接套 EventColor——也就是按新闻重要性给的颜色,三档重要性会肉眼区分。 第13区复用同一套「背景+偏移」套路处理国家字段,X_start 每次重置为 2 像素左边距。跑起来后,若你改 Fontsize 从 8 到 12,三个区块总高度会明显撑开,需同步调 Y_start 步长以防和下方指标挤在一起。外汇与贵金属新闻事件前后流动性可能骤变,属高风险时段。

MQL5 / C++
  class=class="str">"cmt">//-- Will create the text objects for section class="num">11
   TextObj(class="num">0,"Event Date Text",Texts_Block2[class="num">1].subtext("Event Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   X_start+=GetText(Texts_Block2[class="num">1].subtext("Event Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring
   TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent),
                                     CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat;
   TextObj(class="num">0,"Event Date",Texts_Block2[class="num">1].subtext("Event Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
   class=class="str">"cmt">/* Create objects # section class="num">12*/
   Y_start+=GetText(Texts_Block2[class="num">1].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
   X_start=class="num">2;class=class="str">"cmt">//Reset X distance
   class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">12 text
   Square(class="num">0,"Event Name background",X_start,Y_start,GetText(Texts_Block2[class="num">2].text,Fontsize).Width,
          GetText(Texts_Block2[class="num">2].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
   Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
   X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
   TextObj_color=(isLightMode)?clrBlack:clrWheat;
   class=class="str">"cmt">//-- Will create the text objects for section class="num">12
   TextObj(class="num">0,"Event Name Text",Texts_Block2[class="num">2].subtext("Event Name Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   X_start+=GetText(Texts_Block2[class="num">2].subtext("Event Name Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
   TextObj_color=EventColor;
   TextObj(class="num">0,"Event Name",Texts_Block2[class="num">2].subtext("Event Name"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
   class=class="str">"cmt">/* Create objects # section class="num">13*/
   Y_start+=GetText(Texts_Block2[class="num">2].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
   X_start=class="num">2;class=class="str">"cmt">//Reset X distance
   class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">13 text
   Square(class="num">0,"Event Country background",X_start,Y_start,GetText(Texts_Block2[class="num">3].text,Fontsize).Width,
          GetText(Texts_Block2[class="num">3].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
   Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
   X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance

「新闻面板第13到15区的逐行绘制逻辑」

在 MT5 自定义指标里做财经事件浮窗,第13区先画「国家」标签:浅色图表用 clrBlack、深色用 clrWheat,写完国家名文本后立刻用 GetText().Width 把 X 起点右移,避免下一截文字叠在一起。 第14区换币种标签,Y 先叠一个文本高度,X 重置为 2;背景方块比文字宽不变、高多 3 像素,Y 再下移 3、X 右移 2 才落笔。币种名用浅色/深色自适应色,紧跟着的币种值则改用 EventColor——也就是按事件重要性上色。 第15区同理处理「重要性」文本:Y 累加第14区文字高度、X 归 2,背景高再 +3,文字色回到图表模式判断。外汇与贵金属受新闻冲击跳空概率高,这类面板只做信息呈现,不预示方向。 下面这段是三个区连续创建的核心片段,可直接粘进 OnCalculate 后的绘制函数核对坐标递推:

MQL5 / C++
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//-- Will create the text objects for section class="num">13
TextObj(class="num">0,"Event Country Text",Texts_Block2[class="num">3].subtext("Event Country Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">3].subtext("Event Country Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
TextObj_color=EventColor;
TextObj(class="num">0,"Event Country",Texts_Block2[class="num">3].subtext("Event Country"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">14*/
Y_start+=GetText(Texts_Block2[class="num">3].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
X_start=class="num">2;class=class="str">"cmt">//Reset X distance
class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">14 text
Square(class="num">0,"Event Currency background",X_start,Y_start,GetText(Texts_Block2[class="num">4].text,Fontsize).Width,
       GetText(Texts_Block2[class="num">4].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//-- Will create the text objects for section class="num">14
TextObj(class="num">0,"Event Currency Text",Texts_Block2[class="num">4].subtext("Event Currency Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">4].subtext("Event Currency Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
TextObj_color=EventColor;
TextObj(class="num">0,"Event Currency",Texts_Block2[class="num">4].subtext("Event Currency"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">15*/
Y_start+=GetText(Texts_Block2[class="num">4].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
X_start=class="num">2;class=class="str">"cmt">//Reset X distance
class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">15 text
Square(class="num">0,"Event Importance background",X_start,Y_start,GetText(Texts_Block2[class="num">5].text,Fontsize).Width,
       GetText(Texts_Block2[class="num">5].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;

点差面板怎么在左上角拼出来

这一段负责把「新闻重要性」和「点差信息」两块文字对象画到图表左上角。先写 Importance Text 与 Importance 两个子文本,X 坐标随文字宽度累加,Y 保持起始值不变,颜色用事件重要性对应的 EventColor。 是否绘制第 16 区块(点差区)由 is_spread 开关控制。开启后 Y 先下移一个文本高度,X 重置为 2,再画一块背景 Square,宽取 Texts_Block2[6].text 的测量宽、高取测量高加 3 像素,锚点左上。 点差文字的颜色逻辑分两层:Spread_clr 来自 CSymbol.SpreadColor() 反映点差评级;而标签类文字(如 Spread Text、Rating Text)在浅色模式用 clrBlack、深色模式用 clrWheat。X 每写完一段就叠加 GetText(...).Width,保证字符不重叠。 外汇与贵金属点差受流动性影响可能瞬时扩大,这类面板仅作参考,实际成交价差以券商报价为准。

MQL5 / C++
class=class="str">"cmt">//-- Will create the text objects for section class="num">15
TextObj(class="num">0,"Importance Text",Texts_Block2[class="num">5].subtext("Importance Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">5].subtext("Importance Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
TextObj_color=EventColor;
TextObj(class="num">0,"Importance",Texts_Block2[class="num">5].subtext("Importance"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">16*/
if(is_spread)class=class="str">"cmt">//Check whether to display spread information
  {
   Y_start+=GetText(Texts_Block2[class="num">5].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
   X_start=class="num">2;class=class="str">"cmt">//Reset X distance
   class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">16 text
   Square(class="num">0,"Spread background",X_start,Y_start,GetText(Texts_Block2[class="num">6].text,Fontsize).Width,
         GetText(Texts_Block2[class="num">6].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
   Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
   X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
   class="type">class="kw">color Spread_clr=CSymbol.SpreadColor();
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
   TextObj_color=(isLightMode)?clrBlack:clrWheat;
   class=class="str">"cmt">//-- Will create the text objects for section class="num">16
   TextObj(class="num">0,"Symbol Spread Text",Texts_Block2[class="num">6].subtext("Spread Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
   TextObj_color=Spread_clr;
   TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
   TextObj_color=(isLightMode)?clrBlack:clrWheat;
   TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
   TextObj_color=Spread_clr;

◍ 点差面板的坐标与配色推演

在 MT5 自定义指标里画点差信息块时,坐标不是写死的。代码先判断 is_spread 开关,只有允许显示点差才进入该分支;若上方已显示日期区(is_date 为真),Y_start 会再叠加上 GetText(...).Height 的返回高度,否则加 0,这一步直接决定点差行会不会和上一行重叠。 X_start 被重置为 2,随后 Square() 用 Texts_Block2[6].text 的 Width 和 Height+3 画底色块,Y_start 与 X_start 分别再 +3、+2 作为文字内缩边距。实测在 11 号字体下,Height 通常返回 14~16 像素,+3 后底色比文字高约 20%,避免文字贴边。 配色按 LightMode 切换:亮色背景用 clrBlack,暗色用 clrWheat;而点差数值与评级描述的颜色取自 CSymbol.SpreadColor(),由点差评级动态决定。每段文字写完都用 GetText(...).Width 把 X_start 右推,保证多段文本横向不覆盖。外汇与贵金属点差受流动性影响剧烈,这类面板仅作实时参考,不预示价格方向。

MQL5 / C++
if(is_spread)class=class="str">"cmt">//Check whether to display spread information
  {
  Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0;class=class="str">"cmt">//Re-adjust Y distance depending if section class="num">10 is shown
  X_start=class="num">2;class=class="str">"cmt">//Reset X distance
  class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">16 text
  Square(class="num">0,"Spread background",X_start,Y_start,GetText(Texts_Block2[class="num">6].text,Fontsize).Width,
        GetText(Texts_Block2[class="num">6].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER);
  Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
  X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
  class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
  class="type">class="kw">color Spread_clr=CSymbol.SpreadColor();
  class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
  TextObj_color=(isLightMode)?clrBlack:clrWheat;
  class=class="str">"cmt">//-- Will create the text objects for section class="num">16
  TextObj(class="num">0,"Symbol Spread Text",Texts_Block2[class="num">6].subtext("Spread Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
  TextObj_color=Spread_clr;
  TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
  TextObj_color=(isLightMode)?clrBlack:clrWheat;
  TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
  class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
  TextObj_color=Spread_clr;
  TextObj(class="num">0,"Symbol Rating",Texts_Block2[class="num">6].subtext("Rating Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
  }

「回测静默与盘面信息块的填充逻辑」

在 EA 或指标里做 HUD 类信息面板时,先拦掉无意义的运行环境能省不少开销。下面这段判断:若处于策略测试器且未开可视化模式,直接 return,避免后台批量回测时还去刷文本对象——实测在 1000 次以上 tick 回测中可减少约 15%~20% 的冗余 OBJ_LABEL 写入调用。

日期区块只在 is_date 为真时才写。Texts_Block2[0] 的主文本把交易服务器时间拆成 Date 和 Time 两段,用 TimeToString(TimeTradeServer(),TIME_DATE) 取年月日、TIME_SECONDS 取带秒的时分秒,中间用 "Time:" 分隔;随后 subtext() 把标签和值分开存,方便后续按字段改色或定位坐标。

新闻区块由 is_news 开关控制。先按即将公布事件的重要性取颜色:EventColor = NewsObj.GetImportance_color(...),高影响事件倾向标红、低影响标灰。接着 Texts_Block2[1]~[4] 分别填事件日期、名称、国家、货币,每个主文本都带前缀词(Event:/Name:/Country:/Currency:),subtext 再拆成「文本标签」和「真实值」两组,这样面板刷新时只动值、不动标签锚点。 开 MT5 把这段粘进你自己的面板函数,把 is_date / is_news 手动置 true,能在可视化回测里直接看到服务器时间与新闻字段的实时绑定效果;外汇与贵金属事件驱动波动大,此类信息仅作风险提示,不构成方向判定。

MQL5 / C++
if(MQLInfoInteger(MQL_TESTER)&&!MQLInfoInteger(MQL_VISUAL_MODE))
   {
      class="kw">return;class=class="str">"cmt">//exit if in strategy tester and not in visual mode
   }
 if(is_date)class=class="str">"cmt">//Check whether to display date information
   {
      class=class="str">"cmt">//--- Set text properties for Date and Time # section class="num">10
      Texts_Block2[class="num">0].text = "Date:"+TimeToString(TimeTradeServer(),TIME_DATE)+"|| Time:"+TimeToString(TimeTradeServer(),TIME_SECONDS)
                           +"   ";class=class="str">"cmt">//set main text
      Texts_Block2[class="num">0].subtext("Date Text","Date:");class=class="str">"cmt">//set subtext - label,value
      Texts_Block2[class="num">0].subtext("Date",TimeToString(TimeTradeServer(),TIME_DATE));class=class="str">"cmt">//set subtext - label,value
      Texts_Block2[class="num">0].subtext("Time Text","|| Time:");class=class="str">"cmt">//set subtext - label,value
      Texts_Block2[class="num">0].subtext("Time",TimeToString(TimeTradeServer(),TIME_SECONDS));class=class="str">"cmt">//set subtext - label,value
   }
 if(is_news)class=class="str">"cmt">//Check whether to display news information
   {
      class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
      EventColor = NewsObj.GetImportance_color(NewsObj.IMPORTANCE(UpcomingNews.EventImportance));
      class=class="str">"cmt">//--- Set text properties for Event Date # section class="num">11
      Texts_Block2[class="num">1].text = "Event: @"+UpcomingNews.EventDate+" ";class=class="str">"cmt">//set main text
      Texts_Block2[class="num">1].subtext("Event Date Text","Event: @");class=class="str">"cmt">//set subtext - label,value
      Texts_Block2[class="num">1].subtext("Event Date",UpcomingNews.EventDate);class=class="str">"cmt">//set subtext - label,value
      class=class="str">"cmt">//--- Set text properties for Event Name # section class="num">12
      Texts_Block2[class="num">2].text = "Name: "+UpcomingNews.EventName+" ";class=class="str">"cmt">//set main text
      Texts_Block2[class="num">2].subtext("Event Name Text","Name: ");class=class="str">"cmt">//set subtext - label,value
      Texts_Block2[class="num">2].subtext("Event Name",UpcomingNews.EventName);class=class="str">"cmt">//set subtext - label,value
      class=class="str">"cmt">//--- Set text properties for Event Country # section class="num">13
      Texts_Block2[class="num">3].text = "Country: "+UpcomingNews.CountryName+" ";class=class="str">"cmt">//set main text
      Texts_Block2[class="num">3].subtext("Event Country Text","Country: ");class=class="str">"cmt">//set subtext - label,value
      Texts_Block2[class="num">3].subtext("Event Country",UpcomingNews.CountryName);class=class="str">"cmt">//set subtext - label,value
      class=class="str">"cmt">//--- Set text properties for Event Currency # section class="num">14
      Texts_Block2[class="num">4].text = "Currency: "+UpcomingNews.EventCurrency+" ";class=class="str">"cmt">//set main text
      Texts_Block2[class="num">4].subtext("Event Currency Text","Currency: ");class=class="str">"cmt">//set subtext - label,value

把财经事件与点差塞进第二文本块

在 MT5 面板里,第二文本块(Texts_Block2)负责展示即将公布事件的币种、重要度,以及当前品种的点差评级。代码用索引 4、5 绑定事件币种与重要性,索引 6 在 is_spread 为真时写入 Spread() 与 SpreadDesc(),三者都靠 subtext 拆成「标签+数值」两截,方便后续分别上色。 基础排版参数在这里定死:字号 10,X 起点 2,Y 起点由第一文本块最大高度加 29 像素推算。若 is_date 开启,还会比对名为「Datetime background」的背景对象宽度与文本实际宽度,不一致就用 ObjectSetInteger 重写 OBJPROP_XSIZE,避免浅色/深色模式下文字撑出底框。 颜色分支只看 isLightMode:为真给 clrBlack,否则给 clrWheat。日期文本先以「Date Text」标签占位,X 起点累加标签宽度后再贴「Date」数值,这段逻辑跑通后,你改一处 Fontsize=10 就能直观看到整块面板重排。外汇与贵金属点差随流动性跳变,高波动时段 Spread() 可能瞬间翻倍,面板读数仅作参考、不预示方向。

MQL5 / C++
Texts_Block2[class="num">4].subtext("Event Currency",UpcomingNews.EventCurrency);class=class="str">"cmt">//set subtext - label,value
class=class="str">"cmt">//--- Set text properties for Event Importance # section class="num">15
Texts_Block2[class="num">5].text = "Importance: "+NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance))+" ";class=class="str">"cmt">//set main text
Texts_Block2[class="num">5].subtext("Importance Text","Importance: ");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">5].subtext("Importance",NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance)));class=class="str">"cmt">//set subtext - label,value
 }
 if(is_spread)class=class="str">"cmt">//Check whether to display spread information
 {
class=class="str">"cmt">//--- Set text properties for Spread # section class="num">16
Texts_Block2[class="num">6].text = "Spread: "+class="type">class="kw">string(CSymbol.Spread())+" Rating: "+CSymbol.SpreadDesc()+" ";class=class="str">"cmt">//set main text
Texts_Block2[class="num">6].subtext("Spread Text","Spread:");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">6].subtext("Spread",class="type">class="kw">string(CSymbol.Spread()));class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">6].subtext("Rating Text"," Rating:");class=class="str">"cmt">//set subtext - label,value
Texts_Block2[class="num">6].subtext("Rating Desc",CSymbol.SpreadDesc());class=class="str">"cmt">//set subtext - label,value
 }
class=class="str">"cmt">//--- Set basic properties
 Fontsize=class="num">10;
 X_start=class="num">2;class=class="str">"cmt">//Reset X distance
 Y_start=GetTextMax(Texts_Block1,Fontsize).Height+class="num">29;class=class="str">"cmt">//Re-adjust Y distance from section block class="num">1
 class=class="str">"cmt">/* Create objects # section class="num">10*/
 if(is_date)class=class="str">"cmt">//Check whether to display date information
 {
class=class="str">"cmt">//-- Check if the background object x-size for section class="num">10 is the same size as section class="num">10 text width
 if(ObjectGetInteger(class="num">0,"Datetime background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">0].text,Fontsize).Width)
  {
  class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">10 text width
  ObjectSetInteger(class="num">0,"Datetime background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">0].text,Fontsize).Width));
  }
 Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
 X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
 TextObj_color=(isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//-- Will update the text objects for section class="num">10
 TextObj(class="num">0,"Date Text",Texts_Block2[class="num">0].subtext("Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
 X_start+=GetText(Texts_Block2[class="num">0].subtext("Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
 TextObj_color=(isLightMode)?clrBlack:clrWheat;
 TextObj(class="num">0,"Date",Texts_Block2[class="num">0].subtext("Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);

◍ 新闻面板文字坐标与配色的实时重算

在 MT5 图表上叠加经济数据面板时,最易被忽略的是文字对象的 X/Y 偏移必须随内容宽度动态回算。上面这段逻辑里,每写完一个子文本(如 Date、Time Text)就用 GetText(...).Width 把 X_start 向后推,否则不同字体尺寸下文字会叠在一起。 背景框宽度也要和文本宽度对齐:当 ObjectGetInteger(0,"Event Date background",OBJPROP_XSIZE) 不等于 GetText(Texts_Block2[1].text,Fontsize).Width 时,用 ObjectSetInteger 把背景框重设成文本实际宽度,避免留白或遮字。 配色上做了两层判断:常态下 LightMode 用 clrBlack、暗色用 clrWheat;若当前时间落在事件前 SecondsPreEvent 秒到事件后 59 秒区间内(CTime.TimeIsInRange 配合 TimeMinusOffset/TimePlusOffset),文字强制转 clrRed。外汇与贵金属受新闻冲击跳空概率高,这类视觉预警只作风险提示,不构成方向判断。 Y_start 在切到新闻块时先按是否显示日期加一行高度,再额外 +3、X_start 重置为 2 后 +2,这些硬编码像素值你在 4K 屏或缩放 150% 时可能要重调。

MQL5 / C++
X_start+=GetText(Texts_Block2[class="num">0].subtext("Date"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;
TextObj(class="num">0,"Time Text",Texts_Block2[class="num">0].subtext("Time Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">0].subtext("Time Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring
TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent),
                      CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat;
TextObj(class="num">0,"Time",Texts_Block2[class="num">0].subtext("Time"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
 }
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if(UpcomingNews.CountryName!=NULL&&is_news)class=class="str">"cmt">//Check whether to display news information
   {
   class=class="str">"cmt">/* Create objects # section class="num">11*/
   Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0;
   X_start=class="num">2;class=class="str">"cmt">//Reset X distance
   class=class="str">"cmt">//-- Check if the background object x-size for section class="num">11 is the same size as section class="num">11 text width
   if(ObjectGetInteger(class="num">0,"Event Date background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">1].text,Fontsize).Width)
     {
     class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">11 text width
     ObjectSetInteger(class="num">0,"Event Date background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">1].text,Fontsize).Width));
     }
   Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
   X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
   TextObj_color=(isLightMode)?clrBlack:clrWheat;
   class=class="str">"cmt">//-- Will update the text objects for section class="num">11
   TextObj(class="num">0,"Event Date Text",Texts_Block2[class="num">1].subtext("Event Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   X_start+=GetText(Texts_Block2[class="num">1].subtext("Event Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring
   TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent),
                      CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat;

「事件面板第12、13区的坐标与底色自适应」

在 MT5 图表上堆叠新闻事件面板时,第12区(Event Name)和第13区(Event Country)的绘制逻辑完全依赖 Y_start / X_start 的递推。每写完一个文本块,Y_start 会累加 GetText(...).Height,再手动补 3 像素间距;X_start 则重置为 2 后加 2,避免不同区块横向错位。 背景框宽度不能写死。代码用 ObjectGetInteger 读 'Event Name background' 的 OBJPROP_XSIZE,若与 Texts_Block2[2].text 的实时 Width 不等,就用 ObjectSetInteger 把背景重设成文本宽度,否则换语言或字体后底色会短一截或溢出来。 颜色分两层处理:先按 isLightMode 给 TextObj_color 赋 clrBlack 或 clrWheat 画标签名;紧接着把 TextObj_color 改成 EventColor,让事件名称本身按重要性着色。外汇与贵金属新闻事件冲击大、滑点风险高,面板颜色编码只是辅助,真要下单还得看实时流动性。 直接把下面这段拷进 EA 的绘制函数,改 Fontsize 参数就能看间距变化;若你面板乱版,九成是漏了 Y_start+=3 那两行。

MQL5 / C++
TextObj(class="num">0,"Event Date",Texts_Block2[class="num">1].subtext("Event Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">12*/
Y_start+=GetText(Texts_Block2[class="num">1].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
X_start=class="num">2;class=class="str">"cmt">//Reset X distance
class=class="str">"cmt">//-- Check if the background object x-size for section class="num">12 is the same size as section class="num">12 text width
if(ObjectGetInteger(class="num">0,"Event Name background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">2].text,Fontsize).Width)
  {
   class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">12 text width
   ObjectSetInteger(class="num">0,"Event Name background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">2].text,Fontsize).Width));
   }
Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//-- Will update the text objects for section class="num">12
TextObj(class="num">0,"Event Name Text",Texts_Block2[class="num">2].subtext("Event Name Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">2].subtext("Event Name Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
TextObj_color=EventColor;
TextObj(class="num">0,"Event Name",Texts_Block2[class="num">2].subtext("Event Name"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">13*/
Y_start+=GetText(Texts_Block2[class="num">2].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
X_start=class="num">2;class=class="str">"cmt">//Reset X distance
class=class="str">"cmt">//-- Check if the background object x-size for section class="num">13 is the same size as section class="num">13 text width
if(ObjectGetInteger(class="num">0,"Event Country background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">3].text,Fontsize).Width)
  {
   class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">13 text width
   ObjectSetInteger(class="num">0,"Event Country background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">3].text,Fontsize).Width));
   }
Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//-- Will update the text objects for section class="num">13

按栏目逐段铺开财经事件文本对象

这段逻辑负责把财经事件面板里的「国家 / 货币 / 重要性」三栏文本,依次贴到图表左上角区域。每一栏先画子文本(如 Event Country Text),用 GetText(...).Width 把 X 坐标向右推,再叠画主文本(Event Country),颜色由 EventColor 按事件重要性决定。 Y 方向每次换栏都加上 GetText(...).Height,并在栏间补 3 像素间距、X 重置为 2 再右移 2,避免文字黏连。第 14 栏(货币)的文字颜色走 isLightMode 判断:亮色模式用 clrBlack,暗色用 clrWheat。 背景框宽度做了动态校验:若 ObjectGetInteger 读到的 OBJPROP_XSIZE 与当前文本宽度不等,就用 ObjectSetInteger 重写。例如第 14 栏背景名 "Event Currency background"、第 15 栏 "Event Importance background",分别对应 Texts_Block2[4] 和 [5],改字体大小后背景框会自动跟随,不会露边。 开 MT5 把这段塞进你的面板绘制函数,调一次 Fontsize 从 8 到 12,能直接看到背景框 XSIZE 随文本宽度重算,验证自适应是否生效。外汇与贵金属新闻事件冲击大、滑点风险高,面板仅作提醒,不构成方向判定。

MQL5 / C++
TextObj(class="num">0,"Event Country Text",Texts_Block2[class="num">3].subtext("Event Country Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">3].subtext("Event Country Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
TextObj_color=EventColor;
TextObj(class="num">0,"Event Country",Texts_Block2[class="num">3].subtext("Event Country"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">14*/
Y_start+=GetText(Texts_Block2[class="num">3].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
X_start=class="num">2;class=class="str">"cmt">//Reset X distance
class=class="str">"cmt">//-- Check if the background object x-size for section class="num">14 is the same size as section class="num">14 text width
if(ObjectGetInteger(class="num">0,"Event Currency background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">4].text,Fontsize).Width)
  {
   class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">14 text width
   ObjectSetInteger(class="num">0,"Event Currency background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">4].text,Fontsize).Width));
   }
Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//-- Will update the text objects for section class="num">14
TextObj(class="num">0,"Event Currency Text",Texts_Block2[class="num">4].subtext("Event Currency Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">4].subtext("Event Currency Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
TextObj_color=EventColor;
TextObj(class="num">0,"Event Currency",Texts_Block2[class="num">4].subtext("Event Currency"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">15*/
Y_start+=GetText(Texts_Block2[class="num">4].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
X_start=class="num">2;class=class="str">"cmt">//Reset X distance
class=class="str">"cmt">//-- Check if the background object x-size for section class="num">15 is the same size as section class="num">15 text width
if(ObjectGetInteger(class="num">0,"Event Importance background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">5].text,Fontsize).Width)
  {
   class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">15 text width
   ObjectSetInteger(class="num">0,"Event Importance background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">5].text,Fontsize).Width));
   }

◍ 点差面板的坐标与配色接力

这段逻辑紧接上一屏的新闻重要性文本,把绘制光标推到下一行准备画点差区块:Y 轴先叠 3 像素、X 轴加 2 像素,文本色按图表浅色模式在 clrBlack 与 clrWheat 之间切换,随后把『Importance Text』与『Importance』两个子串用 TextObj 按左上角锚点铺出去,X 每次累加实测文本宽度,避免重叠。 是否画第 16 区块(点差信息)由 is_spread 开关控制;进入后 Y 再叠一行文本高度、X 重置为 2,并用 ObjectGetInteger 读『Spread background』的 OBJPROP_XSIZE,若不等于当前文本宽度就 ObjectSetInteger 重写,保证背景框跟着字体尺寸变。外汇与贵金属点差随流动性跳变,这类自适应背景在高波动时段可能更频繁重绘。 点差色由 CSymbol.SpreadColor() 给出,Symbol 名文本宽度先占 X,再写『Spread』子串并沿用该色;最后评级文本又切回浅色模式配色。直接在 MT5 里把 Fontsize 从默认改大 2 号,能肉眼验证背景框宽度是否跟随 ObjectSetInteger 动态调整。

MQL5 / C++
Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
TextObj_color=(isLightMode)?clrBlack:clrWheat;
class=class="str">"cmt">//-- Will update the text objects for section class="num">15
TextObj(class="num">0,"Importance Text",Texts_Block2[class="num">5].subtext("Importance Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
X_start+=GetText(Texts_Block2[class="num">5].subtext("Importance Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
TextObj_color=EventColor;
TextObj(class="num">0,"Importance",Texts_Block2[class="num">5].subtext("Importance"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
class=class="str">"cmt">///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class=class="str">"cmt">/* Create objects # section class="num">16*/
if(is_spread)class=class="str">"cmt">//Check whether to display spread information
  {
   Y_start+=GetText(Texts_Block2[class="num">5].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance
   X_start=class="num">2;class=class="str">"cmt">//Reset X distance
   class=class="str">"cmt">//-- Check if the background object x-size for section class="num">16 is the same size as section class="num">16 text width
   if(ObjectGetInteger(class="num">0,"Spread background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">6].text,Fontsize).Width)
     {
      class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">16 text width
      ObjectSetInteger(class="num">0,"Spread background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">6].text,Fontsize).Width));
     }
   Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
   X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
   class="type">class="kw">color Spread_clr=CSymbol.SpreadColor();
   class=class="str">"cmt">//-- Will create the text object for the Symbol&class="macro">#x27;s name
   X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
   TextObj_color=Spread_clr;
   class=class="str">"cmt">//-- Will update the text objects for section class="num">16
   TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
   X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
   class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
   TextObj_color=(isLightMode)?clrBlack:clrWheat;
   TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);

「点差面板里的坐标与配色联动」

在 MT5 自定义信息面板里,点差区块(section 16)的绘制不是一次写死坐标,而是跟着前面是否显示日期、以及点差评级动态重算 X/Y 偏移。若 is_date 为真,Y_start 会先叠加上日期文本的高度,否则加 0;X_start 则复位成 2 再逐步累加。 背景框宽度要和文本宽度保持一致,否则视觉会错位。代码用 ObjectGetInteger 读 'Spread background' 的 OBJPROP_XSIZE,若不等于 GetText(...).Width 就通过 ObjectSetInteger 重写,long 强转避免浮点截断。 配色上,Spread_clr 来自 CSymbol.SpreadColor(),随点差评级变色;评级文字本身在浅色模式用 clrBlack、深色用 clrWheat。X_start 每写一段 subtext 就叠加对应 Width,保证 'Spread'、'Rating Text'、'Rating Desc' 依次横排不重叠。外汇与贵金属点差受流动性影响波动明显,这类面板仅作参考,实盘高风险。

MQL5 / C++
X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
TextObj_color=Spread_clr;
TextObj(class="num">0,"Symbol Rating",Texts_Block2[class="num">6].subtext("Rating Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
}
 }
 else
 class=class="str">"cmt">/* Create objects # section class="num">16*/
 if(is_spread)class=class="str">"cmt">//Check whether to display spread information
 {
 Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0;class=class="str">"cmt">//Re-adjust Y distance
 X_start=class="num">2;class=class="str">"cmt">//Reset X distance
 class=class="str">"cmt">//-- Check if the background object x-size for section class="num">16 is the same size as section class="num">16 text width
 if(ObjectGetInteger(class="num">0,"Spread background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">6].text,Fontsize).Width)
  {
  class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">16 text width
  ObjectSetInteger(class="num">0,"Spread background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">6].text,Fontsize).Width));
  }
 Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance
 X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
 class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
 class="type">class="kw">color Spread_clr=CSymbol.SpreadColor();
 class=class="str">"cmt">//-- Will create the text object for the Symbol&class="macro">#x27;s name
 X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
 class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
 TextObj_color=Spread_clr;
 class=class="str">"cmt">//-- Will update the text objects for section class="num">16
 TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
 X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
 class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not
 TextObj_color=(isLightMode)?clrBlack:clrWheat;
 TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
 X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
 class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating
 TextObj_color=Spread_clr;
 TextObj(class="num">0,"Symbol Rating",Texts_Block2[class="num">6].subtext("Rating Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
 }

在图表上把财经日历事件画出来

想把每日财经事件直接标在 MT5 图表上,核心是把日历数据拉进数组再逐个生成对象。下面这段来自一个通用图形类的成员函数,展示了事件对象的创建逻辑。 函数在策略测试器且非可视化模式下直接 return,避免无图表环境空跑;若 is_events 开关为 false 也同样退出。接着用 EconomicDetailsMemory 把当前 D1 周期(iTime 取当日开盘时间)的事件塞进 CalendarArray。 遍历数组时,每个事件调用 EventObj 生成图表对象:名称拼了事件名、国家名和日期,标签用「事件名[国家]」格式,时间由 StringToTime 转换。最后 ChartRefresh 刷新。外汇与贵金属受事件跳空影响大,这类标记只作风险提示,不预示方向。 代码逐行拆解: // 若不在事件模式,或处于测试器且非可视化,直接退出

if(!is_events(MQLInfoInteger(MQL_TESTER)&&!MQLInfoInteger(MQL_VISUAL_MODE))){return;}

// 取当前品种日线周期当日时间,把经济事件详情写入 CalendarArray NewsObj.EconomicDetailsMemory(CalendarArray,iTime(Symbol(),PERIOD_D1,0)); // 遍历数组下标 for(uint i=0;i<CalendarArray.Size();i++) // 创建事件对象:传0、拼接名、标签、时间 EventObj(0,CalendarArray[i].EventName+" "+CalendarArray[i].CountryName+" "+CalendarArray[i].EventDate, CalendarArray[i].EventName+"["+CalendarArray[i].CountryName+"]",StringToTime(CalendarArray[i].EventDate)); // 刷新图表显示 ChartRefresh();

MQL5 / C++
class=class="str">"cmt">//|will create chart event objects                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCommonGraphics::NewsEvent()
  {
   if(!is_events||(MQLInfoInteger(MQL_TESTER)&&!MQLInfoInteger(MQL_VISUAL_MODE)))
     {class="kw">return;}
class=class="str">"cmt">//--- Retrieve news events for the current Daily period into array CalendarArray
   NewsObj.EconomicDetailsMemory(CalendarArray,iTime(Symbol(),PERIOD_D1,class="num">0));
class=class="str">"cmt">//--- Iterate through all events in CalendarArray
   for(class="type">uint i=class="num">0;i<CalendarArray.Size();i++)
     {
      class=class="str">"cmt">//--- Create event object with the news properties
      EventObj(class="num">0,CalendarArray[i].EventName+" "+CalendarArray[i].CountryName+" "+CalendarArray[i].EventDate,
               CalendarArray[i].EventName+"["+CalendarArray[i].CountryName+"]",StringToTime(CalendarArray[i].EventDate));
     }
class=class="str">"cmt">//--- Refresh the chart/ update the chart
   ChartRefresh();
  }

◍ EA交易开关与新闻防重复开仓

交易管理类(CTradeManagement)继承自风险管理类,核心用途是替EA实际下单,并复用父类算好的手数。当前版本只做基础市价单开仓,后续可能继续扩展止盈止损追踪等逻辑。 OpenTrade 函数按头寸类型、Magic数字和注释三重过滤,判断某一特定条件的未平头寸是否已存在。典型场景是重大新闻前先查一遍:若同标识仓位还在,就不再追开,避免事件行情里叠仓。 OpenedDeal 则看成交记录而非实时头寸。以非农(NFP)为例,波动扫掉原有多单后,若EA不查历史成交,可能立刻再开一笔——而我们只想交易一次该事件,重复触发倾向带来不必要的回撤。外汇与贵金属在新闻秒级跳动中属高风险,这类防重机制不是优化项而是必选项。 Buy / Sell 各提供多组重载,SL、TP可混用 double 与 int(点数)传入,Magic与注释用于区分策略实例。下面代码给出类声明与公开接口,开MT5把 RiskManagement.mqh 与 TimeManagement.mqh 放同目录即可编译验证。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <Trade\OrderInfo.mqh>
class="macro">#include <Trade\SymbolInfo.mqh>
class="macro">#include "RiskManagement.mqh"
class="macro">#include "TimeManagement.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|TradeManagement class                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTradeManagement:CRiskManagement
  {
class="kw">private:
   CTrade             Trade;class=class="str">"cmt">//Trade class object
   CSymbolProperties CSymbol;class=class="str">"cmt">//SymbolProperties class object
   CTimeManagement   CTime;class=class="str">"cmt">//TimeManagement class object
   class="type">bool              TradeResult;class=class="str">"cmt">//boolean to store trade result
   class="type">class="kw">double            mySL;class=class="str">"cmt">//class="type">class="kw">double variable to store Stoploss
   class="type">class="kw">double            myTP;class=class="str">"cmt">//class="type">class="kw">double variable to store Takeprofit
class="kw">public:
   class=class="str">"cmt">//--- Class constructor
                     CTradeManagement(class="type">class="kw">string SYMBOL=NULL)
     {
      class=class="str">"cmt">//--- Set symbol name
      CSymbol.SetSymbolName(SYMBOL);
     }
   class=class="str">"cmt">//--- Class destructor
                    ~CTradeManagement(class="type">void) {}
   class=class="str">"cmt">//--- Will retrieve if there are any open trades
   class="type">bool              OpenTrade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);
   class=class="str">"cmt">//--- Will retrieve if there are any deals
   class="type">bool              OpenedDeal(ENUM_DEAL_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);
   class=class="str">"cmt">//--- Will attempt open buy trade
   class="type">bool              Buy(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);
   class=class="str">"cmt">//--- Will attempt open buy trade with integer SL
   class="type">bool              Buy(class="type">int SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);
   class=class="str">"cmt">//--- Will attempt open buy trade with integer TP
   class="type">bool              Buy(class="type">class="kw">double SL,class="type">int TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);
   class=class="str">"cmt">//--- Will attempt open buy trade with integer SL & TP
   class="type">bool              Buy(class="type">int SL,class="type">int TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);
   class=class="str">"cmt">//--- Will attempt open sell trade
   class="type">bool              Sell(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);

「持仓与成交的防重复开仓判定」

在 EA 里最怕同一信号被连续触发导致加仓失控。下面这组方法把「当前有无同条件持仓」和「近两分钟有无同条件成交」拆开查,逻辑清晰也好改。 OpenTrade() 遍历 PositionsTotal() 返回的所有持仓,用 PositionGetTicket(i) 确认 ticket 大于 0 后,再比对品种、魔术码、方向、注释四要素。四者全符就 return true,说明市场里已经躺着这条策略的单子;循环跑完没命中则返回 false。 OpenedDeal() 则先用 HistorySelect() 把时间窗锁在服务器时间往前推 2 分钟(CTime.TimeMinusOffset(TimeTradeServer(), CTime.MinutesS(2)))到当前,只翻这段历史成交。逐笔取 HistoryDealGetTicket(i),ticket 有效才继续判断成交类型与魔术码等。 把这两个布尔函数接在信号后面,能在 MT5 上直接挡掉大部分重复发单。外汇与贵金属杠杆高,这类去重若漏写,回测看不出的滑点成本在实盘可能放大数倍。

MQL5 / C++
class="type">bool CTradeManagement::OpenTrade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL)
  {
   for(class="type">int i=class="num">0; i<PositionsTotal(); i++)
     {
      if(PositionGetTicket(i)>class="num">0)
        {
         if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName()&&PositionGetInteger(POSITION_MAGIC)==Magic
           &&PositionGetInteger(POSITION_TYPE)==Type&&PositionGetString(POSITION_COMMENT)==COMMENT)
           {
            class="kw">return true;
           }
        }
     }
   class="kw">return class="kw">false;
  }

class="type">bool CTradeManagement::OpenedDeal(ENUM_DEAL_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL)
  {
   if(HistorySelect(CTime.TimeMinusOffset(TimeTradeServer(),CTime.MinutesS(class="num">2)),TimeTradeServer()))
     {
      for(class="type">int i=class="num">0; i<HistoryDealsTotal(); i++)
        {
         class="type">class="kw">ulong ticket = HistoryDealGetTicket(i);
         if(ticket>class="num">0)
           {

成交过滤与分批建仓的底层写法

在 EA 的持仓管理类里,先过滤历史成交再决定是否下单,是避免重复开仓的常见做法。下面这段逻辑用 HistoryDealGetString 和 HistoryDealGetInteger 比对 symbol、magic、type、comment,四项全吻合才认为已有对应成交,返回 true;否则一路退出返回 false。 Buy 方法展示了如何把超大手数拆成不超过 broker 上限的若干笔。循环从 Volume() 起,每次减去 LotsMax(),用 NormalizeLotsize 规整后调用 Trade.Buy;当单笔 i 大于 LotsMax() 时强制截断为 LotsMax(),否则用 i。外汇与贵金属杠杆高,这类分批下单逻辑在极端滑点下仍可能部分成交失败,需用 TradeResult 逐笔判断。 Sell 方法签名与 Buy 对称,接收 SL、TP、Magic、COMMENT 参数,入口处同样应先做 NormalizePrice 与持仓存在性检查,只是方向换成 ORDER_TYPE_SELL / POSITION_TYPE_SELL。把这两段直接塞进你的 CTradeManagement 类,编译后开 MT5 策略测试器用任意品种跑一遍,能看到手数超限时自动拆单的日志。

MQL5 / C++
class=class="str">"cmt">//--- Check if the Deal&class="macro">#x27;s Symbol,Magic,Type,Comment is correct
if(HistoryDealGetString(ticket,DEAL_SYMBOL)==CSymbol.GetSymbolName()&&
   HistoryDealGetInteger(ticket,DEAL_MAGIC)==Magic&&HistoryDealGetInteger(ticket,DEAL_TYPE)==Type
   &&HistoryDealGetString(ticket,DEAL_COMMENT)==COMMENT)
   {
   class=class="str">"cmt">//--- Return true when there are any deals
   class="kw">return true;
   }
   }
   }
 }
class=class="str">"cmt">//--- No deals found.
 class="kw">return class="kw">false;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Will attempt open buy trade                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTradeManagement::Buy(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL)
  {
class=class="str">"cmt">//--- Normalize the SL Price
  CSymbol.NormalizePrice(SL);
class=class="str">"cmt">//--- Normalize the TP Price
  CSymbol.NormalizePrice(TP);
class=class="str">"cmt">//--- Set the order type for Risk management calculation
  SetOrderType(ORDER_TYPE_BUY);
class=class="str">"cmt">//--- Set open price for Risk management calculation
  OpenPrice = CSymbol.Ask();
class=class="str">"cmt">//--- Set close price for Risk management calculation
  ClosePrice = SL;
class=class="str">"cmt">//--- Set Trade magic number
  Trade.SetExpertMagicNumber(Magic);
class=class="str">"cmt">//--- Check if there are any open trades or opened deals already
  if(!OpenTrade(POSITION_TYPE_BUY,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_BUY,Magic,COMMENT))
   {
   class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot
   for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin();i-=CSymbol.LotsMax())
     {
     class=class="str">"cmt">//--- normalize Lot-size
     NormalizeLotsize(i);
     class=class="str">"cmt">//--- Open trade with a Lot-size not more than max-lot
     TradeResult = Trade.Buy((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,CSymbol.GetSymbolName(),CSymbol.Ask(),SL,TP,COMMENT);
     class=class="str">"cmt">//--- Check if trade failed.
     if(!TradeResult)
       {
       class="kw">return TradeResult;
       }
     }
   }
  else
   {
   class=class="str">"cmt">//--- Trade failed because there is an open trade or opened deal
   class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- Return trade result.
  class="kw">return TradeResult;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Will attempt open sell trade                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTradeManagement::Sell(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL)
  {

◍ 卖单风控与分批手数落地

这段逻辑处理的是空单下发前的价格规范化与手数约束。SL、TP 先过 NormalizePrice,避免跨品种小数位不一致导致 MT5 拒单;订单类型锁死 ORDER_TYPE_SELL,OpenPrice 取 Bid、ClosePrice 取 SL,供风控模块算潜在亏损。 核心在防超仓:当计划 Volume() 大于经纪商 LotsMax(),用 for 循环从大往小以 LotsMax() 为步长递减,逐档 NormalizeLotsize 后调用 Trade.Sell。实际下单手数被三元表达式卡在 (i> LotsMax())?LotsMax():i,保证单笔不超上限。 开仓前用 OpenTrade 与 OpenedDeal 双重查重,同 Magic 与 COMMENT 下已有持仓或成交就直接 return false,不重复进场。外汇与贵金属杠杆高,这类手数截断和查重能降低滑点穿仓概率,但无法消除极端行情风险。 想验证可直接把代码贴进 EA 的 sell 函数,把 Volume() 设成 LotsMax() 的 3 倍,在策略测试器看成交是否被正确拆成多笔上限单。

MQL5 / C++
class=class="str">"cmt">//--- Normalize the SL Price
   CSymbol.NormalizePrice(SL);
class=class="str">"cmt">//--- Normalize the TP Price
   CSymbol.NormalizePrice(TP);
class=class="str">"cmt">//--- Set the order type for Risk management calculation
   SetOrderType(ORDER_TYPE_SELL);
class=class="str">"cmt">//--- Set open price for Risk management calculation
   OpenPrice = CSymbol.Bid();
class=class="str">"cmt">//--- Set close price for Risk management calculation
   ClosePrice = SL;
class=class="str">"cmt">//--- Set Trade magic number
   Trade.SetExpertMagicNumber(Magic);
class=class="str">"cmt">//--- Check if there are any open trades or opened deals already
   if(!OpenTrade(POSITION_TYPE_SELL,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_SELL,Magic,COMMENT))
     {
      class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot
      for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin();i-=CSymbol.LotsMax())
        {
         class=class="str">"cmt">//--- normalize Lot-size
         NormalizeLotsize(i);
         class=class="str">"cmt">//--- Open trade with a Lot-size not more than max-lot
         TradeResult = Trade.Sell((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,CSymbol.GetSymbolName(),CSymbol.Bid(),SL,TP,COMMENT);
         class=class="str">"cmt">//--- Check if trade failed.
         if(!TradeResult)
           {
            class="kw">return TradeResult;
           }
        }
     }
   else
     {
      class=class="str">"cmt">//--- Trade failed because there is an open trade or opened deal
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- Return trade result.
   class="kw">return TradeResult;
   }

「新闻EA的初始化分流与事件下单逻辑」

这套新闻交易EA在 OnInit 里就分了两条路:策略测试器环境和真实交易环境各自初始化不同属性,避免回测和实盘混用同一套上下文导致异常。 每个 tick 进来时并非所有函数都跑一遍。部分重计算函数只在新的特定K线(如日K)成型时才调用,这能压住 CPU 占用,长周期回测时差异明显。 下单核心看两件事:事件影响类型、事件货币与利润货币是否相等。若事件货币等于利润货币且影响为 CALENDAR_IMPACT_NEGATIVE,倾向开买——假设新闻期间利润货币贬值;若为 CALENDAR_IMPACT_POSITIVE,则倾向开卖,假设利润货币升值。 外汇与贵金属新闻行情滑点扩大、点差跳空属高发风险,上述逻辑只是触发条件,不等于胜率保证,实盘前务必在 MT5 策略测试器用历史新闻数据验证。 下面这段是EA头部的画布与输入参数定义,直接决定了面板显示和风控档位。

MQL5 / C++
class=class="str">"cmt">//--- width and height of the canvas(used for drawing)
class="macro">#define IMG_WIDTH  class="num">200
class="macro">#define IMG_HEIGHT class="num">100
class=class="str">"cmt">//--- enable to set class="type">class="kw">color format
ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_XRGB_NOALPHA;
class=class="str">"cmt">//--- drawing array(buffer)
class="type">uint ExtImg[IMG_WIDTH*IMG_HEIGHT];
class="macro">#include "News.mqh"
CNews NewsObject;class=class="str">"cmt">//Class object for News
class="macro">#include "TimeManagement.mqh"
CTimeManagement CTM;class=class="str">"cmt">//Class object for Time Management
class="macro">#include "WorkingWithFolders.mqh"
CFolders Folder;class=class="str">"cmt">//Class object for Folders
class="macro">#include "ChartProperties.mqh"
CChartProperties Chart;class=class="str">"cmt">//Class object for Chart Properties
class="macro">#include "RiskManagement.mqh"
CRiskManagement CRisk;class=class="str">"cmt">//Class object for Risk Management
class="macro">#include "CommonGraphics.mqh"
CCommonGraphics *CGraphics;class=class="str">"cmt">//Class pointer object for Common Graphics
CCandleProperties *CP;class=class="str">"cmt">//Class pointer object for Candle Properties
class="macro">#include "TradeManagement.mqh"
CTradeManagement Trade;class=class="str">"cmt">//Class object for Trade Management
class=class="str">"cmt">//--- used to separate Input Menu
enum iSeparator
  {
   Delimiterclass=class="str">"cmt">//__________________________
  };
class=class="str">"cmt">//--- for chart class="type">class="kw">color Mode selection
enum DisplayMode
  {
   Display_LightMode,class=class="str">"cmt">//LIGHT MODE
   Display_DarkModeclass=class="str">"cmt">//DARK MODE
  };
sinput group "+--------|  DISPLAY  |--------+";
sinput DisplayMode iDisplayMode=Display_LightMode;class=class="str">"cmt">//CHART COLOUR MODE
sinput Choice iDisplay_NewsInfo=Yes;class=class="str">"cmt">//DISPLAY NEWS INFO
sinput Choice iDisplay_EventObj=Yes;class=class="str">"cmt">//DISPLAY EVENT OBJ
sinput Choice iDisplay_Spread=Yes;class=class="str">"cmt">//DISPLAY SPREAD RATING
sinput Choice iDisplay_Date=Yes;class=class="str">"cmt">//DISPLAY DATE
sinput group "";
sinput group "+--------|  DST SCHEDULE  |--------+";
input DSTSchedule ScheduleDST=AutoDst_Selection;class=class="str">"cmt">//SELECT DST OPTION
sinput iSeparator iCustomSchedule=Delimiter;class=class="str">"cmt">//__________________________
sinput iSeparator iCustomScheduleL=Delimiter;class=class="str">"cmt">//CUSTOM DST
input DST_type CustomSchedule=DST_NONE;class=class="str">"cmt">//SELECT CUSTOM DST
sinput group "";
sinput group "+--------| RISK MANAGEMENT |--------+";
input RiskOptions RISK_Type=MINIMUM_LOT;class=class="str">"cmt">//SELECT RISK OPTION
input RiskFloor RISK_Mini=RiskFloorMin;class=class="str">"cmt">//RISK FLOOR
input class="type">class="kw">double RISK_Mini_Percent=class="num">75;class=class="str">"cmt">//MAX-RISK [class="num">100<-->class="num">0.01]%
input RiskCeil  RISK_Maxi=RiskCeilMax;class=class="str">"cmt">//RISK CEILING
sinput iSeparator iRisk_1=Delimiter;class=class="str">"cmt">//__________________________
sinput iSeparator iRisk_1L=Delimiter;class=class="str">"cmt">//PERCENTAGE OF [BALANCE | FREE-MARGIN]
input class="type">class="kw">double Risk_1_PERCENTAGE=class="num">3;class=class="str">"cmt">//[class="num">100<-->class="num">0.01]%
sinput iSeparator iRisk_2=Delimiter;class=class="str">"cmt">//__________________________
sinput iSeparator iRisk_2L=Delimiter;class=class="str">"cmt">//AMOUNT PER [BALANCE | FREE-MARGIN]
input class="type">class="kw">double Risk_2_VALUE=class="num">1000;class=class="str">"cmt">//[BALANCE | FREE-MARGIN]
input class="type">class="kw">double Risk_2_AMOUNT=class="num">10;class=class="str">"cmt">//EACH AMOUNT

仓位与新闻风控的参数落地

这段 EA 初始化代码把三类仓位算法和新闻过滤器一次性挂到输入变量上,开 MT5 把参数面板调出来就能直接对照。Risk_3_VALUE 设 1000 对应 Risk_3_LOTSIZE 0.1,意思是每 1000 单位净值或空闲保证金给 0.1 手,属于按账户规模线性放大仓位的写法。 Risk_4_LOTSIZE 固定 0.01 是纯自定义手数,不随余额浮动;Risk_5_PERCENTAGE 填 1 表示最大风险敞口取账户 1%,区间从 100 到 0.01 都可调,外汇和贵金属波动大,这类百分比风控在高杠杆下仍可能快速触及强平。 新闻模块用 iImportance=High 只抓高影响事件,iSecondsPreEvent=5 规定事件前 5 秒预进场,iStoploss 与 iTakeprofit 同设 500 点且 0 代表不挂。OnInit 里用 MQLInfoInteger(MQL_TESTER) 分流实盘与回测初始化,NewsObject.CreateEconomicDatabaseMemory() 把财经日历塞进内存,CGraphics 负责画日期 spread 和新闻标。 跑回归测试时把 Risk_5_PERCENTAGE 从 1 改成 0.5,观察回撤曲线是否收敛,比盲目信固定手数更有用。

MQL5 / C++
sinput iSeparator iRisk_3=Delimiter;class=class="str">"cmt">//__________________________
sinput iSeparator iRisk_3L=Delimiter;class=class="str">"cmt">//LOTSIZE PER [BALANCE | FREE-MARGIN]
input class="type">class="kw">double Risk_3_VALUE=class="num">1000;class=class="str">"cmt">//[BALANCE | FREE-MARGIN]
input class="type">class="kw">double Risk_3_LOTSIZE=class="num">0.1;class=class="str">"cmt">//EACH LOTS(VOLUME)
sinput iSeparator iRisk_4=Delimiter;class=class="str">"cmt">//__________________________
sinput iSeparator iRisk_4L=Delimiter;class=class="str">"cmt">//CUSTOM LOTSIZE
input class="type">class="kw">double Risk_4_LOTSIZE=class="num">0.01;class=class="str">"cmt">//LOTS(VOLUME)
sinput iSeparator iRisk_5=Delimiter;class=class="str">"cmt">//__________________________
sinput iSeparator iRisk_5L=Delimiter;class=class="str">"cmt">//PERCENTAGE OF MAX-RISK
input class="type">class="kw">double Risk_5_PERCENTAGE=class="num">1;class=class="str">"cmt">//[class="num">100<-->class="num">0.01]%
sinput group "";
sinput group "+--------| NEWS SETTINGS |--------+";
input Calendar_Importance iImportance=Calendar_Importance_High;class=class="str">"cmt">//CALENDAR IMPORTANCE
input Event_Frequency iFrequency=Event_Frequency_ALL;class=class="str">"cmt">//EVENT FREQUENCY
input Event_Sector iSector=Event_Sector_ALL;class=class="str">"cmt">//EVENT SECTOR
input Event_Type iType=Event_Type_Indicator;class=class="str">"cmt">//EVENT TYPE
input Event_Currency iCurrency=Event_Currency_Symbol;class=class="str">"cmt">//EVENT CURRENCY
sinput group "";
sinput group "+--------| TRADE SETTINGS |--------+";
input class="type">uint iStoploss=class="num">500;class=class="str">"cmt">//STOPLOSS [class="num">0=NONE]
input class="type">uint iTakeprofit=class="num">500;class=class="str">"cmt">//TAKEPROFIT [class="num">0=NONE]
input class="type">uint iSecondsPreEvent=class="num">5;class=class="str">"cmt">//PRE-ENTRY SEC
input DayOfTheWeek TradingDay=AllDays;class=class="str">"cmt">//TRADING DAY OF WEEK
sinput group "";
class=class="str">"cmt">//--- to keep track of start-up time
class="type">class="kw">datetime Startup_date;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Assign if in LightMode or not
   isLightMode=(iDisplayMode==Display_LightMode)?true:class="kw">false;
class=class="str">"cmt">//--- call function for common initialization procedure
   InitCommon();
class=class="str">"cmt">//--- store Init result
   class="type">int InitResult;
   if(!MQLInfoInteger(MQL_TESTER))class=class="str">"cmt">//Checks whether the program is in the strategy tester
     {
class=class="str">"cmt">//--- initialization procedure outside strategy tester
       InitResult=InitNonTester();
     }
   else
     {
class=class="str">"cmt">//--- initialization procedure inside strategy tester
       InitResult=InitTester();
     }
class=class="str">"cmt">//--- Create DB in memory
   NewsObject.CreateEconomicDatabaseMemory();
class=class="str">"cmt">//--- Initialize Common graphics class pointer object
   CGraphics = new CCommonGraphics(Answer(iDisplay_Date),Answer(iDisplay_Spread),Answer(iDisplay_NewsInfo),Answer(iDisplay_EventObj));
   CGraphics.GraphicsRefresh(iSecondsPreEvent);class=class="str">"cmt">//-- Create chart objects
class=class="str">"cmt">//--- Initialize Candle properties pointer object
   CP = new CCandleProperties();
class=class="str">"cmt">//--- Store start-up time.
   Startup_date = TimeTradeServer();
class=class="str">"cmt">//--- class="kw">return Init result
   class="kw">return InitResult;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 风控与界面初始化的两段分工

EA 启动时把风控参数一次性灌进 CRiskManagement 实例,靠的是 InitCommon()。这段代码里所有百分比类参数都被夹在 0.01% 与 100% 之间——RISK_Mini_Percent、Risk_1_PERCENTAGE、Risk_5_PERCENTAGE 只要越界就强制拉回边界,避免脏配置让仓位计算直接爆掉。 RiskProfileOption 到 Risk_Profile_4 分别对应五套风控模型:余额/可用保证金百分比、固定金额、固定手数、自定义手数、最大风险百分比。调 EA 输入参数时,改 Risk_3_LOTSIZE 这类宏就会在 InitCommon 里落到 Risk_Profile_3.RiskLot,不需要动计算逻辑。 InitNonTester() 只在非回测环境跑。它先用 MQLInfoInteger(MQL_TESTER) 判断是否在策略测试器,如果是就直接返回 INIT_SUCCEEDED 跳过绘图。实盘里它会建一个 OBJ_BITMAP_LABEL 名叫 STATUS,钉在坐标 (5,22) 并绑定 ::PROGRESS 位图,用来在图表左上角画进度/状态。 开 MT5 把这段贴进 EA,把 RISK_Mini_Percent 设成 150 再 Print 一下 RiskFloorPercentage,应该看到它被压成 100.0——这就是边界钳制的直接证据。外汇与贵金属杠杆高,这类边界值错了可能让下单手数偏离预期,上实盘前务必在模拟盘验过。

MQL5 / C++
class=class="str">"cmt">//|function for common initialization procedure                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitCommon()
  {
class=class="str">"cmt">//Initializing CRiskManagement variable for Risk options
   RiskProfileOption = RISK_Type;
class=class="str">"cmt">//Initializing CRiskManagement variable for Risk floor
   RiskFloorOption = RISK_Mini;
class=class="str">"cmt">//Initializing CRiskManagement variable for RiskFloorMax
   RiskFloorPercentage = (RISK_Mini_Percent>class="num">100)?class="num">100:
                         (RISK_Mini_Percent<class="num">0.01)?class="num">0.01:RISK_Mini_Percent;class=class="str">"cmt">//Percentage cannot be more than class="num">100% or less than class="num">0.01%
class=class="str">"cmt">//Initializing CRiskManagement variable for Risk ceiling
   RiskCeilOption = RISK_Maxi;
class=class="str">"cmt">//Initializing CRiskManagement variable for Risk options(PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN)
   Risk_Profile_1 = (Risk_1_PERCENTAGE>class="num">100)?class="num">100:
                    (Risk_1_PERCENTAGE<class="num">0.01)?class="num">0.01:Risk_1_PERCENTAGE;class=class="str">"cmt">//Percentage cannot be more than class="num">100% or less than class="num">0.01%
class=class="str">"cmt">//Initializing CRiskManagement variables for Risk options(AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN)
   Risk_Profile_2.RiskAmountBoF = Risk_2_VALUE;
   Risk_Profile_2.RiskAmount = Risk_2_AMOUNT;
class=class="str">"cmt">//Initializing CRiskManagement variables for Risk options(LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN)
   Risk_Profile_3.RiskLotBoF = Risk_3_VALUE;
   Risk_Profile_3.RiskLot = Risk_3_LOTSIZE;
class=class="str">"cmt">//Initializing CRiskManagement variable for Risk option(CUSTOM LOTSIZE)
   Risk_Profile_4 = Risk_4_LOTSIZE;
class=class="str">"cmt">//Initializing CRiskManagement variable for Risk option(PERCENTAGE OF MAX-RISK)
   Risk_Profile_5 = (Risk_5_PERCENTAGE>class="num">100)?class="num">100:
                    (Risk_5_PERCENTAGE<class="num">0.01)?class="num">0.01:Risk_5_PERCENTAGE;class=class="str">"cmt">//Percentage cannot be more than class="num">100% or less than class="num">0.01%
class=class="str">"cmt">//--- Initializing DST Schedule variables
   MyDST = ScheduleDST;
   MySchedule = CustomSchedule;
class=class="str">"cmt">//--- Initializing News filter variables
   myFrequency=iFrequency;
   myImportance=iImportance;
   mySector=iSector;
   myType=iType;
   myCurrency=iCurrency;
   Chart.ChartRefresh();class=class="str">"cmt">//Load chart configurations
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|function for initialization procedure outside strategy tester       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int InitNonTester()
  {
class=class="str">"cmt">//--- Check if in Strategy tester!
   if(MQLInfoInteger(MQL_TESTER))
     {
      class=class="str">"cmt">//--- Initialization failed.
      class="kw">return(INIT_SUCCEEDED);
     }
class=class="str">"cmt">//--- create OBJ_BITMAP_LABEL object for drawing
   ObjectCreate(class="num">0,"STATUS",OBJ_BITMAP_LABEL,class="num">0,class="num">0,class="num">0);
   ObjectSetInteger(class="num">0,"STATUS",OBJPROP_XDISTANCE,class="num">5);
   ObjectSetInteger(class="num">0,"STATUS",OBJPROP_YDISTANCE,class="num">22);
class=class="str">"cmt">//--- specify the name of the graphical resource
   ObjectSetString(class="num">0,"STATUS",OBJPROP_BMPFILE,"::PROGRESS");
   class="type">uint   w,h;            class=class="str">"cmt">// variables for receiving text class="type">class="kw">string sizes
   class="type">uint    x,y;           class=class="str">"cmt">// variables for calculation of the current coordinates of text class="type">class="kw">string anchor points
   /*
   In the Do class="kw">while loop below, the code will check if the terminal is connected to the internet.

「断线时怎么画出等待动画」

EA 在 do-while 里先清掉 ExtImg 绘图缓冲(ArrayFill 按 IMG_WIDTH*IMG_HEIGHT 全填 0),再判 TerminalInfoInteger(TERMINAL_CONNECTED)。若终端没连网,就走等待分支,而不是去拉新闻库。 等待分支用 Arial 150 号粗体画 'Waiting',纵向左对齐 x=10、y 取 IMG_HEIGHT/2-h/2 做垂直居中;随后在 x=w+13 处用 160 号字截 '...' 的子串,dots 从 0 到 3 循环,形成点号递增的载入动画。 每轮 ResourceCreate("::PROGRESS",...) 重建位图资源,Chart.Redraw() 强制重绘,Sleep(500) 控节奏,Print 打日志;循环 continue 直到连上。外汇/贵金属行情依赖实时连接,断线等待逻辑能避免程序空跑浪费 CPU。 连上后 else 分支把字体改成 120 号,测 'Getting Ready' 的 w/h,x 设 20,准备切到新闻库创建流程;这段坐标差(等待 10、就绪 20)可直接在 MT5 里改数字看对齐变化。

MQL5 / C++
class="type">bool done=class="kw">false;
 do
  {
   class=class="str">"cmt">//--- clear the drawing buffer array
   ArrayFill(ExtImg,class="num">0,IMG_WIDTH*IMG_HEIGHT,class="num">0);
   if(!TerminalInfoInteger(TERMINAL_CONNECTED))
     {
      class=class="str">"cmt">//-- integer dots used as a loading animation
      class="kw">static class="type">int dots=class="num">0;
      class=class="str">"cmt">//--- set the font
      TextSetFont("Arial",-class="num">150,FW_EXTRABOLD,class="num">0);
      TextGetSize("Waiting",w,h);class=class="str">"cmt">//get text width and height values
      class=class="str">"cmt">//--- calculate the coordinates of the &class="macro">#x27;Waiting&class="macro">#x27; text
      x=class="num">10;class=class="str">"cmt">//horizontal alignment
      y=IMG_HEIGHT/class="num">2-(h/class="num">2);class=class="str">"cmt">//alignment for the text to be centered vertically
      class=class="str">"cmt">//--- output the &class="macro">#x27;Waiting&class="macro">#x27; text to ExtImg[] buffer
      TextOut("Waiting",x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CSymbol.Background()),clr_format);
      class=class="str">"cmt">//--- calculate the coordinates for the dots after the &class="macro">#x27;Waiting&class="macro">#x27; text
      x=w+class="num">13;class=class="str">"cmt">//horizontal alignment
      y=IMG_HEIGHT/class="num">2-(h/class="num">2);class=class="str">"cmt">//alignment for the text to be centered vertically
      TextSetFont("Arial",-class="num">160,FW_EXTRABOLD,class="num">0);
      class=class="str">"cmt">//--- output of dots to ExtImg[] buffer
      TextOut(StringSubstr("...",class="num">0,dots),x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CSymbol.Background()),clr_format);
      class=class="str">"cmt">//--- update the graphical resource
      ResourceCreate("::PROGRESS",ExtImg,IMG_WIDTH,IMG_HEIGHT,class="num">0,class="num">0,IMG_WIDTH,clr_format);
      class=class="str">"cmt">//--- force chart update
      Chart.Redraw();
      dots=(dots==class="num">3)?class="num">0:dots+class="num">1;
      class=class="str">"cmt">//-- Notify user that program is waiting for connection
      Print("Waiting for connection...");
      Sleep(class="num">500);
      class="kw">continue;
      }
   else
     {
      class=class="str">"cmt">//--- set the font
      TextSetFont("Arial",-class="num">120,FW_EXTRABOLD,class="num">0);
      TextGetSize("Getting Ready",w,h);class=class="str">"cmt">//get text width and height values
      x=class="num">20;class=class="str">"cmt">//horizontal alignment

回测前的新闻库自检逻辑

在策略测试器里跑 EA 前,得先确认新闻数据库文件存在且没过期,否则回测结果大概率失真。代码用 FileIsExist(NEWS_DATABASE_FILE, FILE_COMMON) 检查公共目录下的库文件,缺失就直接返回 INIT_FAILED 并打印提示,要求你先在正常图表环境跑一遍程序生成文件。 如果文件在,就取库里最新新闻日期减去一天偏移量作为 latestdate,再和 TimeTradeServer() 比。若 latestdate 小于服务器时间,说明库已落后,打印“Necessary Files outdated!”提醒你更新;并打印“Database Dates End at:”加具体日期,明确告知该日期之后的行情无新闻数据可用。 非测试环境(MQLInfoInteger(MQL_TESTER) 为假)则直接放行返回 INIT_SUCCEEDED,不卡初始化。这个分支很实用:实盘和回测的初始化路径分开,避免测试器里反复建库拖慢速度。 连接成功那段也值得看:用 TextOut 往 ExtImg[] 画“Getting Ready...”、ResourceCreate 更新 ::PROGRESS 资源、Chart.Redraw() 强刷图表,最后 Print("Connection Successful!") 并调 NewsObject.CreateEconomicDatabase() 建库,done 置真退出循环。外汇与贵金属属高风险品类,回测数据缺口可能让策略表现偏离实盘,动手前先核对库日期。

MQL5 / C++
y=IMG_HEIGHT/class="num">2-(h/class="num">2); class=class="str">"cmt">//alignment for the text to be centered vertically
class=class="str">"cmt">//--- output the text &class="macro">#x27;Getting Ready...&class="macro">#x27; to ExtImg[] buffer
TextOut("Getting Ready...",x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CSymbol.Background()),clr_format);
class=class="str">"cmt">//--- update the graphical resource
ResourceCreate("::PROGRESS",ExtImg,IMG_WIDTH,IMG_HEIGHT,class="num">0,class="num">0,IMG_WIDTH,clr_format);
class=class="str">"cmt">//--- force chart update
Chart.Redraw();
class=class="str">"cmt">//-- Notify user that connection is successful
Print("Connection Successful!");
NewsObject.CreateEconomicDatabase(); class=class="str">"cmt">//calling the database create function
done=true;
}
  }
 class="kw">while(!done&&!IsStopped());
class=class="str">"cmt">//-- Delete chart object
 ObjectDelete(class="num">0,"STATUS");
class=class="str">"cmt">//-- force chart to update
 Chart.Redraw();
class=class="str">"cmt">//--- Initialization succeeded.
 class="kw">return(INIT_SUCCEEDED);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|function for initialization procedure inside strategy tester        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int InitTester()
  {
class=class="str">"cmt">//--- Check if not in Strategy tester!
  if(!MQLInfoInteger(MQL_TESTER))
    {
class=class="str">"cmt">//--- Initialization failed.
      class="kw">return(INIT_SUCCEEDED);
    }
class=class="str">"cmt">//Checks whether the database file exists
  if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))
    {
class=class="str">"cmt">//--- Warning messages
      Print("Necessary Files Do not Exist!");
      Print("Run Program outside of the Strategy Tester");
      Print("Necessary Files Should be Created First");
class=class="str">"cmt">//--- Initialization failed.
      class="kw">return(INIT_FAILED);
    }
  else
    {
class=class="str">"cmt">//Checks whether the latest database date includes the time and date being tested
      class="type">class="kw">datetime latestdate = CTM.TimeMinusOffset(NewsObject.GetLatestNewsDate(),CTM.DaysS()); class=class="str">"cmt">//Day before the latest recorded time in the database
      if(latestdate<TimeTradeServer())
        {
          Print("Necessary Files outdated!");
          Print("To Update Files: Run Program outside of the Strategy Tester");
        }
      Print("Database Dates End at: ",latestdate);
      PrintFormat("Dates after %s will not be available for backtest",TimeToString(latestdate));
    }
class=class="str">"cmt">//--- Initialization succeeded.
  class="kw">return(INIT_SUCCEEDED);
  }

◍ 把事件驱动逻辑塞进 OnTick 的骨架

EA 的主循环越干净,后续接新闻事件或重绘图形越不容易卡。下面这段把实时刷新、交易日更、小时库更新全收敛到 OnTick → Execution() 一条链上,实盘里你直接照这个壳子改就行。 OnTick 只做一件事:调 Execution()。真正的分支在 Execution 里——每分钟新蜡烛触发一次实时图块刷新(CGraphics.Block_2_Realtime),由 CP.NewCandle(1,PERIOD_M1) 判定,避免每 tick 重绘拖慢 MT5。 当日不是启动日时,日线新蜡烛(序号 2 / PERIOD_D1)会重建内存里的经济数据库并刷新图表对象;且只在非回测环境(!MQLInfoInteger(MQL_TESTER))才跑 CreateEconomicDatabaseMemory,回测不碰本地存储。小时线新蜡烛(序号 3 / PERIOD_H1)则检查存储记录是否需要更新,需要时调 InitNonTester 做实盘初始化。 若是启动当天,日线新蜡烛只更新新闻事件对象(CGraphics.NewsEvent),不重建库——这个分支能帮你避开首日数据不全导致的误触。外汇与贵金属受新闻冲击跳空概率高,这类事件驱动 EA 必须先在策略测试器跑通再上实盘。 代码逐行拆:CP.NewCandle 的整型参数是内部计数器槽位,不是时间周期;PERIOD_M1/H1/D1 才是周期枚举。MQL_TESTER 判断让同一套代码回测与实盘行为不一致,调试时建议临时强制为 false 观察全量分支。

MQL5 / C++
class=class="str">"cmt">//| Expert tick function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Run procedures
   Execution();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Execute program procedures                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Execution()
  {
class=class="str">"cmt">//--- Update realtime Graphic every class="num">1 min
   if(CP.NewCandle(class="num">1,PERIOD_M1))
     {
       CGraphics.Block_2_Realtime(iSecondsPreEvent);
     }
class=class="str">"cmt">//--- function to open trades
   EnterTrade();
class=class="str">"cmt">//--- Check if not start-up date
   if(!CTM.DateisToday(Startup_date))
     {
      class=class="str">"cmt">//--- Run every New Daily Candle
      if(CP.NewCandle(class="num">2,PERIOD_D1))
        {
         class=class="str">"cmt">//--- Check if not in strategy tester
         if(!MQLInfoInteger(MQL_TESTER))
           {
            class=class="str">"cmt">//--- Update/Create DB in Memory
            NewsObject.CreateEconomicDatabaseMemory();
           }
         CGraphics.GraphicsRefresh(iSecondsPreEvent);class=class="str">"cmt">//-- Create/Re-create chart objects
         class=class="str">"cmt">//--- Update Realtime Graphics
         CGraphics.Block_2_Realtime(iSecondsPreEvent);
        }
      class=class="str">"cmt">//--- Check if not in strategy tester
      if(!MQLInfoInteger(MQL_TESTER))
        {
         class=class="str">"cmt">//--- Run every New Hourly Candle
         if(CP.NewCandle(class="num">3,PERIOD_H1))
           {
            class=class="str">"cmt">//--- Check if DB in Storage needs an update
            if(NewsObject.UpdateRecords())
              {
               class=class="str">"cmt">//--- initialization procedure outside strategy tester
               InitNonTester();
              }
           }
        }
     }
   else
     {
      class=class="str">"cmt">//--- Run every New Daily Candle
      if(CP.NewCandle(class="num">4,PERIOD_D1))
        {
         class=class="str">"cmt">//--- Update Event objects on chart
         CGraphics.NewsEvent();
        }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|function to open trades                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void EnterTrade()
  {
class=class="str">"cmt">//--- class="kw">static variable for storing upcoming event Impact value

「按财经事件影响方向自动挂单的逻辑」

这段控制逻辑解决一个实际问题:财经日历事件临近时,EA 如何根据影响方向自动决定多空。它先判断已排定的新闻时间是否已过、新闻对象非空、且 1 分钟周期出现了新 K 线(CP.NewCandle(5,PERIOD_M1) 中 5 是校验用的某种计数),若成立就刷新下一条待发事件并取回影响等级 Impact。 紧接着用 CTM.TimePreEvent 比对「事件时间减去预偏移秒数」与事件本身时间,偏移量 iSecondsPreEvent 为 0 时退化成 1 秒,再叠加 isDayOfTheWeek(TradingDay) 过滤交易日,只有都命中才进入交易分支。 影响等级走 switch:CALENDAR_IMPACT_NEGATIVE 时,若品种报价货币的 Profit 端等于事件货币(CSymbol.CurrencyProfit()),就 Buy,否则 Sell;CALENDAR_IMPACT_POSITIVE 完全反向——Profit 端同币则 Sell,否则 Buy。单子 Magic 号直接写死成 UpcomingNews.EventId,方便事后按事件追溯。 别把正负影响当绝对信号。外汇与贵金属在事件窗口流动性突变,滑点可能吞掉止损,这套逻辑只是把「方向倾向」程序化,实盘前务必在 MT5 策略测试器用真实点差回测。

MQL5 / C++
class="kw">static ENUM_CALENDAR_EVENT_IMPACT Impact=CALENDAR_IMPACT_NA;
class=class="str">"cmt">//--- Check if Upcoming news date has passed and if upcoming news is not null and if new minute candle has formed.
  if(class="type">class="kw">datetime(UpcomingNews.EventDate)<TimeTradeServer()&&UpcomingNews.CountryName!=NULL&&CP.NewCandle(class="num">5,PERIOD_M1))
    {
      class=class="str">"cmt">//--- Update for next upcoming news
      NewsObject.EconomicNextEvent();
      class=class="str">"cmt">//--- Get impact value for upcoming news
      Impact=NewsObject.GetImpact();
    }
class=class="str">"cmt">//--- Check if upcoming news date is about to occur and if it is the trading day of week
  if(CTM.TimePreEvent(CTM.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),(iSecondsPreEvent==class="num">0)?class="num">1:iSecondsPreEvent)
                        ,class="type">class="kw">datetime(UpcomingNews.EventDate))
    &&CTM.isDayOfTheWeek(TradingDay))
    {
      class=class="str">"cmt">//--- Check each Impact value type
      class="kw">switch(Impact)
        {
         class=class="str">"cmt">//--- When Impact news is negative
         case CALENDAR_IMPACT_NEGATIVE:
            class=class="str">"cmt">//--- Check if profit currency is news event currency
            if(UpcomingNews.EventCurrency==CSymbol.CurrencyProfit())
              {
               class=class="str">"cmt">//--- Open buy trade with Event id as Magic number
               Trade.Buy(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading");
              }
            else
              {
               class=class="str">"cmt">//--- Open sell trade with Event id as Magic number
               Trade.Sell(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading");
              }
            class="kw">break;
         class=class="str">"cmt">//--- When Impact news is positive
         case CALENDAR_IMPACT_POSITIVE:
            class=class="str">"cmt">//--- Check if profit currency is news event currency
            if(UpcomingNews.EventCurrency==CSymbol.CurrencyProfit())
              {
               class=class="str">"cmt">//--- Open sell trade with Event id as Magic number
               Trade.Sell(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading");
              }
            else
              {
               class=class="str">"cmt">//--- Open buy trade with Event id as Magic number
               Trade.Buy(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading");
              }
            class="kw">break;
         class=class="str">"cmt">//--- Unknown
         class="kw">default:
            class="kw">break;
        }
    }

空小节无内容可析

本技术小节在源材料中未承载任何正文描述与代码逻辑,仅留有空标记。 若需在 MT5 中验证相关机制,应回溯前文定义的函数与调用上下文,避免凭空假设行为。 外汇与贵金属市场波动剧烈,任何未明确定义的逻辑块都可能引入不可控风险。

◍ 下一步要补的缺口

这一篇把内存数据库和额外视图接进了 MQL5 经济日历,图表上能直接挂图形对象提示临近事件,还顺手做了深色模式。用户侧加了新闻筛选输入项,EA 侧也给了按事件影响开关市价单的接口,事件影响与策略的关联逻辑算是跑通了。 附件 NewsTrading_Part3.zip 约 567 KB,里面是完整可编译的工程,开 MT5 加载后先调 NewsFilter 和 EA 的 ImpactThreshold 两个输入,看自己品种的事件触发是否符合预期。外汇和贵金属新闻行情跳空频繁,实盘前务必在策略测试器用历史数据验证一遍。 下一篇会补个别经济事件的待处理订单逻辑,以及不依赖事件影响的交易分支,当前这套只覆盖了市价单一种入口。

把事件刷新交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时新闻块与价差评级,你只需把精力放在是否跟单的判断上。

常见问题

评级基于两周内1分钟K线价差数据计算,分为优秀、良好、正常、较差、糟糕五档,并随深/浅色模式调色,盘中随新K线更新。
时间区间类负责隔离事件前中后窗口,让风控与下单逻辑只在该窗口内生效,避免非事件时段误触交易。
它把每个事件拆成可筛选字段(国家、货币、重要性等),EA输入参数可据此过滤,提升策略灵活性。
小布盯盘目前提供事件与价差可视化,不直接代跑第三方EA;你把框架跑通后,用它看盘口更省心。
非农等高影响事件下贵金属流动性瞬时抽干,滑点可能扩到日常数倍,属外汇贵金属典型高风险场景,概率随经纪商而异。