MQL5 酷宝书 财经日历(基础篇)
「用 MT5 自带的财经日历抓事件窗口」
MT5 终端里内置了财经日历,不用另开网页就能直接看宏观事件对盘面可能的扰动。它按时间轴排布,每条事件带发布时间和影响等级,适合在重要数据前把图表切到对应品种盯盘。 以一组真实发布记录为例:2022 年 1 月 20 日 12:55 这条日历条目,页面显示有 2 562 次浏览、26 条评论,作者标注为 Denis Kirichenko。这个浏览量级说明该类事件在交易者中关注度不低,数据公布前后贵金属与外汇直盘常出现短时波动率抬升,属于高风险的事件驱动窗口。 实操上,打开 MT5 终端底部的「日历」标签,把时间过滤调到当前交易日,就能看到类似上述条目的实时推送。建议你先挑 EUR/USD 或 XAU/USD,在日历事件前 5 分钟挂好边界挂单,验证一下滑点和高波动的触发概率。
◍ 终端里的财经日历能写进EA吗
MT5 终端和 MQL5 这几年持续迭代,市场分析相关的接口和自动化能力都在往外扩。内置的财经日历就是其中一个较新的组件,它既能人工在终端日历页翻看,也能被机器人调用。 日历本身的呈现不算死板:终端选项卡可配置筛选,网页能嵌,手机端也有独立版本。但对做算法交易的人来说,值得盯的只有一件事——它能不能被代码直接读。 本篇要做的,就是把财经日历在 MQL5 里的编程接口拆开,看清楚哪些事件字段可取、怎样在EA里订阅。
MT5 财经日历接口到底给了什么
MT5 内置的财经日历走的是一套标准 MQL5 资源接口:文档里列了 10 个可直接调用的函数,例如 CalendarCountries() 拉国家清单、CalendarValueHistoryByEvent() 取某事件的历史值、CalendarValueLast() 取最近一次发布值。它们要么返回国家 / 事件 / 数值三类属性,要么回吐历史序列,逻辑不绕,但字段细节藏坑。 底层用三个结构撑起数据模型:MqlCalendarCountry 描述国家维度,MqlCalendarEvent 装事件元数据,MqlCalendarValue 存具体数值。国家层面目前覆盖 21 个国家加上欧盟和全球事件,俄罗斯不在清单里,做卢布相关基本面的得自己另找源。 MqlCalendarValue 里 actual_value、forecast_value、prev_value、revised_prev_value 这四个字段被放大了一百万倍存储。没值的时候填的是 LONG_MIN(-9223372036854775808),有值就得除以 1000000 才拿到真实小数。结构自带两组方法:一组判字段是否存在,一组直接取值——直接取可能拿到 LONG_MIN,用 GetForecastValue() 这类方法会返回 nan,最稳的是先判存在再读。 跑 Test_empty_value.mq5 盯日本央行利率决议就能看到三种取法在日志里的差异。结构间关系是两层一对多:国家 ID 链事件,事件 ID 链数值。运行时错误码也分了组,5400 是数组太小装不下、5401 是超时、5402 是国家没匹配到,写脚本时得自己接这些错。 下面这段是 CalendarCountries() 实际吐出的前几行,能直接核对 ID 与货币符号的对应关系: [id] 0 999 European Union / EU / EUR / € [id] 1 124 Canada / CA / CAD / $ [id] 4 392 Japan / JP / JPY / ¥ 做外汇或贵金属联动分析时,这种映射错了会导致事件过滤全偏,属于高风险环节,建议先在小周期回测里验证一遍再上实盘。
[id] [name] [code] [currency] [currency_symbol] [url_name] [reserved] [ class="num">0] class="num">999 "European Union" "EU" "EUR" "€" "european-union" ... [ class="num">1] class="num">124 "Canada" "CA" "CAD" "$" "canada" ... [ class="num">2] class="num">36 "Australia" "AU" "AUD" "$" "australia" ... [ class="num">3] class="num">554 "New Zealand" "NZ" "NZD" "$" "new-zealand" ... [ class="num">4] class="num">392 "Japan" "JP" "JPY" "¥" "japan" ... [ class="num">5] class="num">156 "China" "CN" "CNY" "¥" "china" ... [ class="num">6] class="num">276 "Germany" "DE" "EUR" "€" "germany" ... [ class="num">7] class="num">250 "France" "FR" "EUR" "€" "france" ...
「国家与货币代码的底层映射表」
在 MT5 的底层数据结构中,经济体与货币并非靠名称字符串硬匹配,而是用一组固定字段做映射:数字 ID、国家名、双字母国家码、ISO 货币码、货币符号、以及小写连字符形式的检索键。 上面这段原始记录展示了 12 个具体条目,从 Italy 的 ID 380 到 Switzerland 的 ID 756,中间还插了一条 ID 为 0 的 Worldwide 虚拟项,其货币码被标为 ALL、符号留空。 注意 Singapore 的货币符号字段写的是 "R$",与 Brazil 的 "R$" 撞车,而 South Africa 用的是单字符 "R"——如果你在 EA 里用符号反查国家,就会踩到歧义坑。 开 MT5 后,可把这些条目直接塞进一个结构体数组做枚举,用数字 ID 做主键比用字符串可靠得多;外汇与贵金属杠杆交易高风险,任何映射错误都可能让订单发往错误标的。
[ class="num">8] class="num">380 "Italy" "IT" "EUR" "€" "italy" ... [ class="num">9] class="num">76 "Brazil" "BR" "BRL" "R$" "brazil" ... [class="num">10] class="num">344 "Hong Kong" "HK" "HKD" "HK$" "hong-kong" ... [class="num">11] class="num">702 "Singapore" "SG" "SGD" "R$" "singapore" ... [class="num">12] class="num">484 "Mexico" "MX" "MXN" "Mex$" "mexico" ... [class="num">13] class="num">710 "South Africa" "ZA" "ZAR" "R" "south-africa" ... [class="num">14] class="num">356 "India" "IN" "INR" "₹" "india" ... [class="num">15] class="num">578 "Norway" "NO" "NOK" "Kr" "norway" ... [class="num">16] class="num">0 "Worldwide" "WW" "ALL" "" "worldwide" ... [class="num">17] class="num">840 "United States" "US" "USD" "$" "united-states" ... [class="num">18] class="num">826 "United Kingdom" "GB" "GBP" "£" "united-kingdom" ... [class="num">19] class="num">756 "Switzerland" "CH" "CHF" "₣" "switzerland" ...
◍ 经济日历事件值的取值与校验
MqlCalendarValue 结构体提供了成对的存在判断与取值方法:HasActualValue / GetActualValue、HasForecastValue / GetForecastValue、HasPreviousValue / GetPreviousValue、HasRevisedValue / GetRevisedValue。判断类方法返回布尔值,取值类方法在未设置对应字段时返回 double 类型的 nan,而不是 0,这一点在写过滤逻辑时容易踩坑。 日历国家列表中可以看到索引 20 为韩国(代码 KR、货币 KRW、符号 ₩),索引 21 为西班牙(ES、EUR、€),索引 22 为瑞典(SE、SEK、Kr),说明底层枚举是按数字 id 连续排布的,做本地化映射时直接读数组下标即可。 下方示例拉取了 2021 年 9 月 22 日日本央行利率决议(event_id = 392060022)当天的日历值。先用 CalendarValueHistoryByEvent 按日切片取回 values[],再用 union LongDouble 把同一段内存分别当 long 和 double 来解释,对比 forecast_value 字段直读与 GetForecastValue() 方法读出的差异。 HasForecastValue() 返回 false 时,GetForecastValue() 必为 nan;实盘里若拿这个值去算偏离度,必须先过存在性判断,否则 nan 参与运算会静默污染后续信号。外汇与贵金属受央行决议驱动波动剧烈,这类数据缺口不处理,策略可能在高风险事件窗直接失真。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| LongDouble | class=class="str">"cmt">//+------------------------------------------------------------------+ union LongDouble { class="type">long long_value; class="type">class="kw">double double_value; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Bank of Japan(BoJ) Interest Rate Decision on class="num">22 Sep class="num">2021 class="num">02:class="num">47 GMT class="type">class="kw">ulong event_id = class="num">392060022; class=class="str">"cmt">// "boj-interest-rate-decision" MqlCalendarValue values[]; class="type">class="kw">datetime date_from, date_to; date_from = D&class="macro">#x27;class="num">22.09.class="num">2021&class="macro">#x27;; date_to = date_from + PeriodSeconds(PERIOD_D1); if(::CalendarValueHistoryByEvent(event_id, values, date_from, date_to)) { LongDouble forecast_val; class=class="str">"cmt">//--- class="num">1) "forecast_value" field forecast_val.long_value = values[class="num">0].forecast_value; ::PrintFormat("\"forecast_value\" field: %I64d", forecast_val.long_value); class=class="str">"cmt">//--- class="num">2) MqlCalendarValue::GetForecastValue() forecast_val.double_value = values[class="num">0].GetForecastValue(); ::PrintFormat("MqlCalendarValue::GetForecastValue(): %g", forecast_val.double_value); class=class="str">"cmt">//--- class="num">3) MqlCalendarValue::HasForecastValue() if(!values[class="num">0].HasForecastValue())
空值字段在日志里长什么样
跑完那段测试脚本,MT5 日志会直接把日历事件里没填的预测值暴露出来。USDCAD H1 品种下,forecast_value 字段打印为 -9223372036854775808,这正是 MQL5 用 long 类型空值占位的表现,不是真实报价。 紧接着 GetForecastValue() 返回 nan,HasForecastValue() 返回 false,说明接口层已经识别该字段无效。做事件驱动策略时,若不清掉这类空值,拿 nan 去算偏离度会直接污染信号。 外汇与贵金属受数据市冲击大、杠杆高,用日历值前务必先判 HasForecastValue(),否则在重大数据空窗期可能触发误判交易。
::PrintFormat("MqlCalendarValue::HasForecastValue(): %s", (class="type">class="kw">string)false); } } class=class="str">"cmt">//+------------------------------------------------------------------+ GR class="num">0 class="num">21:class="num">23:class="num">36.076 Test_empty_value(USDCAD,H1) "forecast_value" field: -class="num">9223372036854775808 LH class="num">0 class="num">21:class="num">23:class="num">36.080 Test_empty_value(USDCAD,H1) MqlCalendarValue::GetForecastValue(): nan HM class="num">0 class="num">21:class="num">23:class="num">36.080 Test_empty_value(USDCAD,H1) MqlCalendarValue::HasForecastValue(): false