建立自动新闻交易程序·综合运用
📰

建立自动新闻交易程序·综合运用

(3/3)· 从类结构到回测,完整拼图让你不再手动盯非农数据发布会

案例拆解 第 3/3 篇
很多交易者把非农当赌局,手动在行情跳动时追单,滑点吃掉大半利润。EA 若没有独立的新闻容器,往往新闻已过才反应。把日历先落进文件系统,EA 才有提前布阵的凭据。

「把财经新闻塞进内存容器」

做新闻驱动型 EA,第一步不是算信号,而是先把散落在 csv 里的事件读进内存。下面这个类用 CArrayObj 承载新闻对象,避免每次都去磁盘翻文件。

MQL5 / C++
class=class="str">"cmt">//|                                                                 Copyright © class="num">2013, Jordi Bassagañas |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Files\FileTxt.mqh>
class="macro">#include <Arrays\ArrayObj.mqh>
class="macro">#include <..\Experts\NewsWatcher\CNew.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNewsContainer Class                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CNewsContainer
  {
class="kw">protected:
   class="type">class="kw">string            m_csv;            class=class="str">"cmt">// The name of the csv file
   CFileTxt          m_fileTxt;        class=class="str">"cmt">// MQL5 file functionality
   class="type">int               m_currentIndex;   class=class="str">"cmt">// The index of the next news to be processed in the container
   class="type">int               m_total;          class=class="str">"cmt">// The total number of news to be processed
   CArrayObj        *m_news;           class=class="str">"cmt">// News list in the computer&class="macro">#x27;s memory, loaded from the csv file
class="kw">public:
   class=class="str">"cmt">//--- Constructor and destructor methods
                     CNewsContainer(class="type">class="kw">string csv);
                    ~CNewsContainer(class="type">void);
   class=class="str">"cmt">//--- Getter methods
   class="type">int               GetCurrentIndex(class="type">void);
   class="type">int               GetTotal(class="type">void);
   CNew             *GetCurrentNew();
   CArrayObj        *GetNews(class="type">void);
   class=class="str">"cmt">//--- Setter methods
   class="type">void              SetCurrentIndex(class="type">int index);
   class="type">void              SetTotal(class="type">int total);
   class="type">void              SetNews(class="type">void);
   class=class="str">"cmt">//--- CNewsContainer methods
   class="type">bool              Init();
   class="type">void              Deinit(class="type">void);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constuctor                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNewsContainer::CNewsContainer(class="type">class="kw">string csv)
  {
   m_csv=csv;
   m_news=new CArrayObj;
   SetNews();
   }
逐行拆解:前三行 include 分别拉入文本文件读写、动态数组基类、以及自定义的新闻结构 CNew;m_csv 记文件名,m_fileTxt 提供底层 IO,m_currentIndex 指向待处理的下一条,m_total 是总条数,m_news 是内存里的对象数组。 构造函数只做三件事:存下 csv 路径、new 一个 CArrayObj、调 SetNews() 把磁盘数据搬进内存。你在 MT5 里复刻时,只要改 include 路径匹配自己工程,就能直接挂一个新闻容器跑起来。外汇与贵金属受新闻跳空影响大,这类加载逻辑只是风控前置,不预示任何方向。

MQL5 / C++
class=class="str">"cmt">//|                                                                 Copyright © class="num">2013, Jordi Bassagañas |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Files\FileTxt.mqh>
class="macro">#include <Arrays\ArrayObj.mqh>
class="macro">#include <..\Experts\NewsWatcher\CNew.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNewsContainer Class                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CNewsContainer
  {
class="kw">protected:
   class="type">class="kw">string            m_csv;            class=class="str">"cmt">// The name of the csv file
   CFileTxt          m_fileTxt;        class=class="str">"cmt">// MQL5 file functionality
   class="type">int               m_currentIndex;   class=class="str">"cmt">// The index of the next news to be processed in the container
   class="type">int               m_total;          class=class="str">"cmt">// The total number of news to be processed
   CArrayObj        *m_news;           class=class="str">"cmt">// News list in the computer&class="macro">#x27;s memory, loaded from the csv file
class="kw">public:
   class=class="str">"cmt">//--- Constructor and destructor methods
                     CNewsContainer(class="type">class="kw">string csv);
                    ~CNewsContainer(class="type">void);
   class=class="str">"cmt">//--- Getter methods
   class="type">int               GetCurrentIndex(class="type">void);
   class="type">int               GetTotal(class="type">void);
   CNew             *GetCurrentNew();
   CArrayObj        *GetNews(class="type">void);
   class=class="str">"cmt">//--- Setter methods
   class="type">void              SetCurrentIndex(class="type">int index);
   class="type">void              SetTotal(class="type">int total);
   class="type">void              SetNews(class="type">void);
   class=class="str">"cmt">//--- CNewsContainer methods
   class="type">bool              Init();
   class="type">void              Deinit(class="type">void);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constuctor                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNewsContainer::CNewsContainer(class="type">class="kw">string csv)
  {
   m_csv=csv;
   m_news=new CArrayObj;
   SetNews();
   }

◍ 新闻容器的存取接口怎么写

做 MT5 新闻事件过滤时,CNewsContainer 这类封装类最容易被忽略的是析构与索引边界。析构函数直接调 Deinit() 释放内部对象数组,避免在 EA 重载时留下悬空指针。 GetCurrentIndex 和 GetTotal 只是把私有成员 m_currentIndex、m_total 透出来,外部靠这两个值判断当前遍历到哪条、一共几条。GetNews 返回 CArrayObj 指针,GetCurrentNew 则用 m_news.At(m_currentIndex) 取当前指针——这里若 m_currentIndex 越界会返回 NULL,调用前必须自己兜底。 SetNews 里先把当前索引归零,再定义分号分隔符 sep 和 u_sep 准备切字符串。外汇与贵金属受新闻跳空影响大,这类容器若没正确重置索引,可能在重大数据发布时误读上一条旧闻,带来错误信号。 下面这段是接口原貌,逐行看清楚成员怎么暴露:析构走 Deinit;取数全是 return 成员;SetCurrentIndex / SetTotal 只做赋值;SetNews 开头先 SetCurrentIndex(0) 再声明 sep=";" 与 u_sep。

MQL5 / C++
class=class="str">"cmt">//| Destructor                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNewsContainer::~CNewsContainer(class="type">void)
  {
   Deinit();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetCurrentIndex                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CNewsContainer::GetCurrentIndex(class="type">void)
  {  
   class="kw">return m_currentIndex;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetTotal                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CNewsContainer::GetTotal(class="type">void)
  {  
   class="kw">return m_total;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetNews                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CArrayObj *CNewsContainer::GetNews(class="type">void)
  {  
   class="kw">return m_news;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetCurrentNew                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNew *CNewsContainer::GetCurrentNew(class="type">void)
  {  
   class="kw">return m_news.At(m_currentIndex);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetCurrentIndex                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNewsContainer::SetCurrentIndex(class="type">int index)
  {
   m_currentIndex=index;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetTotal                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNewsContainer::SetTotal(class="type">int total)
  {
   m_total=total;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetNews                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNewsContainer::SetNews(class="type">void)
  {
   class=class="str">"cmt">//--- let&class="macro">#x27;s first init some vars!
   SetCurrentIndex(class="num">0);
   class="type">class="kw">string sep= ";";
   class="type">class="kw">ushort u_sep;

把财经CSV塞进MT5内存的对象化写法

做新闻日历类指标时,第一步往往是把外部 CSV 读进 MT5 再转成可操作对象。下面这段逻辑用 StringSplit 按分隔符切行,再把每行构造成 CNew 实例塞进 m_news 动态数组,省去手写解析的状态机。 注意 FileReadString 配合 FILE_CSV 标志会按行返回,循环条件用 FileIsEnding 判断句柄是否到尾。实测若 CSV 首行是表头(如 country,time,event),必须在读完后 m_news.Delete(0) 清掉,否则第一条新闻会是脏数据。 析构侧也不能漏:Deinit 里用 DeleteRange(0, m_total-1) 批量释放,再 delete(m_news) 交还堆内存。外汇与贵金属受新闻跳空影响大,这类日历加载失败(Invalid_Handle)时务必 Print(GetLastError()) 定位路径权限问题,避免静默空跑。 别让表头混进新闻队列 m_news.Delete(0) 这行看着不起眼,但漏掉的话后续统计 Total() 会多算一条无效记录,导致小布类播报把「country」当国名念出来。

MQL5 / C++
  class="type">class="kw">string substrings[];
  u_sep=StringGetCharacter(sep,class="num">0);
  class=class="str">"cmt">//--- then open and process the CSV file
  class="type">int file_handle=m_fileTxt.Open(m_csv, FILE_READ|FILE_CSV);
  if(file_handle!=INVALID_HANDLE)
  {
    while(!FileIsEnding(file_handle))
    {
      class="type">class="kw">string line = FileReadString(file_handle);
      class="type">int k = StringSplit(line,u_sep,substrings);
      CNew *current = new CNew(substrings[class="num">0],(class="type">class="kw">datetime)substrings[class="num">1],substrings[class="num">2]);
      m_news.Add(current);
    }
    FileClose(file_handle);
    class=class="str">"cmt">//--- and finally refine and count the news
    m_news.Delete(class="num">0); class=class="str">"cmt">// --- here we class="kw">delete the CSV&class="macro">#x27;s header!
    SetTotal(m_news.Total());
  }
  else
  {
    Print("Failed to open the file ",m_csv);
    Print("Error code ",GetLastError());
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNewsContainer initialization                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CNewsContainer::Init(class="type">void)
  {
class=class="str">"cmt">// Initialization logic here...
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNewsContainer deinitialization                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNewsContainer::Deinit(class="type">void)
  {
   m_news.DeleteRange(class="num">0, m_total-class="num">1);
   class="kw">delete(m_news);
   Print("CNewsContainer deinitialization performed!");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                     CNew.mqh |
class=class="str">"cmt">//|                                                                 Copyright © class="num">2013, Jordi Bassagañas |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Object.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNew Class                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CNew : class="kw">public CObject
  {
class="kw">protected:
   class="type">class="kw">string            m_country;       class=class="str">"cmt">// The country&class="macro">#x27;s name

「新闻事件类的存取与构造拆解」

在 MT5 里把一条财经新闻封装成对象,最朴素的做法就是建一个 CNew 类,把国家、发布时间、名称塞进私有成员。下面这段声明里,m_time_release 用 datetime 类型存发布时刻,m_name 用 string 存事件名,外部只能通过 Getter/Setter 碰它们。 构造函数 CNew::CNew 接收三个参数并直接赋值给成员变量,没有做校验——这意味着你传错时区或空字符串,对象也不会报错,回测时可能悄悄漏掉关键事件。 析构函数只调了 Deinit(),把资源清理交给单独方法,这种写法在 EA 里方便复用初始化逻辑。GetCountry 和 GetTimeRelease 都是单行 return,开销极低,适合在 OnTick 里高频调用。 别把正态当圣经:datetime 本质是秒级整数,跨服务器时区不对齐时,新闻触发判断会偏移数小时,实盘前务必用 Print(TimeToString(m_time_release)) 在 MT5 终端核对一次。

MQL5 / C++
  class="type">class="kw">datetime            m_time_release;       class=class="str">"cmt">// The date and time of the news
  class="type">class="kw">string              m_name;                class=class="str">"cmt">// The name of the news

class="kw">public:
  class=class="str">"cmt">//--- Constructor and destructor methods
                     CNew(class="type">class="kw">string country,class="type">class="kw">datetime time_release,class="type">class="kw">string name);
                    ~CNew(class="type">void);
  class=class="str">"cmt">//--- Getter methods
  class="type">class="kw">string              GetCountry(class="type">void);
  class="type">class="kw">datetime            GetTimeRelease(class="type">void);
  class="type">class="kw">string              GetName(class="type">void);
  class=class="str">"cmt">//--- Setter methods
  class="type">void                SetCountry(class="type">class="kw">string country);
  class="type">void                SetTimeRelease(class="type">class="kw">datetime time_release);
  class="type">void                SetName(class="type">class="kw">string name);
  class=class="str">"cmt">//--- CNew specific methods
  class="type">bool                Init();
  class="type">void                Deinit(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constuctor                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNew::CNew(class="type">class="kw">string country,class="type">class="kw">datetime time_release,class="type">class="kw">string name)
  {
  m_country=country;
  m_time_release=time_release;
  m_name=name;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNew::~CNew(class="type">void)
  {
  Deinit();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetCountry                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CNew::GetCountry(class="type">void)
  {
  class="kw">return m_country;
  }  
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetTimeRelease                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">datetime CNew::GetTimeRelease(class="type">void)
  {
  class="kw">return m_time_release;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 新闻对象的存取与初始化封装

在 MT5 自建新闻解析类时,把字段访问做成显式 getter / setter 是最省心的做法。下面这段 CNew 类的成员函数,把名称、国家、发布时间三个字段和外部隔离,调用方改不了内部变量,只能走接口。 GetName 直接返回私有成员 m_name,没有参数;SetCountry 和 SetName 接收 string 入参并写回对应私有变量;SetTimeRelease 接收 datetime 类型,专门存发布时间戳。注意 datetime 是 MT5 原生时间类型,和服务器时间对齐时不用自己换算。 Init 目前只返回 true,注释里留了初始化逻辑位,实盘里你大概率要在这里拉一次经济日历接口或读文件把 m_name 等填实。Deinit 调了 Print 打一行注销日志,回测时能在专家日志里看到「CNew deinitialization performed!」确认对象销毁。 别把 Init 空壳当完成。真要跑新闻触发策略,至少得在 Init 里把 m_time_release 用 iTime 或 WebRequest 拿到的值填进去,否则后续按时间过滤财经事件会全部失效。

MQL5 / C++
class=class="str">"cmt">//| GetName                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CNew::GetName(class="type">void)
  {
   class="kw">return m_name;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetCountry                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNew::SetCountry(class="type">class="kw">string country)
  {
   m_country=country;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetTimeRelease                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNew::SetTimeRelease(class="type">class="kw">datetime timeRelease)
  {
   m_time_release=timeRelease;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetName                                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNew::SetName(class="type">class="kw">string name)
  {
   m_name=name;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNew initialization                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CNew::Init(class="type">void)
  {
class=class="str">"cmt">//--- initialization logic here...
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNew deinitialization                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNew::Deinit(class="type">void)
  {
class=class="str">"cmt">//--- deinitialization logic here...
   Print("CNew deinitialization performed!");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

把新闻Watcher跑进MT5回测台

这套新闻驱动 EA 的工程文件拆成两块:头文件(.mqh)放 MQL5\Include\Mine,主程序与类库放 MQL5\Experts\NewsWatcher。附带的数据文件原始是 .txt,必须手动改名为 news_watcher.csv 再丢进 data_folder\MQL5\FILES\,因为发布平台当时拦了 .csv 后缀。编译过后,就能像普通 EA 一样挂进策略测试器。 回测先用一组人为错开时间的假想新闻(动量值从 100.47 到 99.81 不等)验证可控环境下的触发逻辑,再换成 DailyFX 真实日程。初始参数固定为:周期 1 分钟、止损 400 点、止盈 600 点、手数 0.01、CsvFile 指向 news_watcher.csv。 真实数据下 EA 只进场三次。根因是现实新闻常重叠——比如 2013.07.15 12:30:00 同时撞上零售销售等多条——而程序逻辑是「有未平仓位就忽略后续新闻,先把当前序列平掉」。若同一时刻有多条重要事件,得在 CSV 里逐条单列,否则只认第一条。 下面这段是 EA 内部取动量并框定新闻前监听窗口的核心片段,timeWindow=600 表示新闻发布前 10 分钟开始盯盘: double momentumBuffer[]; GetTechIndicators().GetMomentum().GetBuffer(momentumBuffer, 2); //--- Number of seconds before the news releases. GMT +- timeWindow is the real time from which the robot starts //--- listening to the market. For instance, if there is a news release programmed at 13:00 GMT you can set TimeWindow //--- to 900 seconds so that the EA starts listening to the market fifteen minutes before that news release. int timeWindow=600; CNew *currentNew = GetBrain().GetNewsContainer().GetCurrentNew(); int indexCurrentNew = GetBrain().GetNewsContainer().GetCurrentIndex(); if(TimeGMT() >= currentNew.GetTimeRelease() + timeWindow) { GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+1); return; } //--- is there any open position? 外汇与贵金属受新闻跳空影响大,回测结果仅反映历史样本行为,实盘可能因点差扩大或流动性枯竭而偏离,属高风险验证。

MQL5 / C++
Country;Time;Event
USD;class="num">2013.06.class="num">03 class="num">17:class="num">19:class="num">00;A. Momentum equals class="num">100.47
USD;class="num">2013.06.class="num">13 class="num">17:class="num">09:class="num">00;B. Momentum equals class="num">100.40
USD;class="num">2013.06.class="num">21 class="num">18:class="num">52:class="num">00;C. Momentum equals class="num">100.19
USD;class="num">2013.07.class="num">01 class="num">17:class="num">32:class="num">00;D. Momentum equals class="num">100.18
USD;class="num">2013.07.class="num">08 class="num">15:class="num">17:class="num">00;E. Momentum equals class="num">100.18
USD;class="num">2013.07.class="num">16 class="num">10:class="num">00:class="num">00;F. Momentum equals class="num">99.81
USD;class="num">2013.07.class="num">24 class="num">09:class="num">30:class="num">00;G. Momentum equals class="num">100.25
Country;Time;Event
USD;class="num">2013.07.class="num">15 class="num">12:class="num">00:class="num">00;USD Fed&class="macro">#x27;s Tarullo Speaks on Banking Regulation in Washington
USD;class="num">2013.07.class="num">15 class="num">12:class="num">30:class="num">00;USD Advance Retail Sales(JUN) and others
USD;class="num">2013.07.class="num">15 class="num">14:class="num">00:class="num">00;USD USD Business Inventories(MAY)
USD;class="num">2013.07.class="num">15 class="num">21:class="num">00:class="num">00;USD EIA Gasoline and Diesel Fuel Update
USD;class="num">2013.07.class="num">16 class="num">12:class="num">30:class="num">00;USD Several Consumer Price Indexes
USD;class="num">2013.07.class="num">16 class="num">13:class="num">00:class="num">00;USD USD Net Long-term TIC Flows(MAY) & USD Total Net TIC Flows(MAY)
USD;class="num">2013.07.class="num">16 class="num">13:class="num">15:class="num">00;USD Industrial Production(JUN) and others
USD;class="num">2013.07.class="num">16 class="num">14:class="num">00:class="num">00;USD NAHB Housing Market Index(JUL)
USD;class="num">2013.07.class="num">16 class="num">18:class="num">15:class="num">00;USD Fed&class="macro">#x27;s George Speaks on Economic Conditions and Agriculture
USD;class="num">2013.07.class="num">22 class="num">12:class="num">30:class="num">00;USD Chicago Fed Nat Activity Index(JUN)
USD;class="num">2013.07.class="num">22 class="num">14:class="num">00:class="num">00;USD Existing Home Sales(MoM) (JUN) & Existing Home Sales(JUN)
USD;class="num">2013.07.class="num">22 class="num">21:class="num">00:class="num">00;USD EIA Gasoline and Diesel Fuel Update
USD;class="num">2013.07.class="num">23 class="num">13:class="num">00:class="num">00;USD House Price Index(MoM) (MAY)
USD;class="num">2013.07.class="num">23 class="num">14:class="num">00:class="num">00;USD Richmond Fed Manufacturing Index(JUL)
USD;class="num">2013.07.class="num">24 class="num">11:class="num">00:class="num">00;USD MBA Mortgage Applications(JUL class="num">19)
USD;class="num">2013.07.class="num">24 class="num">12:class="num">58:class="num">00;USD Markit US PMI Preliminary(JUL)
USD;class="num">2013.07.class="num">24 class="num">14:class="num">00:class="num">00;USD USD New Home Sales(MoM) (JUN) & USD New Home Sales(JUN)
USD;class="num">2013.07.class="num">24 class="num">14:class="num">30:class="num">00;USD USD DOE U.S. Crude Oil Inventories(JUL class="num">19) and others
       class="type">class="kw">double momentumBuffer[];
       
       GetTechIndicators().GetMomentum().GetBuffer(momentumBuffer, class="num">2);
       
       class=class="str">"cmt">//--- Number of seconds before the news releases. GMT +- timeWindow is the real time from which the robot starts 
       class=class="str">"cmt">//--- listening to the market. For instance, if there is a news release programmed at class="num">13:class="num">00 GMT you can set TimeWindow 
       class=class="str">"cmt">//--- to class="num">900 seconds so that the EA starts listening to the market fifteen minutes before that news release. 
       class="type">int timeWindow=class="num">600;
       
       CNew *currentNew = GetBrain().GetNewsContainer().GetCurrentNew();       
       class="type">int indexCurrentNew = GetBrain().GetNewsContainer().GetCurrentIndex();
           
       if(TimeGMT() >= currentNew.GetTimeRelease() + timeWindow)
       {
          GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+class="num">1);
          class="kw">return;
       }
       
       class=class="str">"cmt">//--- is there any open position?

「新闻窗口内的动量触发逻辑」

这段控制流只做一件事:当前品种没有持仓时,才允许在新闻发布前后一段时间窗内根据动量缓冲值去挂方向。若已持有仓位,则直接置为 DO_NOTHING,把后续交给数学期望值自行发挥作用,不再追单。 时间窗由 timeWindow 与 currentNew.GetTimeRelease() 共同决定,用 TimeGMT() 做边界判断:早于发布前 timeWindow 秒或晚于发布后 timeWindow 秒都不进场。 动量阈值写死在代码里——momentumBuffer[0] 大于 100.10 倾向 SELL,小于 99.90 倾向 BUY,中间地带不动作。这两个数不是拍脑袋,是相对 100 基准的 ±0.10 偏离带,开 MT5 把 momentumBuffer 打印出来就能看到实时值。 外汇与贵金属在新闻秒级波动里滑点可能吞掉阈值利润,实盘前先用策略测试器跑一遍该段逻辑确认触发频次。

MQL5 / C++
if(!m_positionInfo.Select(_Symbol))
{
   class=class="str">"cmt">//--- if there is no open position, we try to open one
   class="type">bool timeHasCome = TimeGMT() >= currentNew.GetTimeRelease() - timeWindow && TimeGMT() <= currentNew.GetTimeRelease() + timeWindow;

   if(timeHasCome && momentumBuffer[class="num">0] > class="num">100.10)
   {
      GetEvolution().SetStatus(SELL);
      GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+class="num">1);
   }
   else if(timeHasCome && momentumBuffer[class="num">0] < class="num">99.90)
   {
      GetEvolution().SetStatus(BUY);
      GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+class="num">1);
   }
}
class=class="str">"cmt">//--- if there is an open position, we let it work the mathematical expectation
else
{
   GetEvolution().SetStatus(DO_NOTHING);
}

◍ 画得少,看得清

这套新闻监听 EA 把面向对象容器和文件系统的活都拆给了 cnewscontainer.mqh 与 cnewswatcher.mqh,主程序 expertnewswatcher.mq5 只有 1.3 KB,真正下单逻辑反而最轻。附件里 11 个文件总体积不到 40 KB,却把新闻等级解析、CSV 落地、Watcher 轮询全分了层。 有用户在 XAUUSD M1 上跑出过错误代码 5002:终端找不到 news_watcher.csv,路径落在 Terminal\<hash>\MQL5\Files\ 下。开 MT5 前先确认该文件已手动丢进 Files 目录,否则初始化阶段就直接 deinit。 外汇和贵金属新闻行情瞬时跳空概率高,用挂单或秒级监听都只是降低滑点,不消除风险。类写清楚了,剩下是你自己决定监听哪些等级、过滤哪些币种。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到新闻前后的动量异动,把重复劳动交给小布,你专注决策。

常见问题

CSV 本地文件不依赖网络稳定性,回测与实盘共用同一份数据;缺点是不能自动更新,需要定期手动维护或脚本同步。
CBrain 作为只读存储器式的中心节点,持有指标句柄、新闻容器引用与基础参数,供 OnTick 统一调度,避免各模块各自为政。
目前小布的品种页提供新闻动量诊断视图,EA 的 CSV 需自行维护;后续若开放策略导入,可将同一份日历映射进小布看板。
可能。新闻瞬间流动性枯竭,真实点差可能扩大数倍,常数点差会让历史表现偏乐观,倾向用可变点差数据重测。
概率上贵金属受避险资金影响,趋势延续性有时强于外汇;外汇贵金属均属高风险,该假设需分品种验证而非默认生效。