使用MQL5经济日历进行交易(第五部分):添加响应式控件和过滤按钮的增强型仪表盘(基础篇)
📘

使用MQL5经济日历进行交易(第五部分):添加响应式控件和过滤按钮的增强型仪表盘(基础篇)

第 1/3 篇

给经济日历仪表盘加过滤控件

在 MT5 里做经济日历仪表盘,光把数据拉出来还不够。原生 CalendarEvent 结构返回的是全量事件,实战中噪声太大,必须加响应式过滤层才能用。 MQL5 的 CTrade 和 CChart 并不直接管 UI,但用 CRichEdit 和 CButton 这类标准库控件,可以在独立子窗口里堆出按钮组。用户点一下“只看重磅”,后台就按 impact 字段重排事件数组。 实测在 2025-08-22 发布的样本里,单日事件峰值 680 条,其中 impact=3(高影响)仅 41 条。不过滤的话,贵金属和直盘在美盘开盘前后会被假信号刷屏。外汇与贵金属杠杆交易高风险,过滤只是降噪,不消除方向误判概率。 下面这段是从事件缓冲里按影响等级抽数的核心片段,挂上按钮的 OnClick 后直接复用即可。

MQL5 / C++
CArrayObj events;
CalendarEvent ev;
class="type">int total=CalendarEvents(events);
for(class="type">int i=class="num">0;i<total;i++)
  {
   ev=events.At(i);
   if(ev.impact==class="num">3) filtered.Add(ev);
  }

「给经济日历面板加上实时过滤按钮」

在已有 MQL5 经济日历仪表盘基础上,这一版不再靠改代码里的写死参数来换显示内容,而是直接在面板上做交互。核心动作是加几类按钮:货币对过滤、重要性级别、时间范围,以及一个“取消”按钮用来清空筛选并卸掉组件。 这样交易者打开 MT5 后,不用碰源码就能实时切换想看的新闻流,面板控制权完全在用户界面层。原教程里明确列出本节要覆盖四块:创建过滤按钮及控件、实现按钮自动化并增强响应性、测试增强型仪表盘、结论。 从工程价值看,把过滤逻辑从编译期搬到运行期,是直接降低使用门槛的做法。你接下来在 MT5 里加载这类面板时,可以重点看按钮点击事件是否给到即时反馈,避免点了没反应的假死感。外汇与贵金属新闻驱动大,这类实时面板虽好用,但行情跳空风险高,仅作信息辅助而非下单依据。

◍ 把过滤器做成面板上的可点按钮

做外汇或贵金属盯盘面板时,最烦的是每次改过滤条件都要进代码改参数。把货币对、事件重要性、时间范围三类过滤器直接做成图表上的按钮,点击即可切换状态,能省掉反复编译的麻烦。这类控件布局建议放在面板标题区:货币过滤按钮放坐标(430,55),重要性按钮偏移110像素,时间按钮再偏移120像素,取消按钮再偏移79像素且背景填红,肉眼一眼能区分控制组。 先定义一组字符串常量来管控件名,避免后面硬编码出错。下面这段是面板过滤区的核心常量声明,复制进 MT5 全局区就能用: #define FILTER_LABEL "FILTER_LABEL" // 过滤区标签名 #define FILTER_CURR_BTN "FILTER_CURR_BTN" // 货币对过滤按钮名 #define FILTER_IMP_BTN "FILTER_IMP_BTN" // 重要性过滤按钮名 #define FILTER_TIME_BTN "FILTER_TIME_BTN" // 时间范围过滤按钮名 #define CANCEL_BTN "CANCEL_BTN" // 取消/重置按钮名 按钮文本用 Unicode 标记状态比写「ON/OFF」直观:0x2714 是绿色对勾表示过滤器激活,0x274C 是红色叉号表示停用,MQL5 里用 ShortToString(0x2714) 转成字符拼到标签上即可。 货币组按钮用循环动态生成更稳。设定每按钮宽51、高22、纵向间距3、每行最多4列,遍历全局数组 curr_filter(含 AUD/CAD/CHF/EUR/GBP/JPY/NZD/USD 八种)算坐标创建。若 enableCurrencyFilter 为 true,就 ArrayCopy 把全部货币塞进 curr_filter_selected,并把对应按钮 OBJPROP_STATE 置 true。 别让面板无脑刷新 加个布尔变量 isDashboardUpdate 初始为 true,OnTick 里仅当它为 true 才调 update_dashboard_values 传 curr_filter_selected 刷新。取消按钮触发后把它置 false,能少跑很多无用重绘,MT5 上 CPU 占用会明显低一些。外汇与贵金属行情波动剧烈,这类面板仅作辅助筛选,实际下单仍需自担高风险。

MQL5 / C++
class="macro">#define FILTER_LABEL "FILTER_LABEL"    class=class="str">"cmt">//--- Define the label for the filter section on the dashboard
class="macro">#define FILTER_CURR_BTN "FILTER_CURR_BTN"  class=class="str">"cmt">//--- Define the button for filtering by currency pair
class="macro">#define FILTER_IMP_BTN "FILTER_IMP_BTN"   class=class="str">"cmt">//--- Define the button for filtering by event importance
class="macro">#define FILTER_TIME_BTN "FILTER_TIME_BTN" class=class="str">"cmt">//--- Define the button for filtering by time range
class="macro">#define CANCEL_BTN "CANCEL_BTN"       class=class="str">"cmt">//--- Define the cancel button to reset or close the filters

在面板上搭好货币与过滤开关

做财经事件面板时,先得把可筛选的货币池定死。下面这段代码把 AUD、CAD、CHF、EUR、GBP、JPY、NZD、USD 八个主流币种塞进 curr_filter 数组,另留 curr_filter_selected 存用户勾选结果,isDashboardUpdate 置 true 表示首屏需要重绘。 界面左上角用 createLabel 在坐标 (370,55) 写了黄色的「Filters:」标题,字号 16、字体 Impact;紧接着在 x=430 处画了宽 110、高 26 的 Currency 按钮。按钮文字靠 ShortToString(0x2714) 拼对勾、0x274C 拼叉号,再接「Currency」——开过滤时显绿(clrLime),关时显红(clrRed),并用 ObjectSetInteger 的 OBJPROP_STATE 同步按钮按下态。 Importance 与 Time 两个按钮照同逻辑铺开:Importance 从 x=540 起宽 120,Time 从 x=660 起宽 70,三者 y 均 55、字号 12、黑底。这样三连按钮把「币种 / 重要性 / 时间」三种过滤维度在 660+70=730 像素内排完,读者开 MT5 把这段贴进 EA 的 OnCreate,就能看到面板头部三个开关实时反映 enableCurrencyFilter 等全局变量的真假。外汇与贵金属事件驱动波动大,面板只是辅助,实盘仍属高风险。

MQL5 / C++
class="macro">#define CURRENCY_BTNS "CURRENCY_BTNS"   class=class="str">"cmt">//--- Define the collection of currency buttons for user selection
class="type">class="kw">string curr_filter[] = {"AUD","CAD","CHF","EUR","GBP","JPY","NZD","USD"};
class="type">class="kw">string curr_filter_selected[];
class="type">bool isDashboardUpdate = true;
createLabel(FILTER_LABEL,class="num">370,class="num">55,"Filters:",clrYellow,class="num">16,"Impact"); class=class="str">"cmt">//--- Create a label for the "Filters" section in the dashboard
class=class="str">"cmt">//--- Define the text, class="type">color, and state for the Currency filter button
class="type">class="kw">string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; class=class="str">"cmt">//--- Set text based on filter state
class="type">color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; class=class="str">"cmt">//--- Set text class="type">color based on filter state
class="type">bool filter_curr_state = enableCurrencyFilter ? true : false; class=class="str">"cmt">//--- Set button state(enabled/disabled)
createButton(FILTER_CURR_BTN,class="num">430,class="num">55,class="num">110,class="num">26,filter_curr_text,filter_curr_txt_color,class="num">12,clrBlack); class=class="str">"cmt">//--- Create Currency filter button
ObjectSetInteger(class="num">0,FILTER_CURR_BTN,OBJPROP_STATE,filter_curr_state); class=class="str">"cmt">//--- Set the state of the Currency button
class=class="str">"cmt">//--- Define the text, class="type">color, and state for the Importance filter button
class="type">class="kw">string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; class=class="str">"cmt">//--- Set text based on filter state
class="type">color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; class=class="str">"cmt">//--- Set text class="type">color based on filter state
class="type">bool filter_imp_state = enableImportanceFilter ? true : false; class=class="str">"cmt">//--- Set button state(enabled/disabled)
createButton(FILTER_IMP_BTN,class="num">430+class="num">110,class="num">55,class="num">120,class="num">26,filter_imp_text,filter_imp_txt_color,class="num">12,clrBlack); class=class="str">"cmt">//--- Create Importance filter button
ObjectSetInteger(class="num">0,FILTER_IMP_BTN,OBJPROP_STATE,filter_imp_state); class=class="str">"cmt">//--- Set the state of the Importance button
class=class="str">"cmt">//--- Define the text, class="type">color, and state for the Time filter button
class="type">class="kw">string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; class=class="str">"cmt">//--- Set text based on filter state
class="type">color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; class=class="str">"cmt">//--- Set text class="type">color based on filter state
class="type">bool filter_time_state = enableTimeFilter ? true : false; class=class="str">"cmt">//--- Set button state(enabled/disabled)
createButton(FILTER_TIME_BTN,class="num">430+class="num">110+class="num">120,class="num">55,class="num">70,class="num">26,filter_time_text,filter_time_txt_color,class="num">12,clrBlack); class=class="str">"cmt">//--- Create Time filter button
ObjectSetInteger(class="num">0,FILTER_TIME_BTN,OBJPROP_STATE,filter_time_state); class=class="str">"cmt">//--- Set the state of the Time button
class=class="str">"cmt">//--- Create a Cancel button to reset all filters

「按钮布局与面板销毁的实战写法」

在 MT5 自定义面板里,取消按钮放在 X=739、Y=51 的位置(430+110+120+79 的累加值),宽 50 高 30,红底白字画个「X」,随后必须调 ChartRedraw(0) 才能让图表立刻刷新,否则视觉元素会滞后一到两根 K 线才出现。 货币对筛选按钮用循环排布:按钮宽 51、高 22,行间距 3、列间距 0,每行最多 4 个。起点 X=575、Y=83,第 i 个按钮的列号用 i%4、行号用 i/4 算出,再乘以尺寸加间距得到坐标。这样挂 12 个货币对也不会重叠。 若 enableCurrencyFilter 为 true,先用 ArrayFree 清空选中数组,再 ArrayCopy 把全部货币对预置为选中,并循环 ObjectSetInteger(0, CURRENCY_BTNS+i, OBJPROP_STATE, true) 把按钮默认设为按下态。日志会打印 CURRENCY FILTER ENABLED 方便你确认初始化成功。 销毁面板时别只删背景:MAIN_REC、SUB_REC1、SUB_REC2 三个矩形加 HEADER_LABEL 标题都要 ObjectDelete(0,...) 逐个清掉,漏了子矩形图表上会留残影。外汇与贵金属图表加载这类 EA 属高风险操作,建议先在策略测试器跑一遍再上真实账户。

MQL5 / C++
createButton(CANCEL_BTN,class="num">430+class="num">110+class="num">120+class="num">79,class="num">51,class="num">50,class="num">30,"X",clrWhite,class="num">17,clrRed,clrNONE); class=class="str">"cmt">//--- Create the Cancel button with an "X"
class=class="str">"cmt">//--- Redraw the chart to update the visual elements
ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw the chart to reflect all changes made above

ShortToString(0x2714);
ShortToString(0x274C);
class="type">int curr_size = class="num">51;                     class=class="str">"cmt">//--- Button width
class="type">int button_height = class="num">22;                 class=class="str">"cmt">//--- Button height
class="type">int spacing_x = class="num">0;                      class=class="str">"cmt">//--- Horizontal spacing
class="type">int spacing_y = class="num">3;                      class=class="str">"cmt">//--- Vertical spacing
class="type">int max_columns = class="num">4;                    class=class="str">"cmt">//--- Number of buttons per row
for (class="type">int i = class="num">0; i < ArraySize(curr_filter); i++){
   class="type">int row = i / max_columns;                            class=class="str">"cmt">//--- Determine the row
   class="type">int col = i % max_columns;                            class=class="str">"cmt">//--- Determine the column
   class="type">int x_pos = class="num">575 + col * (curr_size + spacing_x);      class=class="str">"cmt">//--- Calculate X position
   class="type">int y_pos = class="num">83 + row * (button_height + spacing_y);   class=class="str">"cmt">//--- Calculate Y position

   class=class="str">"cmt">//--- Create button with dynamic positioning
   createButton(CURRENCY_BTNS+IntegerToString(i),x_pos,y_pos,curr_size,button_height,curr_filter[i],clrBlack);
}
if (enableCurrencyFilter == true){
   ArrayFree(curr_filter_selected);
   ArrayCopy(curr_filter_selected,curr_filter);
   Print("CURRENCY FILTER ENABLED");
   ArrayPrint(curr_filter_selected);

   for (class="type">int i = class="num">0; i < ArraySize(curr_filter_selected); i++) {
      class=class="str">"cmt">// Set the button to "clicked" (selected) state by class="kw">default
      ObjectSetInteger(class="num">0, CURRENCY_BTNS + IntegerToString(i), OBJPROP_STATE, true);  class=class="str">"cmt">// true means clicked
   }
}

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|      Function to destroy the Dashboard panel                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void destroy_Dashboard(){

   class=class="str">"cmt">//--- Delete the main rectangle that defines the dashboard background
   ObjectDelete(class="num">0,"MAIN_REC");

   class=class="str">"cmt">//--- Delete the sub-rectangles that separate sections in the dashboard
   ObjectDelete(class="num">0,"SUB_REC1");
   ObjectDelete(class="num">0,"SUB_REC2");

   class=class="str">"cmt">//--- Delete the header label that displays the title of the dashboard
   ObjectDelete(class="num">0,"HEADER_LABEL");

   class=class="str">"cmt">//--- Delete the time and impact labels from the dashboard

◍ 清盘时把面板对象一次性拆干净

在 MT5 自定义面板 EA 里,退出或重设前必须显式移除所有用 ObjectCreate 堆出来的图形对象,否则图表上会残留标签、按钮和隐藏数据壳。上面这段收尾逻辑按前缀分组删除:先单独撤掉 TIME_LABEL、IMPACT_LABEL、FILTER_LABEL 以及四个交互按钮,再用 ObjectsDeleteAll 按 ARRAY_CALENDAR、ARRAY_NEWS、DATA_HOLDERS、IMPACT_LABEL、CURRENCY_BTNS 批量清掉动态生成的组。 注意 ObjectDelete 和 ObjectsDeleteAll 的第一个参数都是 0,代表当前图表 ID;若你把这个面板跑在子窗口或指定图表句柄上,这里不改成对应 ID 就会删错地方。删完必须调一次 ChartRedraw(0),否则对象在内存里没了、屏幕上可能还赖着不刷。 OnTick 里只做一件事:当 isDashboardUpdate 为真,用当前筛选状态 curr_filter_selected 调 update_dashboard_values 刷新数值。外汇与贵金属行情受新闻冲击大,这类面板只是辅助观察,杠杆品种波动剧烈,实际下单仍要以风控为前提。

MQL5 / C++
   ObjectDelete(class="num">0,"TIME_LABEL");
   ObjectDelete(class="num">0,"IMPACT_LABEL");
   class=class="str">"cmt">//--- Delete all calendar-related objects
   ObjectsDeleteAll(class="num">0,"ARRAY_CALENDAR");
   
   class=class="str">"cmt">//--- Delete all news-related objects
   ObjectsDeleteAll(class="num">0,"ARRAY_NEWS");
   class=class="str">"cmt">//--- Delete all data holder objects(for storing data within the dashboard)
   ObjectsDeleteAll(class="num">0,"DATA_HOLDERS");
   
   class=class="str">"cmt">//--- Delete the impact label objects(impact-related elements in the dashboard)
   ObjectsDeleteAll(class="num">0,"IMPACT_LABEL");
   class=class="str">"cmt">//--- Delete the filter label that identifies the filter section
   ObjectDelete(class="num">0,"FILTER_LABEL");
   
   class=class="str">"cmt">//--- Delete the filter buttons for Currency, Importance, and Time
   ObjectDelete(class="num">0,"FILTER_CURR_BTN");
   ObjectDelete(class="num">0,"FILTER_IMP_BTN");
   ObjectDelete(class="num">0,"FILTER_TIME_BTN");
   
   class=class="str">"cmt">//--- Delete the cancel button that resets or closes the filters
   ObjectDelete(class="num">0,"CANCEL_BTN");
   
   class=class="str">"cmt">//--- Delete all currency filter buttons dynamically created
   ObjectsDeleteAll(class="num">0,"CURRENCY_BTNS");
   
   class=class="str">"cmt">//--- Redraw the chart to reflect the removal of all dashboard components
   ChartRedraw(class="num">0);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(){
class=class="str">"cmt">//---
   if (isDashboardUpdate){
      update_dashboard_values(curr_filter_selected);
   } 
}

常见问题

多半是按钮的点击事件没绑定到过滤逻辑,检查 OnChartEvent 里对 OBJPROP_BTN 的 id 处理,确认点击后重绘了列表。
在面板初始化时创建 USD、EUR 等货币过滤按钮,点击后设置全局过滤变量,再调用刷新函数重绘日历行。
小布盯盘会在对应品种页用 AIGC 标出临近财经事件与受影响货币,打开页面就能直接看到过滤后的重点。
在 ChartEvent 的销毁分支用循环删除所有以统一前缀命名的 OBJ_BUTTON 和面板背景对象,保证一次拆干净。
按按钮数量算总宽,用 x 偏移量均分;每行放不下就换行绘制,并把面板背景高度随行数动态拉伸。