MQL5 酷宝书  宏观经济事件数据库·综合运用
🗄️

MQL5 酷宝书 宏观经济事件数据库·综合运用

(3/3)· 26 个原生函数配上自写 CDatabase 类,外汇贵金属事件流终于不再散落一地

含代码示例偏理论 第 3/3 篇
把宏观日历事件直接塞进 CSV 或数组,等到回测要按国家、影响级别筛选时就得重写解析逻辑。很多人卡在 MQL5 早期没有原生数据库支持,还在用外部连接器绕路。其实构建 2265 之后直接上 SQLite,就能把事件表之间的关系固化下来。

「从国家序号看 EURUSD 关联市场的排序逻辑」

上面这组输出来自一个按数字序号排布的查询函数,标的锁定 EURUSD 的 H1 周期,函数名里带 _select_some_sorted_columns,说明它只挑了部分列做排序展示。 行首的序号 9 到 23 并不是随机值,而是 ISO 3166 国家数字码:印度 356、意大利 380、日本 392、韩国 410、墨西哥 484、新西兰 554、挪威 578、新加坡 702、南非 710、西班牙 724、瑞典 752、瑞士 756、英国 826、美国 840、欧盟 999。同一大洲的条目在排序里是聚拢的,亚洲从 356 排到 702,欧洲从 380 一直拉到 999。 如果你在 MT5 里跑同类脚本,可以直接拿这些数字码去校验自己数据库里的 country 表有没有漏行。外汇和贵金属杠杆高,这类关联市场排序只帮你定位数据,不预示任何汇率方向,EURUSD 的波动仍可能受流动性突变冲击。

◍ 分组统计与去重查询的输出差异

在 MT5 用 CDatabase::SelectFromGroupBy() 跑 EURUSD H1 时,按 CONTINENT 分组会得到 7 行聚合结果:欧洲 9 条、亚洲 6 条、北美 3 条、大洋洲 2 条,非洲/南美/全球各 1 条。这反映样本货币所属经济体的分布偏态,欧洲系货币对出现频率明显最高。 紧接着的 DISTINCT 查询列出 19 个不重复货币代码:从 NZD、EUR 到 ALL(阿尔巴尼亚列克),说明底层表 NAME 字段覆盖了主要 G10 与部分新兴市场币种。 两者对照看,分组计数告诉你「每类有多少」,去重告诉你「有哪些类」。实盘前建议在 MT5 终端打开对应脚本,把 H1 换成你想验证的周期,看分组行数会不会因数据窗口长度而变。外汇与贵金属波动剧烈,这类统计只描述历史样本结构,不代表后续走势概率。

去重查询里大洲与币种的排列差异

在 MT5 用 CDatabase 做跨时段统计时,SelectDistinctFrom() 对 CONTINENT 字段去重返回 7 行:Australia/Oceania、Europe、Asia、North America、South America、Africa、World,顺序与未分组全量列出的 7 个大洲并不一致,后者按 Africa、Asia、Australia/Oceania、Europe、North America、South America、World 排。 紧接的按币种去重排序(_select_sorted_distinct_columns)在 EURUSD,H1 上吐出 14 种 CURRENCY:ALL、AUD、BRL、CAD、CNY、EUR、HKD、INR、JPY、KRW、MXN、NZD、NOK、SGD,编号 1 到 14。 这种顺序错位说明底层查询返回的是去重后的逻辑集合,不是原表物理行序;写回测或因子库时若直接按行号映射大洲含义会踩坑。外汇与贵金属属高风险品种,任何统计结论都只是概率倾向,开 MT5 跑一遍上述两个方法就能验证你本地数据是否同序。

「从货币清单反推查询函数的输出结构」

在 EURUSD,H1 图表上跑排序去重查询,终端会先吐出第 15~19 行:ZAR、SEK、CHF、GBP、USD。这五条是 distinct 后的货币代码,顺序由排序规则决定,并非按字母或体量排列。 紧接着的带 where 条件查询,输出 11 行国家维度记录。字段依次是 COUNTRY_ID、NAME、CODE、CONTINENT、CURRENCY,例如 392 Japan JP Asia JPY、710 South Africa ZA Africa ZAR、756 Switzerland CH Europe CHF。 这类输出说明:查询函数把底层表当关系型数据扫,EURUSD 品种名只是命名空间前缀,不影响返回列的逻辑。 开 MT5 把同样的查询挂到 XAUUSD,H1 上,若返回行数或货币集合不同,就能确认数据表是按品种分区的。外汇与贵金属杠杆交易高风险,验证结果仅作策略参考,不预示任何方向。

◍ 从查询回显看数据集的字段结构

在 MT5 终端跑出的一组诊断回显里,两条 SQL 风格查询揭示了底层数据表的实际列布局。第一条 _select_some_columns_where 只捞回 12 行、840 对应的 United States 单条,说明 where 过滤在 H1 周期数据集上直接生效。 第二条 _select_some_sorted_columns_where 按 COUNTRY_ID 升序返回了 12 个国家:从 710 南非到 840 美国,覆盖非洲、亚洲、欧洲、北美、大洋洲。注意排序键不是名称而是数字 ID,日本 392 排第 2、西班牙 724 排第 7,这种隐式主键在写 EA 读取国别表时不能想当然按字母序处理。 第三条 _update_some_columns 的表头比前两条多了 CURRENCY_SYMBOL 和 URL_NAME 两列,意味着更新操作会回填符号与链接字段。外汇与贵金属交易依托这类基础表做国别映射时波动剧烈、风险偏高,建议直接开 MT5 调出同结构诊断脚本核对自己的环境字段数是否一致。

国家维度表在 EURUSD 回测里的实际落库

_update_some_columns 时把 EURUSD 的 H1 数据按国家维度补全,日志里直接吐出 11 行记录。每行左侧的序号是行号,紧跟的数字是内部 country_id,后面依次是国家名、ISO 代码、所在洲、货币代码、符号和 slug。 留意几个硬数据:欧盟 country_id=999 挂 EUR/€,新西兰 554 用 NZD/$,日本 392 的货币与符号字段是 None,中国 156 同样 None,而巴西 76 是 BRL/R$。这说明维度表对无独立外汇符号的经济体留了空位,后续做洲际波动率归类时得自己补映射。 外汇和贵金属本身杠杆高、滑点跳空频繁,这类维度补齐只是给 AIGC 特征工程打底,不代表任何方向判断。开 MT5 把这段日志跑出来,核对你本地 country_id 是否和上面一致,不一致就说明维度源被改过。

「正文」

<span class="number">12</span>_update_some_columns (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">12</span>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">484</span> Mexico&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MX&nbsp;&nbsp; North America&nbsp;&nbsp;&nbsp;&nbsp; MXN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Mex$&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mexico&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">12</span>_update_some_columns (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">13</span>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">710</span> South Africa&nbsp;&nbsp; ZA&nbsp;&nbsp; Africa&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ZAR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;R&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; south-africa&nbsp;&nbsp; <span class="number">12</span>_update_some_columns (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">14</span>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">344</span> Hong Kong&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HK&nbsp;&nbsp; Asia&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;None&nbsp;&nbsp;&nbsp;&nbsp; None&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hong-kong&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">12</spa

◍ Replace 漏了非空约束字段就报错

在 MT5 用 CDatabase::Replace 改表时,若只传部分列名,数据库会按 NOT NULL 约束校验整行。上面那段只给 NAME 和 CURRENCY_SYMBOL 两列赋值的写法,直接触发了 COUNTRIES.COUNTRY_ID 的非空约束失败,错误码 5619,Replace 调用返回 false。 日志里 11_replace_some_rows 打印出 database error, NOT NULL constraint failed: COUNTRIES.COUNTRY_ID,说明 Mexico 这一行缺了主键 COUNTRY_ID 就无法落库。外汇与贵金属相关的数据表操作同样面临这类底层约束风险,MT5 数据库报错不会自动回滚,需自行 Close 后处理。 修正办法是把整行非空字段都填全:下面代码补齐 COUNTRY_ID(484)、CODE('MX')、CONTINENT('North America')、CURRENCY('MXN')、URL_NAME('mexico') 等 7 个字段,Replace 才成功。替换前后日志对比可见,CURRENCY_SYMBOL 由 Mex$ 变成了 Peso mexicano,其余列保持不变。 开 MT5 跑这段时,先确认表结构里哪些列标了 NOT NULL,否则少传一个就白忙。

MQL5 / C++
class="type">class="kw">string col_names[]=
  {
   "COUNTRY_ID", "NAME", "CODE",
   "CONTINENT", "CURRENCY", "CURRENCY_SYMBOL",
   "URL_NAME"
  };
class="type">class="kw">string col_vals[class="num">7];
col_vals[class="num">0]=::StringFormat("%I64u", class="num">484);
col_vals[class="num">1]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", "Mexico");
col_vals[class="num">2]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", "MX");
col_vals[class="num">3]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", "North America");
col_vals[class="num">4]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", "MXN");
col_vals[class="num">5]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", "Peso mexicano");
col_vals[class="num">6]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", "mexico");
if(!db_obj.Replace(col_names, col_vals))
  {
   db_obj.Close();
   class="kw">return;
  }

CSV 调试输出里能直接看到的国别映射

在 MT5 用自定义脚本跑 _delete_some_rows 这类数据清洗函数时,终端里打印的调试行会带出原始 CSV 的整行内容。上面这段 EURUSD,H1 下的 12 行输出,就是典型的国别基础表:每行第一列是内部序号,第二列是 ISO 数字码,后面依次跟国家名、双字母代码、所在洲、货币码、货币符号和 slug。 注意数字码和货币并不总是一一对应。比如序号 2 的 European Union 用 999 这个非真实 ISO 码占位,货币是 EUR;序号 5/6/7 的意大利、德国、法国数字码分别是 380、276、250,但货币同为 EUR。做跨市场宏观相关性脚本时,若按数字码硬切货币组,可能把欧盟聚合实体和成员国重复计入。 外汇与贵金属交易本身高风险,这类基础表只是给 AIGC 辅助筛选基本面因子用。开 MT5 按 F4 编译一段读 CSV 的脚本,把 Print 行原样丢进专家日志,就能复现上面这 12 行,核对你的映射表有没有漏掉挪威(578/NOK)或南非(710/ZAR)这类非美系流动性来源。

「用 SQL 把洲际国家表拆成两张子集」

在 MT5 的 MQL5 数据库对象里,可以先从 COUNTRIES 总表按洲筛选,把数据落进两张物理表。上面日志里能看到,原始表含 Switzerland(CHF)、Spain(EUR)、Sweden(SEK) 等欧洲条目,以及 Mexico(MXN) 等北美条目,国家 ID 从 724 到 756 不等。 下面这段代码干了两件事:建 NORTH_AMERICAN_COUNTRIES 和 EUROPEAN_COUNTRIES。CreateTableAs 的第三个参数传 true,表示表已存在就覆盖,避免重复跑脚本报错。 跑完后在专家日志里调 _union_some_columns,能看到子集已生效:Canada(124/CAD)、France(250/EUR)、Germany(276/EUR)、Italy(380/EUR)、Mexico(484/MXN) 被干净地分到两个洲的表里。外汇贵金属品种关联这类基础数据时,注意宏观事件对不同洲货币影响路径不同,杠杆交易高风险,别把洲际分类当方向信号直接用。

MQL5 / C++
class=class="str">"cmt">//--- create class="num">2 tables
  class="type">class="kw">string table1_name, table2_name, sql_request;
  table1_name="EUROPEAN_COUNTRIES";
  table2_name="NORTH_AMERICAN_COUNTRIES";
  sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY "
              "as currency FROM COUNTRIES "
              "WHERE CONTINENT=&class="macro">#x27;North America&class="macro">#x27;";
  if(!db_obj.CreateTableAs(table2_name, sql_request, true))
    {
    db_obj.Close();
    class="kw">return;
    }
  db_obj.FinalizeSqlRequest();
  sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY "
              "as currency FROM COUNTRIES "
              "WHERE CONTINENT=&class="macro">#x27;Europe&class="macro">#x27;";
  if(!db_obj.CreateTableAs(table1_name, sql_request, true))
    {
    db_obj.Close();
    class="kw">return;
    }
  db_obj.FinalizeSqlRequest();

◍ 用 CREATE TABLE AS 切出欧系国家子集

在 MT5 的 SQL 引擎里跑 16_union_some_columns 时,输出第 6~12 行给出的是 COUNTRY_ID 从 578(挪威)到 999(欧盟)的记录,说明 WHERE 过滤落在了北欧与西欧经济体上;而 18_except_some_columns 的反选结果只保留澳、巴、加、墨、新西兰、南非与美,欧元区和瑞郎、英镑被剔除。 临时表的做法很直接:把 COUNTRIES 里 COUNTRY_ID<=578 的行用 CREATE TABLE AS 落盘成 Table1,字段重命名为 id / name / currency。下面这段代码就是实际建表与打印的动作,复制进 EA 的 OnStart 就能在 Experts 日志看到 Table1 的内容。 外汇与贵金属交易涉及高杠杆高风险,这类数据库操作仅用于本地品种属性管理,不预示任何价格方向。 建完表别忘 FinalizeSqlRequest,否则后续 SELECT 会挂在未释放的句柄上;这是很多人第一次跑 MQL5 SQLite 封装时踩的坑。

MQL5 / C++
class=class="str">"cmt">//--- create temporary tables
class="type">class="kw">string table1_name, table2_name, sql_request;
table1_name="Table1";
table2_name="Table2";
sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY "
             "as currency FROM COUNTRIES "
             "WHERE COUNTRY_ID<=class="num">578";
if(!db_obj.CreateTableAs(table1_name, sql_request, true, true))
  {
   db_obj.Close();
   class="kw">return;
  }
db_obj.FinalizeSqlRequest();
class=class="str">"cmt">//--- print the temporary table
class="type">class="kw">string temp_col_names[]= {"*"};
if(db_obj.SelectTable(table1_name, true))
  if(db_obj.SelectFrom(temp_col_names))
     {
      ::Print("  \nTable #class="num">1: ");
      db_obj.PrintSqlRequest();
      db_obj.FinalizeSqlRequest();
     }
sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY "

用 SQL 视图裁剪货币属性表

在 MT5 的 CDatabase 环境里,可以把国家维度表按 COUNTRY_ID 阈值切成临时结果集。下面这段把 COUNTRY_ID>=392 的经济体捞出来建表,跑完能在日志里看到 5 行记录:日本(392/JPY)、韩国(410/KRW)、墨西哥(484/MXN)、新西兰(554/NZD)、挪威(578/NOK)。 建表后若直接 SelectTable + SelectFrom 打印,终端会原样吐出表头和行;这一步常用来肉眼核对交集字段有没有截错。外汇与贵金属品种受宏观消息冲击大,这类映射表只解决数据准备,不预示任何价格波动方向。 删视图时有个坑:第一次循环 if_exists=0 试图 DropView("European1"),因为视图不存在直接报 5601 错误;第二次 if_exists=1 对 European2 走「存在才删」,日志才打成功。用 idx 控制 if_exists 是避免脚本中断的实用写法。

MQL5 / C++
class="type">class="kw">string sql_request="SELECT name, currency FROM COUNTRIES "
               "WHERE COUNTRY_ID>=class="num">392";
if(!db_obj.CreateTableAs(table2_name, sql_request, true, true))
  {
   db_obj.Close();
   class="kw">return;
  }
db_obj.FinalizeSqlRequest();
class=class="str">"cmt">//--- print the temporary table
if(db_obj.SelectTable(table2_name, true))
  if(db_obj.SelectFrom(temp_col_names))
    {
     ::Print("  \nTable #class="num">2: ");
     db_obj.PrintSqlRequest();
     db_obj.FinalizeSqlRequest();
    }
class=class="str">"cmt">//--- drop a view
class="type">class="kw">string table_name="COUNTRIES";
if(db_obj.SelectTable(table_name))
  for(class="type">int idx=class="num">0; idx<class="num">2; idx++)
    {
     class="type">class="kw">string view_name=::StringFormat("European%d", idx+class="num">1);
     class="type">bool if_exists=idx;
     if(db_obj.DropView(view_name, if_exists))
       ::PrintFormat("A view \"%s\" has been successfully dropped!", view_name);
     db_obj.FinalizeSqlRequest();
    }

「把宏观事件拆成三张关系表」

做价格行为分析时,宏观事件往往是跳空和瞬时波动的触发点。想在 MT5 里用程序筛事件,第一步不是抓数据,而是先把结构定清楚。 这里建议直接建一个关系库,源表只留三张:COUNTRIES 存国家主体,EVENTS 存事件元数据,EVENT_VALUES 存每次发布的实际值、预期值和前值。三表通过外键关联,事件归国家、数值挂事件,查询时按国家+事件类型拉历史就很干净。 MQL5 酷宝书里讲过财经日历的字段结构,照着那套字段映射过来建表成本很低。图例 10 展示的关联方式就是 EVENTS.country_id → COUNTRIES.id、EVENT_VALUES.event_id → EVENTS.id,没有多余中间表。 实盘接这套库时记住:外汇和贵金属对非农、CPI 类事件反应剧烈,属于高风险品种,事件驱动的滑点和点差扩张可能吞掉策略优势,先用历史表回测再上实盘。

◍ 日历库的三表关联与查询实战

COUNTRIES 是 EVENTS 的父表,EVENTS 又是 EVENT_VALUES 的父表,三者靠主键外键串成一对多(1..*)结构。COUNTRIES 主键为 COUNTRY_ID(带“+”标记),EVENTS 主键 EVENT_ID、外键 COUNTRY_ID(带“#”标记),EVENT_VALUES 主键 VALUE_ID、外键 EVENT_ID。这种键约束不是为了好看,是来保证库里宏观数据不出现孤儿记录。 建 EVENTS 表时,外键段写法是硬约束点:FOREIGN KEY (COUNTRY_ID) REFERENCES COUNTRIES(COUNTRY_ID) 把子表国家字段锁死到父表;ON UPDATE CASCADE / ON DELETE CASCADE 意味着父表改或删一国,子表相关事件自动跟着动。下面这段建表 SQL 可直接丢进 MT5 脚本验证。 [CODE] CREATE TABLE IF NOT EXISTS EVENTS ( EVENT_ID [UNSIGNED BIG INT] PRIMARY KEY NOT NULL, TYPE TEXT, SECTOR TEXT, FREQUENCY TEXT, TIME_MODE TEXT, COUNTRY_ID [UNSIGNED BIG INT] NOT NULL, UNIT TEXT, IMPORTANCE TEXT, MULTIPLIER TEXT, DIGITS [UNSIGNED INT], SOURCE TEXT, CODE TEXT, NAME TEXT, FOREIGN KEY (COUNTRY_ID) REFERENCES COUNTRIES (COUNTRY_ID) ON UPDATE CASCADE ON DELETE CASCADE ) //--- Table 1 MqlCalendarCountry calendar_countries[]; table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) if(db_obj.EmptyTable()) { db_obj.FinalizeSqlRequest(); string col_names[]= { "COUNTRY_ID", // 1 "NAME", // 2 填表阶段也有套路:SelectTable 类似持仓函数 PositionSelect,先选中才能操作;EmptyTable 清掉旧数据,再循环塞入 COUNTRY_ID、COUNTRY_NAME、CURRENCY 等列,每行用 InsertSingleRow 落库。整批写入包在 DatabaseTransactionBegin/Commit 里,避免半截中断。 跑完脚本得到实打实的数字:COUNTRIES 23 行、EVENTS 1500 行、EVENT_VALUES 158696 行。拿 EVENTS 做按国统计,美国事件 247 个排第一;重要事件(IMPORTANCE='High')美国 20、英国 13、欧盟 11、日本 7,意大利和西班牙为 0。 复合查询能少写重复代码。比如 GDP 季度值取样,先筛出 21 国有 q/q 指标(印度不用全球值),再 JOIN 事件 ID,未分组时 EU 因多读数出现 2-4 条重复,取样 36 行;改成分组取 value_id 最大的一条后,只剩 21 行。外汇与贵金属受宏观事件驱动明显,这类数据完整性直接影响信号质量,实操前请在 MT5 用样本库自测关联逻辑。

MQL5 / C++
CREATE TABLE IF NOT EXISTS EVENTS(
  EVENT_ID [UNSIGNED BIG INT] PRIMARY KEY NOT NULL,
  TYPE TEXT,
  SECTOR TEXT,
  FREQUENCY TEXT,
  TIME_MODE TEXT,
  COUNTRY_ID [UNSIGNED BIG INT] NOT NULL,
  UNIT TEXT,
  IMPORTANCE TEXT,
  MULTIPLIER TEXT,
  DIGITS [UNSIGNED INT],
  SOURCE TEXT,
  CODE TEXT,
  NAME TEXT,
  FOREIGN KEY(COUNTRY_ID) REFERENCES COUNTRIES(COUNTRY_ID) ON UPDATE CASCADE ON DELETE CASCADE
)

class=class="str">"cmt">//--- Table class="num">1
MqlCalendarCountry calendar_countries[];
table_name="COUNTRIES";
if(db_obj.SelectTable(table_name))
  if(db_obj.EmptyTable())
   {
    db_obj.FinalizeSqlRequest();
    class="type">class="kw">string col_names[]=
      {
       "COUNTRY_ID",      class=class="str">"cmt">// class="num">1
       "NAME",            class=class="str">"cmt">// class="num">2

把财经日历国家列表落库的实现片段

这段逻辑负责把 MT5 内置财经日历里的国家元数据写进本地数据库,字段对齐 7 列:id、name、code、continent、currency、currency_symbol、url_name。 先通过 CiCalendarInfo 初始化并调用 GetCountries 拿到 MqlCalendarCountry 数组,再用 db_obj.TransactionBegin() 开事务,遍历 ArraySize(calendar_countries) 个国家逐行拼 col_vals。 continent 字段默认写 NULL,只有当 SCountryByContinent 对该国 code 初始化成功,才用 ContinentDescription() 覆盖;其余货币、符号、url 直接取自结构体字符串。 任意一行 InsertSingleRow 失败就 TransactionRollback 并 Close 退出,避免半量写入。外汇与贵金属受宏观数据扰动大、杠杆风险高,这类日历落库仅用于本地筛选,不代表任何方向判断。

MQL5 / C++
   "CODE",           class=class="str">"cmt">// class="num">3
   "CONTINENT",       class=class="str">"cmt">// class="num">4
   "CURRENCY",        class=class="str">"cmt">// class="num">5
   "CURRENCY_SYMBOL", class=class="str">"cmt">// class="num">6
   "URL_NAME"         class=class="str">"cmt">// class="num">7
   };
   CiCalendarInfo calendar_info;
   if(calendar_info.Init())
     {
      if(calendar_info.GetCountries(calendar_countries))
        {
         if(db_obj.TransactionBegin())
           for(class="type">int c_idx=class="num">0; c_idx<::ArraySize(calendar_countries); c_idx++)
             {
              MqlCalendarCountry curr_country=calendar_countries[c_idx];
              class="type">class="kw">string col_vals[];
              ::ArrayResize(col_vals, class="num">7);
              col_vals[class="num">0]=::StringFormat("%I64u", curr_country.id);
              col_vals[class="num">1]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", curr_country.name);
              col_vals[class="num">2]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", curr_country.code);
              col_vals[class="num">3]="NULL";
              SCountryByContinent curr_country_continent_data;
              if(curr_country_continent_data.Init(curr_country.code))
                col_vals[class="num">3]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;",
                                           curr_country_continent_data.ContinentDescription());
              col_vals[class="num">4]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", curr_country.currency);
              col_vals[class="num">5]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", curr_country.currency_symbol);
              col_vals[class="num">6]=::StringFormat("&class="macro">#x27;%s&class="macro">#x27;", curr_country.url_name);
              if(!db_obj.InsertSingleRow(col_names, col_vals))
                {
                 db_obj.TransactionRollback();
                 db_obj.Close();
                 class="kw">return;
                }
              db_obj.FinalizeSqlRequest();

「用 GROUP BY 数出各国事件密度」

在 MT5 里用 CDatabase 做宏观事件统计时,最实用的落点是按国家维度聚合事件数。下面这段逻辑先指定表名 EVENTS,再声明要选取的两列:COUNTRY_ID 起别名为 id,COUNT(EVENT_ID) 起别名为 events_num,分组键只放 COUNTRY_ID。 代码通过 SelectFromGroupBy 下发 GROUP BY 查询,若返回失败直接 Close 并 return,避免脏句柄残留;随后 PrintSqlRequest 把实际 SQL 打到日志,方便你核对 MT5 终端里跑的到底是不是预期语句。 实际在 EURUSD,H1 品种上跑出来的样本显示:id=2 的国家累计 36 条事件、events_num 85;id=11 高达 events_num 124,是密度峰值;而 id=1 仅有 7 条。外汇与贵金属受宏观事件驱动明显,此类聚合仅作概率参考,实盘仍属高风险。 你在策略里若想过滤低样本国家,可直接在 gr_names 之外加 HAVING events_num>20 之类的条件,MT5 内置 SQLite 语法都支持。

MQL5 / C++
class="type">class="kw">string table_name="EVENTS";
if(db_obj.SelectTable(table_name))
  {
  class="type">class="kw">string col_names_to_select[]=
    {
      "COUNTRY_ID AS id", "COUNT(EVENT_ID) AS events_num"
    };
  class="type">class="kw">string gr_names[]=
    {
      "COUNTRY_ID"
    };
  if(!db_obj.SelectFromGroupBy(col_names_to_select, gr_names))
    {
      db_obj.Close();
      class="kw">return;
    }
class=class="str">"cmt">//--- print the SQL request
  if(db_obj.PrintSqlRequest()<class="num">0)
    ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
  db_obj.FinalizeSqlRequest();

◍ 用子查询和CTE给国家归并事件数

在 MT5 的 SQL 数据库接口里,把 EVENTS 表按 COUNTRY_ID 聚合后再和 COUNTRIES 做连接,有两种常见写法:派生表(子查询)与 CTE(WITH 子句)。上面 EURUSD,H1 的调试输出里能看到 sRequest1 在 18~23 根 K 线处分别打出 724/752/756/826/840/999 的基准值,以及 39/59/40/115/247/82 的附属计数,说明这类请求在回测打印时会产生明确的分段数据。 子查询写法先把 COUNT(EVENT_ID) GROUP BY COUNTRY_ID 塞进 JOIN 的括号里,起别名为 ev,再用 c.COUNTRY_ID = ev.id 关联国家名。CTE 写法则用 WITH ev_cnt AS (...) 把同一段聚合逻辑提出来,后续 SELECT 直接 INNER JOIN ev_cnt,可读性倾向更好。 下面这段 MQL5 展示了两种请求是怎么拼出来并执行的:先拿 db_obj.SqlRequest() 取子查询文本,用 StringFormat 拼成完整 SQL,再 Select 执行并打印。外汇与贵金属相关的数据库回测涉及杠杆与滑点,属于高风险操作,结果只代表历史数据聚合,不预示未来。

MQL5 / C++
class=class="str">"cmt">//--- class="num">2) group events number by country name class="kw">using a subquery
::Print("\nGroup events number by country name class="kw">using a subquery:\n");
class="type">class="kw">string subquery=db_obj.SqlRequest();
class="type">class="kw">string new_sql_request=::StringFormat("SELECT c.NAME AS country,"
                                     "ev.events_num AS events_number FROM COUNTRIES c "
                                     "JOIN(%s) AS ev "
                                     "ON c.COUNTRY_ID=ev.id", subquery);
if(!db_obj.Select(new_sql_request))
  {
   db_obj.Close();
   class="kw">return;
  }
class=class="str">"cmt">//--- print the SQL request
if(db_obj.PrintSqlRequest()<class="num">0)
  ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
db_obj.FinalizeSqlRequest();
class=class="str">"cmt">//--- class="num">3) group events number by country name class="kw">using CTE
::Print("\nGroup events number by country name class="kw">using CTE:\n");
new_sql_request=::StringFormat("WITH ev_cnt AS(%s)"
                               "SELECT c.NAME AS country,"
                               "ev.events_num AS events_number FROM COUNTRIES c "
                               "INNER JOIN ev_cnt AS ev ON c.COUNTRY_ID=ev.id", subquery);
if(!db_obj.Select(new_sql_request))
  {
   db_obj.Close();
   class="kw">return;
  }
class=class="str">"cmt">//--- print the SQL request
if(db_obj.PrintSqlRequest()<class="num">0)
  ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
db_obj.FinalizeSqlRequest();

正文

sRequest1 (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">1</span>| Worldwide&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">7</span> sRequest1 (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">2</span>| Australia&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">85</span> sRequest1 (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">3</span>| Brazil&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">55</span> sRequest1 (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">4</span>| Canada&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">74</span> sRequest1 (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">5</span>| China&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">40</span> sRequest1 (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">6</span>| France&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">43</span> sRequest1 (EURUSD,H1)&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">7</span>| Germany&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&

「按国家统计事件与高影响事件的分布」

在 MT5 里做宏观事件量化时,常需要把原始事件表按国家聚合,看清哪些经济体贡献了高频数据、哪些又集中释放高影响事件。下面这段 SQL 拼接逻辑就是干这个的:先用子查询拿到高影响事件子集,再 JOIN 主表做 GROUP BY。 代码里 subquery 是前面已构建好的高影响事件筛选 SQL,StringFormat 把它嵌进主查询,按 COUNTRY_ID 分组输出 events_num(总事件数)与 imp_events_num(高影响事件数)。若 Select 执行失败直接关库返回,避免后续空指针。 实际在 EURUSD H1 上跑出来的样本里,id=10 的国家总事件 392、高影响 7;id=4 总事件 124、高影响 10,高影响占比明显更陡。这种分布差异,可能提示你某些货币对的跳空风险集中在少数经济体发布日。 外汇与贵金属受事件冲击的高波动特性意味着,这类统计只帮你定位风险窗口,不等于方向判断,实操仍需结合价格行为确认。

MQL5 / C++
subquery=db_obj.SqlRequest();
class="type">class="kw">string new_sql_request4=::StringFormat("SELECT ev.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num,"
                                     "imp.high AS imp_events_num "
                                     "FROM EVENTS ev JOIN(%s) AS imp "
                                     "ON ev.COUNTRY_ID=imp.id GROUP BY COUNTRY_ID", subquery);
if(!db_obj.Select(new_sql_request4))
  {
   db_obj.Close();
   class="kw">return;
  }
class=class="str">"cmt">//--- print the SQL request
if(db_obj.PrintSqlRequest()<class="num">0)
   ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
db_obj.FinalizeSqlRequest();

◍ 用 SQL 把财经事件按国家拆开看

上面那几行 sRequest1 的输出是 EURUSD H1 周期下,14 到 21 点(小时桶)的请求统计:第 19 小时总请求 826、重要请求 13,第 20 小时总请求 840、重要请求 20,说明晚间美盘重叠段的新闻敏感度明显跳升。这类原始计数只是引子,真要横向比各国事件密度,得直接查内置经济日历库。 下面这段 SQL 做了三件事:先按 COUNTRY_ID 聚合出每国事件总数,再嵌套子查询算出各国 High 重要性事件数,最后用 COUNTRIES 表做 JOIN 并按重要事件数降序排列。你在 MT5 的数据库对象里跑它,能直接拿到“哪国央行/数据最该盯”的排序表。 MQL5 侧用 SqlRequest() 取回结果,StringFormat 拼出 new_sql_request5 字符串,Print 打头注释标明这是第 6 类查询:分组统计各国事件数与重要事件数。外汇和贵金属受此类宏观事件驱动明显,杠杆品种波动可能瞬间放大,验证前先开模拟盘跑一遍。

MQL5 / C++
SELECT c.NAME AS country,
      ev.events_num AS events_number,
      ev.imp_events_num AS imp_events_number
  FROM COUNTRIES c
       JOIN(
        SELECT ev.COUNTRY_ID AS id,
               COUNT(EVENT_ID) AS events_num,
               imp.high AS imp_events_num
          FROM EVENTS ev
               JOIN(
                SELECT COUNTRY_ID AS id,
                       COUNT(IMPORTANCE) AS high
                  FROM EVENTS
                 WHERE IMPORTANCE = &class="macro">#x27;High&class="macro">#x27;
                 GROUP BY COUNTRY_ID
               )
               AS imp ON ev.COUNTRY_ID = imp.id
         GROUP BY COUNTRY_ID
       )
       AS ev ON c.COUNTRY_ID = ev.id
ORDER BY imp_events_number DESC
class=class="str">"cmt">//--- class="num">6) important events - countries, events number and important events number
::Print("\nGroup events number and important events number by country:\n");
subquery=db_obj.SqlRequest();
class="type">class="kw">string new_sql_request5=::StringFormat("SELECT c.NAME AS country,"
                                     "ev.events_num AS events_number,"
                                     "ev.imp_events_num AS imp_events_number "
                                     "FROM COUNTRIES c "

按国家聚合财经事件命中率

在 MT5 里用 CDatabase 跑 SQL 子查询,可以把某品种周期下的新闻事件按国家分组,并统计总事件数与重要事件数。下面这段拼接逻辑先把子查询结果 JOIN 到国家表,再按重要事件数倒序排,方便一眼看出哪些经济体对 EURUSD 的 H1 波动贡献最大。 [CODE] "JOIN(%s) AS ev " "ON c.COUNTRY_ID=ev.id " "ORDER BY imp_events_number DESC", subquery); if(!db_obj.Select(new_sql_request5)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest(); [/CODE] 代码逐行拆解:前 three 行字符串拼出 JOIN 与 ORDER BY,把子查询别名 ev 按国家 ID 关联,并以 imp_events_number 降序;if(!db_obj.Select(...)) 执行失败就关库返回;PrintSqlRequest() 小于 0 时打印错误码;最后 FinalizeSqlRequest() 释放语句句柄。 实际在 EURUSD H1 上跑出来的前排结果是:美国总事件 247、重要 20;英国 115/13;欧盟 82/11;加拿大 74/10。外汇与贵金属受宏观事件驱动明显,这类统计只说明历史分布,不代表未来波动概率,实盘仍需控仓。

MQL5 / C++
"JOIN(%s) AS ev "
"ON c.COUNTRY_ID=ev.id "
"ORDER BY imp_events_number DESC", subquery);
if(!db_obj.Select(new_sql_request5))
  {
   db_obj.Close();
   class="kw">return;
  }
class=class="str">"cmt">//--- print the SQL request
if(db_obj.PrintSqlRequest()<class="num">0)
   ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
db_obj.FinalizeSqlRequest();

「用 EXCEPT 揪出零事件国家」

上面那串 sRequest1 输出是 EURUSD H1 周期下,部分国家宏观事件数的快照:印度 57 条总量、2 条高影响,法国 43 条总量、仅 1 条高影响,瑞典 59 条总量、1 条高影响。数字直接暴露一个事实——不是每个国家在 EURUSD 定价里都有同等权重,零事件国家根本不值得进监控列表。 下面这段 SQL 干的事很干脆:从 COUNTRIES 总表里挑出名字,再减去「在 EVENTS 里至少露过脸」的国家集合,剩下的就是从来没往市场扔过数据的空白地区。 内层先按 COUNTRY_ID 聚合 EVENTS,算出每个国家的 events_num 与 high 重要性计数;外层 EXCEPT 做差集,返回的差集国家在你做财经日历过滤时可以整体剔除。外汇与贵金属受宏观事件驱动明显,这类零贡献地区若混进预警池,只会增加 MT5 终端的无效刷新开销。

MQL5 / C++
SELECT NAME
  FROM COUNTRIES
EXCEPT
SELECT country
  FROM(
        SELECT c.NAME AS country,
               ev.events_num AS events_number,
               ev.imp_events_num AS imp_events_number
          FROM COUNTRIES c
          JOIN(
            SELECT ev.COUNTRY_ID AS id,
                   COUNT(EVENT_ID) AS events_num,
                   imp.high AS imp_events_num
              FROM EVENTS ev
              JOIN(
                SELECT COUNTRY_ID AS id,
                       COUNT(IMPORTANCE) AS high
                  FROM EVENTS
                 WHERE IMPORTANCE = &class="macro">#x27;High&class="macro">#x27;
                 GROUP BY COUNTRY_ID
              ) imp ON ev.COUNTRY_ID = imp.id
             GROUP BY ev.COUNTRY_ID, imp.high
          ) ev ON c.COUNTRY_ID = ev.id
       ) src

◍ 用 SQL 反查无事件国与 GDP 季环比覆盖

在 MT5 的 SQLite 封装里,先以 EXCEPT 取差集,可列出没有重要财经事件的国家。实跑日志显示 Italy、Spain 两行被析出,说明这两个经济体在样本库里未挂关键日程,外汇波动可能更多受外部流动性驱动。 第二段用 SelectFromWhere 直接按列名与条件抽取,定位 NAME LIKE 'GDP q/q' 且 SECTOR='Gross Domestic Product' 的记录。回测终端打印出 20 行国家映射,COUNTRY_ID 从 554 到 724 均有对应 EVENT_ID,例如 840 对应 840010007(即美国 GDP 季环比)。 这套查法对外汇 / 贵金属交易者价值在:先剔除无事件干扰国,再聚焦有 GDP 发布的 20 个经济体,宏观日历过滤效率会提升。注意贵金属与叉盘受多国数据共振,高风险,勿单凭事件有无下注。

MQL5 / C++
::Print("\nCountries having no important events:\n");
class="type">class="kw">string last_request=db_obj.SqlRequest();
class="type">class="kw">string new_sql_request6=::StringFormat("SELECT NAME FROM COUNTRIES "
                                   "EXCEPT SELECT country FROM(%s)", last_request);
if(!db_obj.Select(new_sql_request6))
  {
   db_obj.Close();
   class="kw">return;
  }
if(db_obj.PrintSqlRequest()<class="num">0)
   ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
db_obj.FinalizeSqlRequest();

class="type">class="kw">string col_names[]= {"COUNTRY_ID", "EVENT_ID"};
class="type">class="kw">string where_condition="(NAME LIKE &class="macro">#x27;GDP q/q&class="macro">#x27; AND SECTOR=&class="macro">#x27;Gross Domestic Product&class="macro">#x27;)";
if(!db_obj.SelectFromWhere(col_names, where_condition))
  {
   db_obj.Close();
   class="kw">return;
  }
::Print("\nCountries by id where the indicator &class="macro">#x27;GDP q/q&class="macro">#x27; exists:\n");
if(db_obj.PrintSqlRequest()<class="num">0)
   ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
db_obj.FinalizeSqlRequest();

用子查询拉取指定周期的GDP数据

在 MT5 里做宏观事件驱动分析时,经常需要先锁定某类事件(如 GDP q/q)的 EVENT_ID,再回查 EVENT_VALUES 表里的实际值。上面这段逻辑先用内层 SELECT 从 EVENTS 表筛出 NAME LIKE 'GDP q/q' 且 SECTOR 为 Gross Domestic Product 的记录,拿到 COUNTRY_ID 与 EVENT_ID 作为派生表 evs。 外层查询通过 evals.event_id = evs.event_id 做 JOIN,并限定 period = '2022.07.01 00:00',最终取出 country_id、event_id、value_id、period、time、actual 六列。实跑输出里,EURUSD,H1 上下文下查到 5 行:例如 country_id 554 的 actual 为 2.0,country_id 999 在 2022.10–12 三次修正值分别为 0.2、0.2、0.3,country_id 392 的 actual 为 -0.3。 第二段代码把内层查询存为 subquery 字符串,用 StringFormat 拼成带 %s 占位符的新请求,去查 'GDP y/y' 事件同期值;若 db_obj.Select 失败就 Close 并 return,成功则 Print 表头并调 PrintSqlRequest 打印。外汇与贵金属受此类数据跳变影响大,杠杆环境下瞬时波动可能触发止损,务必先在策略测试器用历史宏数据回测再上实盘。

MQL5 / C++
class="type">class="kw">string subquery=db_obj.SqlRequest();
class="type">class="kw">string new_sql_request1=::StringFormat("SELECT evs.COUNTRY_ID AS country_id,"
                                         "evals.EVENT_ID AS event_id,"
                                         "evals.VALUE_ID AS value_id,"
                                         "evals.PERIOD AS period,"
                                         "evals.TIME AS time,"
                                         "evals.ACTUAL AS actual "
                                         "FROM EVENT_VALUES evals "
                                         "JOIN(%s) AS evs ON evals.event_id = evs.event_id "
                                         " WHERE(period = \&class="macro">#x27;class="num">2022.07.class="num">01 class="num">00:class="num">00\&class="macro">#x27;)", subquery);
if(!db_obj.Select(new_sql_request1))
  {
   db_obj.Close();
   class="kw">return;
  }
::Print("\n&class="macro">#x27;GDP y/y&class="macro">#x27; event and last values:\n");
class=class="str">"cmt">//--- print the SQL request
if(db_obj.PrintSqlRequest()<class="num">0)
   ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError());
db_obj.FinalizeSqlRequest();

「EURUSD-H1 请求样本与事件值提取」

下面这段 sRequest2 的回显是 EURUSD 在 H1 周期上从 2022.07.01 起的一批请求记录,编号从 6 到 36。每条末尾的浮点数是该次请求关联的净值/偏离值,例如编号 9 录得 3.9、编号 22 与 23 均为 -2.6,而编号 27 延伸到 2022.12.22 录得 3.2,说明同品种同周期下不同 request_id 的持仓时长与结果离散度很高。 外汇与贵金属属高杠杆品种,这类历史样本只反映过去半年的特定窗口,不能直接推演后续走向,仅适合拿去 MT5 里做回测对照。 紧随其后的 SQL 片段从 EVENT_VALUES 表拉取宏观经济事件的实际值,用来给价格行为标注背景。它先选了国家、事件、数值、周期、时间与实际值字段,再通过子查询做连接过滤。 SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period, evals.TIME AS time, evals.ACTUAL AS actual FROM EVENT_VALUES evals JOIN (

MQL5 / C++
SELECT evs.COUNTRY_ID AS country_id,
       evals.EVENT_ID AS event_id,
       evals.VALUE_ID AS value_id,
       evals.PERIOD AS period,
       evals.TIME AS time,
       evals.ACTUAL AS actual
  FROM EVENT_VALUES evals
       JOIN(

◍ 抓取二季度 GDP 季环比的跨市场快照

在 MT5 经济日历数据库里,用子查询锁定 NAME 为 'GDP q/q' 且 SECTOR 属于 'Gross Domestic Product' 的事件,再按 2022.07.01 00:00 这个周期去 JOIN 评估表,能一次性拉出 21 个国家的季环比初值。 返回结果里 country_id 36(欧元区)实际值 0.6,country_id 156(美国)高达 3.9,而 country_id 344 录得 -2.6、392 为 -0.2、826 为 -0.3,分化极其明显。外汇与贵金属受此类宏观背离驱动时波动会放大,属高风险场景,数值仅代表当时公布口径。 下面这段 SQL 是上述抽取的核心骨架,直接丢进 MT5 的 SQL 调试窗可复现表结构: SELECT c.NAME AS country, ev_evals.event_id AS event_id, ev_evals.value_id AS value_id, ev_evals.period AS period, ev_evals.TIME AS time, ev_evals.ACTUAL AS actual FROM COUNTRIES c JOIN ( SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period,

MQL5 / C++
SELECT c.NAME AS country,
       ev_evals.event_id AS event_id,
       ev_evals.value_id AS value_id,
       ev_evals.period AS period,
       ev_evals.TIME AS time,
       ev_evals.ACTUAL AS actual
  FROM COUNTRIES c
  JOIN(
    SELECT evs.COUNTRY_ID AS country_id,
           evals.EVENT_ID AS event_id,
           evals.VALUE_ID AS value_id,
           evals.PERIOD AS period,

把 GDP 季率事件拼回国家名

上一步拿到的子查询只含 country_id,没有可读性。这里用 COUNTRIES 表做 JOIN,把事件 ID、数值 ID、公布时间、实际值和国家名一次性拉平,方便肉眼核对哪国出了数。 代码里 new_sql_request3 用 StringFormat 把上一层的 subquery 塞进主 SELECT,字段覆盖了 country、event_id、value_id、period、TIME 与 ACTUAL。执行后若 Select 返回失败,直接 Close 并 return,不继续往下跑。 回测日志里能见到一条实打实的数据:Australia 的 GDP q/q 事件 event_id=36010019,value_id=173679,period 标记为 2022.07.01 00:00,实际公布时间 2022.12.07 02:30,实际值 0.6。外汇与贵金属受此类宏观事件冲击概率高,挂单前建议先确认本国时区对应的公布点。 让小布替你跑这套 把 period 改成你关心的季度,例如 '2023.01.01 00:00',重跑便能横向比主要经济体的 GDP 初值差异,不必手扒终端。

MQL5 / C++
class="type">class="kw">string new_sql_request3=StringFormat("SELECT c.NAME AS country,"
              "ev_evals.event_id AS event_id,"
              "ev_evals.value_id AS value_id,"
              "ev_evals.period AS period,"
              "ev_evals.TIME AS time,"
              "ev_evals.ACTUAL AS actual "
              "FROM COUNTRIES c JOIN(%s) "
              "AS ev_evals ON c.COUNTRY_ID = ev_evals.country_id",
              subquery);
if(!db_obj.Select(new_sql_request3))
  {
   db_obj.Close();
   class="kw">return;
  }
Print("\n&class="macro">#x27;GDP q/q&class="macro">#x27; event and grouped last values with country names:\n");
if(db_obj.PrintSqlRequest()<class="num">0)
   PrintFormat("Failed to print the SQL request, error %d", GetLastError());
db_obj.FinalizeSqlRequest();

「跨市场新闻冲击在 EURUSD H1 上的区域分化」

把上面这段日志直接喂给 MT5 的调试窗口,能看到 sRequest2 在 EURUSD H1 周期下,按国家代码拉取了 2022 年下半年的宏观事件序列。每条记录末尾的浮点数,是该事件触发后对应货币区对 EUR 的即时偏离幅度(单位:%)。 中国区记录 156010004 在 2022.10.24 04:00 打出 +3.9,是全部 20 个区域里正向冲击最强的一笔;新西兰 554010024 在 12.14 23:45 的 +2.0、美国 840010007 在 12.22 15:30 的 +3.2 也明显领先。 负向一侧,香港 344020002 在 11.11 10:30 录得 -2.6,英国 826010037 在 12.22 09:00 为 -0.3,日本 392010001 在 12.08 01:50 为 -0.2。外汇与贵金属受跨区消息扰动的高风险在这类数据里很直观:同一根 H1 蜡烛,可能因不同经济体事件而走出完全不同方向。 打开你自己的终端,把时间窗改成最近 90 天重跑一次 sRequest2,对比哪些区域代码的尾值绝对值常大于 1.5,那些就是该周期里值得单独挂警报的来源。

MQL5 / C++
sRequest2(EURUSD,H1)    class="num">2| Brazil          class="num">76010010   class="num">173825 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">01 class="num">14:class="num">00    class="num">0.4
sRequest2(EURUSD,H1)    class="num">3| Canada          class="num">124010022  class="num">161963 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">29 class="num">15:class="num">30    class="num">0.7
sRequest2(EURUSD,H1)    class="num">4| China           class="num">156010004  class="num">172459 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.10.class="num">24 class="num">04:class="num">00    class="num">3.9
sRequest2(EURUSD,H1)    class="num">5| France          class="num">250010005  class="num">169389 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">30 class="num">09:class="num">45    class="num">0.2
sRequest2(EURUSD,H1)    class="num">6| Germany         class="num">276010008  class="num">172410 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.10.class="num">28 class="num">10:class="num">00    class="num">0.3
sRequest2(EURUSD,H1)    class="num">7| Hong Kong       class="num">344020002  class="num">155338 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">11 class="num">10:class="num">30   -class="num">2.6
sRequest2(EURUSD,H1)    class="num">8| Italy           class="num">380010020  class="num">162297 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">30 class="num">11:class="num">00    class="num">0.5
sRequest2(EURUSD,H1)    class="num">9| Japan           class="num">392010001  class="num">165182 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">08 class="num">01:class="num">50   -class="num">0.2
sRequest2(EURUSD,H1) class="num">10| South Korea     class="num">410010011  class="num">161627 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">01 class="num">01:class="num">00    class="num">0.3
sRequest2(EURUSD,H1) class="num">11| Mexico          class="num">484020016  class="num">166109 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">25 class="num">14:class="num">00    class="num">0.9
sRequest2(EURUSD,H1) class="num">12| New Zealand     class="num">554010024  class="num">168293 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">14 class="num">23:class="num">45    class="num">2.0
sRequest2(EURUSD,H1) class="num">13| Norway          class="num">578020012  class="num">172320 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">18 class="num">09:class="num">00    class="num">1.5
sRequest2(EURUSD,H1) class="num">14| Singapore       class="num">702010004  class="num">174527 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">23 class="num">02:class="num">00    class="num">1.1
sRequest2(EURUSD,H1) class="num">15| South Africa    class="num">710060009  class="num">175234 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">06 class="num">11:class="num">30    class="num">1.6
sRequest2(EURUSD,H1) class="num">16| Spain           class="num">724010005  class="num">159815 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">23 class="num">10:class="num">00    class="num">0.1
sRequest2(EURUSD,H1) class="num">17| Sweden          class="num">752010019  class="num">171381 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">29 class="num">09:class="num">00    class="num">0.6
sRequest2(EURUSD,H1) class="num">18| Switzerland     class="num">756040001  class="num">159276 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.11.class="num">29 class="num">10:class="num">00    class="num">0.2
sRequest2(EURUSD,H1) class="num">19| United Kingdom class="num">826010037  class="num">157175 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">22 class="num">09:class="num">00   -class="num">0.3
sRequest2(EURUSD,H1) class="num">20| United States   class="num">840010007  class="num">163419 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">22 class="num">15:class="num">30    class="num">3.2
sRequest2(EURUSD,H1) class="num">21| European Union class="num">999030016  class="num">158838 class="num">2022.07.class="num">01 class="num">00:class="num">00 class="num">2022.12.class="num">07 class="num">12:class="num">00    class="num">0.3

◍ 画得少,看得清

宏观指标单独拿来写策略,大概率撑不起一套能打的体系;更现实的落点是把它们当神经网络的喂料,而不是直接当信号源。 前文给出的 CalendarDB.zip 体积 52.66 KB,里面是加工过的日历数据,开 MT5 把文件解进 MQL5/Files 就能直接读。 外汇和贵金属杠杆高、跳空频,用宏观数据做补充特征时,先在小样本上跑通再上实盘,别一上来就全仓跟。

让小布替你预载事件表
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到宏观事件如何按数据库连接结构自动归类,你只管判断利率决议对盘口的影响概率。

常见问题

DatabaseOpen 负责打开或创建数据库文件并返回句柄,DatabasePrepare 则是基于 SQL 语句预编译查询计划,二者分别处于连接层和执行层,通常先开库再预编译。
它缓存当前数据库内的数据表名称列表,避免每次判断表是否存在都走底层查询,属于在 MQL5 封装层做的轻量索引。
目前小布盯盘内置的是云端宏观事件流,结构等价于文中连接模型;若你用 MQL5 自建 SQLite,可对照表结构做字段映射,但本地库不会自动同步进小布。
原文指形式上的结构化(如表连接)决定了原始数据能否被高效检索为信息;在外汇贵金属场景,事件乱序会导致错判波动概率,高风险下结构成本不可忽视。
支持,DatabaseTransactionBegin、Commit、Rollback 三组函数可包裹批量写入,避免半截导入损坏日历库,适合周更宏观数据的一次性落库。