建立自动新闻交易程序(基础篇)
📘

建立自动新闻交易程序(基础篇)

第 1/3 篇

「在 MT5 里搭一个新闻自动交易框架」

想在 MT5 上做新闻行情的自动化,第一步不是写策略,而是先把「事件触发」和「订单执行」两条链路打通。MetaTrader 5 自带的事件定时器与 Trade 类接口,足以在不依赖第三方插件的情况下完成基础闭环。

  • 年 5 月 19 日那篇原始思路里,作者用 4371 次浏览、11 条讨论验证了这类需求的真实热度——说明手动刷新闻再下单的痛点一直存在。你可以直接在 MT5 策略测试器里用历史新闻时间戳回放,看滑点分布再决定要不要上实盘。

外汇与贵金属在重大数据发布前后点差可能瞬间拉大数倍,自动化程序若没做点差过滤,大概率会在最坏价位成交。先跑通模拟盘的事件响应,再谈参数优化。

新闻数据怎么搅动货币盘面

新闻交易者指的是依据宏观新闻做交易决策的人。真正能掀起行情的,往往是GDP、消费者信心、就业这类硬指标发布的前后几分钟。 以美国非农为例,过去多年里该数据公布后EURUSD在15分钟内出现超过40个点的瞬时波动属于常态,这种脉冲经常成为短线趋势反转的触发器。 如果你在MT5上把1分钟和5分钟图叠加非农发布时间轴,会直观看到流动性瞬间抽干又回补的痕迹。外汇与贵金属受此类事件驱动明显,属于高风险博弈,仓位需留足缓冲。

◍ 用 OOP 把新闻日历喂给 EA 的大脑

这套自动交易的核心思路不复杂:用动量指标量化新闻冲击,再把筛选过的财经日历以 CSV 落盘,由 EA 在内存里实时读取并决策。外汇与贵金属受新闻跳空影响大,这类策略回测与实盘偏差可能显著,属高风险玩法。 代码层面主类叫 CNewsWatcher,它把 EA 拆成「大脑 CBrain」「指标组」「新闻容器」几块。CBrain 像只读存储器,集中存放运行必需的状态;新闻容器则类似一份报纸,装着用户指定要交易的那几条事件。 OnTick 里最关键的动作就三行:取实时 tick、更新动量缓冲、把买卖价丢给 NW.OnTick 处理。动量周期写死 13、收盘价计算,新闻文件默认 news_watcher.csv。强加速出现时 EA 倾向逆向下单——上冲卖、下砸买,赌短期反抽,手数 0.01 起、止损 400 点、止盈 600 点,显然只是技术骨架,实盘需自调。 别把逆向下单当真理 原文明确说这是反应性编程的简陋版,强势上扬卖、强势下行买的逻辑可改进空间很大;直接照搬可能在连续趋势中连续止损,MT5 里把 LotSize 调到 0.1 前先想清楚账户耐受度。

MQL5 / C++
class=class="str">"cmt">//+---------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 ExpertNewsWatcher.mq5 |
class=class="str">"cmt">//|          Copyright © class="num">2013, laplacianlab, Jordi Bassagañas | 
class=class="str">"cmt">//+---------------------------------------------------------------------+
class="macro">#class="kw">property copyright   "Copyright © class="num">2013, laplacianlab. Jordi Bassagañas"
class="macro">#class="kw">property link        "[MQL5官方文档]
class="macro">#class="kw">property version     "class="num">1.00"
class="macro">#class="kw">property tester_file "news_watcher.csv"  
class="macro">#include <..\Experts\NewsWatcher\CNewsWatcher.mqh>
input ENUM_TIMEFRAMES  Period=PERIOD_M1;
input class="type">int              StopLoss=class="num">400;
input class="type">int              TakeProfit=class="num">600;
input class="type">class="kw">double           LotSize=class="num">0.01;
input class="type">class="kw">string           CsvFile="news_watcher.csv";
class="type">MqlTick tick;
CNewsWatcher* NW = new CNewsWatcher(StopLoss,TakeProfit,LotSize,CsvFile);
class="type">int OnInit(class="type">void)
  {
   NW.Init();
   NW.GetTechIndicators().GetMomentum().SetHandler(Symbol(), Period, class="num">13, PRICE_CLOSE);
   class="kw">return(class="num">0);
  }
class="type">void OnDeinit(const class="type">int reason)
  {
   class="kw">delete(NW);
  }
class="type">void OnTick()
  {
   SymbolInfoTick(_Symbol, tick);              
   NW.GetTechIndicators().GetMomentum().UpdateBuffer(class="num">2);
   NW.OnTick(tick.ask,tick.bid);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 CNewsWatcher.mqh |
class=class="str">"cmt">//|                     Copyright © class="num">2013, Jordi Bassagañas |
class=class="str">"cmt">//+---------------------------------------------------------------------+
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <Mine\Enums.mqh>
class="macro">#include <..\Experts\NewsWatcher\CBrain.mqh>

「新闻监控类的骨架与对象装配」

要把财经新闻事件接入 MT5 自动交易,先得有一个统管脑模块、进化模块与技术指标模块的容器类。下面这段 CNewsWatcher 的定义就是干这个的:它保护式持有 CBrain、CEvolution、CTechIndicators 三个自定义指针,以及 CTrade、CPositionInfo 两个 MQL5 原生交易对象指针。 构造时传入的四个参数很直接——stop_loss 与 take_profit 以点数计,lot_size 是下单手数,csv_file 是新闻日历路径。注意 m_evolution 被初始化为 new CEvolution(DO_NOTHING),意味着进化逻辑在对象诞生那一刻是挂起状态,不会自动改参数。 析构走 Deinit() 统一释放,避免 EA 重载时漏掉指针。Getter 全返回指针,外部能直接拿到脑模块去读信号,也能拿 CTrade 去发单。 把这段代码贴进 MT5 的 Include 链(需同级有 NewsWatcher 目录下的 CEvolution.mqh 与 CTechIndicators.mqh),编译过即说明你的新闻Watcher骨架立住了,下一步才是填 OnTick 的触发规则。外汇与贵金属行情受新闻跳空影响大,实盘前务必在策略测试器用历史数据验证。

MQL5 / C++
class="macro">#include <..\Experts\NewsWatcher\CEvolution.mqh>
class="macro">#include <..\Experts\NewsWatcher\CTechIndicators.mqh>
class=class="str">"cmt">//+---------------------------------------------------------------------+
class=class="str">"cmt">//| CNewsWatcher Class                                                  |
class=class="str">"cmt">//+---------------------------------------------------------------------+
class CNewsWatcher
  {
class="kw">protected:
   class=class="str">"cmt">//--- Custom types
   CBrain              *m_brain;
   CEvolution          *m_evolution;
   CTechIndicators     *m_techIndicators;
   class=class="str">"cmt">//--- MQL5 types
   CTrade              *m_trade;
   CPositionInfo       *m_positionInfo;
class="kw">public:
   class=class="str">"cmt">//--- Constructor and destructor methods
                       CNewsWatcher(class="type">int stop_loss,class="type">int take_profit,class="type">class="kw">double lot_size,class="type">class="kw">string csv_file);
                      ~CNewsWatcher(class="type">void);
   class=class="str">"cmt">//--- Getter methods
   CBrain              *GetBrain(class="type">void);
   CEvolution          *GetEvolution(class="type">void);
   CTechIndicators     *GetTechIndicators(class="type">void);
   CTrade              *GetTrade(class="type">void);
   CPositionInfo       *GetPositionInfo(class="type">void);
   class=class="str">"cmt">//--- CNewsWatcher methods
   class="type">bool                Init();
   class="type">void                Deinit(class="type">void);
   class="type">void                OnTick(class="type">class="kw">double ask,class="type">class="kw">double bid);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNewsWatcher::CNewsWatcher(class="type">int stop_loss,class="type">int take_profit,class="type">class="kw">double lot_size, class="type">class="kw">string csv_file)
  {
   m_brain=new CBrain(stop_loss,take_profit,lot_size,csv_file);
   m_evolution=new CEvolution(DO_NOTHING);
   m_techIndicators=new CTechIndicators;
   m_trade=new CTrade();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNewsWatcher::~CNewsWatcher(class="type">void)
  {
   Deinit();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetBrain                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CBrain *CNewsWatcher::GetBrain(class="type">void)
  {

新闻监听器里的取值接口与 OnTick 触发逻辑

CNewsWatcher 类把内部子模块都包了一层 getter,外部只需调 GetBrain()、GetEvolution()、GetTechIndicators()、GetTrade()、GetPositionInfo() 就能拿到对应指针,不用碰私有成员。 这些接口本质就是返回成员变量:m_brain 回传给策略大脑,m_evolution 给进化模块,m_techIndicators 给技术指标集,m_trade 给交易对象,m_positionInfo 给持仓信息。复制下面代码到 MT5 的 EA 头文件里就能直接编译。

MQL5 / C++
CEvolution *CNewsWatcher::GetEvolution(class="type">void)
  {
   class="kw">return m_evolution;
  }
CTechIndicators *CNewsWatcher::GetTechIndicators(class="type">void)
  {
   class="kw">return m_techIndicators;
  }
CTrade *CNewsWatcher::GetTrade(class="type">void)
  {
   class="kw">return m_trade;
  }
CPositionInfo *CNewsWatcher::GetPositionInfo(class="type">void)
  {
   class="kw">return m_positionInfo;
  }
OnTick(ask, bid) 是真正的监听入口。先判断新闻容器里还有没有未处理的新闻:GetCurrentIndex() 小于 GetTotal() 才继续。 进入后先取动量指标最近 2 根缓冲:GetMomentum().GetBuffer(momentumBuffer, 2)。timeWindow 写死成 600 秒,意味着新闻发布时间过 10 分钟之后,机器人就把当前新闻索引 +1 跳过去,不再监听。外汇与贵金属新闻行情跳空剧烈,这套时间窗逻辑只是过滤噪音,不等于能规避滑点风险。 想验证的话,把 timeWindow 改成 900 再跑回测,对比 600 与 900 两种参数下新闻前 15 分钟内的动量缓冲采样密度差异。

MQL5 / C++
CEvolution *CNewsWatcher::GetEvolution(class="type">void)
  {
   class="kw">return m_evolution;
  }
CTechIndicators *CNewsWatcher::GetTechIndicators(class="type">void)
  {
   class="kw">return m_techIndicators;
  }
CTrade *CNewsWatcher::GetTrade(class="type">void)
  {
   class="kw">return m_trade;
  }
CPositionInfo *CNewsWatcher::GetPositionInfo(class="type">void)
  {
   class="kw">return m_positionInfo;
  }
class="type">void CNewsWatcher::OnTick(class="type">class="kw">double ask,class="type">class="kw">double bid)
  {
class=class="str">"cmt">//--- are there some news to process?
   if(GetBrain().GetNewsContainer().GetCurrentIndex() < GetBrain().GetNewsContainer().GetTotal())
     {
      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;
      }
     }

◍ 新闻窗口内的动量触发与持仓让利

这段逻辑只在一个前提下动手:当前品种没有未平仓位。先用 Select(_Symbol) 探一下池子,若返回 false 才进入建仓判断,否则直接把状态置为 DO_NOTHING,让已有仓位去跑数学期望,不中途干预。 时间窗由新闻发布时间加减 timeWindow 决定,用 TimeGMT() 比对。只有当时间落在这个区间、且动量缓冲值 momentumBuffer[0] 越过阈值时才发信号:大于 100.10 倾向 SELL,小于 99.90 倾向 BUY,同时把新闻容器索引往后推一位。两个阈值对称留了 0.10 的缓冲带,避免微幅波动误触。 真正下单在 switch 里完成。BUY 时 tp=ask+TakeProfit*_Point、sl=bid-StopLoss*_Point;SELL 反过来,sl=ask+StopLoss*_Point、tp=bid-TakeProfit*_Point。外汇与贵金属杠杆高,这类新闻动量策略滑点和点差扩张可能吞掉预期优势,上 MT5 用策略测试器跑同段新闻日历验证再说。 Init 与 Deinit 只是类骨架,初始化具体逻辑留空返回 true,实盘接进去前得自己补 Select 失败处理和新闻容器绑定。

MQL5 / C++
  class=class="str">"cmt">//--- is there any open position?
  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);      
  }  
  
  class="type">class="kw">double tp;
  class="type">class="kw">double sl;
  class="kw">switch(GetEvolution().GetStatus())
  {     
    case BUY:
      tp = ask + m_brain.GetTakeProfit() * _Point;
      sl = bid - m_brain.GetStopLoss() * _Point;
      GetTrade().PositionOpen(_Symbol,ORDER_TYPE_BUY,m_brain.GetSize(),ask,sl,tp);
      class="kw">break;
    case SELL:
      sl = ask + m_brain.GetStopLoss() * _Point;
      tp = bid - m_brain.GetTakeProfit() * _Point;
      GetTrade().PositionOpen(_Symbol,ORDER_TYPE_SELL,m_brain.GetSize(),bid,sl,tp);
      class="kw">break;
    case DO_NOTHING:
      class=class="str">"cmt">// Nothing...
      class="kw">break;
  }
   }  
class=class="str">"cmt">//--- we exit when all the container&class="macro">#x27;s news have been processed
   else class="kw">return;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNewsWatcher initialization                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CNewsWatcher::Init(class="type">void)
  {
class=class="str">"cmt">// Initialization logic here...
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CNewsWatcher deinitialization                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNewsWatcher::Deinit(class="type">void)

常见问题

历史看重大美国数据公布后数分钟内黄金常出现 10~20 美元急拉或急挫,但属高风险事件,滑点和点差扩张可能吞掉策略空间,建议先用模拟盘跑框架。
用 OOP 把日历封装成监听器对象更稳,EA 主循环只管装配和调用,后续换数据源或加过滤条件不用重写整体逻辑。
可以,小布盯盘的 AIGC 已内置新闻窗口识别与动量扫描,打开对应货币或贵金属页就能看到异动标注,不用自己写监听器。
新闻单别用固定小止损,点差扩大易扫损;可让持仓在前一根突破幅度外挂跟踪,或限定只做突破确认后第二根回调入场的单。
会,应在对象内做时间边界缓存,只在距新闻前后 N 分钟内才开放取值接口,其余时间 OnTick 直接跳过监听分支。