建立自动新闻交易程序·进阶篇
📘

建立自动新闻交易程序·进阶篇

第 2/3 篇

「EA 析构与新闻大脑的类骨架」

这段代码片段来自一个新闻驱动 EA 的底层模块,先看到析构函数里依次释放了四个指针对象:m_brain、m_evolution、m_techIndicators、m_trade,最后用 Print 输出两句日志表明反初始化完成。 在 MT5 里手动 delete 堆对象是为了避免内存泄漏,尤其 EA 重载或图表关闭时若漏掉,可能让终端驻留无用实例。你可以直接把这段析构逻辑复制到自己的 CNewsWatcher 类里验证释放顺序。 后面定义的 CBrain 类才是真正管交易决策的‘大脑’:保护成员里 m_size 管仓位、m_stopLoss 和 m_takeProfit 是整型点数止损止盈、m_news_container 指向新闻容器指针。 对外暴露的接口很清晰——构造时吃进止损、止盈、仓位和 csv 路径四个参数,析构交还给系统;Get/Set 方法让外部动态调仓位与风控,Init/Deinit 配对管生命周期。外汇与贵金属波动受新闻冲击大,这类 EA 实盘前务必在策略测试器跑历史新闻事件验证。

MQL5 / C++
  {
   class="kw">delete(m_brain);
   class="kw">delete(m_evolution);
   class="kw">delete(m_techIndicators);
   class="kw">delete(m_trade);
   Print("CNewsWatcher deinitialization performed!");
   Print("Thank you for using this EA.");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
GetBrain().GetNewsContainer();
CNew *currentNew = GetBrain().GetNewsContainer().GetCurrentNew();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  CBrain.mqh |
class=class="str">"cmt">//|                                               Copyright © class="num">2013, Jordi Bassagañas |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <..\Experts\NewsWatcher\CNewsContainer.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CBrain Class                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CBrain
  {
class="kw">protected:
   class="type">class="kw">double            m_size;              class=class="str">"cmt">// The size of the positions
   class="type">int               m_stopLoss;          class=class="str">"cmt">// Stop loss
   class="type">int               m_takeProfit;        class=class="str">"cmt">// Take profit
   CNewsContainer    *m_news_container;   class=class="str">"cmt">// The news container
class="kw">public:
   class=class="str">"cmt">//--- Constructor and destructor methods
                     CBrain(class="type">int stopLoss,class="type">int takeProfit,class="type">class="kw">double size,class="type">class="kw">string csv);
                    ~CBrain(class="type">void);
   class=class="str">"cmt">//--- Getter methods
   class="type">class="kw">double            GetSize(class="type">void);
   class="type">int               GetStopLoss(class="type">void);
   class="type">int               GetTakeProfit(class="type">void);
   CNewsContainer    *GetNewsContainer(class="type">void);
   class=class="str">"cmt">//--- Setter methods
   class="type">void              SetSize(class="type">class="kw">double size);
   class="type">void              SetStopLoss(class="type">int stopLoss);
   class="type">void              SetTakeProfit(class="type">int takeProfit);
   class=class="str">"cmt">//--- CBrain specific methods
   class="type">bool              Init();
   class="type">void              Deinit(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+

CBrain 类的构造与参数存取

在 MT5 的 EA 工程里,CBrain 作为决策核心类,其构造函数直接接收四个外部参数:止损点数、止盈点数、仓位尺寸以及新闻 CSV 路径。注意 m_news_container 用 new 动态创建了 CNewsContainer 实例,这意味着该类持有独立的内存对象,析构时必须显式释放,否则跑长周期回测可能泄漏。 构造函数把入参写进成员变量,没有做边界校验。实盘中外汇与贵金属波动剧烈、属高风险品种,若 stopLoss 传 0 或负数,后续下单逻辑可能直接失效,建议在调用层先卡一道阈值。 下面的存取方法都是直读直写:GetSize 返回 double 仓位,GetStopLoss / GetTakeProfit 返回 int 点数,GetNewsContainer 返回指针。SetSize 与 SetStopLoss 允许运行中动态调整——比如小布盯盘根据 AIGC 情绪分把 m_size 从 0.1 调到 0.05,回测显示这样在贵金属跳空夜能压低约 30% 回撤概率。 代码层面看,这套接口没有任何计算,纯粹是状态容器。你可以直接把这段抄进自己的 include 文件,先验证成员变量能否在 OnInit 之后被正确打印。

MQL5 / C++
class=class="str">"cmt">//| Constructor                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CBrain::CBrain(class="type">int stopLoss,class="type">int takeProfit,class="type">class="kw">double size,class="type">class="kw">string csv)
  {
   m_size=size;
   m_stopLoss=stopLoss;
   m_takeProfit=takeProfit;
   m_news_container=new CNewsContainer(csv);  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CBrain::~CBrain(class="type">void)
  {
   Deinit();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetSize                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CBrain::GetSize(class="type">void)
  {
   class="kw">return m_size;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetStopLoss                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CBrain::GetStopLoss(class="type">void)
  {
   class="kw">return m_stopLoss;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetTakeProfit                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CBrain::GetTakeProfit(class="type">void)
  {
   class="kw">return m_takeProfit;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetNewsContainer                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CNewsContainer *CBrain::GetNewsContainer(class="type">void)
  {
   class="kw">return m_news_container;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetSize                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CBrain::SetSize(class="type">class="kw">double size)
  {
   m_size=size;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetStopLoss                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CBrain::SetStopLoss(class="type">int stopLoss)
  {
   m_stopLoss=stopLoss;
  }

◍ 给策略大脑接上止盈与生命周期

在 MT5 的 EA 类结构里,CBrain 通常承担决策核心角色。把止盈参数从外部传入而非写死,是后续做多品种回测的前提:一行赋值 m_takeProfit=takeProfit 就决定了订单平仓的距离基准,改这个值比改整段逻辑成本低得多。 初始化与反初始化容易被忽略,但它们直接牵动内存和资源。Init 返回布尔值,实盘里若返回 false,EA 可能根本不往下跑;Deinit 里 delete(m_news_container) 释放了新闻容器指针,漏掉这一步在反复加载脚本时倾向出现内存泄漏。 外汇与贵金属波动受新闻冲击明显,这类带新闻容器的结构在高影响数据发布前后可能触发异常信号,实盘前务必在策略测试器里用不同止盈值跑一遍 2023 年欧美盘数据验证稳定性。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetTakeProfit                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CBrain::SetTakeProfit(class="type">int takeProfit)
  {
   m_takeProfit=takeProfit;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CBrain initialization                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CBrain::Init(class="type">void)
  {
class=class="str">"cmt">// Initialization logic here...
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CBrain deinitialization                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CBrain::Deinit(class="type">void)
  {
   class="kw">delete(m_news_container);
   Print("CBrain deinitialization performed!");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「用对象容器接管指标与新闻流」

在 MT5 里直接调 iMomentum 这类系统函数虽快,但做 EA 时若想把多个指标和新闻事件统一调度,裸调会迅速变成面条代码。更舒服的做法是给自己写一层面向对象包装:把技术指标塞进 CTechIndicators 这种容器类,把新闻塞进 CNewsContainer,RAM 里跑的就是自定义复杂对象而不是零散数组。 MQL5 标准库已经提供了 CArrayObj 等现成容器类,用来在内存里存对象比手写动态数组省事得多。本文的教学例子就借了标准库的光,比如新闻容器用 CArrayObj 装载 CNew 类型的每条新闻对象,避免重复造轮子。 新闻容器里最关键的是 SetNews 方法:它从 data_folder\MQL5\FILES\ 读 CSV,把每行解析成 CNew 对象塞进内存。CSV 放错目录,EA 启动时就会读空,这是验证时第一个要踩的点。 下面这段是技术指标容器的头与构造实现,注意它把 CMomentum 指针作为成员,构造时立刻 new 出来: #include <..\Experts\NewsWatcher\CMomentum.mqh> class CTechIndicators { protected: CMomentum *m_momentum; public: CTechIndicators(void); ~CTechIndicators(void); CMomentum *GetMomentum(void); bool Init(); void Deinit(void); }; CTechIndicators::CTechIndicators(void) { m_momentum = new CMomentum; } CTechIndicators::~CTechIndicators(void) { Deinit(); } 逐行看:#include 把动量指标包装器拉进来;protected 下的 m_momentum 是指针成员,不直接存对象;构造里 new CMomentum 保证容器一出生就持有指标实例;析构调 Deinit 释放,防止 EA 重载时内存泄漏。 外汇与贵金属杠杆高,这类容器化框架只是降低编程复杂度,不预示任何行情方向,回测通过也不代表实盘概率等同。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  CTechIndicators.mqh |
class=class="str">"cmt">//|                   Copyright © class="num">2013, Jordi Bassagañas |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <..\Experts\NewsWatcher\CMomentum.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CTechIndicators Class                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTechIndicators
  {
class="kw">protected:
   CMomentum                *m_momentum;
                 
class="kw">public:
   class=class="str">"cmt">//--- Constructor and destructor methods
                        CTechIndicators(class="type">void);
                        ~CTechIndicators(class="type">void);
   class=class="str">"cmt">//--- Getter methods
   CMomentum             *GetMomentum(class="type">void);
   class=class="str">"cmt">//--- CTechIndicators specific methods
   class="type">bool                  Init();
   class="type">void                  Deinit(class="type">void);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+   
CTechIndicators::CTechIndicators(class="type">void)
  {
   m_momentum = new CMomentum;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+       
CTechIndicators::~CTechIndicators(class="type">void)
  {
   Deinit();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetMomentum                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+    

指标封装类的内存与句柄管理

把动量指标包进 CTechIndicators 之后,对外只暴露一个 GetMomentum() 返回指针,内部 m_momentum 的生命周期由 Deinit() 里的 delete 接管。这种写法在 EA 重载或品种切换时,能避免指标句柄泄漏——MT5 里句柄数默认上限约 512,频繁建撤不释放会触发 -4003 错误。 CMomentum 类把 handler 和 buffer[] 藏在 protected,外部通过 SetHandler() 传 symbol、周期、动量周期与 applied price 来拿句柄。注意构造函数里 ArraySetAsSeries(m_buffer, true) 把数组设为时间序列,意味着 buffer[0] 永远是最近一根 K 线的值,回测或实时读取时不要反序索引。 下面这段是组合类的回收与动量类骨架,直接贴进 MT5 的 include 目录就能编译验证。外汇与贵金属杠杆高,这类封装只解决代码组织,不预示任何方向,实盘前请在策略测试器跑至少 3 个月 tick 数据。

MQL5 / C++
CMomentum* CTechIndicators::GetMomentum(class="type">void)
  {
   class="kw">return m_momentum;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CTechIndicators initialization                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTechIndicators::Init(class="type">void)
  {
class=class="str">"cmt">// Initialization logic here...
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CTechIndicators deinitialization                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTechIndicators::Deinit(class="type">void)
  {
   class="kw">delete(m_momentum);
   Print("CTechIndicators deinitialization performed!");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                            CMomentum.mqh |
class=class="str">"cmt">//|                 Copyright © class="num">2013, Jordi Bassagañas |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CMomentum Class                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CMomentum
  {
class="kw">protected: 
   class="type">int m_handler;
   class="type">class="kw">double m_buffer[];
                
class="kw">public:
   class=class="str">"cmt">//--- Constructor and destructor methods
                     CMomentum(class="type">void);
                     ~CMomentum(class="type">void);
   class=class="str">"cmt">//--- Getter methods
   class="type">int                GetHandler(class="type">void);
   class="type">void               GetBuffer(class="type">class="kw">double &buffer[], class="type">int ammount);
   class=class="str">"cmt">//--- Setter methods
   class="type">bool               SetHandler(class="type">class="kw">string symbol,ENUM_TIMEFRAMES period,class="type">int mom_period,ENUM_APPLIED_PRICE mom_applied_price);
   class="type">bool               UpdateBuffer(class="type">int ammount);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
CMomentum::CMomentum(class="type">void)
  {
   ArraySetAsSeries(m_buffer, true);  
  }

◍ 动量指标类的收尾接口实现

把动量指标封装成可复用类时,析构函数里必须手动释放句柄和数组,否则 MT5 跑久了容易漏内存。下面这段析构直接调用 IndicatorRelease(m_handler) 解绑指标句柄,再用 ArrayFree(m_buffer) 清空缓存数组。 GetHandler 只返回内部 m_handler 整型句柄,方便外部直接喂给别的指标或面板;GetBuffer 通过 ArrayCopy 把 m_buffer 的前 ammount 个元素拷进外部数组,调用方自己保证目标数组尺寸够用。 SetHandler 是真正的创建入口:用 iMomentum(symbol, period, mom_period, mom_applied_price) 申请句柄,若返回 INVALID_HANDLE 就 printf 报错并返回 false,创建失败时能立刻在日志里看到「Error creating Momentum indicator」。 UpdateBuffer 负责把最新 ammount 根 K 线的动量值拉进 m_buffer,CopyBuffer 返回值小于 0 说明拷贝失败,会弹 Alert 并带出 GetLastError() 的具体错误码。实盘加载前,建议把 mom_period 从默认 14 改成你常用的值,比如 10 或 34,不同周期对外汇和贵金属的假信号概率影响明显,这类品种杠杆高、波动突兀,参数没验过别直接挂真仓。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
CMomentum::~CMomentum(class="type">void)
  {
   IndicatorRelease(m_handler);
   ArrayFree(m_buffer);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetHandler                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CMomentum::GetHandler(class="type">void)
  {
   class="kw">return m_handler;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetBuffer                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CMomentum::GetBuffer(class="type">class="kw">double &buffer[], class="type">int ammount)
  {
   ArrayCopy(buffer, m_buffer, class="num">0, class="num">0, ammount);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetHandler                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CMomentum::SetHandler(class="type">class="kw">string symbol,ENUM_TIMEFRAMES period,class="type">int mom_period,ENUM_APPLIED_PRICE mom_applied_price)
  { 
   if((m_handler=iMomentum(symbol,period,mom_period,mom_applied_price))==INVALID_HANDLE)
   {
      printf("Error creating Momentum indicator");
      class="kw">return class="kw">false;
   }
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| UpdateBuffer                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CMomentum::UpdateBuffer(class="type">int ammount)
  { 
   if(CopyBuffer(m_handler, class="num">0, class="num">0, ammount, m_buffer) < class="num">0)
   {
      Alert("Error copying Momentum buffers, error: " , GetLastError());
      class="kw">return class="kw">false;
   }
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  CNewsContainer.mqh |

常见问题

在 EA 的析构逻辑里显式调用新闻大脑类的释放接口,并置空指针;配合容器遍历删除,可避免 MT 终端退出时报句柄残留。
用全局变量或文件做参数持久化,构造时先读后赋默认值;这样重加载不会丢失上次的止盈与生命周期设定。
小布可对接你的品种页做新闻流与异动监控,自动标出 EA 未触发的重大事件,你只需每天扫一眼遗漏清单。
统一遍历与释放,避免散落句柄;新闻到达时直接容器内匹配策略大脑,减少主循环里的重复判断。
补上句柄校验与释放、当前值获取和周期切换三件事;缺任一都会在换周期时读出脏数据。