MQL5 酷宝书 财经日历·综合运用
(3/3)·从国家结构到非商用净持仓指标,36节打通MQL5财经日历的算法落地最后一公里
- 给财经日历对象做初始化守卫
- 经济日历对象的初始化边界
- 日历对象重复初始化的坑
- 用 CiCalendarInfo 拉德美两国财经事件
- 用 CiCalendarInfo 抓英国财经事件做随机抽样
- 把财经事件的结构化字段逐条打出来
- 把财经日历事件属性逐条打印出来
- 枚举描述在USDCAD-H1上的打印结果
- 枚举描述在日志里的实际打印样貌
- 枚举描述在日志里的实际打印形态
- 枚举描述在日志里的实际输出
- 枚举描述打印里的量级与冲击分级
- 用日历事件 ID 拉取 NFP 历史值
- 非农时间序列的回测打印长什么样
- 抓日历国家与大陆维度的两种取法
- 按大洲枚举国家与单国属性查询
- 从财经日历里抓国家与洲际分布
- 正文
- 从日志看洲际与国家枚举的遍历结果
- 用位掩码切新闻类型与板块
- 新闻过滤器里的频率、重要度与单位位掩码
- 用位掩码切经济数据过滤器
- 用位掩码宏筛宏观数据字段
- 用名字捞财经事件的两个容器
- 按重要性分级抓取财经事件
- 按低重要度与无重要度筛事件并打印
- USDCAD 财经事件过滤的日志实证
- 从日志拆出一个事件对象的全字段
- 日志里读出的财经事件过滤器实况
- 从日志拆解一条财经事件的结构字段
- 事件过滤日志里读出的真实结构
- 把 CFTC 净持仓搬进 MT5 副图
- 净持仓序列的裁剪与对齐
- 按K线时间推进观测值缓冲
- 用经济日历值驱动指标缓冲
- 把日历类接进你自己的工程
给财经日历对象做初始化守卫
在 MT5 里用 CiCalendarInfo 拉取央行日程前,Init() 是第一道闸门。它先查 m_is_init 标志,若对象已经初始化过就直接 PrintFormat 报错并返回 false,避免重复挂同一套国家/事件过滤器。 国家列表靠 CalendarCountries() 填充,若本地 m_countries 数组长度小于 1 才去调接口;返回 0 时 ResetLastError 后打 Error 码并退出。实测若网络不通,该函数返回 0 的概率明显上升,EA 启动阶段就该拦住。 货币与国别用字符串和 id 双匹配:遍历 countries_cnt 个国家,StringCompare 命中 currency 就写 m_currency,country_id 非 WRONG_VALUE 且相等才记 m_country_id。最后 ValidateProperties() 不过就返回 false,to_log 为真才往日志吐出 Currency / Country id 便于肉眼核对。
class="type">bool CiCalendarInfo::Init(class="kw">const class="type">class="kw">string currency = NULL, class=class="str">"cmt">// country currency code name class="kw">const class="type">class="kw">ulong country_id = WRONG_VALUE, class=class="str">"cmt">// country ID class="kw">const class="type">class="kw">ulong event_id = WRONG_VALUE, class=class="str">"cmt">// event ID class="kw">const class="type">bool to_log = true class=class="str">"cmt">// to log? ) { class=class="str">"cmt">//--- check reinitialization if(m_is_init) { ::PrintFormat(__FUNCTION__ + ": CiCalendarInfo object already initialized!"); class="kw">return false; } class=class="str">"cmt">//--- check countries class="type">int countries_cnt = ::ArraySize(m_countries); if(countries_cnt < class="num">1) { ::ResetLastError(); countries_cnt = ::CalendarCountries(m_countries); if(countries_cnt < class="num">1) { ::PrintFormat(__FUNCTION__ + ": CalendarCountries() returned class="num">0! Error %d", ::GetLastError()); class="kw">return false; } } for(class="type">int c_idx = class="num">0; c_idx < countries_cnt; c_idx++) { MqlCalendarCountry curr_country = m_countries[c_idx]; class=class="str">"cmt">//--- check currency if(!::StringCompare(curr_country.currency, currency)) { m_currency = currency; } class=class="str">"cmt">//--- check country if(country_id != WRONG_VALUE) if(curr_country.id == country_id) { m_country_id = country_id; } } class=class="str">"cmt">//--- check event if(event_id != WRONG_VALUE) { m_event_id = event_id; } class=class="str">"cmt">//--- validate properties if(!this.ValidateProperties()) class="kw">return false; class=class="str">"cmt">//--- if(to_log) { ::Print("\n---== New Calendar Info object ==---"); if(m_currency != NULL) ::PrintFormat(" Currency: %s", m_currency); if(m_country_id != WRONG_VALUE) ::PrintFormat(" Country id: %I64u", m_country_id);
◍ 经济日历对象的初始化边界
MT5 自带的 CiCalendarInfo 类用 Init() 重载来圈定你要盯的基本面事件范围,从全市场到某个具体指标都能锁。写脚本时最容易栽在「货币、国家、事件 ID 三者不匹配」上,编译器不报错,但对象初始化会直接返回 false。 下面这段演示了 7 种组合:前 4 种参数自洽,Init() 返回 true;后 3 种把德国塞进美国非农 ID、或给 EUR 配美国国家码,全部返回 false。日志里能看到 currency=EUR、country id=276(德国)这类落地值,证明对象确实按入参建好了。 外汇与贵金属受事件冲击跳空概率高,用这类过滤器前先在策略测试器跑一遍 Init 返回值,别等实盘才发现日历对象根本没加载到目标事件。
if(m_event_id != WRONG_VALUE) ::PrintFormat(" Event id: %I64u", m_event_id); } m_is_init = true; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- TRUE class=class="str">"cmt">//--- class="num">1) all currencies, all countries, all events CiCalendarInfo calendar_info1; class="type">bool is_init = calendar_info1.Init(); class=class="str">"cmt">//--- class="num">2) EUR, all countries, all events CiCalendarInfo calendar_info2; is_init = calendar_info2.Init("EUR"); class=class="str">"cmt">//--- class="num">3) EUR, Germany, all events CiCalendarInfo calendar_info3; is_init = calendar_info3.Init("EUR", class="num">276); class=class="str">"cmt">//--- class="num">4) EUR, Germany, HICP m/m CiCalendarInfo calendar_info4; is_init = calendar_info4.Init("EUR", class="num">276, class="num">276010022); class=class="str">"cmt">//--- FALSE class=class="str">"cmt">//--- class="num">5) EUR, Germany, nonfarm-payrolls CiCalendarInfo calendar_info5; is_init = calendar_info5.Init("EUR", class="num">276, class="num">840030016); class=class="str">"cmt">//--- class="num">6) EUR, US, nonfarm-payrolls CiCalendarInfo calendar_info6; is_init = calendar_info6.Init("EUR", class="num">840, class="num">840030016); class=class="str">"cmt">//--- class="num">7) EUR, all countries, nonfarm-payrolls CiCalendarInfo calendar_info7; is_init = calendar_info7.Init("EUR", WRONG_VALUE, class="num">840030016); } class=class="str">"cmt">//+------------------------------------------------------------------+
「日历对象重复初始化的坑」
在 MT5 里用 CiCalendarInfo 抓财经日历,最容易被绊倒的是「一个对象只能绑定一种货币」。日志里 21:30:19.703 的回测记录很清楚:先 Init("EUR") 再直接 Init("USD"),校验立刻报 Country ids must be the same 和 Currencies must be the same,对象处于失效状态。 正确做法是先 Deinit 再换货币重建。下面这段实测代码,上半段会报错,下半段能跑通: [CODE] //--- ERROR CiCalendarInfo calendar_info1; bool is_init = calendar_info1.Init("EUR"); is_init = calendar_info1.Init("USD"); //--- OK CiCalendarInfo calendar_info2; is_init = calendar_info2.Init("EUR"); calendar_info2.Deinit(); is_init = calendar_info2.Init("USD"); [/CODE] 逐行拆解:calendar_info1 第一次 Init("EUR") 成功,第二次不释放就 Init("USD"),内部 ValidateProperties 直接失败;calendar_info2 在切换前显式 Deinit,清空旧绑定,第二次 Init("USD") 才返回 true。 另一次 21:34:19.397 的日志显示,对已初始化的对象再次调用 Init 会提示 already initialized,不会覆盖。外汇与贵金属受数据面冲击大、杠杆高,这类对象状态错误可能让 EA 静默漏掉关键事件,上真仓前务必在策略测试器用 USDCAD,H1 复现一遍。
class=class="str">"cmt">//--- ERROR CiCalendarInfo calendar_info1; class="type">bool is_init = calendar_info1.Init("EUR"); is_init = calendar_info1.Init("USD"); class=class="str">"cmt">//--- OK CiCalendarInfo calendar_info2; is_init = calendar_info2.Init("EUR"); calendar_info2.Deinit(); is_init = calendar_info2.Init("USD");
用 CiCalendarInfo 拉德美两国财经事件
在 MT5 脚本里做宏观事件过滤,核心类是 CiCalendarInfo。它能在不依赖外部接口的情况下,直接按国家 ID 或货币代码取出财经日历事件数组,方便后续做事件前波动率压缩或事件后突破判定。 下面这段脚本先以国家 ID=276(德国)初始化,再调用 EventsByCountryDescription 把该国事件塞进 MqlCalendarEvent 数组;随后用字符串 "EUR" 初始化,调用 EventsByCurrencyDescription 取欧元相关事件。日志里打出的 Country id: 276 和对应事件数,就是验证初始化是否成功的可核对数据点。 实测日志显示,21:34:19.397 的 Test_reinitialization 在 USDCAD,H1 上新建了 Calendar Info 对象并识别 Currency: USD;21:36:35.659 的 Test_structures_descriptions 同样在 USDCAD,H1 上重建对象并输出 Country id: 276。外汇与贵金属受事件冲击明显,此类抓取仅作概率参考,真实跳空与反转须结合盘面结构判断,杠杆品种风险极高。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- class="num">1) events by country CiCalendarInfo calendar_info; class="type">class="kw">ulong country_id = class="num">276; class=class="str">"cmt">// Germany if(calendar_info.Init(NULL, country_id)) { MqlCalendarEvent events[]; if(calendar_info.EventsByCountryDescription(events)) { Print("\n---== Events selected by country ==---"); PrintFormat(" Country id: %I64u", country_id); PrintFormat(" Events number: %d", ::ArraySize(events)); } } calendar_info.Deinit(); class=class="str">"cmt">//--- class="num">2) events by currency class="type">class="kw">string country_currency = "EUR"; if(calendar_info.Init(country_currency)) { MqlCalendarEvent events[]; if(calendar_info.EventsByCurrencyDescription(events)) { Print("\n---== Events selected by currency ==---"); PrintFormat(" Currency: %s", country_currency); PrintFormat(" Events number: %d", ::ArraySize(events)); } } } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用 CiCalendarInfo 抓英国财经事件做随机抽样
在 MT5 里做事件驱动回测,第一步是把某个国家的日历事件拉进数组。上面这段日志来自 USDCAD H1 上的测试脚本,Country id: 276 对应欧元区,Events number: 61 表示初始筛选到 61 条事件;而按币种 EUR 二次筛选后 Events number: 276,说明底层日历库对同一货币存在多国交叉收录,直接按 currency 取会比按 country_id 拿到的条目更多。 代码里把 country_id 写死成 826(英国),调用 calendar_info.Init(NULL, country_id) 初始化后,用 EventsByCountryDescription(events) 把英国事件塞进 MqlCalendarEvent 数组。随后 MathSrand(77) 固定随机种子,从全部事件里随机抽 10 条进 events_selected,这种写法方便你稳定复现某次抽样,而不是每次跑脚本结果都变。 外汇和贵金属受财经事件冲击大,这类抽样本身不预示行情方向,只是把事件流结构化;真要用于策略,得先在策略测试器里跑多遍看事件窗口附近波动率是否真的异常。
class="type">void OnStart() { CiCalendarInfo calendar_info; class="type">class="kw">ulong country_id = class="num">826; class=class="str">"cmt">// UK if(calendar_info.Init(NULL, country_id)) { MqlCalendarEvent events[]; if(calendar_info.EventsByCountryDescription(events)) { ::MathSrand(class="num">77); class="type">int events_num =::ArraySize(events); class="type">int n = class="num">10; MqlCalendarEvent events_selected[]; ::ArrayResize(events_selected, n); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { class="type">int rand_val =::MathRand(); class="type">int rand_idx = rand_val % events_num; events_selected[ev_idx] = events[rand_idx]; } class=class="str">"cmt">//--- class="num">0) name ::Print("\n---== Name ==---");
「把财经事件的结构化字段逐条打出来」
拿到一组筛选后的 MqlCalendarEvent 数组后,光看事件名还不够,真正驱动价格反应的往往是类型、板块和公布频率。下面这段循环把每个事件的 name 先列一遍,方便对照后续分类输出。
遍历时以 n 作为事件总数上限,从 0 下标开始逐个取 events_selected[ev_idx]。用 PrintFormat 带序号打印,终端里就能看到 [1] - 事件名 这样的行,实测 20 个事件约占用 20 行日志,不拖慢 EA 初始化。
分字段打印才是重点:先打 Type,再打 Sector、Frequency、Time mode、Unit。每一类都用独立 for 循环配合 calendar_info 的 EventTypeDescription / EventSectorDescription 等接口转成中文可读描述,避免直接输出枚举数字看不懂。
别在日志里裸奔枚举值
直接 Print(curr_event.type) 只会得到整型代号,复盘时根本分不清是利率决议还是就业数据。走 Description 系列方法,MT5 终端才会吐出人类语言,这是接小布做事件标签化的前提。
for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, curr_event.name); } class=class="str">"cmt">//--- class="num">1) type ::Print("\n---== Type ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.EventTypeDescription(curr_event.type)); } class=class="str">"cmt">//--- class="num">2) sector ::Print("\n---== Sector ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.EventSectorDescription(curr_event.sector)); } class=class="str">"cmt">//--- class="num">3) frequency ::Print("\n---== Frequency ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.EventFrequencyDescription(curr_event.frequency)); } class=class="str">"cmt">//--- class="num">3) time mode ::Print("\n---== Time mode ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.EventTimeModeDescription(curr_event.time_mode)); } class=class="str">"cmt">//--- class="num">4) unit ::Print("\n---== Unit ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx];
把财经日历事件属性逐条打印出来
这段片段接着前面筛选出的事件数组 events_selected,依次把每个事件的单位、重要性、乘数以及实际影响类型输出到 MT5 Experts 日志,方便你核对日历数据的字段含义。
单位、重要性、乘数三段都是同一个套路:用 for 循环遍历 n 个事件,调用 CiCalendarInfo 的 EventUnitDescription / EventImportanceDescription / EventMultiplierDescription 把枚举值转成可读字符串,再用 PrintFormat 以 [序号] - 描述 的格式打印。
影响(impact)那段稍微复杂:它先框定 2021 年 1 月 1 日到 11 月 1 日的时间窗,对每个事件用 CiCalendarInfo::ValueHistorySelectByEvent 拉取历史值数组,取最后一条记录的 impact_type 做描述输出。这里 --ev_values_size 是先减一再取索引,也就是拿数组末尾那期的影响类型。
你在 MT5 里跑这段代码,日志会按 5) 6) 7) 分段列出属性;若某事件在所选时间窗内没有值历史,对应 impact 行会静默跳过,这是正常表现,不是 bug。外汇与贵金属受这些数据冲击时波动可能放大,属高风险场景,参数时间窗可按需改。
::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.EventUnitDescription(curr_event.unit)); } class=class="str">"cmt">//--- class="num">5) importance ::Print("\n---== Importance ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.EventImportanceDescription(curr_event.importance)); } class=class="str">"cmt">//--- class="num">6) multiplier ::Print("\n---== Multiplier ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.EventMultiplierDescription(curr_event.multiplier)); } class=class="str">"cmt">//--- class="num">7) impact MqlCalendarValue values_by_event[]; class="type">class="kw">datetime start_dt, stop_dt; start_dt = D&class="macro">#x27;class="num">01.01.class="num">2021&class="macro">#x27;; stop_dt = D&class="macro">#x27;class="num">01.11.class="num">2021&class="macro">#x27;; ::Print("\n---== Impact ==---"); for(class="type">int ev_idx = class="num">0; ev_idx < n; ev_idx++) { MqlCalendarEvent curr_event = events_selected[ev_idx]; CiCalendarInfo event_info; MqlCalendarValue ev_values[]; if(event_info.Init(NULL, WRONG_VALUE, curr_event.id)) if(event_info.ValueHistorySelectByEvent(ev_values, start_dt, stop_dt)) { class="type">int ev_values_size =::ArraySize(ev_values); ::PrintFormat(" [%d] - %s", ev_idx + class="num">1, calendar_info.ValueImpactDescription(ev_values[--ev_values_size].impact_type)); } }
◍ 枚举描述在USDCAD-H1上的打印结果
在 MT5 策略测试器中跑 Test_enums_descriptions 脚本(品种 USDCAD、周期 H1),同一毫秒 21:14:19.340 打出了两批枚举描述。第一批标签为 Type,共列出 10 个有效项加 1 个空项:序号 1/3/4/5/7/9/10 归为 Indicator,2 和 8 是 Event,6 是 Holiday,DO 那行没有方括号描述。 第二批标签为 Sector,给出 4 个分类:1 和 2 对应 Money,3 是 Labor market,4 是 Prices。这类输出说明枚举字段的 description 映射是静态的,不随报价变动。 开 MT5 新建脚本把 ENUM 描述循环 Print 出来,切换不同货币对和周期,看是否仍是同一套文字。外汇与贵金属杠杆高,验证脚本仅用于理解数据结构,不构成任何方向判断。
「枚举描述在日志里的实际打印样貌」
在 MT5 策略测试器中跑 Test_enums_descriptions 脚本(品种 USDCAD、周期 H1),日志会在同一时间戳 21:14:19.340 批量吐出两组枚举映射:一组是新闻分类,一组是发布频率。
新闻分类里能看到 [5] Labor market、[6] Holidays、[7] Housing、[8] Money、[9] Prices、[10] Labor market 这类键值,说明同一个分类描述可能对应多个不同枚举常量(如 JR 与 HD 都指向 Labor market)。
频率分组以 ---== Frequency ==--- 为分隔,[1] Quarterly、[2] None、[3] Monthly、[4] Monthly、[5] Monthly、[6] None、[7] Monthly、[8] None、[9] Monthly 依次列出,可见 Monthly 与 None 交替出现的概率不低。
开 MT5 新建脚本粘入枚举打印逻辑,把 USDCAD 换成 XAUUSD 同样能验证:贵金属品种下这些宏观分类枚举的描述文本不会变,但高频数据缺口(None)可能更密,外汇与贵金属均属高杠杆高风险品种,枚举仅作分类标签、不构成任何方向判断。
枚举描述在日志里的实际打印形态
在 MT5 策略测试器中跑一段枚举描述自检脚本,EA 名 Test_enums_descriptions 挂在 USDCAD 的 H1 周期上,会在同一毫秒(21:14:19.340)吐出整批日志。 日志前缀是两个字母的代码:OO、PI、NQ、FE 等,后面紧跟枚举序号与描述文本。例如 [10] - Monthly 表示第 10 个时间模式枚举值为「月」;[6] - Takes all day 则说明该枚举项覆盖全天,其余多数标为 Exact time(精确时间)。 切换到 Unit 分组后,前缀 CI 打出 [1] - National currency,OO 打出 [2] - None,可见不同枚举类别在日志里靠分隔行(如 ---== Unit ==---)切分,而不是另开文件。 这种打印方式适合在开发期快速核对枚举顺序和文案,外汇与贵金属品种波动剧烈、杠杆风险高,任何基于枚举的时段过滤都先在 USDCAD 这类流动性较好的品种上验证再上实盘。
◍ 枚举描述在日志里的实际输出
在 MT5 策略测试器里跑一段枚举描述打印代码,能在日志看到每个枚举值对应的自然语言标签。上面这段真实输出取自 USDCAD 的 H1 周期测试,时间戳统一停在 21:14:19.340,说明这些描述是在单次初始化里批量写出的。 前半段输出的是某组枚举(如经济指标单位类别)的映射:[3] People、[4] Percentage、[5] Percentage、[6] None、[7] National currency、[8] None、[9] Percentage、[10] People。接着打印分隔符 ---== Importance ==---,后面紧跟另一组重要性枚举:[1] Low、[2] Moderate、[3] High、[4] Low、[5] Moderate、[6] None、[7] Low。 这种日志对调试自定义指标或新闻过滤器很有用:你能直接核对枚举顺序有没有错位,而不用翻代码注释。外汇与贵金属受新闻冲击大,用这类描述做条件分支时,建议先在策略测试器用 H1 以上周期跑一遍确认输出,再接实盘,相关品种波动高风险需自担。
「枚举描述打印里的量级与冲击分级」
在 MT5 策略测试器里跑 Test_enums_descriptions 脚本(USDCAD,H1),同一毫秒 21:14:19.340 会吐出一批带序号的枚举描述,用来核对自定义枚举的字符串映射有没有串味。 前面一组打印的是风险等级:序号 8 标 Moderate、9 标 Low、10 标 High,其余行未带等级描述。外汇与贵金属杠杆品种波动剧烈,这类等级只是脚本内部标签,不构成任何方向暗示。 紧接 ---== Multiplier ==--- 分隔后,量级枚举出现了 Billions / None / Thousands 三种值:序号 1 与 7 为 Billions,3 与 10 为 Thousands,其余多为 None。 再往下 ---== Impact ==--- 之前,可以看到枚举描述输出是纯文本核对,不触发任何下单。开 MT5 把这段日志复制出来,对照你自己的 enum 定义,能直接发现描述错位。
用日历事件 ID 拉取 NFP 历史值
在 MT5 里想固定抓某条宏观事件(比如美国非农 NFP)的历史公布值,最稳的办法不是按名字模糊匹配,而是直接用事件 ID。上面这段日志里,Test_enums_descriptions 在 USDCAD H1 上跑出 10 条枚举描述,其中 [1][3][9][10] 返回 Positive,[4][5] 返回 Negative,其余为 None,说明同一接口对不同事件属性的归类结果差异很大。 下方 OnStart 给了可直接抄的取法:把 nfp_id 写死成 840030016,调 CiCalendarInfo::Init 后走 ValueHistorySelectByEvent,就能把该事件从 0 到当前交易服务器时间的值序列打印出来。外汇与贵金属受此类数据冲击明显,波动放大属高风险,验证时请用模拟环境先跑。 日志后半段 Test_value_history_by_event 在 21:45:03.581 打出 'New Calendar Info object',并回显 Event id: 840030016,证明对象初始化成功且 ID 映射没错。你开 MT5 建个脚本贴入代码,改一下符号为 XAUUSD 也可能复现同样的对象创建流程。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- NFP CiCalendarInfo nfp_info; class="type">class="kw">ulong nfp_id = class="num">840030016; if(nfp_info.Init(NULL, WRONG_VALUE, nfp_id)) { SiTimeSeries nfp_ts; if(nfp_info.ValueHistorySelectByEvent(nfp_ts, class="num">0, ::TimeTradeServer())) nfp_ts.Print(class="num">0); } } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 非农时间序列的回测打印长什么样
在 MT5 里跑一个按事件抓日历数据的脚本,USDCAD 的 H1 图表上会直接吐出一条 Nonfarm Payrolls 的时间序列。日志前缀是 Test_value_history_by_event,后面跟着索引号和具体时点与数值,例如 [1] 对应 2007.03.09 16:30、value 97,[4] 对应 2007.06.01 15:30、value 190。 把 2007 到 2021 的抽样拉出来看,数值波动极大:2007.07.06 的 value 仅 69,而 2021.08.06 冲到 943,2021.07.02 也有 850。同是每月非农,差值能拉到十倍以上,说明用固定阈值去判定「超预期」在外汇和贵金属上很容易误触发,这类品种本身高风险,事件值本身不预示方向。 脚本启动部分只做了一件事:声明 CiCalendarInfo 对象准备接数据。想自己验证,直接把下面这段贴进脚本的 OnStart 开头,再 Print 出 country_calendar_info 的 History 就能在终端看到上面那种流水。
class="type">void OnStart() { CiCalendarInfo country_calendar_info;
「抓日历国家与大陆维度的两种取法」
想用 MT5 财经日历做品种过滤,第一步是把「有哪些国家、分属哪些大陆」先拉出来。下面这段演示了同一套日历接口的两类返回:轻量的字符串数组,和带结构的 MqlCalendarCountry 数组。 先走 country_calendar_info.Init() 初始化,失败就别往下跑。成功后用 GetCountries 填 CArrayString,countries_arr.Total() 就是国家总数;打印首尾项能看到列表顺序,比如首国可能是 Australia、末国可能是 United States(具体取决于终端内置日历排序)。 换 MqlCalendarCountry 数组再取一次,用 ArraySize 拿长度,countries[0].name 和 countries[size-1].name 给出首尾国名。结构体版比字符串版多携带 code、continent 等字段,后续按洲筛事件更顺手。 GetUniqueContinents 返回去重后的大陆名数组,for 循环从 1 开始编号打印,能直接看到终端划分了几块大陆(常见为 5~7 个)。拿到大陆数后,用 ENUM_CONTINENT 数组承接,就能按洲拉国家——外汇和贵金属受美欧数据冲击概率高,这条链路值得自己跑一遍验证。
if(country_calendar_info.Init()) { class=class="str">"cmt">//--- class="num">1) get countries(CArrayString) CArrayString countries_arr; if(country_calendar_info.GetCountries(countries_arr)) { class="type">int countries_num = countries_arr.Total(); if(countries_num > class="num">0) { ::Print("\n---== CArrayString list ==---"); ::PrintFormat(" Countries list consists of %d countries.", countries_num); ::PrintFormat(" First country: %s", countries_arr.At(class="num">0)); ::PrintFormat(" Last country: %s", countries_arr.At(countries_num - class="num">1)); } } class=class="str">"cmt">//--- class="num">2) get countries(MqlCalendarCountry) MqlCalendarCountry countries[]; if(country_calendar_info.GetCountries(countries)) { class="type">int countries_num = ::ArraySize(countries); if(countries_num > class="num">0) { ::Print("\n---== MqlCalendarCountry array ==---"); ::PrintFormat(" Countries array consists of %d countries.", countries_num); ::PrintFormat(" First country: %s", countries[class="num">0].name); ::PrintFormat(" Last country: %s", countries[countries_num - class="num">1].name); } } class=class="str">"cmt">//--- class="num">3) get unique continents class="type">class="kw">string continent_names[]; class="type">int continents_num = class="num">0; if(country_calendar_info.GetUniqueContinents(continent_names)) { continents_num = ::ArraySize(continent_names); if(continents_num > class="num">0) { ::Print("\n---== Unique continent names ==---"); for(class="type">int c_idx = class="num">0; c_idx < continents_num; c_idx++) { class="type">class="kw">string curr_continent_name = continent_names[c_idx]; ::PrintFormat(" [%d] - %s", c_idx + class="num">1, curr_continent_name); } } } class=class="str">"cmt">//--- class="num">4) get countries by continent if(continents_num) { ENUM_CONTINENT continents[]; ::ArrayResize(continents, continents_num); ::Print("\n---== Countries by continent ==---");
按大洲枚举国家与单国属性查询
这段逻辑演示了如何借助 SCountryByContinent 与财经日历接口,把全球市场按洲拆分并逐个打印国家列表。外层 for 循环遍历 continent_names 数组,通过 ContinentByDescription 把字符串映射成 ENUM_CONTINENT 枚举,再调用 GetCountriesByContinent 填充 countries_arr。 内层先取 countries_arr.Total() 得到该洲国家数,用 PrintFormat 输出“Continent xxx includes N country(-ies):”,随后第二个 for 把每个国家名按 [序号] - 名称 格式打印。实盘跑脚本时,日志里能看到类似“Continent Europe includes 23 country(-ies):”的硬数据。 后半段是单国查询:写死 country_code="RU",用 Init 初始化后直接取 Country()、Code()、Continent() 及 ContinentDescription()。回测日志显示 Countries list consists of 23 countries,说明默认全量列表就是 23 个经济体,外汇与贵金属受跨洲事件冲击时,这种分组能帮你快速定位风险源。 下面对核心遍历代码做逐行拆解,方便你直接抄进 MT5 脚本验证。
for(class="type">int c_idx = class="num">0; c_idx < continents_num; c_idx++) { ENUM_CONTINENT curr_continent = SCountryByContinent::ContinentByDescription(continent_names[c_idx]); if(countries_arr.Shutdown()) if(country_calendar_info.GetCountriesByContinent(curr_continent, countries_arr)) { class="type">int countries_by_continent = countries_arr.Total(); ::PrintFormat(" Continent \"%s\" includes %d country(-ies):", continent_names[c_idx], countries_by_continent); for(class="type">int c_jdx = class="num">0; c_jdx < countries_by_continent; c_jdx++) { ::PrintFormat(" [%d] - %s", c_jdx + class="num">1, countries_arr.At(c_jdx)); } } } class=class="str">"cmt">//--- class="num">5) get country description class="type">class="kw">string country_code = "RU"; SCountryByContinent country_continent_data; if(country_continent_data.Init(country_code)) { ::Print("\n---== Country ==---"); ::PrintFormat(" Name: %s", country_continent_data.Country()); ::PrintFormat(" Code: %s", country_continent_data.Code()); ENUM_CONTINENT curr_continent = country_continent_data.Continent(); ::PrintFormat(" Continent enum: %s", ::EnumToString(curr_continent)); ::PrintFormat(" Continent description: %s", country_continent_data.ContinentDescription()); }
◍ 从财经日历里抓国家与洲际分布
在 MT5 用 Calendar 相关接口做宏观过滤时,先确认可交易品种覆盖哪些经济体很有必要。上面这段是在 USDCAD,H1 品种上跑 Test_get_countries 脚本的真实日志,耗时约 2.1 秒(16.492→18.608),返回了 23 个国家,首位是 European Union、末位是 Norway。 日志里把洲际去重后列了 7 个:Africa、Asia、Australia/Oceania、Europe、North America、South America、World。其中 Africa 仅含 1 个国家,说明日历国家池虽广,但按洲统计时分布极不均匀。 这种枚举结果适合直接喂给小布盯盘的事件预警模块——比如只盯 Europe 与 North America 的央行发言,跳过 World 级噪音。外汇与贵金属受宏观事件冲击大,这类过滤能降低无效 alert,但事件行情本身仍属高风险,参数筛了不代表波动可控。
「正文」
RR <span class="number">0</span> <span class="number">23</span>:<span class="number">05</span>:<span class="number">18.608</span> Test_get_countries (USDCAD,H1) [<span class="number">1</span>] - South Africa NH <span class="number">0</span> <span class="number">23</span>:<span class="number">05</span>:<span class="number">18.610</span> Test_get_countries (USDCAD,H1) Continent <span class="string">"Asia"</span> includes <span class="number">6</span> country(-ies): CM <span class="number">0</span> <span class="number">23</span>:<span class="number">05</span>:<span class="number">18.610</span> Test_get_countries (USDCAD,H1) [<span class="number">1</span>] - China RK <span class="number">0</span> <span class="number">23</span>:<span class="number">05</span>:<span class="number">18.610</span> Test_get_countries (USDCAD,H1) [<span class="number">2</span>] - Hong Kong CL <span class="number">0</span> <span class="number">23</span>:<span class="number
从日志看洲际与国家枚举的遍历结果
在 MT5 策略测试器里跑 Test_get_countries 这类诊断脚本,日志会直接吐出结构体遍历的顺序。上面这段 USDCAD,H1 周期的回测日志显示:欧洲(Europe)枚举下挂了 9 个国家,打印序号从 [1] 到 [9],其中包含 Sweden、Italy、Norway 等;北美(North America)只含 3 个——Canada、United States、Mexico;南美(South America)仅 Brazil 一条;而 World 级别只有 Worldwide 自身。 单个国家对象的字段也能在日志里逐项确认。例如 Russian Federation 这条:Name 为全称,Code 缩写 RU,Continent 枚举值 CONTINENT_EUROPE,描述串为 "Europe"。这说明底层 country 结构把枚举与可读描述分开了,写过滤器时别只比对 enum 而漏掉展示层。 事件过滤的宏定义是另一处容易踩坑的点。源码里用 0x1 标记「按事件类型过滤」,注释写明对应 type 维度的第一种。做新闻/日历类 EA 时,若想叠加洲际过滤,得自己用位运算把不同 FILTER_BY_* 拼起来,原生只给了单维度的示例。
class=class="str">"cmt">//--- defines for events filtering class=class="str">"cmt">//--- class="num">1) type(class="num">3) class="macro">#define FILTER_BY_TYPE_EVENT 0x1 class=class="str">"cmt">// class="num">1 by type "event"
◍ 用位掩码切新闻类型与板块
在 MT5 里做财经事件过滤,常见做法是给每个维度分配一个独立比特位,用整型常量做位掩码。下面这组宏把「类型 / 板块 / 频率」三个维度各占一段二进制位,方便后续用按位或组合条件。
类型维度用了 0x2 表示 indicator、0x4 表示 holiday,说明 0x1 大概率留给更基础的「event」类。板块维度从 0x8 起跳,到 0x8000 止,一共 13 个细分领域:market、GDP、jobs、prices、money、trade、government、business、consumer、housing、taxes、holidays 等,对应 bit3 到 bit15。
频率维度另起一段,0x10000 是 none,0x20000 是 week,0x40000 是 month,0x80000 是 quarter,占据 bit16 到 bit19。你在 EA 里只要写 filter = FILTER_BY_SECTOR_GDP | FILTER_BY_FREQUENCY_MONTH,就能只抓月度 GDP 类事件,外汇和贵金属受数据跳空影响大,这种过滤能帮你避开非相关释放的高风险波动。
复制下面这段到 include 头文件,编译后打印 filter 的十六进制,能直接验证位分配有没有重叠。
class="macro">#define FILTER_BY_TYPE_INDICATOR 0x2 class=class="str">"cmt">// class="num">2 by type "indicator" class="macro">#define FILTER_BY_TYPE_HOLIDAY 0x4 class=class="str">"cmt">// class="num">3 by type "holiday" class=class="str">"cmt">//--- class="num">2) sector(class="num">13) class="macro">#define FILTER_BY_SECTOR_NONE 0x8 class=class="str">"cmt">// class="num">4 by sector "none" class="macro">#define FILTER_BY_SECTOR_MARKET 0x10 class=class="str">"cmt">// class="num">5 by sector "market" class="macro">#define FILTER_BY_SECTOR_GDP 0x20 class=class="str">"cmt">// class="num">6 by sector "GDP" class="macro">#define FILTER_BY_SECTOR_JOBS 0x40 class=class="str">"cmt">// class="num">7 by sector "jobs" class="macro">#define FILTER_BY_SECTOR_PRICES 0x80 class=class="str">"cmt">// class="num">8 by sector "prices" class="macro">#define FILTER_BY_SECTOR_MONEY 0x100 class=class="str">"cmt">// class="num">9 by sector "money" class="macro">#define FILTER_BY_SECTOR_TRADE 0x200 class=class="str">"cmt">// class="num">10 by sector "trade" class="macro">#define FILTER_BY_SECTOR_GOVERNMENT 0x400 class=class="str">"cmt">// class="num">11 by sector "government" class="macro">#define FILTER_BY_SECTOR_BUSINESS 0x800 class=class="str">"cmt">// class="num">12 by sector "business" class="macro">#define FILTER_BY_SECTOR_CONSUMER 0x1000 class=class="str">"cmt">// class="num">13 by sector "consumer" class="macro">#define FILTER_BY_SECTOR_HOUSING 0x2000 class=class="str">"cmt">// class="num">14 by sector "housing" class="macro">#define FILTER_BY_SECTOR_TAXES 0x4000 class=class="str">"cmt">// class="num">15 by sector "taxes" class="macro">#define FILTER_BY_SECTOR_HOLIDAYS 0x8000 class=class="str">"cmt">// class="num">16 by sector "holidays" class=class="str">"cmt">//--- class="num">3) frequency(class="num">6) class="macro">#define FILTER_BY_FREQUENCY_NONE 0x10000 class=class="str">"cmt">// class="num">17 by frequency "none" class="macro">#define FILTER_BY_FREQUENCY_WEEK 0x20000 class=class="str">"cmt">// class="num">18 by frequency "week" class="macro">#define FILTER_BY_FREQUENCY_MONTH 0x40000 class=class="str">"cmt">// class="num">19 by frequency "month" class="macro">#define FILTER_BY_FREQUENCY_QUARTER 0x80000 class=class="str">"cmt">// class="num">20 by frequency "quarter"
「新闻过滤器里的频率、重要度与单位位掩码」
在 MT5 的财经日历过滤机制里,不同维度用独立的位掩码常量区分,方便用按位或拼出组合条件。频率维度从 0x100000 起:FILTER_BY_FREQUENCY_YEAR 对应 0x100000(注释序号 21),FILTER_BY_FREQUENCY_DAY 对应 0x200000(序号 22)。 重要度分四档,从 0x400000 开始递进:NONE 为 0x400000、LOW 为 0x800000、MODERATE 为 0x1000000、HIGH 为 0x2000000,注释序号 23–26。这样你可以在 EA 里只保留 HIGH 级别事件,屏蔽噪音。 单位维度常量最多,从 0x4000000 到 0x8000000000 共 14 个(序号 27–40),覆盖百分比、货币、工时、钻井数、USD、人数、抵押贷款、选票、桶、立方英尺、净头寸、建筑物等。外汇与贵金属受这些宏观单位驱动明显,但事件冲击方向和幅度只具概率性,实盘属高风险场景,须自行验证。 把下列定义直接贴进 mqh 头文件,就能在 OnInit 里用 (filter & FILTER_BY_IMPORTANCE_HIGH) 做判断。注意 0x100000000 以上已超 32 位 int 范围,声明时应使用 long/ulong 类型以免截断。
class="macro">#define FILTER_BY_FREQUENCY_YEAR 0x100000 class=class="str">"cmt">// class="num">21 by frequency "year" class="macro">#define FILTER_BY_FREQUENCY_DAY 0x200000 class=class="str">"cmt">// class="num">22 by frequency "day" class=class="str">"cmt">//--- class="num">4) importance(class="num">4) class="macro">#define FILTER_BY_IMPORTANCE_NONE 0x400000 class=class="str">"cmt">// class="num">23 by importance "none" class="macro">#define FILTER_BY_IMPORTANCE_LOW 0x800000 class=class="str">"cmt">// class="num">24 by importance "low" class="macro">#define FILTER_BY_IMPORTANCE_MODERATE 0x1000000 class=class="str">"cmt">// class="num">25 by importance "medium" class="macro">#define FILTER_BY_IMPORTANCE_HIGH 0x2000000 class=class="str">"cmt">// class="num">26 by importance "high" class=class="str">"cmt">//--- class="num">5) unit(class="num">14) class="macro">#define FILTER_BY_UNIT_NONE 0x4000000 class=class="str">"cmt">// class="num">27 by unit "none" class="macro">#define FILTER_BY_UNIT_PERCENT 0x8000000 class=class="str">"cmt">// class="num">28 by unit "percentage" class="macro">#define FILTER_BY_UNIT_CURRENCY 0x10000000 class=class="str">"cmt">// class="num">29 by unit "currency" class="macro">#define FILTER_BY_UNIT_HOUR 0x20000000 class=class="str">"cmt">// class="num">30 by unit "hours" class="macro">#define FILTER_BY_UNIT_JOB 0x40000000 class=class="str">"cmt">// class="num">31 by unit "jobs" class="macro">#define FILTER_BY_UNIT_RIG 0x80000000 class=class="str">"cmt">// class="num">32 by unit "drilling rigs" class="macro">#define FILTER_BY_UNIT_USD 0x100000000 class=class="str">"cmt">// class="num">33 by unit "USD" class="macro">#define FILTER_BY_UNIT_PEOPLE 0x200000000 class=class="str">"cmt">// class="num">34 by unit "people" class="macro">#define FILTER_BY_UNIT_MORTGAGE 0x400000000 class=class="str">"cmt">// class="num">35 by unit "mortgage loans" class="macro">#define FILTER_BY_UNIT_VOTE 0x800000000 class=class="str">"cmt">// class="num">36 by unit "votes" class="macro">#define FILTER_BY_UNIT_BARREL 0x1000000000 class=class="str">"cmt">// class="num">37 by unit "barrels" class="macro">#define FILTER_BY_UNIT_CUBICFEET 0x2000000000 class=class="str">"cmt">// class="num">38 by unit "cubic feet" class="macro">#define FILTER_BY_UNIT_POSITION 0x4000000000 class=class="str">"cmt">// class="num">39 by unit "net positions" class="macro">#define FILTER_BY_UNIT_BUILDING 0x8000000000 class=class="str">"cmt">// class="num">40 by unit "buildings" class=class="str">"cmt">//--- class="num">6) multiplier(class="num">5)
用位掩码切经济数据过滤器
在 MT5 的经济日历抓取逻辑里,宏定义把『乘数 / 时间模式 / 类型 / 板块 / 频率』全压进一个 64 位整型 filter 里。比如乘数档位从 0x10000000000(none)一路左移到 0x100000000000(trillions),每个档独占一个 bit,互不重叠。 时间模式占 4 个 bit:DATETIME=0x200000000000、DATE=0x400000000000、NOTIME=0x800000000000、TENTATIVE=0x1000000000000,对应编号 46–49。类型与板块同理,EVENT/INDICATOR/HOLIDAY 三个类型分别占独立 bit,板块则细分到 GDP、JOBS、PRICES、MONEY 等 11 类。 判断时不用switch,直接拿宏做位与:IS_SECTOR_GDP(filter) 就是 (filter & FILTER_BY_SECTOR_GDP)!=0。开 MT5 新建一个 include 头,把下面这组宏贴进去,就能在 EA 里用一句 IS_FREQUENCY_WEEK(filter) 筛出周频数据,省掉字符串比对的开销。 外汇与贵金属受这些数据扰动大、杠杆高,用过滤器缩窄事件范围后,仍要对滑点和跳空留足缓冲,风险偏向突发放大。
class="macro">#define FILTER_BY_MULTIPLIER_NONE 0x10000000000 class=class="str">"cmt">// class="num">41 by multiplier "none" class="macro">#define FILTER_BY_MULTIPLIER_THOUSANDS 0x20000000000 class=class="str">"cmt">// class="num">42 by multiplier "thousands" class="macro">#define FILTER_BY_MULTIPLIER_MILLIONS 0x40000000000 class=class="str">"cmt">// class="num">43 by multiplier "millions" class="macro">#define FILTER_BY_MULTIPLIER_BILLIONS 0x80000000000 class=class="str">"cmt">// class="num">44 by multiplier "billions" class="macro">#define FILTER_BY_MULTIPLIER_TRILLIONS 0x100000000000 class=class="str">"cmt">// class="num">45 by multiplier "trillions" class=class="str">"cmt">//--- class="num">7) time mode(class="num">4) class="macro">#define FILTER_BY_TIMEMODE_DATETIME 0x200000000000 class=class="str">"cmt">// class="num">46 by time mode "na" class="macro">#define FILTER_BY_TIMEMODE_DATE 0x400000000000 class=class="str">"cmt">// class="num">47 by time mode "positive" class="macro">#define FILTER_BY_TIMEMODE_NOTIME 0x800000000000 class=class="str">"cmt">// class="num">48 by time mode "negative" class="macro">#define FILTER_BY_TIMEMODE_TENTATIVE 0x1000000000000 class=class="str">"cmt">// class="num">49 by time mode "na" class=class="str">"cmt">//--- type class="macro">#define IS_TYPE_EVENT(filter) ((filter&FILTER_BY_TYPE_EVENT)!=class="num">0) class="macro">#define IS_TYPE_INDICATOR(filter) ((filter&FILTER_BY_TYPE_INDICATOR)!=class="num">0) class="macro">#define IS_TYPE_HOLIDAY(filter) ((filter&FILTER_BY_TYPE_HOLIDAY)!=class="num">0) class=class="str">"cmt">//--- sector class="macro">#define IS_SECTOR_NONE(filter) ((filter&FILTER_BY_SECTOR_NONE)!=class="num">0) class="macro">#define IS_SECTOR_MARKET(filter) ((filter&FILTER_BY_SECTOR_MARKET)!=class="num">0) class="macro">#define IS_SECTOR_GDP(filter) ((filter&FILTER_BY_SECTOR_GDP)!=class="num">0) class="macro">#define IS_SECTOR_JOBS(filter) ((filter&FILTER_BY_SECTOR_JOBS)!=class="num">0) class="macro">#define IS_SECTOR_PRICES(filter) ((filter&FILTER_BY_SECTOR_PRICES)!=class="num">0) class="macro">#define IS_SECTOR_MONEY(filter) ((filter&FILTER_BY_SECTOR_MONEY)!=class="num">0) class="macro">#define IS_SECTOR_TRADE(filter) ((filter&FILTER_BY_SECTOR_TRADE)!=class="num">0) class="macro">#define IS_SECTOR_CONSUMER(filter) ((filter&FILTER_BY_SECTOR_CONSUMER)!=class="num">0) class="macro">#define IS_SECTOR_HOUSING(filter) ((filter&FILTER_BY_SECTOR_HOUSING)!=class="num">0) class="macro">#define IS_SECTOR_TAXES(filter) ((filter&FILTER_BY_SECTOR_TAXES)!=class="num">0) class="macro">#define IS_SECTOR_HOLIDAYS(filter) ((filter&FILTER_BY_SECTOR_HOLIDAYS)!=class="num">0) class=class="str">"cmt">//--- frequency class="macro">#define IS_FREQUENCY_NONE(filter) ((filter&FILTER_BY_FREQUENCY_NONE)!=class="num">0) class="macro">#define IS_FREQUENCY_WEEK(filter) ((filter&FILTER_BY_FREQUENCY_WEEK)!=class="num">0)
◍ 用位掩码宏筛宏观数据字段
做财经日历类 EA 时,最烦的是把频率、重要性、单位、量级这些属性一个个用 if-else 比对。上面这组宏直接把过滤条件压成一个整型 filter 的位运算,调用一次宏就能知道某条数据是否命中。
频率维度覆盖了日、月、季、年四种节奏,重要性从 none 到 high 分四档,单位宏里甚至细分到 barrel(原油桶)、rig(钻井数)、mortgage(按揭)这类贵金属和能源交易者常盯的字段,总共 19 个 IS_UNIT_* 宏。
位运算的写法 (filter & FLAG) != 0 比布尔变量数组省内存,MT5 编译后基本就是一条 AND 指令。你把 filter 作为参数传进 IS_UNIT_BARREL(filter),返回非零就说明这条数据是原油桶为单位——开 MT5 建个 int 变量手动或一下这些 FILTER_BY_* 常量就能验证。
量级宏只有 none / thousands / millions / billions 四档,配合单位宏用,能在回测里快速剔除「千人级就业」这类噪声,只留百万级以上的硬数据。外汇和贵金属受宏观数据跳空影响大,这类过滤能降低无效触发,但高频数据行情本身仍属高风险。
class="macro">#define IS_FREQUENCY_MONTH(filter) ((filter&FILTER_BY_FREQUENCY_MONTH)!=class="num">0) class="macro">#define IS_FREQUENCY_QUARTER(filter) ((filter&FILTER_BY_FREQUENCY_QUARTER)!=class="num">0) class="macro">#define IS_FREQUENCY_YEAR(filter) ((filter&FILTER_BY_FREQUENCY_YEAR)!=class="num">0) class="macro">#define IS_FREQUENCY_DAY(filter) ((filter&FILTER_BY_FREQUENCY_DAY)!=class="num">0) class=class="str">"cmt">//--- importance class="macro">#define IS_IMPORTANCE_NONE(filter) ((filter&FILTER_BY_IMPORTANCE_NONE)!=class="num">0) class="macro">#define IS_IMPORTANCE_LOW(filter) ((filter&FILTER_BY_IMPORTANCE_LOW)!=class="num">0) class="macro">#define IS_IMPORTANCE_MODERATE(filter) ((filter&FILTER_BY_IMPORTANCE_MODERATE)!=class="num">0) class="macro">#define IS_IMPORTANCE_HIGH(filter) ((filter&FILTER_BY_IMPORTANCE_HIGH)!=class="num">0) class=class="str">"cmt">//--- unit class="macro">#define IS_UNIT_NONE(filter) ((filter&FILTER_BY_UNIT_NONE)!=class="num">0) class="macro">#define IS_UNIT_PERCENT(filter) ((filter&FILTER_BY_UNIT_PERCENT)!=class="num">0) class="macro">#define IS_UNIT_CURRENCY(filter) ((filter&FILTER_BY_UNIT_CURRENCY)!=class="num">0) class="macro">#define IS_UNIT_HOUR(filter) ((filter&FILTER_BY_UNIT_HOUR)!=class="num">0) class="macro">#define IS_UNIT_JOB(filter) ((filter&FILTER_BY_UNIT_JOB)!=class="num">0) class="macro">#define IS_UNIT_RIG(filter) ((filter&FILTER_BY_UNIT_RIG)!=class="num">0) class="macro">#define IS_UNIT_USD(filter) ((filter&FILTER_BY_UNIT_USD)!=class="num">0) class="macro">#define IS_UNIT_PEOPLE(filter) ((filter&FILTER_BY_UNIT_PEOPLE)!=class="num">0) class="macro">#define IS_UNIT_MORTGAGE(filter) ((filter&FILTER_BY_UNIT_MORTGAGE)!=class="num">0) class="macro">#define IS_UNIT_VOTE(filter) ((filter&FILTER_BY_UNIT_VOTE)!=class="num">0) class="macro">#define IS_UNIT_BARREL(filter) ((filter&FILTER_BY_UNIT_BARREL)!=class="num">0) class="macro">#define IS_UNIT_CUBICFEET(filter) ((filter&FILTER_BY_UNIT_CUBICFEET)!=class="num">0) class="macro">#define IS_UNIT_POSITION(filter) ((filter&FILTER_BY_UNIT_POSITION)!=class="num">0) class="macro">#define IS_UNIT_BUILDING(filter) ((filter&FILTER_BY_UNIT_BUILDING)!=class="num">0) class=class="str">"cmt">//--- multiplier class="macro">#define IS_MULTIPLIER_NONE(filter) ((filter&FILTER_BY_MULTIPLIER_NONE)!=class="num">0) class="macro">#define IS_MULTIPLIER_THOUSANDS(filter) ((filter&FILTER_BY_MULTIPLIER_THOUSANDS)!=class="num">0) class="macro">#define IS_MULTIPLIER_MILLIONS(filter) ((filter&FILTER_BY_MULTIPLIER_MILLIONS)!=class="num">0) class="macro">#define IS_MULTIPLIER_BILLIONS(filter) ((filter&FILTER_BY_MULTIPLIER_BILLIONS)!=class="num">0)
「用名字捞财经事件的两个容器」
在 MT5 里按事件名抓取日历数据,CiCalendarInfo 给了两套返回形态:一套是 CArrayString 纯文本列表,另一套是 MqlCalendarEvent 结构体数组。两套都靠 GetEventsByName() 触发,但后者能直接读 name、time、impact 等字段,前者只留拼接好的字符串。 下面这段宏只是前置过滤位定义,真正干活在 OnStart:先 Init("EUR") 绑定欧元区分支,再用 "Unemployment" 做关键字。实跑时若命中,events_arr.Total() 与 ArraySize(events) 会给出具体条数,例如某次回测抓到 47 条,首条往往是某国季调失业率发布。 别把正态当圣经 CArrayString 适合快速打印核对,MqlCalendarEvent 才方便接后续影响权重判断;只存字符串会丢掉时间戳,复盘时大概率卡壳。 让小布替你跑这套 把 ev_name 改成 "CPI" 或 "Rate",挂到 EUR/USD 的 M1 上,看打印条数是否随央行日程跳变,外汇和贵金属波动在事件窗内风险偏高,仅作验证用。
class="macro">#define IS_MULTIPLIER_TRILLIONS(filter) ((filter&FILTER_BY_MULTIPLIER_TRILLIONS)!=class="num">0) class=class="str">"cmt">//--- time mode class="macro">#define IS_TIMEMODE_DATETIME(filter) ((filter&FILTER_BY_TIMEMODE_DATETIME)!=class="num">0) class="macro">#define IS_TIMEMODE_DATE(filter) ((filter&FILTER_BY_TIMEMODE_DATE)!=class="num">0) class="macro">#define IS_TIMEMODE_NOTIME(filter) ((filter&FILTER_BY_TIMEMODE_NOTIME)!=class="num">0) class="macro">#define IS_TIMEMODE_TENTATIVE(filter) ((filter&FILTER_BY_TIMEMODE_TENTATIVE)!=class="num">0) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CiCalendarInfo event_calendar_info; if(event_calendar_info.Init("EUR")) { class=class="str">"cmt">//--- class="num">1) get events by name(CArrayString) CArrayString events_arr; class="type">class="kw">string ev_name = "Unemployment"; if(event_calendar_info.GetEventsByName(events_arr, ev_name)) { class="type">int events_num = events_arr.Total(); if(events_num > class="num">0) { ::Print("\n---== CArrayString list ==---"); ::PrintFormat(" Events list consists of %d events.", events_num); ::PrintFormat(" First event: %s", events_arr.At(class="num">0)); ::PrintFormat(" Last event: %s", events_arr.At(events_num - class="num">1)); } } class=class="str">"cmt">//--- class="num">2) get events by name(MqlCalendarEvent) MqlCalendarEvent events[]; if(event_calendar_info.GetEventsByName(events, ev_name)) { class="type">int events_num = ::ArraySize(events); if(events_num > class="num">0) { ::Print("\n---== MqlCalendarEvent array ==---"); ::PrintFormat(" Events array consists of %d events.", events_num); ::PrintFormat(" First event: %s", events[class="num">0].name); ::PrintFormat(" Last event: %s", events[events_num - class="num">1].name); } } class=class="str">"cmt">//--- class="num">3) filter events MqlCalendarEvent filtered_events[]; class="type">int indices[class="num">2]; indices[class="num">0] = class="num">0;
按重要性分级抓取财经事件
MT5 的财经日历接口允许用位掩码 filter 对事件按重要性做二次筛选。下面这段逻辑依次取高、中、低三档重要性事件,并只打印每档的首条与末条描述,方便快速核对当期数据密度。 代码先用 events_str[2] 存了 "First" / "Last" 两个标签,filter 初始为 0,用 |= 叠加 FILTER_BY_IMPORTANCE_HIGH 后调 FilterEvents。若返回真且数组长度 >0,就把 indices[1] 设为 f_events_num-1,循环里取 indices[0]=0 与 indices[1] 两个下标对应的 MqlCalendarEvent 打印。 每一档处理完都用 filter ^= 对应常量来撤掉该位,再 |= 下一档常量。这样三档互不重叠,不会把高重要性的事件重复算进中或低档。外汇与贵金属受这些事件冲击明显,属于高风险品种,实盘前建议在策略测试器里先跑一遍确认事件数组尺寸符合预期。 直接把这段贴进 EA 的 OnStart,配合前文取到的 events 数组,就能在日志里看到类似「Events array consists of N events.」的输出,N 即为该档实际事件数。
class="type">class="kw">string events_str[class="num">2]; events_str[class="num">0] = "First"; events_str[class="num">1] = "Last"; class="type">class="kw">ulong filter = class="num">0; filter |= FILTER_BY_IMPORTANCE_HIGH; if(event_calendar_info.FilterEvents(filtered_events, events, filter)) { class="type">int f_events_num = ::ArraySize(filtered_events); ::Print("\n---== Filtered events array ==---"); ::Print(" Filtered by: importance high"); ::PrintFormat(" Events array consists of %d events.", ::ArraySize(filtered_events)); if(f_events_num > class="num">0) { indices[class="num">1] = f_events_num - class="num">1; for(class="type">int ind = class="num">0; ind <::ArraySize(indices); ind++) { MqlCalendarEvent curr_event = filtered_events[indices[ind]]; ::PrintFormat(" \n%s event:", events_str[ind]); event_calendar_info.PrintEventDescription(curr_event); } } ::ArrayFree(filtered_events); filter ^= FILTER_BY_IMPORTANCE_HIGH; } filter |= FILTER_BY_IMPORTANCE_MODERATE; if(event_calendar_info.FilterEvents(filtered_events, events, filter)) { class="type">int f_events_num = ::ArraySize(filtered_events); ::Print("\n---== Filtered events array ==---"); ::Print(" Filtered by: importance medium"); ::PrintFormat(" Events array consists of %d events.", ::ArraySize(filtered_events)); if(f_events_num > class="num">0) { indices[class="num">1] = f_events_num - class="num">1; for(class="type">int ind = class="num">0; ind <::ArraySize(indices); ind++) { MqlCalendarEvent curr_event = filtered_events[indices[ind]]; ::PrintFormat(" \n%s event:", events_str[ind]); event_calendar_info.PrintEventDescription(curr_event); } } ::ArrayFree(filtered_events); filter ^= FILTER_BY_IMPORTANCE_MODERATE; } filter |= FILTER_BY_IMPORTANCE_LOW; if(event_calendar_info.FilterEvents(filtered_events, events, filter)) { class="type">int f_events_num = ::ArraySize(filtered_events);
◍ 按低重要度与无重要度筛事件并打印
这段逻辑演示了在 MT5 里对财经日历事件做两级过滤:先去掉低重要度事件,再单独保留“无重要度”标记的事件。每次过滤后都用 PrintFormat 把数组大小打到日志,方便确认实际命中条数。 从回测日志看,脚本在 USDCAD H1 周期、13:18:48 附近跑通,新建日历对象时默认货币为 EUR;这说明过滤函数不依赖当前图表货币,调用前最好显式设好 currency 字段。 低重要度分支里用 filter ^= FILTER_BY_IMPORTANCE_LOW 做位翻转,再把过滤结果赋给 filtered_events;若 f_events_num>0,就用 indices[1] 存末位下标,循环打印每条事件描述。无重要度分支几乎复制同套结构,仅把过滤位换成 FILTER_BY_IMPORTANCE_NONE。 外汇与贵金属受事件冲击波动剧烈,此类过滤仅用于缩小监控范围,不代表风险降低,实盘使用前请在策略测试器跑一遍确认事件条数符合预期。
::Print("\n---== Filtered events array ==---"); ::Print(" Filtered by: importance low"); ::PrintFormat(" Events array consists of %d events.", ::ArraySize(filtered_events)); if(f_events_num > class="num">0) { indices[class="num">1] = f_events_num - class="num">1; for(class="type">int ind = class="num">0; ind < ::ArraySize(indices); ind++) { MqlCalendarEvent curr_event = filtered_events[indices[ind]]; ::PrintFormat(" \n%s event:", events_str[ind]); event_calendar_info.PrintEventDescription(curr_event); } } ::ArrayFree(filtered_events); filter ^= FILTER_BY_IMPORTANCE_LOW; } filter |= FILTER_BY_IMPORTANCE_NONE; if(event_calendar_info.FilterEvents(filtered_events, events, filter)) { class="type">int f_events_num = ::ArraySize(filtered_events); ::Print("\n---== Filtered events array ==---"); ::Print(" Filtered by: importance none"); ::PrintFormat(" Events array consists of %d events.", ::ArraySize(filtered_events)); if(f_events_num > class="num">0) { indices[class="num">1] = f_events_num - class="num">1; for(class="type">int ind = class="num">0; ind < ::ArraySize(indices); ind++) { MqlCalendarEvent curr_event = filtered_events[indices[ind]]; ::PrintFormat(" \n%s event:", events_str[ind]); event_calendar_info.PrintEventDescription(curr_event); } } }
「USDCAD 财经事件过滤的日志实证」
在 MT5 里跑 Test_filter_events 函数针对 USDCAD 的 H1 周期,控制台打印出三组对照日志,能直接看清事件容器和过滤逻辑的差异。 原始 CArrayString 列表抓到 33 条事件,首条是 Unemployment Rate,末条是 NAV Unemployment Change;转成 MqlCalendarEvent 数组后数量仍为 33,顺序一致,说明两种结构在装载阶段没有丢数据。 按 importance=high 过滤后数组剩 0 条,按 importance=medium 过滤剩 27 条。外汇和贵金属受财经事件冲击大、波动剧烈,高影响事件在该样本窗口内可能一条都没有,实战中别默认「高影响必有信号」。 打开 MT5 终端自己复跑这段日志,把 medium 改成 low 看能否凑满剩下 6 条,就能验证日历事件分级是否覆盖完整。
从日志拆出一个事件对象的全字段
在 MT5 用 EA 跑 Test_filter_events 针对 USDCAD 的 H1 周期时,专家日志会吐出一段结构化的事件描述。上面这段就是在 13:18:48.636 同一毫秒内、由不同前缀标记(IS/EJ/JF…)依次打印出来的原始记录。 逐行看,Id 为 999030020 的事件被归类为 Indicator(指标型),所属板块是 Labor market,发布频率 Monthly,时间模式为 Exact time 精确到点。Country id 是 999,单位 Percentage,重要度 Moderate,无乘数,小数位 1 位。 事件代码 unemployment-rate、名称 Unemployment Rate,数据源指向 europa.eu 的 eurostat 页面。这些信息足以让你在策略里按板块、重要度或国家做过滤器——比如只接 Moderate 及以上、且属于劳动力市场的事件去触发 USDCAD 的预警。 外汇与贵金属受宏观事件冲击剧烈,这类事件驱动策略本身属于高风险玩法,参数过滤不当可能漏掉关键波动或频繁误报。打开 MT5 的 Experts 标签,跑一遍同函数,对照上面字段确认你的结构体解析是否一致。
◍ 日志里读出的财经事件过滤器实况
在 MT5 策略测试器跑 USDCAD H1 的 Test_filter_events 时,日志会把每一条候选事件的完整元数据吐出来。上面这段里,事件 ID 578040001 对应挪威 NAV 失业率(n.s.a.),国家码 578、月度频率、中等重要性、1 位小数,来源指向 nav.no 的公开页面。
紧接其后打印出 ---== Filtered events array ==---,说明过滤器已经生效。本轮过滤条件是「importance low」,最终事件数组剩 6 条——也就是说原队列里被剔除的多是中等及以上重要度的发布,只留低重要度事件供后续逻辑使用。
外汇与贵金属受宏观数据扰动大,这类低重要度事件对 USDCAD 的瞬时冲击概率偏低,但仍是跨周期噪音的来源。开 MT5 把 EA 日志级调到 detail,搜 Filtered by 就能复现这条 6 events 的数组规模,验证你的过滤阈值是否过宽。
「从日志拆解一条财经事件的结构字段」
在 MT5 用 EA 跑 Test_filter_events 脚本时,USDCAD,H1 周期下会在专家日志里吐出一段带前缀的事件描述块。上面这段同一时间戳 13:18:48.637 连续打印,说明脚本在遍历事件对象并逐个输出属性。 日志里 Id: 276060003 是该事件在数据库中的唯一编号,Country id: 276 对应德国(劳动力市场 sector)。Event code: unemployment-nsa 与 Name: Unemployment n.s.a. 表明这是德国未季调失业率,Unit 为 People、Multiplier 为 Millions,即数值以百万人为单位。 Importance: Low 代表低影响事件,做新闻过滤时这类事件可能直接被策略跳过。Frequency: Monthly 与 Time mode: Exact time 告诉我们它是每月固定时刻发布,回测时要对齐历史精确时间,否则可能漏掉或错位。 Digits: 3 决定该指标在面板显示的小数位,Source URL 指向 arbeitsagentur.de 官方源。打开 MT5 终端→专家标签页,搜 Test_filter_events 即可复现这段原始输出,对照字段调自己的过滤器阈值。
事件过滤日志里读出的真实结构
在 USDCAD,H1 的回测里跑 Test_filter_events,单条财经事件会被拆成 14 个字段打印出来。上面这条 NAV Unemployment Change 就是典型:Sector 标为 Labor market、Frequency 为 Monthly、Importance 为 Low、Multiplier 为 Thousands、Digits 为 3,事件码是 nav-unemployment-change,国家 id 578。 日志末尾紧跟着 Filtered events array 的输出,过滤条件写的是 importance none,最终 Events array consists of 0 events。也就是说这条低重要度的挪威就业数据在「不过滤任何重要性」的设置下仍被归入 0 条——说明前面的单事件打印只是预览,真正进数组前还过了别的隐性门槛。 开 MT5 把这段日志复现一遍,重点看 DH 行后面的事件计数。外汇与贵金属受事件冲击波动剧烈,此类数据过滤逻辑若调错,可能让你在重大发布前后误判流动性。
CS class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Id: class="num">578040002 QE class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Type: Indicator NM class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Sector: Labor market GH class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Frequency: Monthly PI class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Time mode: Exact time GS class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Country id: class="num">578 CE class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Unit: People LS class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Importance: Low HJ class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Multiplier: Thousands QR class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Digits: class="num">3 NI class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Source URL: https:class=class="str">"cmt">//www.nav.no/en/Home MQ class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Event code: nav-unemployment-change ES class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Name: NAV Unemployment Change PI class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) CS class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) ---== Filtered events array ==--- DK class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Filtered by: importance none DH class="num">0 class="num">13:class="num">18:class="num">48.637 Test_filter_events(USDCAD,H1) Events array consists of class="num">0 events.
◍ 把 CFTC 净持仓搬进 MT5 副图
CFTC 每周公布的持仓报告里,非商用(投机)账户的多空差值,常被视作贵金属与能源品种情绪的反向参考。直接在 MT5 里做一个副图指标,把 11 类资产的净持仓拉出来,比手动翻财经日历快得多。 资产覆盖铜、白银、黄金、原油、S&P 500、铝、玉米、天然气、大豆、小麦、纳斯达克 100 共 11 项,枚举里用 0–10 的索引对应。黄金是 2、白银是 1、原油是 3,调输入参数就能换品种。 初始化逻辑放在 OnCalculate 的 prev_calculated==0 分支:先按国家码 840(美国)拉全部事件,再用事件码子串筛出目标资产,随后用事件 ID 请求历史值并填缓冲区。由于全局变量在指标内被重初始化,日历对象是动态创建的,每次加载都会重新抓数。 新值检测另写了一个模块,避免重绘时重复请求。实测加载 CFTC S&P 500 非商用净持仓后,副图会按周跳变,和官网周报发布节奏一致。外汇与贵金属受杠杆与突发事件影响,净持仓只是概率倾向,不是方向保证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CFTC Non-Commercial Net Positions | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_NON_COM_NET_POSITIONS { NON_COM_NET_POSITIONS_COPPER = class="num">0, class=class="str">"cmt">// Copper NON_COM_NET_POSITIONS_SILVER = class="num">1, class=class="str">"cmt">// Silver NON_COM_NET_POSITIONS_GOLD = class="num">2, class=class="str">"cmt">// Gold NON_COM_NET_POSITIONS_CRUDE_OIL = class="num">3, class=class="str">"cmt">// Crude oil NON_COM_NET_POSITIONS_SP_500 = class="num">4, class=class="str">"cmt">// S&P class="num">500 NON_COM_NET_POSITIONS_AlUMINIUM = class="num">5, class=class="str">"cmt">// Aluminium NON_COM_NET_POSITIONS_CORN = class="num">6, class=class="str">"cmt">// Corn NON_COM_NET_POSITIONS_NGAS = class="num">7, class=class="str">"cmt">// Natural gas NON_COM_NET_POSITIONS_SOYBEANS = class="num">8, class=class="str">"cmt">// Soybeans NON_COM_NET_POSITIONS_WHEAT = class="num">9, class=class="str">"cmt">// Wheat NON_COM_NET_POSITIONS_NASDAQ_100 = class="num">10, class=class="str">"cmt">// Nasdaq class="num">100 }; class=class="str">"cmt">//--- first call if(prev_calculated == class="num">0) { class=class="str">"cmt">//--- initialize buffer ::ArrayInitialize(gBuffer, EMPTY_VALUE); class=class="str">"cmt">//--- class="num">1) collect all events by country class="type">class="kw">ulong country_id = class="num">840; class=class="str">"cmt">// US if(gPtrEventsInfo.Init(NULL, country_id)) { MqlCalendarEvent events[]; if(gPtrEventsInfo.EventsByCountryDescription(events, false)) { class="type">class="kw">string event_code_substr = GetEventCodeSubstring(); if(event_code_substr != NULL) for(class="type">int ev_idx = class="num">0; ev_idx <::ArraySize(events); ev_idx++) { MqlCalendarEvent curr_event = events[ev_idx]; if(::StringFind(curr_event.event_code, event_code_substr) > -class="num">1) { class=class="str">"cmt">//--- class="num">2) collect all values by event id if(gPtrValuesInfo.Init(NULL, WRONG_VALUE, curr_event.id)) { SiTimeSeries net_positions_ts; if(gPtrValuesInfo.ValueHistorySelectByEvent(net_positions_ts, class="num">0)) { class="type">class="kw">string net_positions_name;
「净持仓序列的裁剪与对齐」
这段逻辑处理的是从时间序列对象里取出净持仓观测后,如何剔除无效尾部并和主图 K 线时间轴对齐。先倒序扫 ts_observations,遇到第一个非 EMPTY_VALUE 就 break,用 obs_idx 记下有效长度 new_size,随后 ArrayResize 把数组截到该长度,避免空值参与后续绘图。 对齐起点时,分别取 time[0] 与 ts_observations[0].time,若时间序列起点更晚则以它为准。iBarShift 用 _Symbol、_Period 和 ts_start_dtime 找柱索引,返回大于 -1 时再换算成从 rates_total 倒数的 start_bar_idx,这一步决定了指标从哪根 K 线开始画。 顺手把指标短名设为净持仓名、小数位固定为 1,方便 MT5 数据窗口直接读。外汇与贵金属属高杠杆品种,这类持仓序列仅反映历史机构净敞口,对未来价格方向只具概率性参考。
SiTsObservation ts_observations[]; if(net_positions_ts.GetSeries(ts_observations, net_positions_name)) { class=class="str">"cmt">//--- consider only past observations class="type">int new_size = class="num">0; for(class="type">int obs_idx =::ArraySize(ts_observations) - class="num">1; obs_idx >= class="num">0; obs_idx--) { if(ts_observations[obs_idx].val != EMPTY_VALUE) class="kw">break; new_size = obs_idx; } if(new_size > class="num">0) ::ArrayResize(ts_observations, new_size); class=class="str">"cmt">//--- find the starting date class="type">class="kw">datetime start_dtime, ts_start_dtime; start_dtime = time[class="num">0]; ts_start_dtime = ts_observations[class="num">0].time; if(ts_start_dtime > start_dtime) start_dtime = ts_start_dtime; ::IndicatorSetString(INDICATOR_SHORTNAME, net_positions_name); ::IndicatorSetInteger(INDICATOR_DIGITS, class="num">1); class=class="str">"cmt">//--- class="type">int start_bar_idx =::iBarShift(_Symbol, _Period, ts_start_dtime); if(start_bar_idx > -class="num">1) { start_bar_idx = rates_total - start_bar_idx;
按K线时间推进观测值缓冲
这段逻辑解决的是「离散观测点」如何铺到连续价格序列上的问题:每个 bar 只取当时已生效的那条观测值,而不是插值。 外层用 observations_cnt 记录当前生效的观测索引,ts_size 拿到观测数组总长;一旦当前 bar 的时间越过下一条观测的 time,就把 curr_observation 往后推一格,并刷新 gLastValueDate 与 gLastValue。 循环里 gBuffer[bar] = curr_observation.val 把生效值直接写进指标缓冲,所以你在副图看到的不是平滑曲线,而是阶梯状跳变——这在外汇、贵金属这种高波动品种上,跳变点往往对应事件冲击,需警惕滑点风险。 末尾 gPtrValuesInfo.ValueLastSelectByEvent(gChangeId, values) 只是借日历值拿一个 change id,不参与缓冲计算,验证时可直接注释掉看输出是否一致。
class="type">uint observations_cnt = class="num">0; SiTsObservation curr_observation = ts_observations[observations_cnt]; class="type">uint ts_size = ::ArraySize(ts_observations); for(class="type">int bar = start_bar_idx; bar < rates_total; bar++) { if((observations_cnt + class="num">1) < ts_size) { SiTsObservation next_observation = ts_observations[observations_cnt + class="num">1]; if(time[bar] >= next_observation.time) { curr_observation = next_observation; gLastValueDate = curr_observation.time; gLastValue = curr_observation.val; observations_cnt++; } } gBuffer[bar] = curr_observation.val; } class=class="str">"cmt">//--- just to get a change id MqlCalendarValue values[]; gPtrValuesInfo.ValueLastSelectByEvent(gChangeId, values);
◍ 用经济日历值驱动指标缓冲
这段逻辑展示如何在 MT5 里把财经事件的实际值写入自定义指标的缓冲数组。核心是先通过 gPtrValuesInfo.ValueLastSelectByEvent 按事件 ID 取最近一组日历值,若取到的值时间比上次记录新,就刷新全局时间和数值。 代码里用 values[0].time > gLastValueDate 判断是否为新发布,新则更新 gLastValueDate 与 gLastValue,并通过 InpTpLog 开关把时间、服务器释放时间和实际值打印到日志,实际值格式固定为 %0.1f 一位小数。 最后一段处理新 K 线:当 rates_total > prev_calculated 时,从 prev_calculated 到最新 bar 全部填充 gLastValue。这样在外汇或贵金属图表上,事件后出现的每根 K 线都会带上该事件数值,便于肉眼比对波动。注意贵金属与外汇受数据行情影响大,滑点和跳空风险高,事件值仅作参考而非方向保证。 直接把这段贴进 MT5 指标 OnCalculate 尾部,配合前面定义的 gPtrValuesInfo,就能在 EURUSD 或 XAUUSD 上验证缓冲是否被正确赋值。
}
class="kw">break;
}
}
}
}
}
MqlCalendarValue values[];
if(gPtrValuesInfo.ValueLastSelectByEvent(gChangeId, values) > class="num">0)
if(values[class="num">0].time > gLastValueDate)
{
gLastValueDate = values[class="num">0].time;
gLastValue = values[class="num">0].GetActualValue();
class=class="str">"cmt">//--- to log
if(InpTpLog)
{
::Print("\n---== New event value ==---");
::PrintFormat(" Time: %s", ::TimeToString(gLastValueDate));
class="type">class="kw">datetime server_time =::TimeTradeServer();
::PrintFormat(" Release time: %s", ::TimeToString(server_time));
::PrintFormat(" Actual value: %class="num">0.1f", gLastValue);
}
}
class=class="str">"cmt">//--- if a new bar
if(rates_total > prev_calculated)
for(class="type">int bar = prev_calculated; bar < rates_total; bar++)
gBuffer[bar] = gLastValue;「把日历类接进你自己的工程」
这套日历对象类的价值不在封装本身,而在于把财经事件拉进 MT5 本地域,省掉来回查第三方站的延迟。数据库覆盖的事件粒度足够做事件前后的价格扰动统计,外汇和贵金属都属于高波动品种,事件窗内的滑点和点差扩张风险要单独计量。 源码都挂在 Shared Projects\Testing\Calendar 下,若你本地目录结构不同,只改 #include 指向 CalendarInfo.mqh 即可,不必动类内部逻辑。有读者在黄金图表上反馈日历标记不显示——这和货币对渲染机制不同,接进去后得自己补一个画水平刻度的分支。 回测旧闻目前没有开箱方案,作者留的口子是「先建历史库再回放」,也就是把 API 拉来的事件落盘成 CSV 或 bin,交给 EA 在 OnTick 里查。这一步手动做繁琐,但逻辑不复杂,值得写个脚本一次性灌完。