您应当知道的 MQL5 向导技术(第 21 部分):配以财经日历数据进行测试(基础篇)
📘

您应当知道的 MQL5 向导技术(第 21 部分):配以财经日历数据进行测试(基础篇)

第 1/2 篇

◍ 用财经日历给 EA 做历史回测

在 MT5 里跑 EA 回测,默认是不带新闻冲击的,这让很多策略在样本外表现失真。MQL5 向导生成的框架若想接财经日历,核心是把外部 CSV 或内置宏事件序列注入 OnTick 前的预处理环节。 具体做法是在专家顾问初始化阶段用 FileOpen 读取日历文件,把事件时间、影响等级存进一个结构体数组;回测时若当前 tick 落在事件前后 N 分钟窗口内,就令风控模块降仓或禁开新单。这样测出来的权益曲线,比裸价格回测更接近实盘受数据行情干扰的情形。 需要提醒的是,外汇与贵金属属高杠杆品种,新闻行情滑点可能瞬间放大数倍,即便日历过滤也只「可能」降低尾部风险,而非消除。开 MT5 把任意向导 EA 接一份 2024 年非农日历 CSV,先跑一段 EURUSD M15,对比有无日历约束的回撤,就能验证这套思路。

用 SQLite 把财经日历塞进 EA 测试

技术面指标看的是价格和成交量,但基本面新闻往往才是波动的真正触发器。通胀率、央行利率、失业率这些宏观数据点,在发布瞬间造成的跳空和滑点,比多数技术指标更猛。最典型的例子是美国非农就业数据,几乎每月第一个周五出炉,行情波动经常远超普通交易日。 很多关键新闻没被散户盯上,长期被忽略,反而可能藏着还没被榨干的信息优势。把这类数据接进由 MQL5 向导拼出来的智能系统,能在回测里验证“新闻前离场 / 新闻后跟随”这类思路是否真的有用。 SQLite 数据库可以直接在 MetaEditor 里建。它本质是个本地仓库,能存财经日历,充当 EA 的数据源甚至指标缓冲区。本地存档意味着断网也能跑离线测试;当新闻源损坏、或老数据随时间失效时,你手里还有一份可控备份。本文就落地一件事:怎么用 SQLite 归档财经日历,让向导生成的 EA 拿它发信号。 外汇和贵金属受新闻冲击极快,这类策略实测前务必用历史数据充分回测,真实账户高风险。

「测试器读库卡点与 CSV 迂回」

在 MT5 策略测试器里直接读财经日历数据库,目前仍是个卡点。我实测用上面的 CSignalEconData::Read 在测试器中跑,会抛出错误 5601,提示要访问的数据表不存在;但同一段 SQL 在 MetaEditor 的数据库 IDE 或挂图脚本里都能正常回值。要么是测试器需要补额外代码,要么就是环境层面不允许访问库——服务台机器人给不出有用答复。 变通思路是把库里的财经数据导出成 CSV,让测试器改读文件。但这只是权宜,CSV 远不能替代数据库。数据库靠主键外键做完整性约束,CSV 完全没有;面对大数据,数据库的索引检索效率远高于 CSV 的线性扫描,后者在万行以上就会明显变慢。 并发、安全、备份也都是数据库的强项。多用户实时读写、角色权限与加密、自动恢复工具,CSV 默认一概不提供;SQL 的复杂并表查询也得靠第三方脚本在 CSV 上硬凑。还有规范化减少冗余、版本控制做审计追踪,CSV 扁平结构天生冗余且更新易损坏。做长周期财经研究,这些差异会直接拖垮策略验证质量。 下面这段代码是原本走数据库的方案,可看清字段结构和取数逻辑。测试器跑不通时,再考虑把同样查询改写成读 CSV 的版本。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Read Data
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalEconData::Read(class="type">class="kw">string DB, class="type">class="kw">datetime Time, class="type">class="kw">string Event)
{  class="type">class="kw">double _data = class="num">0.0;
class=class="str">"cmt">//--- create or open the database in the common terminal folder
   ResetLastError();
   class="type">int _db_handle = DatabaseOpen(DB, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
   if(_db_handle == INVALID_HANDLE)
   {  Print("DB: ", DB, " open failed with err: ", GetLastError());
      class="kw">return(_data);
   }
   class="type">class="kw">string _sql =
      "   SELECT ACTUAL " +
      "   FROM "  +
      "   ( "  +
      "   SELECT ACTUAL "  +
      "   FROM PRICES " +
      "   WHERE DATE <= &class="macro">#x27;" + TimeToString(Time) + "&class="macro">#x27; " +
      "   AND EVENT = &class="macro">#x27;" + Event + "&class="macro">#x27;  " +
      "   ORDER BY DATE DESC  " +
      "   LIMIT class="num">1  " +
      "   )";
   class="type">int _request = DatabasePrepare(_db_handle, _sql);
   if(_request == INVALID_HANDLE)
   {  Print("request failed with err: ", GetLastError());
      DatabaseClose(_db_handle);
      class="kw">return(_data);
   }
   while(DatabaseRead(_request))
   {  class=class="str">"cmt">//--- read the values of each field from the obtained entry
      ResetLastError();
      if(!DatabaseColumnDouble(_request, class="num">0, _data))
      {  Print(" DatabaseRead() failed with err: ", GetLastError());
         DatabaseFinalize(_request);
         DatabaseClose(_db_handle);
      }
   }
   class="kw">return(_data);
}

◍ 把财经日历塞进本地 SQLite 的笨办法

在 MT5 里落库财经日历,核心是先开一个数据库句柄,逻辑跟读写文件句柄差不多。下面这段脚本给每个货币对单独建一个 .sqlite 文件,作者自己也承认这是浪费空间的做法——更合理的结构是把所有跨货币数据放进同一个库,但懒得改,读者可以自己合并。 句柄拿到后必须检查是不是 INVALID_HANDLE,有效才说明是空白库能继续建表。本文只盯“板块价格”这一类日历事件(通胀、CPI、PPI 都归在这把伞下),所以表名直接叫 PRICES,目标是后面用两国通胀差做多空信号,这条路是几种替代方案里最省事的。 PRICES 表固定 4 列:DATE 文本存发布时间,FORECAST 实数存预期值,ACTUAL 实数存实际值,EVENT 文本存事件代码。因为同一货币同一天可能有多个价格类事件,EVENT 标签靠 CalendarValueHistoryByEvent 返回的描述码来填,检索时才能对号入座。 写入前先用 DatabaseTransactionBegin 和 DatabaseCommit 包住,比单条插快得多;再用 DatabaseBind 把多维字符串数组 _data 绑进表,第二维长度刚好等于 4 列。插完库发现在策略测试器里读不到,只能再导出 CSV 给回测用——MetaEditor 里直接跑脚本查 USD 库能打印出结果,但测试器环境隔离了数据库访问。 外汇与贵金属杠杆高、新闻跳空风险大,用日历数据建模前先在 demo 跑通读写链路。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Sourced from: [MQL5官方文档]
class=class="str">"cmt">//| and here: [MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
class=class="str">"cmt">//--- create or open a database
   class="type">class="kw">string _db_file = __currency + "_econ_cal.sqlite";
   class="type">int _db_handle = DatabaseOpen(_db_file, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
   if(_db_handle == INVALID_HANDLE)
   {  Print("DB: ", _db_file, " open failed with code ", GetLastError());
      class="kw">return;
   }
   else
      Print("Database: ", _db_file, " opened successfully");
...
      ...
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
      ...
class=class="str">"cmt">//--- if the PRICES table exists, class="kw">delete it
   if(DatabaseTableExists(_db_handle, "PRICES"))
   {  class=class="str">"cmt">//--- class="kw">delete the table
      if(!DatabaseExecute(_db_handle, "DROP TABLE PRICES"))
      {  Print("Failed to drop table PRICES with code ", GetLastError());
         DatabaseClose(_db_handle);
         class="kw">return;
      }
   }
class=class="str">"cmt">//--- create the PRICES table
   if(!DatabaseExecute(_db_handle, "CREATE TABLE PRICES("
                           "DATE            TEXT            ,"
                           "FORECAST        REAL            ,"

常见问题

先把财经日历数据落成本地数据库,回测时让策略按日期读取对应事件,再跑历史数据验证冲击。
测试器直接读库容易中断,可先把库里数据导出成 CSV,EA 在测试中读 CSV 就能绕开卡点。
小布可替你拉取并整理财经日历、生成回测用的数据文件,你只需在策略里调用对应接口。
用现成表格工具分批粘数据再转库,虽慢但能跑通,重点是先保证日期和事件字段对齐。
检查本地数据时区与经纪商行情时区是否一致,并确认事件级别过滤没误删关键数据。