MQL5 中的矩阵和向量(基础篇)
📘

MQL5 中的矩阵和向量(基础篇)

第 1/3 篇

MQL5 里的矩阵与向量到底省了什么

MQL5 处理同类型有序集合时,传统做法是靠数组按索引访问元素。这类结构在线性代数、数学建模和机器学习任务里很常见,但用数组手写矩阵运算,往往要套多层嵌套循环,调试和排错成本偏高。

  • 年 3 月 MetaQuotes 在 MT5 环境引入专门的 matrix 和 vector 数据类型,让代码写法直接贴近数学符号。不用自己维护索引、不用写嵌套循环,就能做矩阵和向量的创建、初始化与运算。

对于做量化模型的交易者,这意味着在 MT5 里验证一个线性变换或特征计算,可以直接调内置对象,而不是从零搭数组框架。外汇和贵金属杠杆高、滑点随机,任何模型都只是概率优势,上线前务必在策略测试器跑历史数据。

「MT5 里的 vector 类型与初始化套路」

MQL5 的 vector 本质是一维 double 数组,支持加减乘以及求模(Norm),但和标准数学里的行/列向量不同,它没有行、列子类型之分——你写代码时得自己记清楚当前用的是哪种逻辑向量,否则矩阵类运算容易跑偏。 创建向量有四类静态方法:Ones(5) 生成 [1,1,1,1,1],Zeros(3) 得到 [0,0,0],Full(6,2.5) 填出六个 2.5,Init(7) 则只分配长度、元素值未定义(实测首次打印可能读到 [4,5,6,8,10,12,12] 这类内存残留)。 用 Resize 往尾部加元素时,新位置会用默认值补位:原 [1,2,3] 调 Resize(5) 后变成 [1,2,3,7,7]。赋值运算符 = 产生副本,改原向量 Fill(7) 后副本 v5 仍保持 [1,2,3,7,7],这点对回测里隔离状态很有用。 Init 还能顺带按函数填值,比如 v.Init(7,Arange,10) 模仿 numpy.arange,在 [0,10) 半开区间以步长 1 生成 10 个元素;若写成 v.Init(7,Arange,10,0,0.5) 则步长 0.5,最终 v.size=20,序列从 0 排到 9.5。Arange 函数自身必须以 vector& 作首参,Init 调用时不必传向量本体。 外汇与贵金属杠杆高、滑点随机,用 vector 预生成价格序列做离线验证前,先在策略测试器跑一遍上述代码确认内存行为,别把未初始化向量直接丢进实盘逻辑。

MQL5 / C++
class="type">void OnStart()
{
class=class="str">"cmt">//--- vector initialization examples
  vector v;
  v.Init(class="num">7);
  Print("v = ", v);
  vector v1=vector::Ones(class="num">5);
  Print("v1 = ", v1);
  vector v2=vector::Zeros(class="num">3);
  Print("v2 = ", v2);
  vector v3=vector::Full(class="num">6, class="num">2.5);
  Print("v3 = ", v3);
  vector v4{class="num">1, class="num">2, class="num">3};
  Print("v4 = ", v4);  
  v4.Resize(class="num">5);
  Print("after Resize(class="num">5) v4 = ", v4);  
  
  vector v5=v4;
  Print("v5 = ", v5);  
  v4.Fill(class="num">7);
  Print("v4 = ", v4, "  v5 =",v5);  
  
}
class=class="str">"cmt">/*
Execution result
v = [class="num">4,class="num">5,class="num">6,class="num">8,class="num">10,class="num">12,class="num">12]
v1 = [class="num">1,class="num">1,class="num">1,class="num">1,class="num">1]
v2 = [class="num">0,class="num">0,class="num">0]
v3 = [class="num">2.5,class="num">2.5,class="num">2.5,class="num">2.5,class="num">2.5,class="num">2.5]
v4 = [class="num">1,class="num">2,class="num">3]
after Resize(class="num">5) v4 = [class="num">1,class="num">2,class="num">3,class="num">7,class="num">7]
v5 = [class="num">1,class="num">2,class="num">3,class="num">7,class="num">7]
v4 = [class="num">7,class="num">7,class="num">7,class="num">7,class="num">7]  v5 =[class="num">1,class="num">2,class="num">3,class="num">7,class="num">7]
*/
class="type">void OnStart()
  {
class=class="str">"cmt">//---
  vector v;
  v.Init(class="num">7,Arange,class="num">10,class="num">0,class="num">0.5); class=class="str">"cmt">// class="num">3 parameters are passed with Arange call
  Print("v = ", v);
  Print("v.size = ",v.Size());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Values are generated within the half-open interval [start, stop)|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Arange(vector& v, class="type">class="kw">double stop, class="type">class="kw">double start = class="num">0, class="type">class="kw">double step = class="num">1) class=class="str">"cmt">// the function has class="num">4 parameters
  {
  if(start >= stop)
    {
      PrintFormat("%s wrong parameters! start=%G  stop=%G", __FILE__,start, stop);
      class="kw">return;
    }
class=class="str">"cmt">//---
  class="type">int size = (class="type">int)((stop - start) / step);
  v.Resize(size);
  class="type">class="kw">double value = start;
  for(class="type">class="kw">ulong i = class="num">0; i < v.Size(); i++)
    {
      v[i] = value;
      value += step;
    }
  }

class=class="str">"cmt">/*
Execution result
v = [class="num">0,class="num">0.5,class="num">1,class="num">1.5,class="num">2,class="num">2.5,class="num">3,class="num">3.5,class="num">4,class="num">4.5,class="num">5,class="num">5.5,class="num">6,class="num">6.5,class="num">7,class="num">7.5,class="num">8,class="num">8.5,class="num">9,class="num">9.5]
v.size = class="num">20
*/
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
  vector v;
  v.Init(class="num">7,Arange,class="num">10);
  Print("v = ", v);
  Print("v.size = ",v.Size());
  }

◍ 用 vector 存 0 到 9 并确认长度

MQL5 里 vector 是定长数值容器,初始化时直接写全元素就能拿到连续序列。上面这段把 0 到 9 十个整数塞进 v,size 返回 10,说明容器长度与字面量个数严格一致。 在 MT5 里跑这段,能验证 vector 的 size 不是按字节算,而是按元素个数计;后续若要做滑动窗口或序列偏移,这个计数方式直接影响下标边界。 外汇与贵金属行情高频更新,用 vector 缓存最近 N 根 K 线数值时,务必以 size 而非预估长度做循环上限,否则可能越界报错。

MQL5 / C++
v = [class="num">0,class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9]
v.size = class="num">10

向量上的标量四则和同维运算

在 MT5 的 vector 类型上,可以直接拿标量做加、减、乘、除,每个元素同步参与运算。比如向量 v={1,2,3,4,5} 加上 5,得到的是 [6,7,8,9,10];乘以 2.0 得到 [2,4,6,8,10],这种广播式写法在写指标平滑系数时很顺手。 两个长度相同的向量之间也支持逐元素加减乘除,不是矩阵乘法。实测 a={1,2,3} 与 b={2,4,6} 做 a*b 得到 [2,8,18],b/a 得到 [2,2,2],相当于把对应位置的值一对一算出来。 注意不赋值的情况下原向量不变:上面例子里先 Print 了 v+5、v*2.0 的结果,再 Print v 本身仍是 [1,2,3,4,5];只有写成 v=v+5 才会把变更存回 v。外汇和贵金属行情的高波动下,用向量批量算止损间距或 ATR 倍数时,分清「临时算」和「落盘改」能少踩坑。 Outer 这类方法则会返回矩阵,行数列数对应相乘向量的大小;Dot 和 MatMul 走的是另一种内积/矩阵乘逻辑,和逐元素运算不要混为一谈。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  vector2_article.mq5 |
class=class="str">"cmt">//|                                                     Copyright class="num">2021, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                                       [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2021, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
class=class="str">"cmt">//---
  vector v= {class="num">1, class="num">2, class="num">3, class="num">4, class="num">5};
  Print("Examples without saving vector changes");
  Print("v = ", v);
  Print("v+class="num">5 = ", v+class="num">5);
  Print("v-Pi= ", v-M_PI);
  Print("v*class="num">2.0= ", v*class="num">2);
  Print("v/class="num">3.0= ", v/class="num">3.0);
  Print("Save all vector changes");
  Print("v = ", v);
  Print("v+class="num">5 = ", v=v+class="num">5);
  Print("v-Pi= ", v=v-M_PI);
  Print("v*class="num">2.0= ", v= v*class="num">2);
  Print("v/class="num">3.0= ", v= v/class="num">3.0);
}
class=class="str">"cmt">/*
Execution result

Examples without saving vector changes
v = [class="num">1,class="num">2,class="num">3,class="num">4,class="num">5]
v+class="num">5 = [class="num">6,class="num">7,class="num">8,class="num">9,class="num">10]
v-Pi= [-class="num">2.141592653589793,-class="num">1.141592653589793,-class="num">0.1415926535897931,class="num">0.8584073464102069,class="num">1.858407346410207]
v*class="num">2.0= [class="num">2,class="num">4,class="num">6,class="num">8,class="num">10]
v/class="num">3.0= [class="num">0.3333333333333333,class="num">0.6666666666666666,class="num">1,class="num">1.333333333333333,class="num">1.666666666666667]
Save all vector changes
v = [class="num">1,class="num">2,class="num">3,class="num">4,class="num">5]
v+class="num">5 = [class="num">6,class="num">7,class="num">8,class="num">9,class="num">10]
v-Pi= [class="num">2.858407346410207,class="num">3.858407346410207,class="num">4.858407346410207,class="num">5.858407346410207,class="num">6.858407346410207]
v*class="num">2.0= [class="num">5.716814692820414,class="num">7.716814692820414,class="num">9.716814692820414,class="num">11.71681469282041,class="num">13.71681469282041]
v/class="num">3.0= [class="num">1.905604897606805,class="num">2.572271564273471,class="num">3.238938230940138,class="num">3.905604897606805,class="num">4.572271564273471]
*/
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   vector a = {class="num">1, class="num">2, class="num">3};
   vector b = {class="num">2, class="num">4, class="num">6};
   Print("a + b = ", a + b);
   Print("a - b = ", a - b);
   Print("a * b = ", a * b);
   Print("b / a = ", b / a);
  }
class=class="str">"cmt">/*
Execution result
a + b = [class="num">3,class="num">6,class="num">9]
a - b = [-class="num">1,-class="num">2,-class="num">3]
a * b = [class="num">2,class="num">8,class="num">18]
b / a = [class="num">2,class="num">2,class="num">2]
*/
class="type">void OnStart()
{
class=class="str">"cmt">//---
  vector a={class="num">1, class="num">2, class="num">3};
  vector b={class="num">4, class="num">5, class="num">6};
  Print("a = ", a);
  Print("b = ", b);

「向量运算在 MT5 里的四种输出差异」

把两个长度相同的向量 a=[1,2,3] 与 b=[4,5,6] 喂进 MQL5 的向量类方法,能直接看到四种代数运算的终端回显区别。Dot 与 MatMul 在这里都返回标量 32.0,因为对一维向量而言二者等价,都是对应位相乘再求和(1*4+2*5+3*6)。 Kron 走的是克罗内克积,输出被压成单行矩阵 [[4,5,6,8,10,12,12,15,18]],长度扩到 9 但降了维度;Outer 则生成标准的 3×3 矩阵,第 i 行第 j 列等于 a[i]*b[j],例如中间行是 [8,10,12]。 在写均线斜率向量或多资产权重矩阵时,选错方法会导致维度爆炸或标量丢失。开 MT5 新建脚本粘下面代码,改 a、b 长度跑一遍,比对 Print 结果就能确认自己调的是哪一种。

MQL5 / C++
  Print("class="num">1) a.Dot(b) = ", a.Dot(b));
  Print("class="num">2) a.MatMul(b) = ", a.MatMul(b));
  Print("class="num">3) a.Kron(b) = ", a.Kron(b));
  Print("class="num">4) a.Outer(b) = \n", a.Outer(b));
}
class=class="str">"cmt">/*
Execution result
a = [class="num">1,class="num">2,class="num">3]
b = [class="num">4,class="num">5,class="num">6]
class="num">1) a.Dot(b) = class="num">32.0
class="num">2) a.MatMul(b) = class="num">32.0
class="num">3) a.Kron(b) = [[class="num">4,class="num">5,class="num">6,class="num">8,class="num">10,class="num">12,class="num">12,class="num">15,class="num">18]]
class="num">4) a.Outer(b) =
[[class="num">4,class="num">5,class="num">6]
[class="num">8,class="num">10,class="num">12]
[class="num">12,class="num">15,class="num">18]]
*/

常见问题

主要省去手写循环做加减乘除和转置,内置类型直接支持同维运算和标量广播,代码更短且不易越界。
用 vector::Arange(10) 生成 0–9 向量,再调 v.Size() 看是否返回 10,即可确认长度正确。
可以,小布盯盘的 AIGC 已内置常见向量运算检查,打开对应品种页就能看到维度与运算异常提示,你专注决策。
这是输出函数差异导致的排版不同,数据本身一致;用 Print 和 Comment 会呈现四种不同格式,按需要选。
不用写循环,标量四则直接对 vector 类型生效,例如 v*2 会把每个元素翻倍,属同维广播运算。