MetaTrader 5 中的并行计算(基础篇)
📘

MetaTrader 5 中的并行计算(基础篇)

第 1/3 篇

「MT5 怎么把活儿分摊给多核」

MT5 终端从架构上支持并行计算,能把原本串行的策略校验、历史扫描丢给多核 CPU 同时跑,而不是像老版本那样挤在单线程里排队。 官方在 2013 年 12 月 26 日放出的示例帖,累计被查看 3513 次、引发 22 条讨论,说明多核调度在实盘群体里一直有刚需。 对做价格行为复盘的交易者来说,最直接的收益是:把多周期形态识别脚本并行化后,一次全市场扫描从分钟级压到秒级,你能在开盘前把贵金属和直盘都过一遍。外汇与贵金属杠杆高、滑点突发,并行提速只解决算力,不替你兜底风险。

多核不等于自动加速

现代 PC 几乎都带多核,核心数从 2、4、6 一路涨,Intel 曾演示过 80 核试验芯片,但那只为探技术上限,不会零售。很多人以为多核电脑天然就能让单程序跑得更快,这其实是错觉。 看单核跑一个程序:核心全程给它,时间 T 内干完定量活。开两个程序时,单核靠飞快切换冒充并行,每秒切多次,体感像同时跑,但实际每个程序耗时都翻倍。 若核心数等于程序数,各自独占核心,速度才回到单程序水平。问题在于,把单程序自动拆给多核并不是处理器能做的事。比如程序先要密码再验证,不可能一边弹密码框一边在另一核做验证,密码没输就验证必失败。 处理器不懂你写的业务逻辑,不会自己切分任务。所以在多核机上跑单个没优化的程序,它只吃一个核,速度和单核机一样。想用满核心,只能由写程序的人在开发时标出可并行片段,运行时交给系统分配核。外汇和贵金属算法回测若涉及重计算,不理解这点可能白买多核机器。

◍ MT5 多核调度的实测边界

MQL5 本身没有内建的线程或并行运算符,想榨干多核要么走 DLL,要么利用终端自身的多线程架构。本文只聊后者:把负载拆到不同图表上的 EA 或指标里,让 MetaTrader 客户端替你分到不同核心。 实测用 i-flood 指标和 e-flood EA 做 CPU 压力测试(内部跑 1e9×10 空循环),配合 Windows 任务管理器看核心占用。结果很直白:同货币对上挂两个指标只占 1 核;不同货币对的两个指标占 2 核;不同图表上的两个 EA(同或不同货币对)都占 2 核。 第 7 种组合最值得记——用 EA 在不同货币对上各开一个 i-flood 副本,也能占满 2 核,这是很多策略里动态建指标的常见写法。外汇与贵金属交易本身高风险,多核并行只解决算力分配,不改善信号质量。 验证方法很简单:把下面代码存成三个文件编译,开 EURUSD 和 GBPUSD 各挂 e-flood-starter,再开任务管理器看 CPU 核心数是否从 1 变 2。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                i-flood.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property indicator_chart_window
input class="type">class="kw">string id;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
   Print(id,": OnInit");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rt,class="kw">const class="type">int pc,class="kw">const class="type">int b,class="kw">const class="type">class="kw">double &p[])
  {
   Print(id,": OnCalculate 开始");

   for (class="type">int i=class="num">0;i<class="num">1e9;i++)
     for (class="type">int j=class="num">0;j<class="num">1e1;j++);

   Print(id,": OnCalculate 结束");
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                e-flood.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">class="kw">string id;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
   Print(id,": OnInit");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   Print(id,": OnTick 开始");

   for (class="type">int i=class="num">0;i<class="num">1e9;i++)
     for (class="type">int j=class="num">0;j<class="num">1e1;j++);

   Print(id,": OnTick 结束");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                          e-flood-starter.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
   class="type">class="kw">string s="EURUSD";

「用循环批量挂接 i-flood 指标句柄」

在 EA 初始化阶段,可以用 for 循环一次性创建多个自定义指标实例,避免重复写死代码。下面这段逻辑遍历 i=1 到 2,分别向当前图表和 GBPUSD 请求名为 "i-flood" 的指标句柄并打印,方便你确认加载是否成功。 [CODE] for(int i=1; i<=2; i++) { Print("指标已创建, 句柄=", iCustom(s,_Period,"i-flood",IntegerToString(i))); s="GBPUSD"; } } //+------------------------------------------------------------------+ [/CODE] 逐行拆解:第 1 行定义整型 i 从 1 循环到 2,共两轮;第 3 行用 iCustom 按符号 s、当前周期 _Period、指标名 "i-flood" 及参数 i 取句柄,并交由 Print 输出;第 5 行把 s 改写为 "GBPUSD",使第二轮循环改为向英镑兑美元要句柄。 外汇与贵金属属高风险品种,指标句柄返回 INVALID_HANDLE(即 -1)时说明加载失败,应在实盘前于 MT5 策略测试器确认日志无报错再启用。

MQL5 / C++
for(class="type">int i=class="num">1; i<=class="num">2; i++)
  {
   Print("指标已创建, 句柄=",
         iCustom(s,_Period,"i-flood",IntegerToString(i)));
   s="GBPUSD";
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

并行系统的构件与同步陷阱

在 MT5 里搭并行系统,本质是把一组指标或 EA 当成一个大程序用,计算摊到多核上。核心分两类:CM 计算模块,数量从 2 到核心数,干并行活;MM 主模块,是指标就画图、是 EA 就下单,还管着所有 CM。 它们不是传统可调用的子程序,而是各自独立的 EA/指标,平时没直接联系,只在终端事件(报价、计时器)触发时才醒。事件之间要传的数据得塞进公共的“数据交换缓冲区”。组合 7 最省事:整系统在一个窗口,EA 自动建指标,不用来回开窗口手放,能少犯低级错误。 传数据有三条路:全局变量(只认 double,少量数字最优,文本得编码)、文件(啥类型都行,量大时比全局变量快)、指标缓冲区(双方都是指标时用,传大数组方便,但不同货币对新柱不同步,编号会偏移)。 同步上别依赖 OnTick:MM 收到报价也只能留个任务等 CM,跨货币对可能等几秒甚至几分钟。用 OnTimer 按频率(如 1 秒)跑,延迟能压住。也可在 OnInit/OnCalculate 里写无限循环模拟嘀嗒。 实测组合 7 下指标里 OnTimer 不触发;无限循环更危险——一个交易品种的所有指标共用一个线程,某个指标死循环会卡死该品种全部指标和 EA 的 OnTick。开发人员确认:脚本和 EA 各占线程,同品种指标全挤同一线程,接续执行。

常见问题

多核不等于自动加速,MT5 主线程仍串行处理事件与下单;把指标计算拆到子线程或精简句柄才能缓解卡顿。
句柄创建有上限且需同步等待初始化,建议在循环里加句柄有效性判断与短暂延时,避免竞态导致空值。
小布可汇总当前品种页的算力占用与句柄数,标红异常品种,你点开就能看到哪张图在拖后腿。
实测中单终端线程数受系统调度限制,过度开并行反而引发上下文切换损耗,建议核心数控制在物理核内。
会,必须加临界区或原子锁同步;否则跨核写冲突会让信号闪烁,回测和实盘表现不一致。