MetaTrader 5 中的并行计算·进阶篇
⚙️

MetaTrader 5 中的并行计算·进阶篇

(2/3)·多核不等于自动加速,MT5 里手动切线程才是实盘提速的关键一步

新手友好 第 2/3 篇
很多人以为把 EA 扔到 8 核电脑上跑就会快 8 倍,结果单核跑满其余围观。MT5 不会替你拆任务,得在代码里显式开线程,否则并行只是机箱上的灯在闪。

◍ 把相似序列策略改造成多核 EA

选一个适合并行化的思路:取最近 N 根柱构成价格序列,在历史里找最相近的一段,看当时价格往哪偏就开对应方向的单。序列短的时候 MT5 里几秒就算完,但把长度拉到 M1 最近 24 小时(1440 根柱)、回搜约 375,000 根柱的一年历史,单线程会卡很久。 这类搜索天然可切分——按 CPU 核心数把历史分段,每个核心只搜自己那段就行。我们搭的 e-MultiThread EA 靠一个 CM 指标在 OnInit 里跑无限循环做并行计算,EA 和指标之间用终端全局变量传数据,并且 EA 能通过参数切换“普通模式”和“多核指标模式”。 下面这段 EA 源码里,Threads 输入项直接决定用几个核;PatternLen=1440、HistoryLen=375000 就是上面说的压力参数。HistNeed 的计算把各段长度加总,若终端 MaxBars 不够就报错让你改设置重启,这是实盘前必须核对的一点。 [CODE] 里的 OnInit 前半段就是在卡历史长度:Bars 不足就循环 CopyClose 补齐。外汇和贵金属波动剧烈、杠杆高风险大,多核只是提速,不意味信号胜率提升,回测也要防过拟合。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e-MultiThread.mq5 |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span> Threads=<span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">// 应该使用多少个内核</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span> MagicNumber=<span class="number">class="num">0</span>;
<span class="comment">class=class="str">"cmt">// 策略参数</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span> PatternLen&nbsp;&nbsp;= <span class="number">class="num">1440</span>;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 分析序列 (模式) 的长度</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span> PrognozeLen = <span class="number">class="num">60</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 预测长度 (柱数)</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span> HistoryLen&nbsp;&nbsp;= <span class="number">class="num">375000</span>; <span class="comment">class=class="str">"cmt">// 搜索的历史长度</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> Lots=<span class="number">class="num">0.1</span>;
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> IndData
&nbsp;&nbsp;{
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ts,te;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">datetime</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;start_time;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prognoze,rating;
&nbsp;&nbsp;};
IndData Calc[];
<span class="keyword">class="type">class="kw">double</span> CurPattern[];
<span class="keyword">class="type">class="kw">double</span> Prognoze;
<span class="keyword">class="type">int</span>&nbsp;&nbsp;HistPatternBarStart;
<span class="keyword">class="type">int</span>&nbsp;&nbsp;ExistsPrognozeLen;
<span class="keyword">class="type">uint</span> TicksStart,TicksEnd;
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="preprocessor">class="macro">#include &lt;ThreadCalc.mqh&gt;</span>
<span class="preprocessor">class="macro">#include &lt;Trade\Trade.mqh&gt;</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">int</span> <span class="functions">OnInit</span>()
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> rates[];
<span class="comment">class=class="str">"cmt">//--- 确保有足够的历史</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span> HistNeed=HistoryLen+Threads+PatternLen+PatternLen+PrognozeLen-<span class="number">class="num">1</span>;
&nbsp;&nbsp; <span class="keyword">if</span>(<span class="functions">TerminalInfoInteger</span>(<span class="keyword">TERMINAL_MAXBARS</span>)&lt;HistNeed)
&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"修改终端设定 \". 图表上的最大柱数\" 为指定值, 不能小于 "</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HistNeed,<span class="class="type">class="kw">string">" 并重新启动终端"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="number">class="num">1</span>);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">class="kw">while</span>(<span class="functions">Bars</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>)&lt;HistNeed)
&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"终端中的历史长度不足 ("</span>,<span class="functions">Bars</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>),<span class="class="type">class="kw">string">") , 上传中..."</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">CopyClose</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>,<span class="number">class="num">0</span>,HistNeed,rates);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"终端中的历史长度: "</span>,<span class="functions">Bars</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>));
<span class="comment">class=class="str">"cmt">//--- 为多核模式创建计算指标</span>
&nbsp;&nbsp; <span class="keyword">if</span>(Threads&gt;<span class="number">class="num">1</span>)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GlobalVarPrefix=<span class="class="type">class="kw">string">"MultiThread_"</span>+<span class="functions">IntegerToString</span>(MagicNumber)+<span class="class="type">class="kw">string">"_"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">GlobalVariablesDeleteAll</span>(GlobalVarPrefix);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ArrayResize</span>(Calc,Threads);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 每个核心的历史长度</span>

「多线程历史匹配的代码骨架」

这段逻辑把历史搜索拆给多个指标线程跑,先按线程数切分历史长度:HistPartLen 用 MathCeil(HistoryLen/Threads) 向上取整,再补上 PatternLen+PrognozeLen-1 得到含边界的 HistPartLenPlus,避免序列头尾被截断。 创建线程指标时有个坑:每个 iCustom 必须挂在非当前图的货币对上,do-while 循环靠 SymbolName 递增跳过 _Symbol,否则 EA 和指标同图会抢数据。句柄若返回 INVALID_HANDLE 直接 return(1) 终止,实战里建议先 Print 出来确认哪一对失败了。 OnTick 里先用 CopyClose 填 CurPattern,不足 PatternLen 就 Sleep(1000) 轮询,再跑 CalcPrognozeLen 和 FindHistoryPrognoze。多线程版 FindHistoryPrognoze 给每个线程 SetParam 发 PrognozeLen 和 Query,然后 Sleep(100) 等 Answer 回包——这里 100 毫秒轮询在高频tick下可能堆积,自己压测时把间隔调到 10~50 看看延迟。 外汇与贵金属杠杆高,多线程历史匹配只是加速近似搜索,匹配结果对后市仅具概率倾向,不能直接当成交依据。

MQL5 / C++
class="type">int HistPartLen=MathCeil(HistoryLen/Threads); class=class="str">"cmt">// 向上取整切分历史长度
class="type">int HistPartLenPlus=HistPartLen+PatternLen+PrognozeLen-class="num">1; class=class="str">"cmt">// 含边界序列长度
class="type">class="kw">string s;
class="type">int snum=class="num">0;
class=class="str">"cmt">// 创建所有用于计算的指标
for(class="type">int t=class="num">0;t<Threads;t++)
  {
   class=class="str">"cmt">// 对于每个指标 - 它们自己的货币对,
   class=class="str">"cmt">// 它不应该和EA交易相同
   do
     s=SymbolName(snum++,class="kw">false);
   class="kw">while(s==_Symbol);
   class="type">int handle=iCustom(s,_Period,"i-Thread",
                      GlobalVarPrefix,t,_Symbol,PatternLen,
                      PatternLen+t*HistPartLen,HistPartLenPlus);
   if(handle==INVALID_HANDLE) class="kw">return(class="num">1);
   Print("指标已创建, 货币对 ",s,", 句柄 ",handle);
   }
   }
 class="kw">return(class="num">0);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  TicksStart=GetTickCount();
  class=class="str">"cmt">// 使用最近的柱填充序列
  class="kw">while(CopyClose(_Symbol,_Period,class="num">0,PatternLen,CurPattern)<PatternLen) Sleep(class="num">1000);
  class=class="str">"cmt">// 如果有开启的仓位, 衡量它的 "年龄"
  class=class="str">"cmt">// 并且为交易的计划剩余时间
  class=class="str">"cmt">// 修改预测范围
  CalcPrognozeLen();
  class=class="str">"cmt">// 寻找历史中最接近的序列
  class=class="str">"cmt">// 并且以此为基础预测价格的移动
  FindHistoryPrognoze();
  class=class="str">"cmt">// 进行必要的交易操作
  Trade();
  TicksEnd=GetTickCount();
  class=class="str">"cmt">// 记录调试信息
  PrintReport();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void FindHistoryPrognoze()
  {
  Prognoze=class="num">0;
  class="type">class="kw">double MaxRating;
  if(Threads>class="num">1)
    {
     class=class="str">"cmt">//--------------------------------------
     class=class="str">"cmt">// 使用计算指标
     class=class="str">"cmt">//--------------------------------------
     class=class="str">"cmt">// 遍历所有记录用指标
     for(class="type">int t=class="num">0;t<Threads;t++)
       {
        class=class="str">"cmt">// 发送计算任务的参数
        SetParam(t,"PrognozeLen",ExistsPrognozeLen);
        class=class="str">"cmt">// "开始计算" 信号
        SetParam(t,"Query");
        }
     for(class="type">int t=class="num">0;t<Threads;t++)
       {
        class=class="str">"cmt">// 等待结果
        class="kw">while(!ParamExists(t,"Answer"))
          Sleep(class="num">100);
        DelParam(t,"Answer");
        class=class="str">"cmt">// 取得结果

从指标取值到持仓止损的闭环逻辑

这段逻辑把图形识别结果转成可交易的数值:先用 GetParam 取出 Prognoze(方向预测值)、Rating(置信评分)和 PatternStart(形态起点索引),再用 CopyTime 把起点对应的 K 线时间写进 Calc 数组。若当前评分高于已记录的最大值,或这是第一笔候选,就把该评分与预测值暂存为最优解,后续只认最高评分那一套。 当指标未被调用时,EA 会在本地直接跑 FindPrognoze 重新算一遍,把结果塞进数据流,不依赖外部缓存。这里要注意:HistoryLen 和 ExistsPrognozeLen 的取值直接影响回看深度,改小前者可能让形态漏判。 CalcPrognozeLen 负责对齐已开仓的存续时间。若当前有持仓,它用 PositionGetInteger(POSITION_TIME) 取开仓时间,通过两次 CopyTime 数出经过的柱数 poslen;若 poslen 小于 PrognozeLen,则 ExistsPrognozeLen = PrognozeLen - poslen,否则归零。这意味着持仓越久,留给新预测的统计窗口越短。 Trade 函数做反向清仓:持多单且 Prognoze <= 0,或持空单且 Prognoze >= 0 时,调 CTrade.PositionClose 平掉。外汇与贵金属杠杆高,这类机械平仓不保证拐点正确,实盘前请在 MT5 策略测试器用历史数据验证信号反转频率。

MQL5 / C++
class="type">class="kw">double progn      = GetParam(t, "Prognoze");
class="type">class="kw">double rating      = GetParam(t, "Rating");
class="type">class="kw">datetime time[];
class="type">int start=GetParam(t,"PatternStart");
CopyTime(_Symbol,_Period,start,class="num">1,time);
Calc [t].prognoze  = progn;
Calc [t].rating    = rating;
Calc [t].start_time = time[class="num">0];
Calc [t].ts        = GetParam(t, "TS");
Calc [t].te        = GetParam(t, "TE");
class=class="str">"cmt">// 选择最佳结果
if((t==class="num">0) || (rating>MaxRating))
  {
   MaxRating = rating;
   Prognoze  = progn;
  }
 }
  }
 else
  {
  class=class="str">"cmt">//----------------------------
  class=class="str">"cmt">// 没有使用的指标
  class=class="str">"cmt">//----------------------------
  class=class="str">"cmt">// 在EA交易中计算所有内容, 放入数据流中
  FindPrognoze(_Symbol,CurPattern,class="num">0,HistoryLen,ExistsPrognozeLen,
         Prognoze,MaxRating,HistPatternBarStart);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CalcPrognozeLen()
  {
   ExistsPrognozeLen=PrognozeLen;
   class=class="str">"cmt">// 如果有开启的仓位, 确定 
   class=class="str">"cmt">// 自从开启以来经过了多少个柱
   if(PositionSelect(_Symbol))
    {
     class="type">class="kw">datetime postime=PositionGetInteger(POSITION_TIME);
     class="type">class="kw">datetime curtime,time[];
     CopyTime(_Symbol,_Period,class="num">0,class="num">1,time);
     curtime=time[class="num">0];
     CopyTime(_Symbol,_Period,curtime,postime,time);
     class="type">int poslen=ArraySize(time);
     if(poslen<PrognozeLen)
        ExistsPrognozeLen=PrognozeLen-poslen;
     else
        ExistsPrognozeLen=class="num">0;
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Trade()
  {
   class=class="str">"cmt">// 平掉开启的仓位, 假如和预测不符
   if(PositionSelect(_Symbol))
    {
     class="type">long type=PositionGetInteger(POSITION_TYPE);
     class="type">bool close=class="kw">false;
     if((type == POSITION_TYPE_BUY)  && (Prognoze <= class="num">0)) close = true;
     if((type == POSITION_TYPE_SELL) && (Prognoze >= class="num">0)) close = true;
     if(close)
      {
       CTrade trade;
       trade.PositionClose(_Symbol);

◍ 无仓时按预测方向开单与回测报告打印

这段逻辑只干一件事:当前账户里没有本符号持仓,且预测值不为零时,才允许进场。Prognoze 大于 0 发 Buy,小于 0 发 Sell,手数由 Lots 控制;若已持仓则直接跳过,避免重复加仓。 PrintReport 函数在 EA 停止或测试结束时输出诊断信息。单线程下会打印「没有使用的指标」并通过 CopyTime 取历史序列起点时间;多线程(Threads>1)则逐指标列出预测值、评分、历史序列起始时间与各线程耗时(毫秒级),方便你核对并行计算的负载分布。 OnDeinit 里给每个指标线程发送 End 命令,确保子进程干净退出,不残留全局变量。外汇与贵金属杠杆高,这类预测开仓逻辑回测胜率随样本漂移可能明显变化,实盘前务必在 MT5 策略测试器用至少 3 个月 Tick 数据验证。 把下面片段直接贴进 EA 的 OnTick 尾段,就能复现「无仓才开」的动作;改 Prognoze 阈值可过滤弱信号。

MQL5 / C++
   class=class="str">"cmt">// 如果没有仓位, 根据预测建立仓位
   if((Prognoze!=class="num">0) && (!PositionSelect(_Symbol)))
     {
      CTrade trade;
      if(Prognoze > class="num">0) trade.Buy(Lots);
      if(Prognoze < class="num">0) trade.Sell(Lots);
     }

「指标常驻监听与EA任务接力」

这段自定义指标不走常规 OnCalculate 算价路线,而是靠 OnInit 里的无限 while 循环常驻内存,专门等 EA 发来的全局变量命令。外汇与贵金属市场跳空频繁,这种异步架构能把形态匹配的重活从 EA 主线程剥离,降低卡顿概率,但长期挂指标也吃 MT5 终端资源,需自行权衡。 输入参数先框定工作范围:DataSymbol 指定货币对,PatternLen 是拿来比对的近期收盘序列长度,BarStart 与 BarCount 决定往历史回看多少根柱。缓冲区 Buffer 与 CurPattern 分别承接输出与当前形态载体,并借 ThreadCalc.mqh 头文件做跨线程参数读写。 循环里先用 ParamExists 探 ThreadNum 下的 End 与 Query 标记:收到 End 就 break 退出;没 Query 就 Sleep(100) 空转继续。一旦拿到 Query,立刻清掉标记、记录 GetTickCount 起点,从 GetParam 取 PrognozeLen,再用 CopyClose 把最近 PatternLen 根收盘塞进 CurPattern,不足就每秒重试。 核心计算交给 FindPrognoze,跑完把 Prognoze、Rating、PatternStart 及起止 ticks 写回全局变量,最后置 Answer 标记通知 EA 取数。OnCalculate 仅返回 0 做空壳,OnDeinit 留了接口处理退出。开 MT5 把这段贴进指标文件,配好 ThreadCalc.mqh,就能看它怎么替 EA 跑后台搜索。

MQL5 / C++
input class="type">class="kw">string DataSymbol; class=class="str">"cmt">// MM-EA 工作的货币对
input class="type">int    PatternLen; class=class="str">"cmt">// 分析序列的长度
input class="type">int    BarStart;   class=class="str">"cmt">// 从历史的哪个柱开始搜索类似序列
input class="type">int    BarCount;   class=class="str">"cmt">// 在历史中搜索多少个柱
class=class="str">"cmt">//--- 指标缓冲区
class="type">class="kw">double Buffer[];
class=class="str">"cmt">//---
class="type">class="kw">double CurPattern[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <ThreadCalc.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
   SetIndexBuffer(class="num">0,Buffer,INDICATOR_DATA);
   GlobalVarPrefix=VarPrefix;
   class=class="str">"cmt">// 无限循环 - 这样指标就会一直 "监听", 
   class=class="str">"cmt">// 为了来自EA的新命令
   class="kw">while(true)
     {
      class=class="str">"cmt">// 如果有结束命令, 结束指标的工作
      if(ParamExists(ThreadNum,"End"))
         class="kw">break;
      class=class="str">"cmt">// 等待信号以开始计算
      if(!ParamExists(ThreadNum,"Query"))
        {
         Sleep(class="num">100);
         class="kw">continue;
        }
      DelParam(ThreadNum,"Query");
      class="type">uint TicksStart=GetTickCount();
      class=class="str">"cmt">// 取得任务的参数
      class="type">int PrognozeLen=GetParam(ThreadNum,"PrognozeLen");
      class=class="str">"cmt">// 从最近的柱填充序列
      class="kw">while(CopyClose(DataSymbol,_Period,class="num">0,PatternLen,CurPattern)
            <PatternLen) Sleep(class="num">1000);
      class=class="str">"cmt">// 进行计算
      class="type">int HistPatternBarStart;
      class="type">class="kw">double Prognoze,Rating;
      FindPrognoze(DataSymbol,CurPattern,BarStart,BarCount,PrognozeLen,
                   Prognoze,Rating,HistPatternBarStart);
      class=class="str">"cmt">// 发送计算的结果
      SetParam(ThreadNum,"Prognoze",Prognoze);
      SetParam(ThreadNum,"Rating",Rating);
      SetParam(ThreadNum,"PatternStart",HistPatternBarStart);
      SetParam(ThreadNum,"TS",TicksStart);
      SetParam(ThreadNum,"TE",GetTickCount());
      class=class="str">"cmt">// 信号 "万事俱备"
      SetParam(ThreadNum,"Answer");
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">int begin,
                class="kw">const class="type">class="kw">double &price[])
  {
   class=class="str">"cmt">// 事件所需的处理函数
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多核占用与线程阻塞提示,把重复劳动交给小布,你专注决策。

常见问题

处理器不能自行拆分程序逻辑,若代码未显式分配线程,默认只占用一个核心,其余核心处于空闲状态。
MT5 提供自身的线程相关机制,并非直接套用某些语言的专用类,具体分配要在开发时标出可并行代码段。
不能,存在先后依赖的逻辑必须串行,强行并行会导致验证失败,这是多核加速的常见误区。
可以,小布盯盘对应品种页有线程与核心占用诊断,能提示计算是否卡在单线程,方便你回头改 EA。
观察相同计算量下耗时是否随核心分配明显下降,且结果数值与串行版本一致,避免并行引入竞态错误。