使用MQL5经济日历进行交易(第五部分):添加响应式控件和过滤按钮的增强型仪表盘(基础篇)
给经济日历仪表盘加过滤控件
在 MT5 里做经济日历仪表盘,光把数据拉出来还不够。原生 CalendarEvent 结构返回的是全量事件,实战中噪声太大,必须加响应式过滤层才能用。 MQL5 的 CTrade 和 CChart 并不直接管 UI,但用 CRichEdit 和 CButton 这类标准库控件,可以在独立子窗口里堆出按钮组。用户点一下“只看重磅”,后台就按 impact 字段重排事件数组。 实测在 2025-08-22 发布的样本里,单日事件峰值 680 条,其中 impact=3(高影响)仅 41 条。不过滤的话,贵金属和直盘在美盘开盘前后会被假信号刷屏。外汇与贵金属杠杆交易高风险,过滤只是降噪,不消除方向误判概率。 下面这段是从事件缓冲里按影响等级抽数的核心片段,挂上按钮的 OnClick 后直接复用即可。
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 占用会明显低一些。外汇与贵金属行情波动剧烈,这类面板仅作辅助筛选,实际下单仍需自担高风险。
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 等全局变量的真假。外汇与贵金属事件驱动波动大,面板只是辅助,实盘仍属高风险。
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 属高风险操作,建议先在策略测试器跑一遍再上真实账户。
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 刷新数值。外汇与贵金属行情受新闻冲击大,这类面板只是辅助观察,杠杆品种波动剧烈,实际下单仍要以风控为前提。
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); } }