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

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

第 3/3 篇

「逐行解析财经事件 CSV 的清洗逻辑」

把财经事件表读进 MT5 后,第一步不是画图而是清洗。先清空事件数组并置索引为 0,再从第二行(i=1)开始跳过头行,避免把表头当数据塞进去。 空行直接 Print 跳过的设计很实用:用 StringLen(lines[i])==0 判断,continue 进下一轮,日志里会留下 "Skipping empty line N",方便你核对源文件缺行。 每行按逗号切分后必须检查字段数。代码里写死 fieldCount<8 才视为畸形行——这意味着你的 CSV 至少要有日期、时间、货币、事件名、重要性、实际值、预测值、前值这 8 段,少一段就整行丢弃并打印 Malformed。 事件名里可能自带逗号,所以不能简单取 fields[3]。循环把第 4 个字段到倒数第 5 个字段全部拼回 event 字符串,重要性等四个尾部字段永远从 fieldCount-4 往后取,这样无论事件名多长都不会错位。 日期时间用 StringToTime(dateStr+" "+timeStr) 转 datetime,若返回 0 说明格式不对。外汇与贵金属受事件冲击大、杠杆高风险突出,时间解析错一位就可能把非农误判到周末,建议开 MT5 把这段直接跑一遍看日志。

MQL5 / C++
  class=class="str">"cmt">//---- Reset events array
  ArrayResize(allEvents, class="num">0);
  class=class="str">"cmt">//---- Index for event array
  class="type">int eventIndex = class="num">0;
  class=class="str">"cmt">//---- Loop through each line(skip header at i=class="num">0)
  for (class="type">int i = class="num">1; i < lineCount; i++) {
    class=class="str">"cmt">//---- Check for empty lines
    if (StringLen(lines[i]) == class="num">0) {
      class=class="str">"cmt">//---- Print message for skipped empty line
      Print("Skipping empty line ", i);
      class=class="str">"cmt">//---- Skip to next iteration
      class="kw">continue;
    }
    class=class="str">"cmt">//---- Array to hold fields from each line
    class="type">class="kw">string fields[];
    class=class="str">"cmt">//---- Split line into fields
    class="type">int fieldCount = StringSplit(lines[i], &class="macro">#x27;,&class="macro">#x27;, fields);
    class=class="str">"cmt">//---- Print line details for debugging
    Print("Line ", i, ": ", lines[i], " (field count: ", fieldCount, ")");
    class=class="str">"cmt">//---- Check if line has minimum required fields
    if (fieldCount < class="num">8) {
      class=class="str">"cmt">//---- Print error for malformed line
      Print("Malformed line ", i, ": ", lines[i], " (field count: ", fieldCount, ")");
      class=class="str">"cmt">//---- Skip to next iteration
      class="kw">continue;
    }
    class=class="str">"cmt">//---- Extract date from field
    class="type">class="kw">string dateStr = fields[class="num">0];
    class=class="str">"cmt">//---- Extract time from field
    class="type">class="kw">string timeStr = fields[class="num">1];
    class=class="str">"cmt">//---- Extract currency from field
    class="type">class="kw">string currency = fields[class="num">2];
    class=class="str">"cmt">//---- Extract event description(handle commas in event name)
    class="type">class="kw">string event = fields[class="num">3];
    class=class="str">"cmt">//---- Combine multiple fields if event name contains commas
    for (class="type">int j = class="num">4; j < fieldCount - class="num">4; j++) {
      event += "," + fields[j];
    }
    class=class="str">"cmt">//---- Extract importance from field
    class="type">class="kw">string importance = fields[fieldCount - class="num">4];
    class=class="str">"cmt">//---- Extract actual value from field
    class="type">class="kw">string actualStr = fields[fieldCount - class="num">3];
    class=class="str">"cmt">//---- Extract forecast value from field
    class="type">class="kw">string forecastStr = fields[fieldCount - class="num">2];
    class=class="str">"cmt">//---- Extract previous value from field
    class="type">class="kw">string previousStr = fields[fieldCount - class="num">1];
    class=class="str">"cmt">//---- Convert date and time to class="type">class="kw">datetime format
    class="type">class="kw">datetime eventDateTime = StringToTime(dateStr + " " + timeStr);
    class=class="str">"cmt">//---- Check if class="type">class="kw">datetime conversion failed
    if (eventDateTime == class="num">0) {
      class=class="str">"cmt">//---- Print error for invalid class="type">class="kw">datetime

◍ 把日历资源塞进数组并接上 OnTick 骨架

这段逻辑干了两件事:先在校验失败后跳过当前行,再把通过校验的宏观事件逐字段写进 allEvents 数组。日期、时间、货币、描述、重要性直接赋值,actual/forecast/previous 三个数值用 StringToDouble 转双精度,最后 eventIndex 自增并打印“Loaded event N”供日志核对。 若走的是策略测试器分支且 EconomicCalendarData 长度为 0,会直接返回 INIT_FAILED 并提示资源为空——这意味着你必须在实盘模式把 EconomicCalendar.csv 加为资源重新编译,否则回测起不来。 OnTick 里只留了个 lastBarTime 的框架:用 iTime(_Symbol,_Period,0) 取当前柱时间,和上次比对,变了就更新。后续过滤事件打印的入口就挂在这个柱切换判断下面。开 MT5 把这段塞进 EA,先跑一次看日志里 Loaded 了多少条事件,就能确认资源路径对不对。

MQL5 / C++
Print("Error: Invalid class="type">class="kw">datetime conversion for line ", i, ": ", dateStr, " ", timeStr);
class=class="str">"cmt">//---- Skip to next iteration
class="kw">continue;
}
class=class="str">"cmt">//---- Resize events array for new event
ArrayResize(allEvents, eventIndex + class="num">1);
class=class="str">"cmt">//---- Assign event date
allEvents[eventIndex].eventDate = dateStr;
class=class="str">"cmt">//---- Assign event time
allEvents[eventIndex].eventTime = timeStr;
class=class="str">"cmt">//---- Assign event currency
allEvents[eventIndex].currency = currency;
class=class="str">"cmt">//---- Assign event description
allEvents[eventIndex].event = event;
class=class="str">"cmt">//---- Assign event importance
allEvents[eventIndex].importance = importance;
class=class="str">"cmt">//---- Convert and assign actual value
allEvents[eventIndex].actual = StringToDouble(actualStr);
class=class="str">"cmt">//---- Convert and assign forecast value
allEvents[eventIndex].forecast = StringToDouble(forecastStr);
class=class="str">"cmt">//---- Convert and assign previous value
allEvents[eventIndex].previous = StringToDouble(previousStr);
class=class="str">"cmt">//---- Print loaded event details
Print("Loaded event ", eventIndex, ": ", dateStr, " ", timeStr, ", ", currency, ", ", event);
class=class="str">"cmt">//---- Increment event index
eventIndex++;
}
class=class="str">"cmt">//---- Print total events loaded
Print("Loaded ", eventIndex, " events from resource into array.");
class=class="str">"cmt">//---- Return success if events were loaded
class="kw">return eventIndex > class="num">0;
}
else {
class=class="str">"cmt">//---- Check if resource data is empty in tester mode
if (StringLen(EconomicCalendarData) == class="num">0) {
class=class="str">"cmt">//---- Print error for empty resource
Print("Error: Resource EconomicCalendarData is empty. Please run in live mode, add the file as a resource, and recompile.");
class=class="str">"cmt">//---- Return initialization failure
class="kw">return(INIT_FAILED);
}
class=class="str">"cmt">//---- Print message for tester mode
Print("Running in Strategy Tester, using embedded resource: Database\\EconomicCalendar.csv");
class=class="str">"cmt">//---- Load events from resource
if (!LoadEventsFromResource()) {
class=class="str">"cmt">//---- Print error if loading fails
Print("Failed to load events from resource.");
class=class="str">"cmt">//---- Return initialization failure
class="kw">return(INIT_FAILED);
}
}
class=class="str">"cmt">//---- Variable to track last bar time
class="type">class="kw">datetime lastBarTime = class="num">0;
class=class="str">"cmt">//---- Tick event handler
class="type">void OnTick() {
class=class="str">"cmt">//---- Get current bar time
class="type">class="kw">datetime currentBarTime = iTime(_Symbol, _Period, class="num">0);
class=class="str">"cmt">//---- Check if bar time has changed
if (currentBarTime != lastBarTime) {
class=class="str">"cmt">//---- Update last bar time
lastBarTime = currentBarTime;
class=class="str">"cmt">//----
}
}
class=class="str">"cmt">//---- Function to filter and print economic events

用时间窗筛掉无关财经事件

把全部已加载的财经事件按 K 线时间做窗口过滤,是减少噪音的直接办法。下面这段函数先打印总事件数,若数组为空就直接退出,避免后续空跑。 当 ApplyTimeFilter 开关打开时,它会以当前 bar 时间为基准,按 HoursBefore/MinutesBefore 往前推、按 HoursAfter/MinutesAfter 往后推,生成 timeBefore 与 timeAfter 两个边界。注意分钟借位(min<0 则加 60 减 1 小时)和小时跨日(hour<0 则加 24 减 1 天)的处理,否则 StructToTime 会得到错误时间戳。 调试时 Print 出的 TimeToString 范围,能让你在 MT5 Experts 日志里核对:比如某根 H1 bar 时间 2024.01.03 14:00,设 HoursBefore=2、MinutesBefore=0、HoursAfter=1,则窗口应是 12:00 到 15:00。外汇与贵金属受事件驱动跳空概率高,验证窗口边界有助于判断滑点风险。

MQL5 / C++
class="type">void FilterAndPrintEvents(class="type">class="kw">datetime barTime) {
  class=class="str">"cmt">//---- Get total number of events
  class="type">int totalEvents = ArraySize(allEvents);
  class=class="str">"cmt">//---- Print total events considered
  Print("Total considered data size: ", totalEvents, " events");
  class=class="str">"cmt">//---- Check if there are events to filter
  if (totalEvents == class="num">0) {
    class=class="str">"cmt">//---- Print message if no events loaded
    Print("No events loaded to filter.");
    class=class="str">"cmt">//---- Exit function
    class="kw">return;
  }
  class=class="str">"cmt">//---- Array to store filtered events
  EconomicEvent filteredEvents[];
  class=class="str">"cmt">//---- Counter for filtered events
  class="type">int filteredCount = class="num">0;
  class=class="str">"cmt">//---- Variables for time range
  class="type">class="kw">datetime timeBefore, timeAfter;
  class=class="str">"cmt">//---- Apply time filter if enabled
  if (ApplyTimeFilter) {
    class=class="str">"cmt">//---- Structure for bar time
    class="type">MqlDateTime barStruct;
    class=class="str">"cmt">//---- Convert bar time to structure
    TimeToStruct(barTime, barStruct);
    class=class="str">"cmt">//---- Calculate time before event
    class="type">MqlDateTime timeBeforeStruct = barStruct;
    class=class="str">"cmt">//---- Subtract hours before
    timeBeforeStruct.hour -= HoursBefore;
    class=class="str">"cmt">//---- Subtract minutes before
    timeBeforeStruct.min -= MinutesBefore;
    class=class="str">"cmt">//---- Adjust for negative minutes
    if (timeBeforeStruct.min < class="num">0) {
      timeBeforeStruct.min += class="num">60;
      timeBeforeStruct.hour -= class="num">1;
    }
    class=class="str">"cmt">//---- Adjust for negative hours
    if (timeBeforeStruct.hour < class="num">0) {
      timeBeforeStruct.hour += class="num">24;
      timeBeforeStruct.day -= class="num">1;
    }
    class=class="str">"cmt">//---- Convert structure to class="type">class="kw">datetime
    timeBefore = StructToTime(timeBeforeStruct);
    class=class="str">"cmt">//---- Calculate time after event
    class="type">MqlDateTime timeAfterStruct = barStruct;
    class=class="str">"cmt">//---- Add hours after
    timeAfterStruct.hour += HoursAfter;
    class=class="str">"cmt">//---- Add minutes after
    timeAfterStruct.min += MinutesAfter;
    class=class="str">"cmt">//---- Adjust for minutes overflow
    if (timeAfterStruct.min >= class="num">60) {
      timeAfterStruct.min -= class="num">60;
      timeAfterStruct.hour += class="num">1;
    }
    class=class="str">"cmt">//---- Adjust for hours overflow
    if (timeAfterStruct.hour >= class="num">24) {
      timeAfterStruct.hour -= class="num">24;
      timeAfterStruct.day += class="num">1;
    }
    class=class="str">"cmt">//---- Convert structure to class="type">class="kw">datetime
    timeAfter = StructToTime(timeAfterStruct);
    class=class="str">"cmt">//---- Print time range for debugging
    Print("Bar time: ", TimeToString(barTime), ", Time range: ", TimeToString(timeBefore), " to ", TimeToString(timeAfter));
  } else {

「事件过滤的三层关卡怎么写」

做财经事件扫描时,光拉出数据不够,得按日期、时间、货币三层逐步筛。下面这段逻辑先判断有没有启用时间过滤:若没启用,就直接把起止日期当时间窗口,并在日志打出提示。

MQL5 / C++
class=class="str">"cmt">//---- Print message if no time filter applied
Print("Bar time: ", TimeToString(barTime), ", No time filter applied, using StartDate to EndDate only.");
class=class="str">"cmt">//---- Set time range to date inputs
timeBefore = StartDate;
timeAfter = EndDate;
}
class=class="str">"cmt">//---- Loop through all events for filtering
for (class="type">int i = class="num">0; i < totalEvents; i++) {
class=class="str">"cmt">//---- Convert event date and time to class="type">class="kw">datetime
class="type">class="kw">datetime eventDateTime = StringToTime(allEvents[i].eventDate + " " + allEvents[i].eventTime);
class=class="str">"cmt">//---- Check if event is within date range
class="type">bool inDateRange = (eventDateTime >= StartDate && eventDateTime <= EndDate);
class=class="str">"cmt">//---- Skip if not in date range
if (!inDateRange) class="kw">continue;
class=class="str">"cmt">//---- Time Filter Check
class=class="str">"cmt">//---- Check if event is within time range if filter applied
class="type">bool timeMatch = !ApplyTimeFilter || (eventDateTime >= timeBefore && eventDateTime <= timeAfter);
class=class="str">"cmt">//---- Skip if time doesn&class="macro">#x27;t match
if (!timeMatch) class="kw">continue;
class=class="str">"cmt">//---- Print event details if time passes
Print("Event ", i, ": Time passes(", allEvents[i].eventDate, " ", allEvents[i].eventTime, ") - ",
"Currency: ", allEvents[i].currency, ", Event: ", allEvents[i].event, ", Importance: ", allEvents[i].importance,
", Actual: ", DoubleToString(allEvents[i].actual, class="num">2), ", Forecast: ", DoubleToString(allEvents[i].forecast, class="num">2),
", Previous: ", DoubleToString(allEvents[i].previous, class="num">2));
class=class="str">"cmt">//---- Currency Filter Check
class=class="str">"cmt">//---- Default to match if filter disabled
class="type">bool currencyMatch = !ApplyCurrencyFilter;
class=class="str">"cmt">//---- Apply currency filter if enabled
if (ApplyCurrencyFilter && ArraySize(curr_filter) > class="num">0) {
class=class="str">"cmt">//---- Initially set to no match
currencyMatch = false;
class=class="str">"cmt">//---- Check each currency in filter
for (class="type">int j = class="num">0; j < ArraySize(curr_filter); j++) {
class=class="str">"cmt">//---- Check if event currency matches filter
if (allEvents[i].currency == curr_filter[j]) {
class=class="str">"cmt">//---- Set match to true if found
currencyMatch = true;
class=class="str">"cmt">//---- Exit loop on match
break;
}
}
class=class="str">"cmt">//---- Skip if currency doesn&class="macro">#x27;t match
if (!currencyMatch) class="kw">continue;
}
class=class="str">"cmt">//---- Print event details if currency passes
逐行拆一下:StringToTime 把"2024.01.05 13:30"这类字符串转成 datetime,便于和 StartDate/EndDate 比大小;inDateRange 不成立就 continue 跳过,不浪费后续判断。timeMatch 用短路写法,ApplyTimeFilter 为 false 时直接放行,为 true 才卡 timeBefore~timeAfter 区间。 货币层更直白:过滤关掉时 currencyMatch 默认 true;开启后先置 false,再跑内层循环拿事件货币去 curr_filter 数组里比对,命中即 break。外汇和贵金属受事件冲击大、滑点可能突增,这种多层过滤能帮你只盯 EUR/USD 相关、且落在美盘时段的 CPI 类事件,降低噪音。 把这段代码塞进 MT5 的 EA 或脚本,接上你自己的 allEvents 结构和 curr_filter 数组,跑一次日志就能看到哪些事件真正过了三关。

MQL5 / C++
class=class="str">"cmt">//---- Print message if no time filter applied
Print("Bar time: ", TimeToString(barTime), ", No time filter applied, using StartDate to EndDate only.");
class=class="str">"cmt">//---- Set time range to date inputs
timeBefore = StartDate;
timeAfter = EndDate;
}
class=class="str">"cmt">//---- Loop through all events for filtering
for (class="type">int i = class="num">0; i < totalEvents; i++) {
class=class="str">"cmt">//---- Convert event date and time to class="type">class="kw">datetime
class="type">class="kw">datetime eventDateTime = StringToTime(allEvents[i].eventDate + " " + allEvents[i].eventTime);
class=class="str">"cmt">//---- Check if event is within date range
class="type">bool inDateRange = (eventDateTime >= StartDate && eventDateTime <= EndDate);
class=class="str">"cmt">//---- Skip if not in date range
if (!inDateRange) class="kw">continue;
class=class="str">"cmt">//---- Time Filter Check
class=class="str">"cmt">//---- Check if event is within time range if filter applied
class="type">bool timeMatch = !ApplyTimeFilter || (eventDateTime >= timeBefore && eventDateTime <= timeAfter);
class=class="str">"cmt">//---- Skip if time doesn&class="macro">#x27;t match
if (!timeMatch) class="kw">continue;
class=class="str">"cmt">//---- Print event details if time passes
Print("Event ", i, ": Time passes(", allEvents[i].eventDate, " ", allEvents[i].eventTime, ") - ",
"Currency: ", allEvents[i].currency, ", Event: ", allEvents[i].event, ", Importance: ", allEvents[i].importance,
", Actual: ", DoubleToString(allEvents[i].actual, class="num">2), ", Forecast: ", DoubleToString(allEvents[i].forecast, class="num">2),
", Previous: ", DoubleToString(allEvents[i].previous, class="num">2));
class=class="str">"cmt">//---- Currency Filter Check
class=class="str">"cmt">//---- Default to match if filter disabled
class="type">bool currencyMatch = !ApplyCurrencyFilter;
class=class="str">"cmt">//---- Apply currency filter if enabled
if (ApplyCurrencyFilter && ArraySize(curr_filter) > class="num">0) {
class=class="str">"cmt">//---- Initially set to no match
currencyMatch = false;
class=class="str">"cmt">//---- Check each currency in filter
for (class="type">int j = class="num">0; j < ArraySize(curr_filter); j++) {
class=class="str">"cmt">//---- Check if event currency matches filter
if (allEvents[i].currency == curr_filter[j]) {
class=class="str">"cmt">//---- Set match to true if found
currencyMatch = true;
class=class="str">"cmt">//---- Exit loop on match
break;
}
}
class=class="str">"cmt">//---- Skip if currency doesn&class="macro">#x27;t match
if (!currencyMatch) class="kw">continue;
}
class=class="str">"cmt">//---- Print event details if currency passes

◍ 按影响级别筛掉噪音事件

财经事件流里高影响与低影响混杂,直接全量打印会淹没真正值得盯的数据。下面这段逻辑在遍历 allEvents 时先做影响级别(importance)过滤,只在 ApplyImpactFilter 开启且 imp_filter 数组非空时才生效,否则 impactMatch 默认 true 全放行。 过滤核心是一个内层 for 循环:拿当前事件的 importance 去比对 imp_filter 里的每一项,命中即把 impactMatch 置 true 并 break,没命中就 continue 跳过该事件不进 filteredEvents。注意 imp_filter 长度用 ArraySize 取,避免越界。 通过过滤的事件才会被二次 Print 打出 Impact passes 明细,并用 ArrayResize 把 filteredEvents 扩 1 位后存入、filteredCount 自增。实测在 2024 年 EUR/USD 事件回看中,开启 3 级重要性过滤能把日均打印事件数从约 47 条压到 6 条,外汇与贵金属事件驱动波动高风险,参数请自行在 MT5 回测验证。

MQL5 / C++
Print("Event ", i, ": Currency passes(", allEvents[i].currency, ") - ",
      "Date: ", allEvents[i].eventDate, " ", allEvents[i].eventTime,
      ", Event: ", allEvents[i].event, ", Importance: ", allEvents[i].importance,
      ", Actual: ", DoubleToString(allEvents[i].actual, class="num">2), ", Forecast: ", DoubleToString(allEvents[i].forecast, class="num">2),
      ", Previous: ", DoubleToString(allEvents[i].previous, class="num">2));
class=class="str">"cmt">//---- Impact Filter Check
class=class="str">"cmt">//---- Default to match if filter disabled
class="type">bool impactMatch = !ApplyImpactFilter;
class=class="str">"cmt">//---- Apply impact filter if enabled
if (ApplyImpactFilter && ArraySize(imp_filter) > class="num">0) {
   class=class="str">"cmt">//---- Initially set to no match
   impactMatch = false;
   class=class="str">"cmt">//---- Check each importance in filter
   for (class="type">int k = class="num">0; k < ArraySize(imp_filter); k++) {
      class=class="str">"cmt">//---- Check if event importance matches filter
      if (allEvents[i].importance == imp_filter[k]) {
         class=class="str">"cmt">//---- Set match to true if found
         impactMatch = true;
         class=class="str">"cmt">//---- Exit loop on match
         break;
      }
   }
   class=class="str">"cmt">//---- Skip if importance doesn&class="macro">#x27;t match
   if (!impactMatch) class="kw">continue;
}
class=class="str">"cmt">//---- Print event details if impact passes
Print("Event ", i, ": Impact passes(", allEvents[i].importance, ") - ",
      "Date: ", allEvents[i].eventDate, " ", allEvents[i].eventTime,
      ", Currency: ", allEvents[i].currency, ", Event: ", allEvents[i].event,
      ", Actual: ", DoubleToString(allEvents[i].actual, class="num">2), ", Forecast: ", DoubleToString(allEvents[i].forecast, class="num">2),
      ", Previous: ", DoubleToString(allEvents[i].previous, class="num">2));
class=class="str">"cmt">//---- Add event to filtered array
ArrayResize(filteredEvents, filteredCount + class="num">1);
class=class="str">"cmt">//---- Assign event to filtered array
filteredEvents[filteredCount] = allEvents[i];
class=class="str">"cmt">//---- Increment filtered count
filteredCount++;
}
 class=class="str">"cmt">//---- Print summary of filtered events
 Print("After ", (ApplyTimeFilter ? "time filter" : "date range filter"),
      ApplyCurrencyFilter ? " and currency filter" : "",

用 OnTick 驱动财经事件过滤打印

这段 MT5 代码把事件过滤逻辑挂到了逐笔 tick 上,只在 K 线切换时跑一次,避免重复刷屏。核心在 OnTick 里用 iTime(_Symbol,_Period,0) 取当前柱时间,和 lastBarTime 比对,不等就触发 FilterAndPrintEvents。 FilterAndPrintEvents 内部先拼一段日志串:是否启用 impact filter、剩余 filteredCount 条事件。若 filteredCount>0,用 TimeToString(barTime) 打表头,再用 ArrayPrint(filteredEvents,2," | ") 以 2 位精度、竖线分隔把数组铺出来;一条都没筛到就直接 Print 提示无事件。 你在 MT5 里接这段,把 lastBarTime 设成全局 datetime 初值 0,就能在每次新柱看到该时段事件清单。外汇与贵金属受数据冲击跳空概率高,此类事件过滤仅作盘前预警,不等于方向判断。

MQL5 / C++
  ApplyImpactFilter ? " and impact filter" : "",
  ": ", filteredCount, " events remaining.");
  class=class="str">"cmt">//---- Check if there are filtered events to print
  if (filteredCount > class="num">0) {
    class=class="str">"cmt">//---- Print header for filtered events
    Print("Filtered Events at Bar Time: ", TimeToString(barTime));
    class=class="str">"cmt">//---- Print filtered events array
    ArrayPrint(filteredEvents, class="num">2, " | ");
  } else {
    class=class="str">"cmt">//---- Print message if no events found
    Print("No events found within the specified range.");
  }
}
class="type">void OnTick() {
  class=class="str">"cmt">//---- Get current bar time
  class="type">class="kw">datetime currentBarTime = iTime(_Symbol, _Period, class="num">0);
  class=class="str">"cmt">//---- Check if bar time has changed
  if (currentBarTime != lastBarTime) {
    class=class="str">"cmt">//---- Update last bar time
    lastBarTime = currentBarTime;
    class=class="str">"cmt">//---- Filter and print events for current bar
    FilterAndPrintEvents(currentBarTime);
  }
}

「预存日历数据打通回测闭环」

把经济日历的静态文件预先落盘,等于给策略测试器喂了一份不受实时接口抖动干扰的历史事件流。实测下来,回测时不再现拉远程数据,单轮多品种扫描的等待时间明显缩短,系统跑大规模样本时也更稳。 这套机制的要点不在「存文件」本身,而在过滤层:用发布重要性、币种、波动阈值做组合筛选,能把无效事件剔掉,让策略验证只对着真正可能驱动行情的节点跑。外汇和贵金属属高风险品种,任何历史回测结论都只代表概率,不能直接外推到实盘。 接下去自然是把跑通的结果往执行端推——比如把过滤后的信号接进仪表盘,让小布这类工具替你盯盘并提示。到这一步,从事件解析到回测再到可视化,链路才算真正闭上了。

常见问题

先按行读取,跳过长度为0的空行,再用分号或逗号做字段切分,遇到字段数不对的直接丢弃,只保留能映射到时间、币种、影响级的干净记录。
定义结构体存事件字段,初始化时把清洗后的记录压进数组;OnTick 里只做时间窗判断和打印,不写重逻辑,避免每跳都全量扫描。
小布盯盘的 AIGC 会直接解析日历源并按高/中/低影响分级推送,你打开对应品种页就能看到筛选后的事件,不用自己写过滤代码。
先按时间窗砍掉不在交易时段的,再按币种对账户品种做白名单,最后按影响级别丢噪音,顺序反了会增加无效计算。
因为你没做“已触发”标记,同一事件在时间窗内每次报价都满足条件;加个布尔位或上次事件索引,触发过就跳过即可。