建立自动新闻交易程序(基础篇)
「在 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 前先想清楚账户耐受度。
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 的触发规则。外汇与贵金属行情受新闻跳空影响大,实盘前务必在策略测试器用历史数据验证。
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 头文件里就能直接编译。
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; }
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 失败处理和新闻容器绑定。
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)