让新闻交易变得容易(第一部分):创建一个数据库·进阶篇
📘

让新闻交易变得容易(第一部分):创建一个数据库·进阶篇

第 2/3 篇

◍ 澳洲夏令时边界怎么写进回测库

做澳元系货币对或 XAU/AUD 的跨年回测时,策略测试器的时区错位会直接污染成交时间。把澳洲夏令时起止做成一张静态表,是让 2006–2027 年回测日期对齐本地时钟的最低成本方案。 下面这段类声明先定义了三个对象指针:savings 存夏令时区间集合,getSavings 作临时取数,dayLight 是单条记录实例;对外暴露的三个方法分别判断某日是否落在区间内、按年取起止、以及把事件时间按夏令时调时区。 构造函数里用 savings.Add 把 2006.10.29 03:00 到 2027.04.04 02:00 共 21 个年度区间硬编码进 CArrayObj。例如 2024 年区间是 D'2024.10.06 03:00:00' 到 D'2025.04.06 02:00:00',每年 10 月首个周日进夏令、次年 4 月首个周日退出,规律稳定但需手动续表。 外汇与贵金属受时区切换影响报价跳空,属于高风险品种;拿这张表接 isDaylightSavings() 做过滤,可能让澳系策略的回测成交时间偏差从数小时降到零。

MQL5 / C++
  class="type">class="kw">datetime              EndDate;
  CArrayObj            *savings;
  CArrayObj            *getSavings;
  CDaylightSavings_AU  *dayLight;
class="kw">public:
                      CDaylightSavings_AU(class="type">void);
                      ~CDaylightSavings_AU(class="type">void);
  class="type">bool                isDaylightSavings(class="type">class="kw">datetime Date);class=class="str">"cmt">//This function checks if a given date falls within Daylight Savings Time.
  class="type">bool                DaylightSavings(class="type">int Year,class="type">class="kw">datetime &startDate,class="type">class="kw">datetime &endDate);class=class="str">"cmt">//Check if Daylight Savings Dates are available for a certain Year
  class="type">void                adjustDaylightSavings(class="type">class="kw">datetime EventDate,class="type">class="kw">string &AdjustedDate);class=class="str">"cmt">//Will adjust the date&class="macro">#x27;s time zone depending on Daylight Savings
  };
CDaylightSavings_AU
CObject
CDaylightSavings_AU::CDaylightSavings_AU(class="type">void)
  {
  savings = new CArrayObj();
class=class="str">"cmt">//Daylight savings dates to readjust dates in the database for accurate testing in the strategy tester
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2006.10.class="num">29 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2007.03.class="num">25 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2007.10.class="num">28 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2008.04.class="num">06 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2008.10.class="num">05 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2009.04.class="num">05 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2009.10.class="num">04 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2010.04.class="num">04 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2010.10.class="num">03 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2011.04.class="num">03 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2011.10.class="num">02 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2012.04.class="num">01 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2012.10.class="num">07 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2013.04.class="num">07 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2013.10.class="num">06 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2014.04.class="num">06 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2014.10.class="num">05 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2015.04.class="num">05 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2015.10.class="num">04 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2016.04.class="num">03 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2016.10.class="num">02 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2017.04.class="num">02 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2017.10.class="num">01 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2018.04.class="num">01 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2018.10.class="num">07 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2019.04.class="num">07 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2019.10.class="num">06 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2020.04.class="num">05 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2020.10.class="num">04 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2021.04.class="num">04 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2021.10.class="num">03 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2022.04.class="num">03 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2022.10.class="num">02 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2023.04.class="num">02 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2023.10.class="num">01 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2024.04.class="num">07 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2024.10.class="num">06 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2025.04.class="num">06 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2025.10.class="num">05 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2026.04.class="num">05 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2026.10.class="num">04 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2027.04.class="num">04 class="num">02:class="num">00:class="num">00&class="macro">#x27;));

澳洲与美国的夏令时判定接口

处理跨时区行情事件时,先把夏令时区间固化进列表最稳妥。下面这段给澳洲类注入了 2027–2029 三个年度的夏令时起止:起始多为 10 月首个周日凌晨 03:00 跳夏令时,结束多在次年 4 月首个周日凌晨 02:00 回拨,例如 2027.10.03 03:00 至 2028.04.02 02:00。 isDaylightSavings() 只做一件事:拿传入日期遍历列表,用 Time.DateIsInRange 比对,命中即返回 true。它不修改任何时间,只回答「此刻是否在夏令时内」,回测里用来决定偏移量加不加。 美国类的 DaylightSavings(Year, startDate, endDate) 则按年反查起止。若某年列表里找不到对应起或止,就把两个引用参数置为 D'1970.01.01 00:00:00' 并返回 false——调用方必须判返回值,否则会拿 epoch 时间当有效区间用。外汇与贵金属受夏令时切换影响报价时区,切换周流动性可能异常,属高风险时段。 adjustDaylightSavings() 的签名显示它会按夏令时把事件时间做时区调整并写回字符串引用,具体偏移逻辑在下一节。开 MT5 把这段塞进 EA,先打印 isDaylightSavings(D'2028.12.25 00:00') 验证澳洲判断是否符合预期。

MQL5 / C++
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2027.10.class="num">03 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2028.04.class="num">02 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
  savings.Add(new CDaylightSavings_AU(D&class="macro">#x27;class="num">2028.10.class="num">01 class="num">03:class="num">00:class="num">00&class="macro">#x27;,D&class="macro">#x27;class="num">2029.04.class="num">01 class="num">02:class="num">00:class="num">00&class="macro">#x27;));
}
class="type">bool                isDaylightSavings(class="type">class="kw">datetime Date);class=class="str">"cmt">//This function checks if a given date falls within Daylight Savings Time.
class="type">bool CDaylightSavings_AU::isDaylightSavings(class="type">class="kw">datetime Date)
  {
class=class="str">"cmt">// Initialize a list to store daylight savings periods.
  getSavings = List();
class=class="str">"cmt">// Iterate through all the periods in the list.
  for(class="type">int i=class="num">0; i<getSavings.Total(); i++)
    {
class=class="str">"cmt">// Access the current daylight savings period.
      dayLight = getSavings.At(i);
class=class="str">"cmt">// Check if the given date is within the current daylight savings period.
      if(Time.DateIsInRange(dayLight.StartDate,dayLight.EndDate,Date))
        {
class=class="str">"cmt">// If yes, class="kw">return true indicating it is daylight savings time.
         class="kw">return true;
        }
    }
class=class="str">"cmt">// If no period matches, class="kw">return false indicating it is not daylight savings time.
  class="kw">return false;
  }
class="type">bool                DaylightSavings(class="type">int Year,class="type">class="kw">datetime &startDate,class="type">class="kw">datetime &endDate);class=class="str">"cmt">//Adjusts time when it is daylight savings an hour behind if outside
class="type">bool CDaylightSavings_US::DaylightSavings(class="type">int Year,class="type">class="kw">datetime &startDate,class="type">class="kw">datetime &endDate)
  {
class=class="str">"cmt">// Initialize a list to store daylight savings periods.
  getSavings = List();
  class="type">bool startDateDetected=false,endDateDetected=false;
class=class="str">"cmt">// Iterate through all the periods in the list.
  for(class="type">int i=class="num">0; i<getSavings.Total(); i++)
    {
      dayLight = getSavings.At(i);
      if(Year==Time.ReturnYear(dayLight.StartDate))class=class="str">"cmt">//Check if a certain year&class="macro">#x27;s date is available within the Daylight Savings start dates in the List
        {
         startDate = dayLight.StartDate;
         startDateDetected = true;
        }
      if(Year==Time.ReturnYear(dayLight.EndDate))class=class="str">"cmt">//Check if a certain year&class="macro">#x27;s date is available within the Daylight Savings end dates in the List
        {
         endDate = dayLight.EndDate;
         endDateDetected = true;
        }
      if(startDateDetected&&endDateDetected)class=class="str">"cmt">//Check if both Daylight Savings start and end dates are found for a certain Year
        {
         class="kw">return true;
        }
    }
  startDate = 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">//Set a class="kw">default start date if no Daylight Saving date is found
  endDate = 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">//Set a class="kw">default end date if no Daylight Saving date is found
  class="kw">return false;
  }
class="type">void                adjustDaylightSavings(class="type">class="kw">datetime EventDate,class="type">class="kw">string &AdjustedDate);class=class="str">"cmt">//Will adjust the date&class="macro">#x27;s time -zone depending on Daylight Savings
class="type">void CDaylightSavings_AU::adjustDaylightSavings(class="type">class="kw">datetime EventDate,class="type">class="kw">string &AdjustedDate)
  {

「夏令时切换下的事件时间补偿」

在 MT5 上跑跨时区经济事件提醒时,最容易被忽略的是经纪商交易服务器时间与事件原始时间是否处于同一夏令时状态。若两者不一致,直接显示 EventDate 会让提醒偏移整整 1 小时(3600 秒),在外汇与贵金属这种高波动时段足以错过入场或风控窗口,相关品种波动风险显著放大。 下面这段逻辑就是做这个补偿:先判断交易服务器当下是否夏令时,再判断事件时间是否夏令时,四种组合里只有「服务器非夏令时而事件为夏令时」才加 3600 秒,「服务器夏令时而事件非夏令时」才减 3600 秒,其余情况原样保留。 [CODE] if(isDaylightSavings(TimeTradeServer())) // 检查当前交易服务器时间是否已进入夏令时 { if(isDaylightSavings(EventDate)) // 检查事件时间是否处于夏令时 { AdjustedDate = TimeToString(EventDate); // 两者都夏令时,原样存事件时间 } else { AdjustedDate = TimeToString((datetime)(EventDate-Time.HoursS())); // 服务器夏令时、事件非夏令时,减1小时(3600秒)补偿 } } else { if(isDaylightSavings(EventDate)) // 检查事件时间是否处于夏令时 { AdjustedDate = TimeToString((datetime)(Time.HoursS()+EventDate)); // 服务器非夏令时、事件夏令时,加1小时(3600秒)补偿 } else { AdjustedDate = TimeToString(EventDate); // 两者都非夏令时,原样存事件时间 } } [/CODE] 配套的 CTimeManagement 类把常用时间换算封装好了,HoursS() 默认返回 3600,即 1 小时对应的秒数,也可传参倍数拿多小时。DateIsInRange 有两个重载:一个判断单点时间是否落在两段之间,另一个判断整段区间是否被包含,做「今日事件过滤」或「本周数据笼子」时直接调,不用自己写差值比较。 开 MT5 把这段塞进 EA 的 OnTimer 或新闻模块,用 2024-03-10(美夏令时切出)和 2024-11-03(切回)两个日期跑一遍,就能验证补偿方向对不对。

MQL5 / C++
if(isDaylightSavings(TimeTradeServer()))class=class="str">"cmt">//Check if the current trade server time is already within the Daylight Savings Period
  {
  if(isDaylightSavings(EventDate))class=class="str">"cmt">//Checks if the event time is during daylight savings
    {
    AdjustedDate = TimeToString(EventDate);class=class="str">"cmt">//storing normal event time
    }
  else
    {
    AdjustedDate = TimeToString((class="type">class="kw">datetime)(EventDate-Time.HoursS()));class=class="str">"cmt">//storing event time and removing an hour for DST
    }
  }
else
  {
  if(isDaylightSavings(EventDate))class=class="str">"cmt">//Checks if the event time is during daylight savings
    {
    AdjustedDate = TimeToString((class="type">class="kw">datetime)(Time.HoursS()+EventDate));class=class="str">"cmt">//storing event time and adding an hour for DST
    }
  else
    {
    AdjustedDate = TimeToString(EventDate);class=class="str">"cmt">//storing normal event time
    }
  }
}

class CTimeManagement
  {
class="kw">private:
  class="type">MqlDateTime      today;class=class="str">"cmt">//class="kw">private variable
  class="type">MqlDateTime      timeFormat;class=class="str">"cmt">//class="kw">private variable
class="kw">public:
  class="type">bool             DateIsInRange(class="type">class="kw">datetime FirstTime,class="type">class="kw">datetime SecondTime,class="type">class="kw">datetime compareTime);class=class="str">"cmt">//Checks if a date is within two other dates
  class="type">bool             DateIsInRange(class="type">class="kw">datetime Start,class="type">class="kw">datetime End,class="type">class="kw">datetime CompareStart,class="type">class="kw">datetime CompareEnd);class=class="str">"cmt">//Check if two dates(Start&End) are within CompareStart & CompareEnd
  class="type">bool             DateisToday(class="type">class="kw">datetime TimeRepresented);class=class="str">"cmt">//Checks if a date is within the current day
  class="type">int              SecondsS(class="type">int multiple=class="num">1);class=class="str">"cmt">//Returns seconds
  class="type">int              MinutesS(class="type">int multiple=class="num">1);class=class="str">"cmt">//Returns Minutes in seconds
  class="type">int              HoursS(class="type">int multiple=class="num">1);class=class="str">"cmt">//Returns Hours in seconds
  class="type">int              DaysS(class="type">int multiple=class="num">1);class=class="str">"cmt">//Returns Days in seconds
  class="type">int              WeeksS(class="type">int multiple=class="num">1);class=class="str">"cmt">//Returns Weeks in seconds
  class="type">int              MonthsS(class="type">int multiple=class="num">1);class=class="str">"cmt">//Returns Months in seconds
  class="type">int              YearsS(class="type">int multiple=class="num">1);class=class="str">"cmt">//Returns Years in seconds
  class="type">int              ReturnYear(class="type">class="kw">datetime time);class=class="str">"cmt">//Returns the Year for a specific date
  };

◍ 时间偏移与经纪商名的代码级处理

做跨时区新闻交易时,经常要把服务器时间向前或向后挪几秒来做窗口判断。下面两个函数直接对 datetime 做加减,入参 timeoffset 单位是秒,返回新的 datetime,逻辑极简但能避开自己写结构体的麻烦。 TimeMinusOffset 与 TimePlusOffset 的实现只有一行运算:standardtime-=timeoffset 或 +=timeoffset。回测时若把偏移量设成 3600,就等价于把时间拨动整一小时,可用于模拟夏令时切换前后的触发点。 DateIsInRange 有两个重载:单时间点版检查 compareTime 是否落在 [FirstTime, SecondTime) 半开区间;双区间版检查 [CompareStart, CompareEnd] 是否被 [Start, End] 完整包住。注意前者用 > 后者用 <,边界包含关系不同,写条件单时容易踩坑。 DateisToday 借助 TimeToStruct 拆出年月日,再和 TimeCurrent 的 today 比对。若你只在美盘盯黄金,可用它过滤掉非当日的挂单信号,降低外汇与贵金属交易的高风险暴露。 取经纪商名用 AccountInfoString(ACCOUNT_COMPANY),随后连续三次 StringReplace 把空格、点、逗号清掉,方便拼文件路径。最后用 #define 把清洗后的 broker 和 "NewsTrading" 文件夹名固化,EA 写文件时直接引用即可。

MQL5 / C++
class="type">class="kw">datetime TimeMinusOffset(class="type">class="kw">datetime standardtime,class="type">int timeoffset);class=class="str">"cmt">//Will class="kw">return a class="type">class="kw">datetime type of a date with an subtraction offset in seconds
class="type">class="kw">datetime TimePlusOffset(class="type">class="kw">datetime standardtime,class="type">int timeoffset);class=class="str">"cmt">//Will class="kw">return a class="type">class="kw">datetime type of a date with an addition offset in seconds
};
Time.DateIsInRange(dayLight.StartDate,dayLight.EndDate,Date)
class="type">bool CTimeManagement::DateIsInRange(class="type">class="kw">datetime FirstTime,class="type">class="kw">datetime SecondTime,class="type">class="kw">datetime compareTime)
  {
   if(FirstTime<=compareTime&&SecondTime>compareTime)
     {
       class="kw">return true;
     }
   class="kw">return false;
  }
class="type">bool DateIsInRange(class="type">class="kw">datetime Start,class="type">class="kw">datetime End,class="type">class="kw">datetime CompareStart,class="type">class="kw">datetime CompareEnd);
class="type">bool CTimeManagement::DateIsInRange(class="type">class="kw">datetime Start,class="type">class="kw">datetime End,class="type">class="kw">datetime CompareStart,class="type">class="kw">datetime CompareEnd)
  {
   if(Start<=CompareStart&&CompareEnd<End)
     {
       class="kw">return true;
     }
   class="kw">return false;
  }
class="type">bool DateisToday(class="type">class="kw">datetime TimeRepresented);class=class="str">"cmt">//Checks if a date is within the current day
class="type">bool CTimeManagement::DateisToday(class="type">class="kw">datetime TimeRepresented)
  {
   class="type">MqlDateTime TiM;
   TimeToStruct(TimeRepresented,TiM);
   TimeCurrent(today);
   if(TiM.year==today.year&&TiM.mon==today.mon&&TiM.day==today.day)
     {
       class="kw">return true;
     }
   class="kw">return false;
  }
class="type">class="kw">datetime TimeMinusOffset(class="type">class="kw">datetime standardtime,class="type">int timeoffset);class=class="str">"cmt">//Will class="kw">return a class="type">class="kw">datetime type of a date with an subtraction offset in seconds
class="type">class="kw">datetime TimePlusOffset(class="type">class="kw">datetime standardtime,class="type">int timeoffset);class=class="str">"cmt">//Will class="kw">return a class="type">class="kw">datetime type of a date with an addition offset in seconds
class="type">class="kw">datetime CTimeManagement::TimeMinusOffset(class="type">class="kw">datetime standardtime,class="type">int timeoffset)
  {
   standardtime-=timeoffset;
   class="kw">return standardtime;
  }
class="type">class="kw">datetime CTimeManagement::TimePlusOffset(class="type">class="kw">datetime standardtime,class="type">int timeoffset)
  {
   standardtime+=timeoffset;
   class="kw">return standardtime;
  }
class="type">class="kw">string broker=AccountInfoString(ACCOUNT_COMPANY);class=class="str">"cmt">//Getting brokers name via AccountInfoString
class="type">int Str = StringReplace(broker," ","");class=class="str">"cmt">//Removing or replacing any spaces in the broker&class="macro">#x27;s name with an empty class="type">class="kw">string
class="type">int Str1 = StringReplace(broker,".","");class=class="str">"cmt">//Removing or replacing any dots in the broker&class="macro">#x27;s name with an empty class="type">class="kw">string
class="type">int Str2 = StringReplace(broker,",","");class=class="str">"cmt">//Removing or replacing any commas in the broker&class="macro">#x27;s name with an empty class="type">class="kw">string
class="macro">#define BROKER_NAME brokerclass=class="str">"cmt">//Broker&class="macro">#x27;s Name
class="macro">#define NEWS_TRADING_FOLDER "NewsTrading"class=class="str">"cmt">//Name of main folder in common/files

财经日历数据的本地落盘结构

想把新闻日历接进 EA,第一步不是抓数据,而是先把文件树和结构体定清楚。下面这套宏把路径拼成 MQL5\Files\NewsTrading\NewsCalendar\<BrokerName>,再往里丢一个 Calendar.sqlite 和一个 CalendarOpen.txt 作标记文件。 Calendar 结构体把单条事件拆成 15 个字段:从 EventIdCountryNameEventForecastEventPrevalEventImpact,基本覆盖了做事件过滤所需的维度。注意 EventId 用的是 ulong,其余全是 string,意味着数值比较前你得自己转类型。 夏令时不能硬算。DST_type 枚举给了 US / UK(EU) / AU / NONE 四态,后续把 UTC 事件时间映射到本地服务器时间时,得先判定经纪商所属时区簇。 CFolders 类只暴露一个构造函数,内部用嵌套 if 逐级建目录:先 NewsTrading,再 NewsCalendar,最后 broker 子目录;任何一级失败就 Print 出具体路径。你在 MT5 里跑这段,打开「文件」目录就能看到三层文件夹是否生成。外汇与贵金属受新闻跳空影响大,这类本地缓存可降低重复拉取延迟,但行情瞬变风险仍高,参数验证请以模拟盘为先。

MQL5 / C++
class="macro">#define NEWS_CALENDAR_FOLDER        StringFormat("%s\\NewsCalendar",NEWS_TRADING_FOLDER)class=class="str">"cmt">//name of subfolder in NewsTrading
class="macro">#define NEWS_CALENDAR_BROKER_FOLDER    StringFormat("%s\\%s",NEWS_CALENDAR_FOLDER,BROKER_NAME)class=class="str">"cmt">//Name of subfolder in NewsCalendar
class="macro">#define NEWS_DATABASE_FILE             StringFormat("%s\\Calendar.sqlite",NEWS_CALENDAR_BROKER_FOLDER)class=class="str">"cmt">//Name of sqlite file in subfolder in "Broker&class="macro">#x27;s Name"
class="macro">#define NEWS_TEXT_FILE                 StringFormat("%s\\CalendarOpen.txt",NEWS_CALENDAR_BROKER_FOLDER)class=class="str">"cmt">//Name of text file to indicate Calendar is open.
class="kw">struct Calendar
  {
   class="type">ulong                EventId;class=class="str">"cmt">//Event Id
   class="type">class="kw">string               CountryName;class=class="str">"cmt">//Event Country
   class="type">class="kw">string               EventName;class=class="str">"cmt">//Event Name
   class="type">class="kw">string               EventType;class=class="str">"cmt">//Event Type
   class="type">class="kw">string               EventImportance;class=class="str">"cmt">//Event Importance
   class="type">class="kw">string               EventDate;class=class="str">"cmt">//Event Date
   class="type">class="kw">string               EventCurrency;class=class="str">"cmt">//Event Currency
   class="type">class="kw">string               EventCode;class=class="str">"cmt">//Event Code
   class="type">class="kw">string               EventSector;class=class="str">"cmt">//Event Sector
   class="type">class="kw">string               EventForecast;class=class="str">"cmt">//Event Forecast Value
   class="type">class="kw">string               EventPreval;class=class="str">"cmt">//Event Previous Value
   class="type">class="kw">string               EventImpact;class=class="str">"cmt">//Event Impact
   class="type">class="kw">string               EventFrequency;class=class="str">"cmt">//Event Frequency
   };
enum DST_type
  {
   US_DST,class=class="str">"cmt">//US Daylight Savings
   UK_DST,class=class="str">"cmt">//UK(EU) Daylight Savings
   AU_DST,class=class="str">"cmt">//AU Daylight Savings
   DST_NONEclass=class="str">"cmt">//No Daylight Savings Available
   };
class CFolders
  {
class="kw">private:
   class="type">bool                 CreateFolder(class="type">class="kw">string FolderPath);class=class="str">"cmt">//Will create a folder with the FolderPath class="type">class="kw">string parameter
class="kw">public:
                       CFolders(class="type">void);class=class="str">"cmt">//Class&class="macro">#x27;s constructor
  };
CFolders::CFolders(class="type">void)
  {
   if(CreateFolder(NEWS_TRADING_FOLDER))class=class="str">"cmt">//Will create the NewsTrading Folder
     {
      if(CreateFolder(NEWS_CALENDAR_FOLDER))class=class="str">"cmt">//Will create the NewsCalendar Folder
        {
         if(!CreateFolder(NEWS_CALENDAR_BROKER_FOLDER))class=class="str">"cmt">//Will create the Broker Folder
           {
            Print("Something went wrong with creating folder: ",NEWS_CALENDAR_BROKER_FOLDER);
           }
        }
     }
  }
class="type">bool CFolders::CreateFolder(class="type">class="kw">string FolderPath)
  {
class=class="str">"cmt">//--- attempt to create a folder relative to the MQL5\Files path

「用类封装把K线属性一次取清」

把常用的开高低收和形态判断收进一个 CCandleProperties 类,比每次手写 iOpen / iHigh 散函数更省事,也方便在小布盯盘脚本里复用。 下面这段先处理公共目录创建:调 FolderCreate 用 FILE_COMMON 标志,成功直接返回 true;失败则打印路径和 GetLastError 代码,最后返回 false。外汇与贵金属品种在 MT5 公共目录写文件有较高权限风险,实盘前先在策略测试器跑一遍确认环境。 类里 Open 方法只是对 iOpen 的薄封装:SYMBOL 传 NULL 就退回当前 Symbol(),Period 默认 PERIOD_CURRENT,返回对应 K 线开盘价。 IsLargerThanPreviousAndNext 用 iBarShift 在 M15 上定位三根 K 线:以 CandleTime 为基准,加减 Offset 得到前后两根的索引,再算基准 K 线高低差。若它比前后都高,可能倾向视作局部放量线,但仅是形态筛选,不代表方向。 复制下面代码到 MT5 的 include 文件,改个 Offset 参数就能在 EURUSD 的 M15 上验证局部大棒识别效果。

MQL5 / C++
  if(FolderCreate(FolderPath,FILE_COMMON))
    {
      class=class="str">"cmt">//--- successful execution
      class="kw">return true;
    }
  else
    {
      PrintFormat("Failed to create the folder %s. Error code %d",FolderPath,GetLastError());
    }
class=class="str">"cmt">//--- execution failed
  class="kw">return false;
  }
class CCandleProperties
  {
class="kw">private:
  CTimeManagement   Time;
class="kw">public:
  class="type">class="kw">double            Open(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle OpenPrice
  class="type">class="kw">double            Close(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle ClosePrice
  class="type">class="kw">double            High(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle HighPrice
  class="type">class="kw">double            Low(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle LowPrice
  class="type">bool              IsLargerThanPreviousAndNext(class="type">class="kw">datetime CandleTime,class="type">int Offset,class="type">class="kw">string SYMBOL);class=class="str">"cmt">//Determine if one candle is larger than two others
  };
class="type">class="kw">double            Open(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle Open Price
class="type">class="kw">double CCandleProperties::Open(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL)
  {
  class="kw">return iOpen(((SYMBOL==NULL)?Symbol():SYMBOL),Period,CandleIndex);class=class="str">"cmt">//class="kw">return candle open price
  }
class="type">bool              IsLargerThanPreviousAndNext(class="type">class="kw">datetime CandleTime,class="type">int Offset,class="type">class="kw">string SYMBOL);class=class="str">"cmt">//Determine if one candle is larger than two others
class="type">bool CCandleProperties::IsLargerThanPreviousAndNext(class="type">class="kw">datetime CandleTime,class="type">int Offset,class="type">class="kw">string SYMBOL)
  {
  class="type">int CandleIndex = iBarShift(SYMBOL,PERIOD_M15,CandleTime);class=class="str">"cmt">//Assign candle index of candletime
  class="type">int CandleIndexMinusOffset = iBarShift(SYMBOL,PERIOD_M15,Time.TimeMinusOffset(CandleTime,Offset));class=class="str">"cmt">//Assign candle index of candletime minus time offset 
  class="type">int CandleIndexPlusOffset = iBarShift(SYMBOL,PERIOD_M15,Time.TimePlusOffset(CandleTime,Offset));class=class="str">"cmt">//Assign candle index of candletime plus time offset
  class="type">class="kw">double CandleHeight = High(CandleIndex,PERIOD_M15,SYMBOL)-Low(CandleIndex,PERIOD_M15,SYMBOL);class=class="str">"cmt">//Assign height of M15 candletime in pips

常见问题

按年份维护澳洲夏令时起始与结束的具体日期边界,以日期区间而非固定月份写入回测库,避免跨年偏差。
分别封装两个地区的夏令时判定接口,输入日期返回布尔值,再对事件时间做对应偏移补偿。
可以,小布能读取你落盘的日历与经纪商名配置,自动标出切换日的时间偏移异常,省去手动比对。
用带时间戳、事件等级、货币对、实际/预期值的扁平表结构,按日期分文件存储最易检索。
能把开盘、收盘、高低、成交量等一次性取出缓存,回测时直接读属性,减少重复请求和卡顿。