使用MQL5经济日历进行交易(第七部分):基于资源型新闻事件分析的策略测试准备·进阶篇
🗂️

使用MQL5经济日历进行交易(第七部分):基于资源型新闻事件分析的策略测试准备·进阶篇

(2/3)·策略测试器不存档历史事件,新闻驱动策略回测缺数据?用资源文件自己造一套可控环境

实战向 第 2/3 篇

不少交易者直接拿策略测试器跑新闻型EA,发现历史经济事件根本调不出来,回测结果自然失真。平台不长期保留日历档案,实盘能看的事件在测试环境里是一片空白。本文接着上篇的自动化信号,先把回测用的静态事件数据搭好。

过滤数组的预处理与影响力分级

做新闻或品种过滤时,先给字符串数组做trim是容易被忽略的一步。下面这段循环把 curr_filter 每个元素去掉首尾空格再写回,并用 Print 打出索引和值,方便在MT5专家日志里确认过滤器到底吃进了什么。 for (int i = 0; i < ArraySize(curr_filter); i++) { string temp = curr_filter[i]; StringTrimLeft(temp); StringTrimRight(temp); curr_filter[i] = temp; Print("Currency filter [", i, "]: '", curr_filter[i], "'"); } 如果勾了 ApplyCurrencyFilter 但数组为空,代码会打印一条警告并把 curr_filter 缩容到0,等于不过滤。这个分支提醒你:空过滤器不代表“全通过”,而是直接跳过逻辑。 影响力过滤用枚举+switch实现。IMP_NONE 到 IMP_HIGH 各自把 imp_filter 缩容成1并写入单一字符串;IMP_NONE_LOW 则缩容成2,同时装 "None" 和 "Low"。回测时若发现低影响新闻仍触发交易,先查这里的枚举映射是不是漏了档位。 外汇与贵金属受新闻冲击波动剧烈,这类过滤只能降低噪声,无法消除跳空风险,参数调完务必在模拟盘验证。

MQL5 / C++
for (class="type">int i = class="num">0; i < ArraySize(curr_filter); i++) {
  class=class="str">"cmt">//---- Temporary variable for trimming
  class="type">class="kw">string temp = curr_filter[i];
  class=class="str">"cmt">//---- Remove leading whitespace
  StringTrimLeft(temp);
  class=class="str">"cmt">//---- Remove trailing whitespace
  StringTrimRight(temp);
  class=class="str">"cmt">//---- Assign trimmed value back to array
  curr_filter[i] = temp;
  class=class="str">"cmt">//---- Print currency filter for debugging
  Print("Currency filter [", i, "]: &class="macro">#x27;", curr_filter[i], "&class="macro">#x27;");
}
} else if (ApplyCurrencyFilter) {
  class=class="str">"cmt">//---- Warn if currency filter is enabled but empty
  Print("Warning: CurrencyFilter is empty, no currency filtering applied");
  class=class="str">"cmt">//---- Resize array to zero if no filter applied
  ArrayResize(curr_filter, class="num">0);
}
}
class=class="str">"cmt">//---- Impact Filter Section(using enum)
class=class="str">"cmt">//---- Check if impact filter is enabled
if (ApplyImpactFilter) {
  class=class="str">"cmt">//---- Switch based on selected importance filter
  class="kw">switch (ImportanceFilter) {
    case IMP_NONE:
      class=class="str">"cmt">//---- Resize array for single importance level
      ArrayResize(imp_filter, class="num">1);
      class=class="str">"cmt">//---- Set importance to "None"
      imp_filter[class="num">0] = "None";
      break;
    case IMP_LOW:
      class=class="str">"cmt">//---- Resize array for single importance level
      ArrayResize(imp_filter, class="num">1);
      class=class="str">"cmt">//---- Set importance to "Low"
      imp_filter[class="num">0] = "Low";
      break;
    case IMP_MEDIUM:
      class=class="str">"cmt">//---- Resize array for single importance level
      ArrayResize(imp_filter, class="num">1);
      class=class="str">"cmt">//---- Set importance to "Medium"
      imp_filter[class="num">0] = "Medium";
      break;
    case IMP_HIGH:
      class=class="str">"cmt">//---- Resize array for single importance level
      ArrayResize(imp_filter, class="num">1);
      class=class="str">"cmt">//---- Set importance to "High"
      imp_filter[class="num">0] = "High";
      break;
    case IMP_NONE_LOW:
      class=class="str">"cmt">//---- Resize array for two importance levels
      ArrayResize(imp_filter, class="num">2);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "None";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Low";
      break;

◍ 新闻过滤器按重要级组合切分

在 MT5 的财经日历模块里,新闻重要性筛选常写成枚举分支,用 imp_filter 字符串数组承接具体级别。上面这段分支覆盖了两级组合与三级组合两种形态:两级时数组长度固定为 2,三级时 ArrayResize 改为 3。 以 IMP_NONE_MEDIUM 为例,先 ArrayResize(imp_filter,2) 腾出两个槽位,再分别写入 "None" 与 "Medium",break 退出。IMP_NONE_LOW_MEDIUM 则把数组扩到 3,依次填 None、Low、Medium,对应想屏蔽高影响外所有新闻的场景。 实盘接这层过滤时要注意,外汇与贵金属受 CPI、非农类 High 新闻冲击,点差可能瞬间放大数倍,属高风险时段;用 IMP_LOW_HIGH 这类组合只留 Low 和 High,等于主动放过了 Medium 的利率决议前瞻,可能漏掉中线波动触发点。

MQL5 / C++
case IMP_NONE_MEDIUM:
      class=class="str">"cmt">//---- Resize array for two importance levels
      ArrayResize(imp_filter, class="num">2);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "None";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Medium";
      break;
case IMP_NONE_HIGH:
      class=class="str">"cmt">//---- Resize array for two importance levels
      ArrayResize(imp_filter, class="num">2);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "None";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "High";
      break;
case IMP_LOW_MEDIUM:
      class=class="str">"cmt">//---- Resize array for two importance levels
      ArrayResize(imp_filter, class="num">2);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "Low";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Medium";
      break;
case IMP_LOW_HIGH:
      class=class="str">"cmt">//---- Resize array for two importance levels
      ArrayResize(imp_filter, class="num">2);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "Low";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "High";
      break;
case IMP_MEDIUM_HIGH:
      class=class="str">"cmt">//---- Resize array for two importance levels
      ArrayResize(imp_filter, class="num">2);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "Medium";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "High";
      break;
case IMP_NONE_LOW_MEDIUM:
      class=class="str">"cmt">//---- Resize array for three importance levels
      ArrayResize(imp_filter, class="num">3);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "None";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Low";
      class=class="str">"cmt">//---- Set third importance level
      imp_filter[class="num">2] = "Medium";
      break;
case IMP_NONE_LOW_HIGH:
      class=class="str">"cmt">//---- Resize array for three importance levels

「用枚举切新闻影响等级过滤档位」

在 MT5 的 EA 或指标里做新闻过滤,常见做法是把影响等级映射成字符串数组。下面这段逻辑按枚举分支给 imp_filter 动态定容并填值:None/Low、None/Medium、Low/Medium 三组都只留 3 格,IMP_ALL 则扩到 4 格把四个档全装进去。 ArrayResize 每次都先于赋值调用,这点不能反——MQL5 里未Resize就写下标会越界报错。分支末尾统一 break,避免穿透到下一个 case 把数组覆盖掉。 初始化后用一个 for 循环把 imp_filter 内容 Print 出来,终端里能看到类似 Impact filter [0]: 'None' 的回显,用来确认当前档位是否生效。若开关关掉,直接 ArrayResize(imp_filter,0) 清空,并打印 Impact filter disabled。 把这段代码塞进 OnInit 调用的 InitializeFilters,编译后开 MT5 专家日志就能验证档位切换:改一下枚举传入值,数组长度和首元素会跟着变。外汇与贵金属受新闻跳空影响大,这类过滤只降低踩雷概率,不保证避损。

MQL5 / C++
      ArrayResize(imp_filter, class="num">3);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "None";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Low";
      class=class="str">"cmt">//---- Set third importance level
      imp_filter[class="num">2] = "High";
      break;
      case IMP_NONE_MEDIUM_HIGH:
      class=class="str">"cmt">//---- Resize array for three importance levels
      ArrayResize(imp_filter, class="num">3);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "None";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Medium";
      class=class="str">"cmt">//---- Set third importance level
      imp_filter[class="num">2] = "High";
      break;
      case IMP_LOW_MEDIUM_HIGH:
      class=class="str">"cmt">//---- Resize array for three importance levels
      ArrayResize(imp_filter, class="num">3);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "Low";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Medium";
      class=class="str">"cmt">//---- Set third importance level
      imp_filter[class="num">2] = "High";
      break;
      case IMP_ALL:
      class=class="str">"cmt">//---- Resize array for all importance levels
      ArrayResize(imp_filter, class="num">4);
      class=class="str">"cmt">//---- Set first importance level
      imp_filter[class="num">0] = "None";
      class=class="str">"cmt">//---- Set second importance level
      imp_filter[class="num">1] = "Low";
      class=class="str">"cmt">//---- Set third importance level
      imp_filter[class="num">2] = "Medium";
      class=class="str">"cmt">//---- Set fourth importance level
      imp_filter[class="num">3] = "High";
      break;
   }
   class=class="str">"cmt">//---- Loop through impact filter array to print values
   for (class="type">int i = class="num">0; i < ArraySize(imp_filter); i++) {
      class=class="str">"cmt">//---- Print each impact filter value
      Print("Impact filter [", i, "]: &class="macro">#x27;", imp_filter[i], "&class="macro">#x27;");
   }
} else {
   class=class="str">"cmt">//---- Notify if impact filter is disabled
   Print("Impact filter disabled");
   class=class="str">"cmt">//---- Resize impact filter array to zero
   ArrayResize(imp_filter, class="num">0);
}
class="type">int OnInit() {
   class=class="str">"cmt">//---- Initialize filters
   InitializeFilters();
   class=class="str">"cmt">//---- Return successful initialization
   class="kw">return(INIT_SUCCEEDED);
}

EA 退出与财经日历数据的初始化校验

EA 卸载时先打印终止原因码,方便在 MT5 Experts 日志里回溯是被手动移除还是因账户断开。OnDeinit 里的这行 Print 不耗资源,但能省掉你事后猜「为啥不跑了」的时间。 实盘环境下(非回测)才去拉日历数据:用 MQLInfoInteger(MQL_TESTER) 判断,避免策略测试器里频繁请求经济事件接口拖慢优化速度。若 StartDate 晚于 EndDate,直接返回 INIT_PARAMETERS_INCORRECT,参数面板填反了 EA 根本不会启动。 CalendarValueHistory 按时间区间把 MqlCalendarValue 数组填满;任一环节取不到事件、国家或数值详情就 continue 跳过,保证 events[] 里只留完整记录。时间字段用 TimeToString 转成『YYYY.MM.DD HH:MM』后,再用 StringSubstr 硬拆出前 10 位日期与后 5 位时间——这种字符串截取方式在 MT5 build 3400+ 均稳定。 外汇与贵金属受 CPI、非农等事件冲击跳空概率高,用这段逻辑把事件时间对齐到本地K线,才可能在大新闻前自动降仓或禁开。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason) {
   class=class="str">"cmt">//---- Print termination reason
   Print("EA terminated, reason: ", reason);
}
class=class="str">"cmt">//---- Check if not running in tester mode
if (!MQLInfoInteger(MQL_TESTER)) {
   class=class="str">"cmt">//---- Validate date range
   if (StartDate >= EndDate) {
      class=class="str">"cmt">//---- Print error for invalid date range
      Print("Error: StartDate(", TimeToString(StartDate), ") must be earlier than EndDate(", TimeToString(EndDate), ")");
      class=class="str">"cmt">//---- Return initialization failure
      class="kw">return(INIT_PARAMETERS_INCORRECT);
   }
   class=class="str">"cmt">//---- Array to hold calendar values
   MqlCalendarValue values[];
   class=class="str">"cmt">//---- Fetch calendar data for date range
   if (!CalendarValueHistory(values, StartDate, EndDate)) {
      class=class="str">"cmt">//---- Print error if calendar data fetch fails
      Print("Error fetching calendar data: ", GetLastError());
      class=class="str">"cmt">//---- Return initialization failure
      class="kw">return(INIT_FAILED);
   }
   class=class="str">"cmt">//---- Array to hold economic events
   EconomicEvent events[];
   class=class="str">"cmt">//---- Counter for events
   class="type">int eventCount = class="num">0;
   class=class="str">"cmt">//---- Loop through calendar values
   for (class="type">int i = class="num">0; i < ArraySize(values); i++) {
      class=class="str">"cmt">//---- Structure for event details
      MqlCalendarEvent eventDetails;
      class=class="str">"cmt">//---- Fetch event details by ID
      if (!CalendarEventById(values[i].event_id, eventDetails)) class="kw">continue;
      class=class="str">"cmt">//---- Structure for country details
      MqlCalendarCountry countryDetails;
      class=class="str">"cmt">//---- Fetch country details by ID
      if (!CalendarCountryById(eventDetails.country_id, countryDetails)) class="kw">continue;
      class=class="str">"cmt">//---- Structure for value details
      MqlCalendarValue value;
      class=class="str">"cmt">//---- Fetch value details by ID
      if (!CalendarValueById(values[i].id, value)) class="kw">continue;
      class=class="str">"cmt">//---- Resize events array for new event
      ArrayResize(events, eventCount + class="num">1);
      class=class="str">"cmt">//---- Convert event time to class="type">class="kw">string
      class="type">class="kw">string dateTimeStr = TimeToString(values[i].time, TIME_DATE | TIME_MINUTES);
      class=class="str">"cmt">//---- Extract date from class="type">class="kw">datetime class="type">class="kw">string
      events[eventCount].eventDate = StringSubstr(dateTimeStr, class="num">0, class="num">10);
      class=class="str">"cmt">//---- Extract time from class="type">class="kw">datetime class="type">class="kw">string
      events[eventCount].eventTime = StringSubstr(dateTimeStr, class="num">11, class="num">5);
      class=class="str">"cmt">//---- Assign currency from country details
      events[eventCount].currency = countryDetails.currency;
      class=class="str">"cmt">//---- Assign event name
      events[eventCount].event = eventDetails.name;
      class=class="str">"cmt">//---- Map importance level from enum to class="type">class="kw">string

◍ 把财经事件落盘成 CSV 的细节

抓取完财经事件后,下一步是把结构体数组写进 CSV,方便在 Excel 或 Python 里做后续统计。MQL5 的 FileOpen 用 FILE_WRITE|FILE_CSV 加逗号分隔,表头写死为 Date,Time,Currency,Event,Importance,Actual,Forecast,Previous 共 8 列。 重要性字段在入库前就做了映射:0 转成 None、1 转 Low、2 转 Medium,其余一律 High,对应平台底层的 CALENDAR_IMPORTANCE_* 宏。实际值、预测值、前值分别由 GetActualValue / GetForecastValue / GetPreviousValue 取回,写文件时统一用 DoubleToString(...,2) 保留两位小数。 循环里每写一行都会顺手 Print 一次,方便在 MT5 专家日志里核对序号和数值;若 FileOpen 返回 INVALID_HANDLE,直接打印错误码并 return,不会继续写空文件。外汇与贵金属受这些数据冲击明显,事件波动属高风险,验证时建议先用小周期回测。

MQL5 / C++
   events[eventCount].importance = (eventDetails.importance == class="num">0) ? "None" :   class=class="str">"cmt">// CALENDAR_IMPORTANCE_NONE
                                         (eventDetails.importance == class="num">1) ? "Low" :    class=class="str">"cmt">// CALENDAR_IMPORTANCE_LOW
                                         (eventDetails.importance == class="num">2) ? "Medium" : class=class="str">"cmt">// CALENDAR_IMPORTANCE_MODERATE
                                         "High";                                     class=class="str">"cmt">// CALENDAR_IMPORTANCE_HIGH
   class=class="str">"cmt">//---- Assign actual value
   events[eventCount].actual = value.GetActualValue();
   class=class="str">"cmt">//---- Assign forecast value
   events[eventCount].forecast = value.GetForecastValue();
   class=class="str">"cmt">//---- Assign previous value
   events[eventCount].previous = value.GetPreviousValue();
   class=class="str">"cmt">//---- Increment event count
   eventCount++;
   }
}
class=class="str">"cmt">//---- Function to write events to a CSV file
class="type">void WriteToCSV(class="type">class="kw">string fileName, EconomicEvent &events[]) {
   class=class="str">"cmt">//---- Open file for writing in CSV format
   class="type">int handle = FileOpen(fileName, FILE_WRITE | FILE_CSV, &class="macro">#x27;,&class="macro">#x27;);
   class=class="str">"cmt">//---- Check if file opening failed
   if (handle == INVALID_HANDLE) {
      class=class="str">"cmt">//---- Print error message with last error code
      Print("Error creating file: ", GetLastError());
      class=class="str">"cmt">//---- Exit function on failure
      class="kw">return;
   }
   class=class="str">"cmt">//---- Write CSV header row
   FileWrite(handle, "Date", "Time", "Currency", "Event", "Importance", "Actual", "Forecast", "Previous");
   class=class="str">"cmt">//---- Loop through all events to write to file
   for (class="type">int i = class="num">0; i < ArraySize(events); i++) {
      class=class="str">"cmt">//---- Write event data to CSV file
      FileWrite(handle, events[i].eventDate, events[i].eventTime, events[i].currency, events[i].event,
                events[i].importance, DoubleToString(events[i].actual, class="num">2), DoubleToString(events[i].forecast, class="num">2),
                DoubleToString(events[i].previous, class="num">2));
      class=class="str">"cmt">//---- Print event details for debugging
      Print("Writing event ", i, ": ", events[i].eventDate, ", ", events[i].eventTime, ", ", events[i].currency, ", ",
            events[i].event, ", ", events[i].importance, ", ", DoubleToString(events[i].actual, class="num">2), ", ",
            DoubleToString(events[i].forecast, class="num">2), ", ", DoubleToString(events[i].previous, class="num">2));
   }

「回写校验与历史数据资源加载」

把事件数组落盘后,别急着认为文件就可靠。代码里用 FileFlush 强制刷缓存、FileClose 释放句柄,再用 FileOpen 以 FILE_READ|FILE_TXT 重新打开同一文件,把 FileSize 返回的字节数和 FileReadString 读出的整段内容打印出来,这一步能让你在日志里直接看到写入是否完整。 实盘模式下数据写到了 Database\EconomicCalendar.csv。若用策略测试器回测,MT5 不会自动读取运行时写的文件,必须手动把该 CSV 添加为资源(#resource)并重新编译,否则 LoadEventsFromResource 读到的可能是空壳。 FileExists 用 FileOpen 试探 FILE_CSV 读权限来判断文件有无,命中就关句柄返回 true。LoadEventsFromResource 则从资源字符串取数据,StringSplit 按 '\n' 切行;当切出 lineCount <= 1 时说明没有数据行,会打印原始内容并返回 false,这种失败在资源未正确嵌入时极易触发。 外汇与贵金属事件驱动回测高风险,资源路径错一处就可能让整段宏观日历失效,建议在 Live 模式先确认 Print 出的字节数符合预期再切测试器。

MQL5 / C++
  class=class="str">"cmt">//---- Flush data to file
  FileFlush(handle);
  class=class="str">"cmt">//---- Close the file handle
  FileClose(handle);
  class=class="str">"cmt">//---- Print confirmation of data written
  Print("Data written to ", fileName, " with ", ArraySize(events), " events.");
  class=class="str">"cmt">//---- Verify written file by reading it back
  class="type">int verifyHandle = FileOpen(fileName, FILE_READ | FILE_TXT);
  class=class="str">"cmt">//---- Check if verification file opening succeeded
  if (verifyHandle != INVALID_HANDLE) {
    class=class="str">"cmt">//---- Read entire file content
    class="type">class="kw">string content = FileReadString(verifyHandle, (class="type">int)FileSize(verifyHandle));
    class=class="str">"cmt">//---- Print file content for verification
    Print("File content after writing(size: ", FileSize(verifyHandle), " bytes):\n", content);
    class=class="str">"cmt">//---- Close verification file handle
    FileClose(verifyHandle);
  }
}

class=class="str">"cmt">//---- Define file path for CSV
class="type">class="kw">string fileName = "Database\\EconomicCalendar.csv";
class=class="str">"cmt">//---- Check if file exists and print appropriate message
if (!FileExists(fileName)) Print("Creating new file: ", fileName);
else Print("Overwriting existing file: ", fileName);
class=class="str">"cmt">//---- Write events to CSV file
WriteToCSV(fileName, events);
class=class="str">"cmt">//---- Print instructions for tester mode
Print("Live mode: Data written. To use in tester, manually add ", fileName, " as a resource and recompile.");

class=class="str">"cmt">//---- Function to check if a file exists
class="type">bool FileExists(class="type">class="kw">string fileName) {
  class=class="str">"cmt">//---- Open file in read mode to check existence
  class="type">int handle = FileOpen(fileName, FILE_READ | FILE_CSV);
  class=class="str">"cmt">//---- Check if file opened successfully
  if (handle != INVALID_HANDLE) {
    class=class="str">"cmt">//---- Close the file handle
    FileClose(handle);
    class=class="str">"cmt">//---- Return true if file exists
    class="kw">return true;
  }
  class=class="str">"cmt">//---- Return false if file doesn&class="macro">#x27;t exist
  class="kw">return false;
}
class=class="str">"cmt">//---- Define resource file for economic calendar data
class="macro">#resource "\\Files\\Database\\EconomicCalendar.csv" as class="type">class="kw">string EconomicCalendarData
class=class="str">"cmt">//---- Function to load events from resource file
class="type">bool LoadEventsFromResource() {
  class=class="str">"cmt">//---- Get data from resource
  class="type">class="kw">string fileData = EconomicCalendarData;
  class=class="str">"cmt">//---- Print raw resource content for debugging
  Print("Raw resource content(size: ", StringLen(fileData), " bytes):\n", fileData);
  class=class="str">"cmt">//---- Array to hold lines from resource
  class="type">class="kw">string lines[];
  class=class="str">"cmt">//---- Split resource data into lines
  class="type">int lineCount = StringSplit(fileData, &class="macro">#x27;\n&class="macro">#x27;, lines);
  class=class="str">"cmt">//---- Check if resource has valid data
  if (lineCount <= class="num">1) {
    class=class="str">"cmt">//---- Print error if no data lines found
    Print("Error: No data lines found in resource! Raw data: ", fileData);
    class=class="str">"cmt">//---- Return false on failure
    class="kw">return false;
  }
把事件集导入交给小布盯盘
小布盯盘的AIGC模块已内置经济事件解析,打开对应品种页即可导出筛选后的CSV,省去自己抓档整理的重复劳动,你只管定义高影响资源新闻的过滤逻辑。

常见问题

测试器不保留长期历史经济事件档案,也不会接入实时资讯推送,因此原生环境下无法回放新闻驱动行情,需要自行准备静态数据集。
可以,小布盯盘的品种页支持按货币与影响级别筛选事件并导出CSV,直接作为本篇所说的嵌入资源文件使用,减少手工整理成本。
不一定,MQL5也支持bin、txt、数据库等,但CSV改起来直观、加载方便,本篇选它平衡了便捷与可控,你可按项目换格式。
事件日期、时间、关联货币、影响级别是底线,资源类还可补商品类别标签,便于在测试器中按自定义条件触发或跳过。
上篇已覆盖倒计时定时器与信号自动化,本篇聚焦测试数据本身,跨篇组合才能跑通从信号到回测的闭环。