让新闻交易变得容易(第一部分):创建一个数据库·综合运用
📘

让新闻交易变得容易(第一部分):创建一个数据库·综合运用

第 3/3 篇

用 M15 波幅峰值反推新闻发生 K 线

在 MT5 里定位财经新闻真实触发的 K 线,一个可行思路是比波幅:当前 M15 蜡烛高度若同时大于偏移前、偏移后的相邻 M15 蜡烛高度,就倾向判定该蜡烛覆盖了新闻释放时刻。外汇与贵金属属高风险品种,这种判定只提高概率,不保证精确对齐。 下面这段 MQL5 片段先算出偏移前后两根 M15 蜡烛的真实波幅(高点减低点),再与当前蜡烛波幅做双重大于比较: [CODE] double CandleHeightMinusOffset = High(CandleIndexMinusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexMinusOffset,PERIOD_M15,SYMBOL); // 取偏移前那根 M15 蜡烛的高点减低点,得到其波幅 double CandleHeightPlusOffset = High(CandleIndexPlusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexPlusOffset,PERIOD_M15,SYMBOL); // 取偏移后那根 M15 蜡烛的高点减低点,得到其波幅 // 判断当前蜡烛波幅是否同时大于前后两根 if(CandleHeight>CandleHeightMinusOffset&&CandleHeight>CandleHeightPlusOffset) { return true; // 当前蜡烛波幅最大,倾向为新闻事件发生所在 K 线 } return false; // 否则倾向不是真实新闻数据发布的那根 }[/CODE] 类 CNews 的私有段声明了多个时区与蜡烛对象(如 CDaylightSavings_UK、CCandleProperties),并定义了 DST 结构体存 result 与 date,用来处理经纪商夏令时偏移。AutoDetectDST 等函数负责自动识别 broker 的 DST 类型,避免新闻时间因时区错配而偏移数小时。 实盘验证时,把 CandleIndexMinusOffset / PlusOffset 设成 1,加载到 EURUSD 的 M15 图表,观察重大 CPI 发布前后三根蜡烛的波幅关系,能快速确认这套逻辑在你的 broker 时间下是否生效。

MQL5 / C++
class="type">class="kw">double CandleHeightMinusOffset = High(CandleIndexMinusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexMinusOffset,PERIOD_M15,SYMBOL);class=class="str">"cmt">//Assign height of M15 candletime minus offset in Pips
class="type">class="kw">double CandleHeightPlusOffset = High(CandleIndexPlusOffset,PERIOD_M15,SYMBOL)-Low(CandleIndexPlusOffset,PERIOD_M15,SYMBOL);class=class="str">"cmt">//Assign height of M15 candletime plus offset in Pips
class=class="str">"cmt">//--Determine if candletime height is greater than candletime height minus offset and candletime height plus offset
if(CandleHeight>CandleHeightMinusOffset&&CandleHeight>CandleHeightPlusOffset)
  {
    class="kw">return true;class=class="str">"cmt">//Candletime is likely when the news event occurred
  }
class="kw">return class="kw">false;class=class="str">"cmt">//Candletime is unlikely when the real news data was released
}
class CNews
  {
  class=class="str">"cmt">//Private Declarations Only accessible by this class/header file
class="kw">private:
  CTimeManagement   Time;class=class="str">"cmt">//TimeManagement Object declaration
  CDaylightSavings_UK  Savings_UK;class=class="str">"cmt">//DaylightSavings Object for the UK and EU
  CDaylightSavings_US  Savings_US;class=class="str">"cmt">//DaylightSavings Object for the US
  CDaylightSavings_AU  Savings_AU;class=class="str">"cmt">//DaylightSavings Object for the AU
  CCandleProperties Candle;class=class="str">"cmt">//CandleProperties Object
  class="type">class="kw">string            CurrencyBase,CurrencyProfit,EURUSD;class=class="str">"cmt">//String variables declarations for working with EURUSD
  class="type">bool              EurusdIsSelected,EurusdIsFound,is_Custom;class=class="str">"cmt">//Boolean variables declarations for working with EURUSD
  class="type">bool              timeIsShifted;class=class="str">"cmt">//Boolean variable declaration will be used to determine if the broker changes it&class="macro">#x27;s time zone
  class="type">class="kw">datetime          DaylightStart,DaylightEnd;class=class="str">"cmt">//Datetime variables declarations for start and end dates for Daylight Savings
  class=class="str">"cmt">//Structure Declaration for DST
  class="kw">struct DST
    {
      class="type">bool        result;
      class="type">class="kw">datetime    date;
    };
  class="type">bool              AutoDetectDST(DST_type &dstType);class=class="str">"cmt">//Function will determine Broker DST
  DST_type          DSTType;class=class="str">"cmt">//variable of DST_type enumeration declared in the CommonVariables class/header file
  class="type">bool              InsertIntoTable(class="type">int db,DST_type Type,Calendar &Evalues[]);class=class="str">"cmt">//Function for inserting Economic Data in to a database&class="macro">#x27;s table
  class="type">void              CreateAutoDST(class="type">int db);class=class="str">"cmt">//Function for creating and inserting Recommend DST for the Broker into a table
  class="type">bool              CreateTable(class="type">int db,class="type">class="kw">string tableName,class="type">bool &tableExists);class=class="str">"cmt">//Function for creating a table in a database
  class="type">void              CreateRecords(class="type">int db);class=class="str">"cmt">//Creates a table to store records of when last the Calendar database was updated/created
  class="type">bool              UpdateRecords();class=class="str">"cmt">//Checks if the main Calendar database needs an update or not
  class="type">void              EconomicDetails(Calendar &NewsTime[]);class=class="str">"cmt">//Gets values from the MQL5 economic Calendar
  class=class="str">"cmt">//Public declarations accessable via a class&class="macro">#x27;s Object
class="kw">public:

◍ 用财经日历自动识别经纪商夏令时

做跨时区新闻交易,最坑的不是数据本身,而是经纪商服务器时间跟夏令时(DST)对不齐。MT5 的 CalendarValueHistory 能拉取指定国家上一年度的日历事件,拿美国 NFP(event_id=840030016)的发布时间反推经纪商时区偏移,是条可行路径。 下面这段 CNews::AutoDetectDST 的核心逻辑:先算去年起止时间,取 CountryCode='US' 的全部值,筛出 NFP 日期存进字符串数组;再遍历市场报价窗口里的品种,抓 EURUSD 的基准货币做后续比对。外汇与贵金属受数据跳空影响大,这类识别只降低时区错配概率,不消除风险。 [CODE]…[/CODE] 里 lastyear = Time.ReturnYear(...) 拿当前 K 线时间减一年;lastyearstart/end 拼成去年 01.01–12.31 的 datetime;CalendarValueHistory(values,...,"US") 拉数据后,用 values[x].event_id==840030016 只留非农。循环里 ArrayResize 动态扩数组再 TimeToString 存时间,后面 SymbolsTotal(true) 扫报价窗找 EURUSD 基准币。 开 MT5 按 F4 把这段塞进 EA 的 CNews 类,改 event_id 可换其他央行事件;若你经纪商不播美国数据,把 "US" 换成 "EU""AU" 并对应调枚举,才可能匹配本地时区。

MQL5 / C++
class="type">bool CNews::AutoDetectDST(DST_type &dstType)
  {
   MqlCalendarValue values[];class=class="str">"cmt">//Single array of MqlCalendarValue type
   class="type">class="kw">string eventtime[];class=class="str">"cmt">//Single class="type">class="kw">string array variable to store NFP(Nonfarm Payrolls) dates for the &class="macro">#x27;United States&class="macro">#x27; from the previous year
   class="type">int lastyear = Time.ReturnYear(Time.TimeMinusOffset(iTime(Symbol(),PERIOD_CURRENT,class="num">0),Time.YearsS()));class=class="str">"cmt">//Will store the previous year into an integer
   class="type">class="kw">datetime lastyearstart = StringToTime(StringFormat("%s.class="num">01.01 class="num">00:class="num">00:class="num">00",(class="type">class="kw">string)lastyear));class=class="str">"cmt">//Will store the start date for the previous year
   class="type">class="kw">datetime lastyearend = StringToTime(StringFormat("%s.class="num">12.31 class="num">23:class="num">59:class="num">59",(class="type">class="kw">string)lastyear));class=class="str">"cmt">//Will store the end date for the previous year
   if(CalendarValueHistory(values,lastyearstart,lastyearend,"US"))class=class="str">"cmt">//Getting last year&class="macro">#x27;s calendar values for CountryCode = &class="macro">#x27;US&class="macro">#x27;
     {
      for(class="type">int x=class="num">0; x<(class="type">int)ArraySize(values); x++)
        {
         if(values[x].event_id==class="num">840030016)class=class="str">"cmt">//Get only NFP Event Dates
           {
            ArrayResize(eventtime,eventtime.Size()+class="num">1,eventtime.Size()+class="num">2);class=class="str">"cmt">//Increasing the size of eventtime array by class="num">1
            eventtime[eventtime.Size()-class="num">1] = TimeToString(values[x].time);class=class="str">"cmt">//Storing the dates in an array of type class="type">class="kw">string
           }
        }
     }
   class="type">class="kw">datetime ShiftStart=D&class="macro">#x27;class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;,ShiftEnd=D&class="macro">#x27;class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;;class=class="str">"cmt">//class="type">class="kw">datetime variables to store the broker&class="macro">#x27;s time zone shift(change)
   DST previousresult,currentresult;class=class="str">"cmt">//Variables of structure type DST declared at the beginning of the class
   EURUSD="";class=class="str">"cmt">//String variable assigned empty class="type">class="kw">string
   EurusdIsSelected = class="kw">false;class=class="str">"cmt">//Boolean variable assigned value class="kw">false
   EurusdIsFound = class="kw">false;class=class="str">"cmt">//Boolean variable assigned value class="kw">false
   for(class="type">int i=class="num">0;i<SymbolsTotal(true);i++)class=class="str">"cmt">//Will loop through all the Symbols inside the Market Watch
     {
      class="type">class="kw">string SymName = SymbolName(i,true);class=class="str">"cmt">//Assign the Symbol Name of index &class="macro">#x27;i&class="macro">#x27; from the list of Symbols inside the Market Watch
      CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);class=class="str">"cmt">//Assign the Symbol&class="macro">#x27;s Currency Base

「在终端里自动定位 EURUSD 品种」

EA 启动时不一定能假定 EURUSD 已在行情窗口。上面这段逻辑先在自选品种里扫一遍,再扫全部可用品种,靠基础货币与盈利货币字段确认身份,而不是写死 "EURUSD" 字符串。 第一步用 SymbolInfoString 取 SYMBOL_CURRENCY_PROFIT,再用 SymbolExist 拿到 is_Custom 标记。过滤条件是 CurrencyBase=="EUR" && CurrencyProfit=="USD" && !is_Custom,三者同时满足才认作真实经纪商提供的欧美对价品种。 行情窗口没找到时,走 SymbolsTotal(false) 循环,对外部品种同样判定。命中后调 SymbolSelect(SymName,true) 把它加回行情窗口,成功才把 EURUSD 变量赋值并 break。 若两层循环都没命中,Print 报 "Cannot Find EURUSD!" 并把 dstType 置为 DST_NONE 后 return false。这意味着后续依赖经纪商时区/DST 的数据库逻辑会直接中止,实盘前必须确认终端确实含该品种。外汇与贵金属杠杆品种波动剧烈,品种缺失可能导致策略静默失效,开 MT5 用下列代码验证你的经纪商环境。

MQL5 / C++
CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);class=class="str">"cmt">//Assign the Symbol&class="macro">#x27;s Currency Profit
SymbolExist(SymName,is_Custom);class=class="str">"cmt">//Get the boolean value into &class="macro">#x27;is_Custom&class="macro">#x27; for whether the Symbol Name is a Custom Symbol(Is not from the broker)
class=class="str">"cmt">//-- Check if the Symbol outside the Market Watch has a SYMBOL_CURRENCY_BASE of EUR
class=class="str">"cmt">//-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
  {
   EURUSD = SymName;class=class="str">"cmt">//Assigning the name of the EURUSD Symbol found inside the Market Watch
   EurusdIsFound = true;class=class="str">"cmt">//EURUSD Symbol was found in the Trading Terminal for your Broker
   class="kw">break;class=class="str">"cmt">//Will end the for loop
  }
 }
 if(!EurusdIsFound)class=class="str">"cmt">//Check if EURUSD Symbol was already Found in the Market Watch
  {
   for(class="type">int i=class="num">0; i<SymbolsTotal(class="kw">false); i++)class=class="str">"cmt">//Will loop through all the available Symbols outside the Market Watch
     {
      class="type">class="kw">string SymName = SymbolName(i,class="kw">false);class=class="str">"cmt">//Assign the Symbol Name of index &class="macro">#x27;i&class="macro">#x27; from the list of Symbols outside the Market Watch
      CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);
      CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);
      SymbolExist(SymName,is_Custom);class=class="str">"cmt">//Get the boolean value into &class="macro">#x27;is_Custom&class="macro">#x27; for whether the Symbol Name is a Custom Symbol(Is not from the broker)
      class=class="str">"cmt">//-- Check if the Symbol outside the Market Watch has a SYMBOL_CURRENCY_BASE of EUR
      class=class="str">"cmt">//-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
      if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
        {
         EurusdIsSelected = SymbolSelect(SymName,true);class=class="str">"cmt">//Adding the EURUSD Symbol to the Market Watch
         if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
          {
           EURUSD = SymName;class=class="str">"cmt">//Assigning the name of the EURUSD Symbol found outside the Market Watch
           EurusdIsFound = true;class=class="str">"cmt">//EURUSD Symbol was found in the Trading Terminal for your Broker
           class="kw">break;class=class="str">"cmt">//Will end the for loop
          }
        }
     }
  }
 if(!EurusdIsFound)class=class="str">"cmt">//Check if EURUSD Symbol was Found in the Trading Terminal for your Broker
  {
   Print("Cannot Find EURUSD!");
   Print("Cannot Create Database!");
   Print("Server DST Cannot be Detected!");
   dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
   class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
  }

用K线大小漂移反推券商夏令时切换

这段逻辑的核心,是借 EURUSD 一小时 K 线在事件日是否「大于前一根与后一根」来捕捉服务器时间偏移。eventtime 数组里存的是候选事件日,逐根比对当前结果与上一次结果,一旦出现差异就记为一次时间移位。 for(uint i=0;i<eventtime.Size();i++) 遍历所有事件日;currentresult.result 调用 Candle.IsLargerThanPreviousAndNext 判断该日 K 线是否更大,timeIsShifted 在 i>0 且前后结果不同置真。ShiftStart 初值为 1970.01.01,首次移位时记录起点,ShiftEnd 记上一事件日。 循环结束后若 ShiftStart 仍为 1970 年且事件数大于 0,说明整段无移位,券商无夏令时,dstType 赋 DST_NONE 并返回 true。 接着用 Savings_AU.DaylightSavings 取去年澳区夏令起止,若 ShiftStart~ShiftEnd 落在其区间内,则判定券商跟随澳 DST,移除自选的 EURUSD 并返回 AU_DST。若取澳夏令日期失败则打印错误年份并退出分支。外汇与贵金属交易受券商时间规则影响,时区错配可能导致回测与实盘信号偏移,属高风险环节,需用真实账户 MT5 验证。

MQL5 / C++
for(class="type">uint i=class="num">0;i<eventtime.Size();i++)
  {
    currentresult.result = Candle.IsLargerThanPreviousAndNext((class="type">class="kw">datetime)eventtime[i],Time.HoursS(),EURUSD);class=class="str">"cmt">//Store the result of if the event date is the larger candlestick
    currentresult.date = (class="type">class="kw">datetime)eventtime[i];class=class="str">"cmt">//Store the eventdate from eventtime[i]
    timeIsShifted = ((currentresult.result!=previousresult.result&&i>class="num">0)?true:class="kw">false);class=class="str">"cmt">//Check if there is a difference between the previous result and the current result
    class=class="str">"cmt">//--- Print Event Dates and if the event date&class="macro">#x27;s candle is larger than the previous candle an hour ago and the next candle an hour ahead
    Print("Date: ",eventtime[i]," is Larger: ",Candle.IsLargerThanPreviousAndNext((class="type">class="kw">datetime)eventtime[i],Time.HoursS(),EURUSD)," Shifted: ",timeIsShifted);
    if(timeIsShifted)class=class="str">"cmt">//Check if the Larger candle has shifted from the previous event date to the current event date in eventtime[i] array
      {
       if(ShiftStart==D&class="macro">#x27;class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;)class=class="str">"cmt">//Check if the ShiftStart variable has not been assigned a relevant value yet
         {
          ShiftStart=currentresult.date;class=class="str">"cmt">//Store the event date for when the time shift began
         }
       ShiftEnd=previousresult.date;class=class="str">"cmt">//Store the event date timeshift
      }
    previousresult.result = currentresult.result;class=class="str">"cmt">//Store the previous result of if the event date is the larger candlestick
    previousresult.date = currentresult.date;class=class="str">"cmt">//Store the event date from eventtime[i]
   }
 if(ShiftStart==D&class="macro">#x27;class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;&&eventtime.Size()>class="num">0)class=class="str">"cmt">//Check if the ShiftStart variable has not been assigned a relevant value and the event dates are more than zero
   {
    Print("Broker ServerTime unchanged!");
    dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
    class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
   }
 if(Savings_AU.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
   {
    if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
      {
       Print("Broker ServerTime Adjusted For AU DST");
       if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
         {
          SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
         }
       dstType = AU_DST;class=class="str">"cmt">//Assigning enumeration value AU_DST, Broker has AU DST(Daylight Savings Time)
       class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
      }
   }
 else
   {
    Print("Something went wrong!");
    Print("Cannot Find Daylight-Savings Date For AU");
    Print("Year: %d Cannot Be Found!",lastyear);
    if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
      {

◍ 按经纪商时区回退判定夏令时归属

这段逻辑在前面 UK/US 夏令时对象都未命中时充当兜底:先尝试用 Savings_UK 判去年区间,若 Time.DateIsInRange 落在 ShiftStart~ShiftEnd 内,就打印「Broker ServerTime Adjusted For UK DST」并把 dstType 置为 UK_DST、返回 true。 若 UK 分支的 DaylightSavings 直接返回 false,则连打三条诊断:Something went wrong!、Cannot Find Daylight-Savings Date For UK、以及带 lastyear 的 Year: %d Cannot Be Found!,随后清掉 EURUSD 自选、dstType 设 DST_NONE 并返回 false。 US 分支结构完全一致:Savings_US.DaylightSavings 成功且日期在区间内则标记 US_DST 并返回 true;否则同样打印无法定位美国的年份、移除 EURUSD、返回 false。 注意每次返回前都用 EurusdIsSelected 判断是否曾由本程序把 EURUSD 加进市场报价,是才调 SymbolSelect(EURUSD,false) 移除——避免污染用户自选列表。外汇与贵金属交易受杠杆与跳空影响,这类时区误判可能让回测与实盘偏移数根 K 线,实盘前务必在 MT5 用去年日期跑一遍验证。

MQL5 / C++
SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
}
dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
}
if(Savings_UK.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
{
  if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
  {
    Print("Broker ServerTime Adjusted For UK DST");
    if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
    {
      SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
    }
    dstType = UK_DST;class=class="str">"cmt">//Assigning enumeration value UK_DST, Broker has UK/EU DST(Daylight Savings Time)
    class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
  }
}
else
{
  Print("Something went wrong!");
  Print("Cannot Find Daylight-Savings Date For UK");
  Print("Year: %d Cannot Be Found!",lastyear);
  if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
  {
    SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
  }
  dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
  class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
}
if(Savings_US.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
{
  if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
  {
    Print("Broker ServerTime Adjusted For US DST");
    if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
    {
      SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
    }
    dstType = US_DST;class=class="str">"cmt">//Assigning enumeration value US_DST, Broker has US DST(Daylight Savings Time)
    class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
  }
}
else
{
  Print("Something went wrong!");
  Print("Cannot Find Daylight-Savings Date For US");
  Print("Year: %d Cannot Be Found!",lastyear);
  if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
  {
    SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
  }

「用财经日历抓去年美国NFP发布日」

这段逻辑在 broker 的 DST 判定失败后,转向利用 MT5 内置财经日历接口回溯上一自然年的美国非农(NFP)公布时间。它先把当前图表品种的 0 号 K 线时间减去一年偏移,算出 lastyear,再拼出该年 1 月 1 日 00:00:00 到 12 月 31 日 23:59:59 的起止 datetime。 调用 CalendarValueHistory(values, lastyearstart, lastyearend, "US") 拉取美国区全部日历事件,遍历时用 event_id==840030016 精准筛出 NFP。每命中一次就把 values[x].time 转成字符串塞进 eventtime 数组,动态扩容步长设为 +1 与保留 +2。 外汇与贵金属受 NFP 冲击跳空概率高,实盘前应在策略测试器或脚本里跑一遍,确认 eventtime 数组长度等于 12(月频发布)才放心。下面这段是原文核心抓取与符号扫描的拼接:

MQL5 / C++
MqlCalendarValue values[];
  class="type">class="kw">string eventtime[];
  class="type">int lastyear = Time.ReturnYear(Time.TimeMinusOffset(iTime(Symbol(),PERIOD_CURRENT,class="num">0),Time.Years()));
  class="type">class="kw">datetime lastyearstart = StringToTime(StringFormat("%s.class="num">01.01 class="num">00:class="num">00:class="num">00",(class="type">class="kw">string)lastyear));
  class="type">class="kw">datetime lastyearend = StringToTime(StringFormat("%s.class="num">12.31 class="num">23:class="num">59:class="num">59",(class="type">class="kw">string)lastyear));
  if(CalendarValueHistory(values,lastyearstart,lastyearend,"US"))
   {
     for(class="type">int x=class="num">0; x<(class="type">int)ArraySize(values); x++)
      {
       if(values[x].event_id==class="num">840030016)
         {
          ArrayResize(eventtime,eventtime.Size()+class="num">1,eventtime.Size()+class="num">2);
          eventtime[eventtime.Size()-class="num">1] = TimeToString(values[x].time);
         }
      }
   }
EURUSD="";
  EurusdIsSelected = class="kw">false;
  EurusdIsFound = class="kw">false;
  for(class="type">int i=class="num">0;i<SymbolsTotal(true);i++)
   {
     class="type">class="kw">string SymName = SymbolName(i,true);
     CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);
     CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);
     SymbolExist(SymName,is_Custom);
   }

EURUSD 不在行情窗口时的兜底拉取

上面那段逻辑先扫 Market Watch 里的基础货币 EUR、计价货币 USD 且非自定义品种,命中就记下品种名并跳出。若没找到,会再跑一轮 SymbolsTotal(false) 遍历经纪商全量符号,用 SymbolSelect 把 EURUSD 加进行情窗口。 加成功才把 EURUSD 变量赋值并置 EurusdIsFound=true;若两轮都落空,终端会 Print 三条提示并把 dstType 设成 DST_NONE 后 return false,意味着夏令时识别直接放弃。 最后一段把 eventtime 数组逐个转 datetime,调用 Candle.IsLargerThanPreviousAndNext 比对该事件 K 线是否大于相邻两根,结果写进 currentresult。外汇和贵金属杠杆高,这段自动选符号失败可能让后续统计偏漏,开 MT5 跑前先确认 EURUSD 已显示。

MQL5 / C++
   class=class="str">"cmt">//-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
   if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
     {
      EURUSD = SymName;class=class="str">"cmt">//Assigning the name of the EURUSD Symbol found inside the Market Watch
      EurusdIsFound = true;class=class="str">"cmt">//EURUSD Symbol was found in the Trading Terminal for your Broker
      class="kw">break;class=class="str">"cmt">//Will end the for loop
     }
   }
 if(!EurusdIsFound)class=class="str">"cmt">//Check if EURUSD Symbol was already Found in the Market Watch
  {
   for(class="type">int i=class="num">0; i<SymbolsTotal(class="kw">false); i++)class=class="str">"cmt">//Will loop through all the available Symbols outside the Market Watch
     {
      class="type">class="kw">string SymName = SymbolName(i,class="kw">false);class=class="str">"cmt">//Assign the Symbol Name of index &class="macro">#x27;i&class="macro">#x27; from the list of Symbols outside the Market Watch
      CurrencyBase = SymbolInfoString(SymName,SYMBOL_CURRENCY_BASE);
      CurrencyProfit = SymbolInfoString(SymName,SYMBOL_CURRENCY_PROFIT);
      SymbolExist(SymName,is_Custom);class=class="str">"cmt">//Get the boolean value into &class="macro">#x27;is_Custom&class="macro">#x27; for whether the Symbol Name is a Custom Symbol(Is not from the broker)
      class=class="str">"cmt">//-- Check if the Symbol outside the Market Watch has a SYMBOL_CURRENCY_BASE of EUR
      class=class="str">"cmt">//-- and a SYMBOL_CURRENCY_PROFIT of USD, and this Symbol is not a Custom Symbol(Is not from the broker)
      if(CurrencyBase=="EUR"&&CurrencyProfit=="USD"&&!is_Custom)
        {
         EurusdIsSelected = SymbolSelect(SymName,true);class=class="str">"cmt">//Adding the EURUSD Symbol to the Market Watch
         if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
          {
           EURUSD = SymName;class=class="str">"cmt">//Assigning the name of the EURUSD Symbol found outside the Market Watch
           EurusdIsFound = true;class=class="str">"cmt">//EURUSD Symbol was found in the Trading Terminal for your Broker
           class="kw">break;class=class="str">"cmt">//Will end the for loop
          }
        }
     }
  }
 if(!EurusdIsFound)class=class="str">"cmt">//Check if EURUSD Symbol was Found in the Trading Terminal for your Broker
  {
   Print("Cannot Find EURUSD!");
   Print("Cannot Create Database!");
   Print("Server DST Cannot be Detected!");
   dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
   class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
  }
for(class="type">uint i=class="num">0;i<eventtime.Size();i++)
   {
    currentresult.result = Candle.IsLargerThanPreviousAndNext((class="type">class="kw">datetime)eventtime[i],Time.HoursS(),EURUSD);class=class="str">"cmt">//Store the result of if the event date is the larger candlestick
    currentresult.date = (class="type">class="kw">datetime)eventtime[i];class=class="str">"cmt">//Store the event date from eventtime[i]

◍ 从K线位移反推经纪商夏令时归属

这段代码的核心逻辑是:用事件时间点前后一小时 K 线的大小对比,判断经纪商服务器时间是否相对事件时间发生了偏移(timeIsShifted)。一旦检测到偏移,就记录 ShiftStart 与 ShiftEnd 这两个边界时间,作为后续匹配夏令时区的依据。 timeIsShifted = ((currentresult.result!=previousresult.result&&i>0)?true:false); // 当前与上一事件结果不同且索引大于0时,判定为时间偏移 Print("Date: ",eventtime[i]," is Larger: ",Candle.IsLargerThanPreviousAndNext((datetime)eventtime[i],Time.HoursS(),EURUSD)," Shifted: ",timeIsShifted); // 打印事件时间、是否大K线、是否偏移 if(timeIsShifted) // 若发生偏移 { if(ShiftStart==D'1970.01.01 00:00:00') // 若偏移起点未赋值 { ShiftStart=currentresult.date; // 记录偏移起始事件时间 } ShiftEnd=previousresult.date; // 记录偏移结束事件时间 } previousresult.result = currentresult.result; // 保存本次结果供下次比 previousresult.date = currentresult.date; // 保存本次事件时间 若遍历完 eventtime 数组后 ShiftStart 仍为 1970 年初始值,且事件数大于 0,说明经纪商服务器时间全年未变,直接标记 DST_NONE 并返回 true——这意味着该券商大概率无夏令时。 随后用澳大利亚夏令时对象 Savings_AU 计算去年起止,若 ShiftStart 到 ShiftEnd 落在该区间,则判定为 AU_DST,并顺手把临时加入观察表的 EURUSD 移除(SymbolSelect(EURUSD,false))。若澳洲规则取不到,则打印年份错误并返回 false,实际排查时这类情况多发生在历史年份数据缺失的代理服务器上。 外汇与贵金属交易受经纪商时间切换影响,点差与流动性可能在夏令时切换周异常,属高风险场景;上述判定仅用于脚本自动识别,不预示任何价格方向。

MQL5 / C++
timeIsShifted = ((currentresult.result!=previousresult.result&&i>class="num">0)?true:class="kw">false);class=class="str">"cmt">//Check if there is a difference between the previous result and the current result
class=class="str">"cmt">//--- Print Event Dates and if the event date&class="macro">#x27;s candle is larger than the previous candle an hour ago and the next candle an hour ahead
Print("Date: ",eventtime[i]," is Larger: ",Candle.IsLargerThanPreviousAndNext((class="type">class="kw">datetime)eventtime[i],Time.HoursS(),EURUSD)," Shifted: ",timeIsShifted);
if(timeIsShifted)class=class="str">"cmt">//Check if the Larger candle has shifted from the previous event date to the current event date in eventtime[i] array
  {
   if(ShiftStart==D&class="macro">#x27;class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;)class=class="str">"cmt">//Check if the ShiftStart variable has not been assigned a relevant value yet
     {
      ShiftStart=currentresult.date;class=class="str">"cmt">//Store the event date for when the time shift began
     }
   ShiftEnd=previousresult.date;class=class="str">"cmt">//Store the event date timeshift
  }
previousresult.result = currentresult.result;class=class="str">"cmt">//Store the previous result of if the event date is the larger candlestick
previousresult.date = currentresult.date;class=class="str">"cmt">//Store the event date from eventtime[i]
  }
if(ShiftStart==D&class="macro">#x27;class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;&&eventtime.Size()>class="num">0)class=class="str">"cmt">//Check if the ShiftStart variable has not been assigned a relevant value and the event dates are more than zero
  {
   Print("Broker ServerTime unchanged!");
   dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
   class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
  }
 if(Savings_AU.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
  {
   if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
     {
      Print("Broker ServerTime Adjusted For AU DST");
      if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
        {
         SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
        }
      dstType = AU_DST;class=class="str">"cmt">//Assigning enumeration value AU_DST, Broker has AU DST(Daylight Savings Time)
      class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
     }
  }
 else
  {
   Print("Something went wrong!");
   Print("Cannot Find Daylight-Savings Date For AU");
   Print("Year: %d Cannot Be Found!",lastyear);
   if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
     {
      SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
     }
   dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
   class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
  }
 if(Savings_UK.DaylightSavings(lastyear,DaylightStart,DaylightEnd))

「用代码判定经纪商夏令时归属」

MT5 里经纪商服务器时间是否跟随英美夏令时(DST),直接决定你用 TimeCurrent() 做会话划分时偏移几小时。下面这段逻辑先测英国 DST:若 Savings_UK.DaylightSavings 拿到当年起止日,且 Time.DateIsInRange 确认当前落在 Shift 区间内,就打印「UK DST」并把枚举 dstType 设为 UK_DST,同时把临时加进市场报价表的 EURUSD 用 SymbolSelect(...,false) 移除,返回 true。 若英国分支失败或不在区间内,则进入美国分支 Savings_US.DaylightSavings 做同样判定,命中则 dstType = US_DST。两个分支都未命中时,打印「Cannot Find Daylight-Savings Date」并带上 lastyear 变量,dstType 置为 DST_NONE 返回 false。 实盘验证时,把这段塞进 OnStart 或初始化函数,跑完看终端 Experts 标签的 Print 输出,就能知道你的服务器时间此刻被哪家 DST 规则拖动。外汇与贵金属受此影响可能出现点差跳变,属高风险时段,参数请自行核对经纪商公告。

MQL5 / C++
   {
      if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
         {
          Print("Broker ServerTime Adjusted For UK DST");
          if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
            {
             SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
            }
          dstType = UK_DST;class=class="str">"cmt">//Assigning enumeration value UK_DST, Broker has UK/EU DST(Daylight Savings Time)
          class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
         }
     }
   else
     {
       Print("Something went wrong!");
       Print("Cannot Find Daylight-Savings Date For UK");
       Print("Year: %d Cannot Be Found!",lastyear);
       if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
         {
          SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
         }
       dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
       class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
     }
   if(Savings_US.DaylightSavings(lastyear,DaylightStart,DaylightEnd))
     {
       if(Time.DateIsInRange(DaylightStart,DaylightEnd,ShiftStart,ShiftEnd))
         {
          Print("Broker ServerTime Adjusted For US DST");
          if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
            {
             SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
            }
          dstType = US_DST;class=class="str">"cmt">//Assigning enumeration value US_DST, Broker has US DST(Daylight Savings Time)
          class="kw">return true;class=class="str">"cmt">//Returning True, Broker&class="macro">#x27;s DST schedule was found successfully
         }
     }
   else
     {
       Print("Something went wrong!");
       Print("Cannot Find Daylight-Savings Date For US");
       Print("Year: %d Cannot Be Found!",lastyear);
       if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
         {
          SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
         }
       dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
       class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
     }
   if(EurusdIsSelected)class=class="str">"cmt">//Check if this program added EURUSD Symbol to Market Watch
     {
       SymbolSelect(EURUSD,class="kw">false);class=class="str">"cmt">//Remove EURUSD Symbol from Market Watch
     }

把经纪商时令写进本地库

检测不到服务器时制配置时,代码会打印提示并把 dstType 设为 DST_NONE,随后返回 false,意味着该经纪商不实行夏令时调整。这一分支在外汇与贵金属行情里很关键,因为欧美盘夏令切换会让每日高低点出现约 1 小时的时移,忽略它可能把新闻日历的触发时刻算错。 CreateAutoDST 负责把推断出的时令落库。它先确认 Calendar 库里有没有 AutoDST 表,没有就执行 CREATE TABLE AutoDST(DST STRING NOT NULL);若建表失败直接关库退出。表已存在则立刻 return,避免重复写入。 插入动作靠 DSTType 三元判断拼 SQL:US_DST 写 Data_US,UK_DST 写 Data_UK,AU_DST 写 Data_AU,其余写 Data_None。执行失败会回滚事务并打印错误码,保证数据库不被半截数据锁死。 CreateTable 则是更通用的建表封装,用 StringFormat("Data_%s", tableName) 拼表名并检查存在性,存在就把引用参数 tableExists 置 true,供上层决定是否跳过。开 MT5 把这段挂到 EA 初始化里,能在首次运行后于本地看到 AutoDST 一行记录,验证经纪商时令是否被识别。

MQL5 / C++
Print("Cannot Detect Broker ServerTime Configuration!");
  dstType = DST_NONE;class=class="str">"cmt">//Assigning enumeration value DST_NONE, Broker has no DST(Daylight Savings Time)
  class="kw">return class="kw">false;class=class="str">"cmt">//Returning False, Broker&class="macro">#x27;s DST schedule was not found
  }
class="type">void                CreateAutoDST(class="type">int db);class=class="str">"cmt">//Function for creating and inserting Recommend DST for the Broker into a table
class="type">void CNews::CreateAutoDST(class="type">int db)
  {
   class="type">bool failed=class="kw">false;class=class="str">"cmt">//boolean variable
   if(!DatabaseTableExists(db,"AutoDST"))class=class="str">"cmt">//Checks if the table &class="macro">#x27;AutoDST&class="macro">#x27; exists in the databse &class="macro">#x27;Calendar&class="macro">#x27;
     {
      class=class="str">"cmt">//--- create the table AutoDST
      if(!DatabaseExecute(db,"CREATE TABLE AutoDST(DST STRING NOT NULL);"))class=class="str">"cmt">//Will attempt to create the table &class="macro">#x27;AutoDST&class="macro">#x27;
        {
         Print("DB: create the AutoDST table failed with code ", GetLastError());
         DatabaseClose(db);class=class="str">"cmt">//Close the database
         class="kw">return;class=class="str">"cmt">//Exits the function if creating the table failed
        }
     }
   else
     {
      class="kw">return;class=class="str">"cmt">//Exits the function if the table AutoDST table already exists
     }
class=class="str">"cmt">//Sql query/request to insert the recommend DST for the Broker class="kw">using the DSTType variable to determine which class="type">class="kw">string data to insert
   class="type">class="kw">string request_text=StringFormat("INSERT INTO &class="macro">#x27;AutoDST&class="macro">#x27;(DST) VALUES(&class="macro">#x27;%s&class="macro">#x27;)",((DSTType==US_DST)?"Data_US":
                                                    (DSTType==UK_DST)?"Data_UK":(DSTType==AU_DST)?"Data_AU":"Data_None"));
   if(!DatabaseExecute(db, request_text))class=class="str">"cmt">//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat("INSERT INTO &class="macro">#x27;AutoDST&class="macro">#x27;(DST) VALUES(&class="macro">#x27;%s&class="macro">#x27;)",((DSTType==US_DST)?"Data_US":
                                                    (DSTType==UK_DST)?"Data_UK":(DSTType==AU_DST)?"Data_AU":"Data_None"));class=class="str">"cmt">//Will print the sql query if failed
      failed=true;class=class="str">"cmt">//assign true if the request failed
     }
   if(failed)
     {
      class=class="str">"cmt">//--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(db);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
     }
  }
class="type">bool                CreateTable(class="type">int db,class="type">class="kw">string tableName,class="type">bool &tableExists);class=class="str">"cmt">//Function for creating a table in a database
class="type">bool CNews::CreateTable(class="type">int db,class="type">class="kw">string tableName,class="type">bool &tableExists)
  {
   if(DatabaseTableExists(db,StringFormat("Data_%s",tableName)))class=class="str">"cmt">//Checks if a table &class="macro">#x27;Data_%s&class="macro">#x27; exists in the database &class="macro">#x27;Calendar&class="macro">#x27;
     {
      tableExists=true;class=class="str">"cmt">//Assigns true to tableExists variable

◍ 建表前先清场的稳妥写法

在 MT5 里用 DatabaseExecute 写财经事件表之前,先判断同名表是否存在并主动 DROP,能避免重复结构导致的写入冲突。下面这段逻辑先拼出 Data_%s 的表名,若已存在就删;删失败则打印错误码并关闭数据库返回 false,不再往下跑。 如果表确实不存在,才用 CREATE TABLE 建一张含 13 个字段的 Data_%s 表:ID 与 EVENTID 是 INT 且 NOT NULL,其余 COUNTRY、EVENTNAME、EVENTTYPE、EVENTIMPORTANCE、EVENTDATE、EVENTCURRENCY、EVENTCODE、EVENTSECTOR、EVENTFORECAST、EVENTPREVALUE 等全是 STRING 且 NOT NULL。注意字段宽度在 SQL 里用双空格隔开类型,MT5 的 SQLite 引擎会原样解析。 开 MT5 按 F4 把下面代码贴进 EA 的初始化段,改个 tableName 就能在终端数据文件夹里看到生成的 .sqlite;若 DROP 报错,专家日志会给出具体 GetLastError 数字,照着查文档即可定位权限或句柄问题。外汇与贵金属事件驱动策略本身高风险,表结构只是基建,不代表任何方向判断。

MQL5 / C++
if(!DatabaseExecute(db,StringFormat("DROP TABLE Data_%s",tableName)))class=class="str">"cmt">//We will drop the table if the table already exists
     {
     class=class="str">"cmt">//If the table failed to be dropped/deleted
     PrintFormat("Failed to drop table Data_%s with code %d",tableName,GetLastError());
     DatabaseClose(db);class=class="str">"cmt">//Close the database
     class="kw">return class="kw">false;class=class="str">"cmt">//will terminate execution of the rest of the code below and class="kw">return class="kw">false, when the table cannot be dropped
     }
   }
 if(!DatabaseTableExists(db,StringFormat("Data_%s",tableName)))class=class="str">"cmt">//If the database table &class="macro">#x27;Data_%s&class="macro">#x27; doesn&class="macro">#x27;t exist
  {
   class=class="str">"cmt">//--- create the table &class="macro">#x27;Data&class="macro">#x27; with the following columns
   if(!DatabaseExecute(db,StringFormat("CREATE TABLE Data_%s("
                                                    "ID INT NOT NULL,"
                                                    "EVENTID  INT   NOT NULL,"
                                                    "COUNTRY  STRING   NOT NULL,"
                                                    "EVENTNAME  STRING   NOT NULL,"
                                                    "EVENTTYPE  STRING   NOT NULL,"
                                                    "EVENTIMPORTANCE  STRING   NOT NULL,"
                                                    "EVENTDATE  STRING   NOT NULL,"
                                                    "EVENTCURRENCY STRING   NOT NULL,"
                                                    "EVENTCODE  STRING   NOT NULL,"
                                                    "EVENTSECTOR STRING   NOT NULL,"
                                                    "EVENTFORECAST STRING   NOT NULL,"
                                                    "EVENTPREVALUE STRING   NOT NULL,"

「把财经日历塞进本地数据库再回读」

建表语句里 EVENTIMPACT、EVENTFREQUENCY 都设了 NOT NULL,主键锁在 ID 上;若 DatabaseExecute 返回非 0 失败,GetLastError 会打印具体错误码并直接 DatabaseClose 退出,这一步不成功后面全白做。 EconomicDetails 先拿 CalendarCountries 拉全量国家数组,count 就是可用国家数;再对每个国家用 CalendarValueHistory 按 date_from=0 到「下个月今日」拉事件,单次循环就可能往 NewsTime[] 里塞几十到上百条,具体量取决于当前月份央行日程密度。 回读时 CalendarEventById 用 values[x].event_id 反查 MqlCalendarEvent,StringReplace 把事件名里的单引号干掉,否则写库会断字符串;CountryName、EventName、EventType 分别落结构体,EventType 靠 EnumToString 从枚举翻成可读文本。 外汇与贵金属受这些数据冲击波动可能放大,属高风险场景;跑这段代码前先在 MT5 终端开「工具—经济日历」确认本地日历已同步,否则 countries 可能为空数组。

MQL5 / C++
              "EVENTIMPACT STRING   NOT NULL,"
              "EVENTFREQUENCY STRING   NOT NULL,"
              "PRIMARY KEY(ID));",tableName)) class=class="str">"cmt">//Checks if the table was successfully created
   {
    Print("DB: create the Calendar table failed with code ", GetLastError());
    DatabaseClose(db); class=class="str">"cmt">//Close the database
    class="kw">return class="kw">false; class=class="str">"cmt">//Function returns class="kw">false if creating the table failed
   }
  }
 class="kw">return true; class=class="str">"cmt">//Function returns true if creating the table was successful
}
class="type">void                EconomicDetails(Calendar &NewsTime[]); class=class="str">"cmt">//Gets values from the MQL5 economic Calendar
class="type">void CNews::EconomicDetails(Calendar &NewsTime[])
  {
   class="type">int Size=class="num">0; class=class="str">"cmt">//to keep track of the size of the events in the NewsTime array
   MqlCalendarCountry countries[];
   class="type">int count=CalendarCountries(countries); class=class="str">"cmt">//Get the array of country names available in the Calendar
   class="type">class="kw">string Country_code="";
   for(class="type">int i=class="num">0; i<count; i++)
    {
     MqlCalendarValue values[];
     class="type">class="kw">datetime date_from=class="num">0; class=class="str">"cmt">//Get date from the beginning
     class="type">class="kw">datetime date_to=(class="type">class="kw">datetime)(Time.MonthsS()+iTime(Symbol(),PERIOD_D1,class="num">0)); class=class="str">"cmt">//Date of the next month from the current day
     if(CalendarValueHistory(values,date_from,date_to,countries[i].code))
      {
       for(class="type">int x=class="num">0; x<(class="type">int)ArraySize(values); x++)
        {
         MqlCalendarEvent event;
         class="type">class="kw">ulong event_id=values[x].event_id; class=class="str">"cmt">//Get the event id
         if(CalendarEventById(event_id,event))
          {
           ArrayResize(NewsTime,Size+class="num">1,Size+class="num">2); class=class="str">"cmt">//Readjust the size of the array to +class="num">1 of the array size
           StringReplace(event.name,"&class="macro">#x27;",""); class=class="str">"cmt">//Removing or replacing single quotes(&class="macro">#x27;) from event name with an empty class="type">class="kw">string
           NewsTime[Size].CountryName = countries[i].name; class=class="str">"cmt">//storing the country&class="macro">#x27;s name from the specific event
           NewsTime[Size].EventName = event.name; class=class="str">"cmt">//storing the event&class="macro">#x27;s name
           NewsTime[Size].EventType = EnumToString(event.type); class=class="str">"cmt">//storing the event type from(ENUM_CALENDAR_EVENT_TYPE) to a class="type">class="kw">string

把财经日历逐条塞进结构体数组

做财经日历聚合时,核心动作是把每条事件的多维属性写进自定义结构体数组 NewsTime[]。下面这段循环把重要性、ID、时间、币种、国码、板块、预测值、前值、影响类型、发布频率全部落库,缺预测或前值时用 "None" 占位,避免后续 SQL 或面板读取时报类型错。 [CODE]NewsTime[Size].EventImportance = EnumToString(event.importance); //把枚举型重要性转成字符串存 NewsTime[Size].EventId = event.id; //存事件 ID NewsTime[Size].EventDate = TimeToString(values[x].time); //普通时间转字符串 NewsTime[Size].EventCurrency = countries[i].currency; //存币种 NewsTime[Size].EventCode = countries[i].code; //存国家代码 NewsTime[Size].EventSector = EnumToString(event.sector); //板块枚举转字符串 if(values[x].HasForecastValue()) //判断有无预测值 NewsTime[Size].EventForecast = (string)values[x].forecast_value; //有则转字符串存 else NewsTime[Size].EventForecast = "None"; //无则写 None if(values[x].HasPreviousValue()) //判断有无前值 NewsTime[Size].EventPreval = (string)values[x].prev_value; //有则转字符串存 else NewsTime[Size].EventPreval = "None"; //无则写 None NewsTime[Size].EventImpact = EnumToString(values[x].impact_type); //影响类型枚举转字符串 NewsTime[Size].EventFrequency = EnumToString(event.frequency); //频率枚举转字符串 Size++; //数组下标自增[/CODE] 落完数组后,InsertIntoTable(int db, DST_type Type, Calendar &Evalues[]) 负责把 Calendar 引用数组写进本地库表。入参 db 是库句柄,Type 决定表名后缀,Evalues 就是前面填好的事件集合;函数内先用 switch(Type) 区分存储目标,再逐条遍历 Evalues.Size() 写入。 开 MT5 把这段结构体填充逻辑接进 CNews 类,跑一次非农或 CPI 发布日,观察 NewsTime 里 EventForecast 为 "None" 的条目比例——实际数据里大约 1~2 成事件不附带预测值,这会影响你做预期差策略时的过滤条件。外汇与贵金属受新闻冲击跳空概率偏高,验证时请用模拟盘。

MQL5 / C++
NewsTime[Size].EventImportance = EnumToString(event.importance);class=class="str">"cmt">//storing the event importance from(ENUM_CALENDAR_EVENT_IMPORTANCE) to a class="type">class="kw">string
NewsTime[Size].EventId = event.id;class=class="str">"cmt">//storing the event id
NewsTime[Size].EventDate = TimeToString(values[x].time);class=class="str">"cmt">//storing normal event time
NewsTime[Size].EventCurrency = countries[i].currency;class=class="str">"cmt">//storing event currency
NewsTime[Size].EventCode = countries[i].code;class=class="str">"cmt">//storing event code
NewsTime[Size].EventSector = EnumToString(event.sector);class=class="str">"cmt">//storing event sector from(ENUM_CALENDAR_EVENT_SECTOR) to a class="type">class="kw">string
if(values[x].HasForecastValue())class=class="str">"cmt">//Checks if the event has a forecast value
  {
  NewsTime[Size].EventForecast = (class="type">class="kw">string)values[x].forecast_value;class=class="str">"cmt">//storing the forecast value into a class="type">class="kw">string
  }
else
  {
  NewsTime[Size].EventForecast = "None";class=class="str">"cmt">//storing &class="macro">#x27;None&class="macro">#x27; as the forecast value
  }
if(values[x].HasPreviousValue())class=class="str">"cmt">//Checks if the event has a previous value
  {
  NewsTime[Size].EventPreval = (class="type">class="kw">string)values[x].prev_value;class=class="str">"cmt">//storing the previous value into a class="type">class="kw">string
  }
else
  {
  NewsTime[Size].EventPreval = "None";class=class="str">"cmt">//storing &class="macro">#x27;None&class="macro">#x27; as the previous value
  }
NewsTime[Size].EventImpact = EnumToString(values[x].impact_type);class=class="str">"cmt">//storing the event impact from(ENUM_CALENDAR_EVENT_IMPACT) to a class="type">class="kw">string
NewsTime[Size].EventFrequency = EnumToString(event.frequency);class=class="str">"cmt">//storing the event frequency from(ENUM_CALENDAR_EVENT_FREQUENCY) to a class="type">class="kw">string
Size++;class=class="str">"cmt">//incrementing the Calendar array NewsTime

class="type">bool InsertIntoTable(class="type">int db,DST_type Type,Calendar &Evalues[]);class=class="str">"cmt">//Function for inserting Economic Data in to a database&class="macro">#x27;s table
class="type">int db
DST_type Type
Calendar &Evalues[]
class="type">bool CNews::InsertIntoTable(class="type">int db,DST_type Type,Calendar &Evalues[])
  {
  class="type">class="kw">string tableName;class=class="str">"cmt">//will store the table name suffix
  for(class="type">uint i=class="num">0; i<Evalues.Size(); i++)class=class="str">"cmt">//Looping through all the Economic Events
    {
    class="type">class="kw">string Date;class=class="str">"cmt">//Will store the date for the economic event
    class="kw">switch(Type)class=class="str">"cmt">//Switch statement to check all possible &class="macro">#x27;case&class="macro">#x27; scenarios for the variable Type

◍ 夏令时分流与数据落库写法

经济日历入库前,先按地区做夏令时(DST)分支。代码用 switch 把 Type 分成 DST_NONE、US_DST、UK_DST、AU_DST 和 default 五类:无夏令时或未知类型直接取 EventDate 原值,表名记成 None;美英澳则调用对应 Savings_XX.adjustDaylightSavings,把 StringToTime(EventDate) 转成时间秒数后按引用回填 Date,表名分别记 US / UK / AU。 落库时表名是动态拼出来的,完整名会是 Data_None、Data_US、Data_UK 或 Data_AU。这意味着同一套 INSERT 语句能写进四张结构相同的表,字段覆盖 ID、EVENTID、COUNTRY、EVENTNAME、EVENTTYPE、EVENTIMPORTANCE、EVENTDATE、EVENTCURRENCY、EVENTCODE、EVENTSECTOR、EVENTFORECAST、EVENTPREVALUE、EVENTIMPACT、EVENTFREQUENCY 共 14 列。 开 MT5 把这段塞进 EA 的日历写入模块,重点核对 AU_DST 分支:澳储行事件常因南半球夏令时反季,若 adjustDaylightSavings 没接对,EVENTDATE 可能偏 1 小时,后续按时间触发的贵金属策略会错拍。外汇与贵金属受此类时区错位影响明显,属高风险环节,建议先用历史经济数据回放验证。

MQL5 / C++
   {
      case DST_NONE:class=class="str">"cmt">//if(Type==DST_NONE) then run code below
         Date = Evalues[i].EventDate;class=class="str">"cmt">//Assign the normal Economic EventDate
         tableName = "None";class=class="str">"cmt">//Full table name will be &class="macro">#x27;Data_None&class="macro">#x27;
         class="kw">break;class=class="str">"cmt">//End class="kw">switch statement
      case US_DST:class=class="str">"cmt">//if(Type==US_DST) then run code below
         Savings_US.adjustDaylightSavings(StringToTime(Evalues[i].EventDate),Date);class=class="str">"cmt">//Assign by Reference the Economic EventDate adjusted for US DST(Daylight Savings Time)
         tableName = "US";class=class="str">"cmt">//Full table name will be &class="macro">#x27;Data_US&class="macro">#x27;
         class="kw">break;class=class="str">"cmt">//End class="kw">switch statement
      case UK_DST:class=class="str">"cmt">//if(Type==UK_DST) then run code below
         Savings_UK.adjustDaylightSavings(StringToTime(Evalues[i].EventDate),Date);class=class="str">"cmt">//Assign by Reference the Economic EventDate adjusted for UK DST(Daylight Savings Time)
         tableName = "UK";class=class="str">"cmt">//Full table name will be &class="macro">#x27;Data_UK&class="macro">#x27;
         class="kw">break;class=class="str">"cmt">//End class="kw">switch statement
      case AU_DST:class=class="str">"cmt">//if(Type==AU_DST) then run code below
         Savings_AU.adjustDaylightSavings(StringToTime(Evalues[i].EventDate),Date);class=class="str">"cmt">//Assign by Reference the Economic EventDate adjusted for AU DST(Daylight Savings Time)
         tableName = "AU";class=class="str">"cmt">//Full table name will be &class="macro">#x27;Data_AU&class="macro">#x27;
         class="kw">break;class=class="str">"cmt">//End class="kw">switch statement
      class="kw">default:class=class="str">"cmt">//if(Type==(Unknown value)) then run code below
         Date = Evalues[i].EventDate;class=class="str">"cmt">//Assign the normal Economic EventDate
         tableName = "None";class=class="str">"cmt">//Full table name will be &class="macro">#x27;Data_None&class="macro">#x27;
         class="kw">break;class=class="str">"cmt">//End class="kw">switch statement
   }
   class="type">class="kw">string request_text =
      StringFormat("INSERT INTO &class="macro">#x27;Data_%s&class="macro">#x27;(ID,EVENTID,COUNTRY,EVENTNAME,EVENTTYPE,EVENTIMPORTANCE,EVENTDATE,EVENTCURRENCY,EVENTCODE,"
                     "EVENTSECTOR,EVENTFORECAST,EVENTPREVALUE,EVENTIMPACT,EVENTFREQUENCY)"
                     "VALUES(%d,%d,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;, &class="macro">#x27;%s&class="macro">#x27;, &class="macro">#x27;%s&class="macro">#x27;, &class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;)",
                     tableName,
                     i,
                     Evalues[i].EventId,
                     Evalues[i].CountryName,
                     Evalues[i].EventName,
                     Evalues[i].EventType,
                     Evalues[i].EventImportance,
                     Date,
                     Evalues[i].EventCurrency,

「把财经事件逐列写进数据库表」

这段逻辑负责把解析好的财经事件结构体 Evalues[i] 逐条插入到 MT5 本地 SQLite 的 Data_%s 表。插入动作调用 DatabaseExecute,若返回 false 表示写入失败,此时会把完整 SQL 语句用 PrintFormat 打出来,方便你直接肉眼查引号或字段错位。 失败分支里先 GetLastError 再打印 SQL,然后 return false 终止循环——这意味着只要有一条记录写不进表,整个批量写入就宣告失败,不会留半张表给你后续误用。 紧随其后的 CreateRecords 方法用于建一张 Records 表,记录日历库最近一次更新时间。它先用 DatabaseTableExists(db,"Records") 判断表是否存在,不存在才走建表分支,避免重复建表报错的冗余逻辑。 在 MT5 里跑这段代码时,建议先手动执行一次打印出的 SQL 看字段顺序;外汇与贵金属受事件冲击波动剧烈,本地日历库写入不完整可能导致小布盯盘漏判高风险事件。

MQL5 / C++
Evalues[i].EventCode,
Evalues[i].EventSector,
Evalues[i].EventForecast,
Evalues[i].EventPreval,
Evalues[i].EventImpact,
Evalues[i].EventFrequency);class=class="str">"cmt">//Inserting all the columns for each event record
if(!DatabaseExecute(db, request_text))class=class="str">"cmt">//Checks whether the event was inserted into the table &class="macro">#x27;Data_%s&class="macro">#x27;
  {
   Print(GetLastError());
   PrintFormat("INSERT INTO &class="macro">#x27;Data_%s&class="macro">#x27;(ID,EVENTID,COUNTRY,EVENTNAME,EVENTTYPE,EVENTIMPORTANCE,EVENTDATE,EVENTCURRENCY,EVENTCODE,"
                "EVENTSECTOR,EVENTFORECAST,EVENTPREVALUE,EVENTIMPACT,EVENTFREQUENCY)"
                "VALUES(%d,%d,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;, &class="macro">#x27;%s&class="macro">#x27;, &class="macro">#x27;%s&class="macro">#x27;, &class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;)",
                tableName,
                i,
                Evalues[i].EventId,
                Evalues[i].CountryName,
                Evalues[i].EventName,
                Evalues[i].EventType,
                Evalues[i].EventImportance,
                Date,
                Evalues[i].EventCurrency,
                Evalues[i].EventCode,
                Evalues[i].EventSector,
                Evalues[i].EventForecast,
                Evalues[i].EventPreval,
                Evalues[i].EventImpact,
                Evalues[i].EventFrequency);class=class="str">"cmt">//Will print the sql query to check for any errors or possible defaults in the query/request
   class="kw">return class="kw">false;class=class="str">"cmt">//Will end the loop and class="kw">return class="kw">false, as values failed to be inserted into the table
  }
 }
 class="kw">return true;class=class="str">"cmt">//Will class="kw">return true, all values were inserted into the table successfully
}
class="type">void                CreateRecords(class="type">int db);class=class="str">"cmt">//Creates a table to store records of when last the Calendar database was updated/created
class="type">void CNews::CreateRecords(class="type">int db)
 {
 class="type">bool failed=class="kw">false;
 if(!DatabaseTableExists(db,"Records"))class=class="str">"cmt">//Checks if the table &class="macro">#x27;Records&class="macro">#x27; exists in the databse &class="macro">#x27;Calendar&class="macro">#x27;
  {
  class=class="str">"cmt">//--- create the table

用 SQLite 给新闻库打时间戳与增量判定

在 MT5 里用 DatabaseExecute 建一张 Records 表,只存一列 RECORDEDTIME(INT 非空),相当于给本地新闻库写一条“最后更新时刻”的痕迹。建表失败就直接 Print 错误码并 Close 退出,不往下走。 插入时用 StringFormat 把 TimeCurrent() 强转成 int 写进 RECORDEDTIME;若 DatabaseExecute 返回 false,把 failed 置 true 并回滚事务 DatabaseTransactionRollback,避免半截脏数据锁库。 UpdateRecords 以 DATABASE_OPEN_READONLY|COMMON 方式开库,若句柄无效且 FileIsExist 也找不到文件,返回 true 表示需要重建;表不存在同样返回 true。 真正判定要不要更新,是靠 SELECT MAX(RECORDEDTIME) FROM Records 取出历史最大时间,用 DatabasePrepare 拿请求句柄,失败则关库返回 true。这条 max 值就是后续比对服务器数据的基准线,外汇与贵金属事件驱动策略里,本地缓存过期判定误差可能带来滑点风险。

MQL5 / C++
if(!DatabaseExecute(db,"CREATE TABLE Records(RECORDEDTIME INT NOT NULL);"))class=class="str">"cmt">//Will attempt to create the table &class="macro">#x27;Records&class="macro">#x27;
      {
       Print("DB: create the Records table failed with code ", GetLastError());
       DatabaseClose(db);class=class="str">"cmt">//Close the database
       class="kw">return;class=class="str">"cmt">//Exits the function if creating the table failed
      }
class=class="str">"cmt">//Sql query/request to insert the current time into the &class="macro">#x27;RECORDEDTIME&class="macro">#x27; column in the table &class="macro">#x27;Records&class="macro">#x27;
   class="type">class="kw">string request_text=StringFormat("INSERT INTO &class="macro">#x27;Records&class="macro">#x27;(RECORDEDTIME) VALUES(%d)",(class="type">int)TimeCurrent());
   if(!DatabaseExecute(db, request_text))class=class="str">"cmt">//Will attempt to run this sql request/query
     {
      Print(GetLastError());
      PrintFormat("INSERT INTO &class="macro">#x27;Records&class="macro">#x27;(RECORDEDTIME) VALUES(%d)",(class="type">int)TimeCurrent());
      failed=true;class=class="str">"cmt">//assign true if the request failed
     }
   if(failed)
     {
      class=class="str">"cmt">//--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(db);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
     }
}
class="type">bool UpdateRecords();class=class="str">"cmt">//Checks if the main Calendar database needs an update or not
class="type">bool CNews::UpdateRecords()
  {
class=class="str">"cmt">//--- open/create
   class="type">int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READONLY|DATABASE_OPEN_COMMON);class=class="str">"cmt">//try to open database Calendar
   if(db==INVALID_HANDLE)class=class="str">"cmt">//Checks if the database was able to be opened
     {
      class=class="str">"cmt">//if opening the database failed
      if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))class=class="str">"cmt">//Checks if the database Calendar exists in the common folder
        {
         class="kw">return true;class=class="str">"cmt">//Returns true when the database was failed to be opened and the file doesn&class="macro">#x27;t exist in the common folder
        }
     }
   if(!DatabaseTableExists(db,"Records"))class=class="str">"cmt">//If the database table &class="macro">#x27;Records&class="macro">#x27; doesn&class="macro">#x27;t exist
     {
      DatabaseClose(db);
      class="kw">return true;
     }
   class="type">int recordtime=class="num">0;class=class="str">"cmt">//will store the maximum date recorded in the database table &class="macro">#x27;Records&class="macro">#x27;
   class="type">class="kw">string request_text="SELECT MAX(RECORDEDTIME) FROM Records";class=class="str">"cmt">//Sql query to determine the lastest or maximum date recorded
   class="type">int request=DatabasePrepare(db,request_text);class=class="str">"cmt">//Creates a handle of a request, which can then be executed class="kw">using DatabaseRead()
   if(request==INVALID_HANDLE)class=class="str">"cmt">//Checks if the request failed to be completed
     {
      Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
      DatabaseClose(db);
      class="kw">return true;
     }

◍ 从财经库里抓最新一条记录的时间

在 MT5 里直接用 SQLite 接口读财经日历库,比每次全表扫更省资源。下面这段逻辑先打开公共目录下的只读库,再用 SELECT MAX(RECORDEDTIME) 拿到库内最新更新时间,若库不存在就返回 1970.01.01 00:00:00 这个 MQL5 时间起点。 打开库用 DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READONLY|DATABASE_OPEN_COMMON),句柄为 INVALID_HANDLE 时先确认文件是否真的缺失。若 FileIsExist 也返回否,直接 return 0,相当于把时间退到 epoch 起点。 请求用 DatabasePrepare 编译 SQL,失败就打印错误码并关库返回。读结果时 DatabaseColumnText 把第 0 列文本塞进 eventtime,任何一次读取失败都立即 Finalize + Close 并回退到 0。 别把只读库当缓存随便写。 forex / 贵金属新闻库多为第三方下发,路径或权限不对时返回 0 会让后续「是否今日」判断全部失效,开盘前手动跑一次 GetLastestNewsDate() 验证句柄更稳。

MQL5 / C++
class="type">class="kw">datetime CNews::GetLastestNewsDate()
  {
  class="type">int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READONLY|DATABASE_OPEN_COMMON);
  if(db==INVALID_HANDLE)
    {
    if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))
      {
      class="kw">return class="num">0;
      }
    }
  class="type">class="kw">string eventtime="class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00";
  class="type">class="kw">string request_text="SELECT MAX(RECORDEDTIME) FROM Records";
  class="type">int request=DatabasePrepare(db,request_text);
  if(request==INVALID_HANDLE)
    {
    Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
    DatabaseClose(db);
    class="kw">return true;
    }
  for(class="type">int i=class="num">0; DatabaseRead(request); i++)
    {
    if(!DatabaseColumnText(request, class="num">0,eventtime))
      {
      Print(i, ": DatabaseRead() failed with code ", GetLastError());
      DatabaseFinalize(request);
      DatabaseClose(db);
      class="kw">return class="num">0;
      }
    }
  DatabaseFinalize(request);

「建库时的文件与表存在性判断」

在 MT5 里用 MQL5 搭一个经济日历本地库,第一步不是直接写数据,而是先确认文件和表是否已经在公共目录(FILE_COMMON)中存在。上面这段 CNews::CreateEconomicDatabase 的逻辑,就是先用 FileIsExist 探 NEWS_DATABASE_FILE 和 NEWS_TEXT_FILE,避免重复建库或覆盖已有记录。

如果数据库文件已存在,就走 UpdateRecords 做增量更新;若文件不存在,则先 AutoDetectDST 处理时区,再开库建表。这里 db 句柄用 DatabaseOpen 带 DATABASE_OPEN_READWRITEDATABASE_OPEN_CREATEDATABASE_OPEN_COMMON,失败会返回 INVALID_HANDLE 并打印 GetLastError 代码,实盘前可在日志里直接抓到是 5001 还是 5002 这类文件错误。

建表环节用 CreateTable 分别对 None、US、UK、AU 四个标识建日历表,只要有一个返回 false,就 FileClose 并 FileDelete 掉刚生成的 NEWS_TEXT_FILE 文本标记,整个函数直接 return。这种「先占位文本文件、建表失败即清理」的做法,能防止半截数据库残留在公共目录导致下次加载卡死。 开 MT5 把 NEWS_DATABASE_FILE 指到 Terminal/Common/Files 下,故意删掉库文件跑一次,就能看到 Print('Please wait...') 后 AutoDetectDST 分支被触发,验证这套容错是不是真生效。

MQL5 / C++
  DatabaseClose(db);class=class="str">"cmt">//Closes the database &class="macro">#x27;Calendar&class="macro">#x27;
  class="kw">return StringToTime(eventtime);class=class="str">"cmt">//Returns the class="type">class="kw">string eventtime converted to class="type">class="kw">datetime
  }
class="type">void                CreateEconomicDatabase();class=class="str">"cmt">//Creates the Calendar database for a specific Broker
class="type">void CNews::CreateEconomicDatabase()
  {
  Print("Please wait...");
  if(FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))class=class="str">"cmt">//Check if the database exists
    {
      if(!UpdateRecords())class=class="str">"cmt">//Check if the database is up to date
        {
         class="kw">return;class=class="str">"cmt">//will terminate execution of the rest of the code below
        }
    }
  else
    {
      if(!AutoDetectDST(DSTType))class=class="str">"cmt">//Check if AutoDetectDST went through all the right procedures
        {
         class="kw">return;class=class="str">"cmt">//will terminate execution of the rest of the code below
        }
    }
  if(FileIsExist(NEWS_TEXT_FILE,FILE_COMMON))class=class="str">"cmt">//Check if the database is open
    {
      class="kw">return;class=class="str">"cmt">//will terminate execution of the rest of the code below
    }
  Calendar Evalues[];class=class="str">"cmt">//Creating a Calendar array variable
  class="type">bool failed=class="kw">false,tableExists=class="kw">false;
  class="type">int file=INVALID_HANDLE;
  class="type">class="kw">datetime lastestdate=D&class="macro">#x27;class="num">1970.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;;
class=class="str">"cmt">//--- open/create the database &class="macro">#x27;Calendar&class="macro">#x27;
  class="type">int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE| DATABASE_OPEN_COMMON);class=class="str">"cmt">//will try to open/create in the common folder
  if(db==INVALID_HANDLE)class=class="str">"cmt">//Checks if the database &class="macro">#x27;Calendar&class="macro">#x27; failed to open/create
    {
      Print("DB: ",NEWS_DATABASE_FILE, " open failed with code ", GetLastError());
      class="kw">return;class=class="str">"cmt">//will terminate execution of the rest of the code below
    }
  else
    {
      file=FileOpen(NEWS_TEXT_FILE,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON);class=class="str">"cmt">//try to create a text file &class="macro">#x27;NewsDatabaseOpen&class="macro">#x27; in common folder
      if(file==INVALID_HANDLE)
        {
         DatabaseClose(db);class=class="str">"cmt">//Closes the database &class="macro">#x27;Calendar&class="macro">#x27; if the News text file failed to be created
         class="kw">return;class=class="str">"cmt">//will terminate execution of the rest of the code below
        }
    }
  DatabaseTransactionBegin(db);class=class="str">"cmt">//Starts transaction execution
  Print("Please wait...");
class=class="str">"cmt">//-- attempt to create the calendar tables
  if(!CreateTable(db,"None",tableExists)||!CreateTable(db,"US",tableExists)
      ||!CreateTable(db,"UK",tableExists)||!CreateTable(db,"AU",tableExists))
    {
      FileClose(file);class=class="str">"cmt">//Closing the file &class="macro">#x27;NewsDatabaseOpen.txt&class="macro">#x27;
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);class=class="str">"cmt">//Deleting the file &class="macro">#x27;NewsDatabaseOpen.txt&class="macro">#x27;
      class="kw">return;class=class="str">"cmt">//
    }

财经日历入库的回滚与提交逻辑

把财经事件写进本地数据库时,先判断表是否已存在:存在就打印 Updating 提示,不存在则打印 Creating 提示,让交易者知道当前是新建还是更新。 真正落库是对 DST_NONE、US_DST、UK_DST、AU_DST 四张表分别调用 InsertIntoTable。只要任意一张插入失败,failed 置为 true,后续统一走回滚分支。 回滚分支里 DatabaseTransactionRollback 撤销全部事务,并打印 GetLastError 返回的错误码;同时关闭并删除临时文本文件 NEWS_TEXT_FILE,再用 ArrayRemove 清空 Evalues 数组,避免脏数据残留。 若全部插入成功,则按 tableExists 状态打印 Updated 或 Created,接着 CreateRecords 建记录表写入当前时间,CreateAutoDST 建自动夏令时表写入券商 DST 排程,最后同样清理文本文件和数组。 两条分支汇合后执行 DatabaseTransactionCommit 提交、DatabaseClose 关库。外汇与贵金属受财经数据冲击大,这类本地日历库的写入完整性直接决定后续事件过滤是否漏单,建议开 MT5 跑一遍插入失败路径验证回滚是否干净。

MQL5 / C++
  EconomicDetails(Evalues);class=class="str">"cmt">//Retrieving the data from the Economic Calendar
  if(tableExists)class=class="str">"cmt">//Checks if there is an existing table within the Calendar Database
    {
      class=class="str">"cmt">//if there is an existing table we will notify the user that we are updating the table.
      PrintFormat("Updating %s",NEWS_DATABASE_FILE);
    }
  else
    {
      class=class="str">"cmt">//if there isn&class="macro">#x27;t an existing table we will notify the user that we about to create one
      PrintFormat("Creating %s",NEWS_DATABASE_FILE);
    }
class=class="str">"cmt">//-- attempt to insert economic event data into the calendar tables
  if(!InsertIntoTable(db,DST_NONE,Evalues)||!InsertIntoTable(db,US_DST,Evalues)
      ||!InsertIntoTable(db,UK_DST,Evalues)||!InsertIntoTable(db,AU_DST,Evalues))
    {
      failed=true;class=class="str">"cmt">//Will assign true if inserting economic vaules failed in any of the Data tables
    }
  if(failed)class=class="str">"cmt">//Checks if the event/s failed to be recorded/inserted into the database table &class="macro">#x27;Data_%s&class="macro">#x27;
    {
      class=class="str">"cmt">//--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(db);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
      FileClose(file);class=class="str">"cmt">//Close the text file &class="macro">#x27;NEWS_TEXT_FILE&class="macro">#x27;
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);class=class="str">"cmt">//Delete the text file, as we are reverted/rolled-back the database
      ArrayRemove(Evalues,class="num">0,WHOLE_ARRAY);class=class="str">"cmt">//Removes the values in the array
    }
  elseclass=class="str">"cmt">//if all the events were recorded or inserted into the tables &class="macro">#x27;Data_%s&class="macro">#x27;
    {
      if(tableExists)
        {
         class=class="str">"cmt">//Let the user/trader know that the database was updated
         PrintFormat("%s Updated",NEWS_DATABASE_FILE);
        }
      else
        {
         class=class="str">"cmt">//Let the user/trader know that the database was created
         PrintFormat("%s Created",NEWS_DATABASE_FILE);
        }
      CreateRecords(db);class=class="str">"cmt">//Will create the &class="macro">#x27;Records&class="macro">#x27; table and insert the current time
      CreateAutoDST(db);class=class="str">"cmt">//Will create the &class="macro">#x27;AutoDST&class="macro">#x27; table and insert the broker&class="macro">#x27;s DST schedule
      FileClose(file);class=class="str">"cmt">//Close the text file &class="macro">#x27;NEWS_TEXT_FILE&class="macro">#x27;
      FileDelete(NEWS_TEXT_FILE,FILE_COMMON);class=class="str">"cmt">//Delete the text file, as we are about to close the database
      ArrayRemove(Evalues,class="num">0,WHOLE_ARRAY);class=class="str">"cmt">//Removes the values in the array
    }
class=class="str">"cmt">//--- all transactions have been performed successfully - record changes and unlock the database
  DatabaseTransactionCommit(db);
  DatabaseClose(db);class=class="str">"cmt">//Close the database
}

◍ 初始化时如何拉起财经数据库

EA 在 OnInit 里先挂三个自定义头文件:新闻、时间管理、文件夹操作。前两者用普通对象声明,文件夹类那行末尾带括号,是直接调了构造函数做初始化。 真正麻烦的是数据库当日有效性判断。代码用 MQLInfoInteger(MQL_TESTER) 区分实盘/测试环境:不在策略测试器里时,才检查 NEWS_DATABASE_FILE 是否存在于公共目录、且修改日期是不是今天。任一不满足,就进 do-while 循环等终端连网,连上了调 NewsObject.CreateEconomicDatabase() 重建库,done 置真才退出。 测试环境下逻辑更硬:文件不存在直接 Print 三行报错,提示必须先脱离策略测试器跑一次让必要文件生成。外汇与贵金属行情受新闻冲击大,这类库缺失会让事件过滤失效,实盘前务必手动确认一次文件日期。 下面这段是 OnInit 前半部分的可验证骨架,复制进 MT5 能直接看连接与建库日志。

MQL5 / C++
class="macro">#include "News.mqh"
CNews NewsObject;
class="macro">#include "TimeManagement.mqh"
CTimeManagement CTM;
class="macro">#include "WorkingWithFolders.mqh"
CFolders Folder();class=class="str">"cmt">//Calling the class&class="macro">#x27;s constructor
class="type">int OnInit()
  {
   if(!MQLInfoInteger(MQL_TESTER))
     {
      if((!CTM.DateisToday((class="type">class="kw">datetime)FileGetInteger(NEWS_DATABASE_FILE,FILE_MODIFY_DATE,true)))||(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON)))
        {
         class="type">bool done=class="kw">false;
         do
           {
            if(IsStopped())
              {
               done=true;
              }
            if(!TerminalInfoInteger(TERMINAL_CONNECTED))
              {
               Print("Waiting for connection...");
               Sleep(class="num">500);
               class="kw">continue;
              }
            else
              {
               Print("Connection Successful!");
               NewsObject.CreateEconomicDatabase();
               done=true;
              }
           }
         class="kw">while(!done);
        }
     }
   else
     {
      if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))
        {
         Print("Necessary Files Do not Exist!");
         Print("Run Program outside of the Strategy Tester");
         Print("Necessary Files Should be Created First");

「新闻库过期校验与跨区时间表拼接」

EA 初始化阶段有一段容易被忽略的防御逻辑:用 CTM.TimePlusOffset 把数据库里最后一条新闻日期加一天,得到 lastestdate,再和 TimeCurrent() 比。若 lastestdate 小于当前时间,说明本地新闻文件已落后于实盘时钟,回测里该日期之后的宏观事件将全部缺席,_print 会打出 'Necessary Files OutDated!' 并提示去策略测试器外跑一次更新程序。 这段校验直接决定回测样本是否含最近的基本面跳空。外汇与贵金属受 CPI、利率决议驱动明显,用过期库跑出来的曲线可能倾向低估滑点和波幅,实盘接手时风险偏高。 新闻时间对齐靠的是 SQLite 多表 INNER JOIN。下面这条查询把 None/US/UK/AU 四张表按 ID 拼起来,统一把 EVENTDATE 里的点号替换成减号再拆出日期与时间,过滤窗是 2023-01-01 到 2024-01-01、EVENTID='840100001',能直接看出同一事件在各时区发布的钟点差。 SELECT None.EVENTNAME, DATE(REPLACE(None.EVENTDATE,'.','-')) as Date, TIME(REPLACE(None.EVENTDATE,'.','-')) as Time_None, TIME(REPLACE(US.EVENTDATE,'.','-')) as Time_US, TIME(REPLACE(UK.EVENTDATE,'.','-')) as Time_UK, TIME(REPLACE(AU.EVENTDATE,'.','-')) as Time_AU FROM 'Data_None' None INNER JOIN 'Data_US' US on US.ID=None.ID INNER JOIN 'Data_UK' UK on UK.ID=None.ID INNER JOIN 'Data_AU' AU on AU.ID=None.ID WHERE Date BETWEEN '2023-01-01' AND '2024-01-01' AND None.EVENTID='840100001'; 想单独拉某个经济体的字段,可用 Distinct(EVENTID) 配合 COUNTRY='New Zealand' 做枚举;只盯物价板块就加 EVENTSECTOR='CALENDAR_SECTOR_PRICES'。高影响事件则用 EVENTIMPORTANCE='CALENDAR_IMPORTANCE_HIGH' 抽出来,UNION 前后两次同条件查询在 MT5 里多出现于调试器看重复行,开 MT5 接 SQLite 插件跑一遍便能验证表结构是否和你本地一致。

MQL5 / C++
SELECT  None.EVENTNAME, DATE(REPLACE(None.EVENTDATE,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) as Date, TIME(REPLACE(None.EVENTDATE,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) as Time_None,
TIME(REPLACE(US.EVENTDATE,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) as Time_US, TIME(REPLACE(UK.EVENTDATE,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) as Time_UK, TIME(REPLACE(AU.EVENTDATE,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) as Time_AU
FROM &class="macro">#x27;Data_None&class="macro">#x27; None
INNER JOIN &class="macro">#x27;Data_US&class="macro">#x27; US on US.ID=None.ID
INNER JOIN &class="macro">#x27;Data_UK&class="macro">#x27; UK on UK.ID=None.ID
INNER JOIN &class="macro">#x27;Data_AU&class="macro">#x27; AU on AU.ID=None.ID
WHERE Date BETWEEN &class="macro">#x27;class="num">2023-class="num">01-class="num">01&class="macro">#x27; AND &class="macro">#x27;class="num">2024-class="num">01-class="num">01&class="macro">#x27; AND None.EVENTID=&class="macro">#x27;class="num">840100001&class="macro">#x27;;
SELECT Distinct(COUNTRY) FROM &class="macro">#x27;Data_None&class="macro">#x27;;
SELECT * FROM &class="macro">#x27;Data_None&class="macro">#x27; where COUNTRY=&class="macro">#x27;New Zealand&class="macro">#x27;;
SELECT Distinct(EVENTID), COUNTRY, EVENTNAME,EVENTTYPE, EVENTSECTOR, EVENTIMPORTANCE, EVENTFREQUENCY,
EVENTCURRENCY, EVENTCODE FROM &class="macro">#x27;Data_None&class="macro">#x27;
where COUNTRY=&class="macro">#x27;New Zealand&class="macro">#x27;;
SELECT Distinct(EVENTID), COUNTRY, EVENTNAME, EVENTTYPE, EVENTIMPORTANCE FROM &class="macro">#x27;Data_None&class="macro">#x27;
where COUNTRY=&class="macro">#x27;New Zealand&class="macro">#x27; and EVENTSECTOR=&class="macro">#x27;CALENDAR_SECTOR_PRICES&class="macro">#x27;;
SELECT COUNTRY,EVENTNAME, EVENTIMPORTANCE FROM &class="macro">#x27;Data_None&class="macro">#x27;
where EVENTIMPORTANCE=&class="macro">#x27;CALENDAR_IMPORTANCE_HIGH&class="macro">#x27;
UNION
SELECT COUNTRY,EVENTNAME, EVENTIMPORTANCE FROM &class="macro">#x27;Data_None&class="macro">#x27;
where EVENTIMPORTANCE=&class="macro">#x27;CALENDAR_IMPORTANCE_HIGH&class="macro">#x27;;

记住这一条就够了

整套新闻交易数据库从选型到落地已经走完:用 MQL5 经济日历覆盖多币种,借 DST 类处理夏令时偏移,再把关键文件写进本地库,按需更新或重建。 建完之后真正值钱的一步,是用基础 SQL 从表里把指定事件捞出来——比如某次 NFP 前后的点差与价格反应,直接喂给后续风控模块。 下一篇会拆风险管理,但这一条先钉死:库建得再漂亮,提不出干净数据就全白搭。

常见问题

先统计该品种 M15 历史波幅,找出显著异常放大的 K 线,再对照财经日历同时段事件,即可锁定新闻发生 K 线。
观察品种 K 线大小在日期上的漂移规律,按经纪商时区回退判定夏令时归属,就能自动定位切换点。
可以,小布能调用财经日历抓去年 NFP 发布日,并按你经纪商时区在终端里自动定位 EURUSD 品种和对应 K 线。
大概率不是数据错,而是券商夏令时切换导致时区偏移、K 线大小漂移,用时区回退法可验证。
去公开财经日历站筛选去年每月首个周五的美国 NFP 记录,整理成日期表即可用于回测。