MQL5 中的矩阵和向量(基础篇)
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 预生成价格序列做离线验证前,先在策略测试器跑一遍上述代码确认内存行为,别把未初始化向量直接丢进实盘逻辑。
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 而非预估长度做循环上限,否则可能越界报错。
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 走的是另一种内积/矩阵乘逻辑,和逐元素运算不要混为一谈。
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 结果就能确认自己调的是哪一种。
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]] */