矩阵实用工具,扩展矩阵和向量的标准库功能(基础篇)
📘

矩阵实用工具,扩展矩阵和向量的标准库功能(基础篇)

第 1/3 篇

◍ 从标准库扩出矩阵实战工具

MT5 自带的矩阵和向量标准库只覆盖最基础的线性代数运算,真要做统计滤波或特征构造,往往要自己补一层工具函数。2023 年 5 月社区里就有作者基于标准库做了扩展封装,这类思路对做多品种协整或 PCA 降维的交易者直接有用。 外汇与贵金属杠杆高、跳空频繁,拿矩阵算相关系数时务必先剔除停牌与异常 tick,否则样本矩阵可能病态不可逆。 这类扩展通常不动底层,只是在 CMatrix 和 CVector 上包一层方法,比如按列标准化、滚动协方差。你可以在 MT5 里新建 include 把标准库 include 进来,照着自己策略补几个方法,比每次手写循环省事且不易错。

给标准库矩阵工具打补丁

MQL5 自带的矩阵标准库已经能覆盖初始化、转换、操纵等基础动作,但在实盘特征工程里经常不够用。比如要把 CSV 里的历史 tick 序列直接读成矩阵、把向量拆成 X/Y 训练集、或者做独热编码,标准库并不会替你写好,得在 Utils 类里扩展静态方法,免得每次都重新造轮子。 下面这些扩展点没有优先级之分,都是直接从工程里抠出来的:从 CSV 读矩阵、读编码值、写回 CSV;矩阵与向量互转、数组与向量互转;删单列/多列/单行、删向量单值;按训练测试比例拆矩阵、拆 X 和 Y;建设计矩阵、独热编码、从向量取类标签;生成随机向量、向量间追加与复制。 外汇和贵金属行情的高波动特性意味着,任何矩阵预处理错误都会直接放大到信号层,扩展这些方法时建议先在 MT5 策略测试器里用小样本 CSV 跑通再接实盘数据。

「把 CSV 塞进矩阵:自己写读取函数」

做信号回测或加载历史成交时,经常要把 CSV 里的数值直接灌进 MT5 的 matrix 对象。标准库里并没有现成的 Matrix.FromFile() 之类接口,作者因此自己写了 ReadCsv 方法,跳过首行字符串表头,只把数值部分落进矩阵。 函数假定 CSV 第一行全是列名(字符串),读取时先塞进 csv_header 数组方便后续对照字段;从第二行开始,每一格字符串转成 double 写入 mat_ 对应坐标。遇到行结束符就给矩阵扩一行,最后统一 Resize 去掉表头占的那一行。 实测中若文件被别的程序占用或路径不对,handle 会返回 INVALID_HANDLE,代码里用 GetLastError 区分「文件在使用中」还是其他错误。外汇与贵金属数据加载同样适用此思路,但行情跳空可能导致某行列数不齐,矩阵维度会报错,需自行校验。 下面这段是核心循环与函数壳,注意 FileReadString 按分隔符切字段,首行 rows==0 只存表头不写矩阵:

MQL5 / C++
matrix CMatrixutils::ReadCsv(class="type">class="kw">string file_name,class="type">class="kw">string delimiter=",")
  {
  matrix mat_ = {};
  class="type">int rows_total=class="num">0;
  class="type">int handle = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,delimiter);
  ResetLastError();
  if(handle == INVALID_HANDLE)
    {
      printf("Invalid %s handle Error %d ",file_name,GetLastError());
      Print(GetLastError()==class="num">0?" TIP | File Might be in use Somewhere else or in another Directory":"");
    }
  else
    {
      class="type">int column = class="num">0, rows=class="num">0;
      class="kw">while(!FileIsEnding(handle))
        {
         class="type">class="kw">string data = FileReadString(handle);
         class=class="str">"cmt">//---
         if(rows ==class="num">0)
           {
            ArrayResize(csv_header,column+class="num">1);
            csv_header[column] = data;
           }
         if(rows>class="num">0)  class=class="str">"cmt">//Avoid the first column which contains the column&class="macro">#x27;s header
           mat_[rows-class="num">1,column] = (class="type">class="kw">double(data));
         column++;
         class=class="str">"cmt">//---
         if(FileIsLineEnding(handle))
           {
            rows++;
            mat_.Resize(rows,column);
            column = class="num">0;
           }
        }
      rows_total = rows;
      FileClose(handle);
    }
  mat_.Resize(rows_total-class="num">1,mat_.Cols());
  class="kw">return(mat_);
  }

◍ 把多资产快照塞进矩阵再打印出来

做跨市场关联分析时,最怕一列列手工对齐。把 S&P500、50SMA、13RSI、NASDAQ 这类异构数据先写进一个 matrix,再用 ArrayPrint 把表头打出来,肉眼就能确认列序没乱。 下面这段在 EURUSD H1 图表上跑出的日志,第一行表头是 "S&P500" "50SMA" "13RSI" "NASDAQ",紧接着是 9 行矩阵数据。例如首行 S&P500=4173.8、50SMA=13386.6、13RSI=34.8、NASDAQ=13067.5;到第 9 行 50SMA 已爬到 13457.8,但 13RSI 始终在 31~37 区间晃,说明均线发散而动量没跟上。 这种打印方式的价值在于:你改了任意一列的计算逻辑,重跑一次就能从终端日志直接比对数值漂移,不用切图表。外汇和贵金属自带高杠杆高风险,这类跨市场矩阵只作共振参考,信号失效概率始终存在。

MQL5 / C++
  ArrayPrint(matrix_utils.csv_header);
  Print(Matrix);
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1) "S&P500" "50SMA"  "13RSI"  "NASDAQ"
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1) [[class="num">4173.8,class="num">13386.6,class="num">34.8,class="num">13067.5]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4179.2,class="num">13396.7,class="num">36.6,class="num">13094.8]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4182.7,class="num">13406.6,class="num">37.5,class="num">13108]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4185.8,class="num">13416.8,class="num">37.1,class="num">13104.3]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4180.8,class="num">13425.2,class="num">34.9,class="num">13082.2]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4174.6,class="num">13432.2,class="num">31.8,class="num">13052]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4174.9,class="num">13440.4,class="num">33.2,class="num">13082.2]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4170.8,class="num">13447.6,class="num">32.2,class="num">13070.6]
CS    class="num">0    class="num">06:class="num">48:class="num">21.228    matrix test(EURUSD,H1)  [class="num">4182.2,class="num">13457.8,class="num">32.5,class="num">13078.8]

把带字符串的 CSV 拆成可编码数组

做分类类策略时,常碰到含字符串的 CSV(比如天气类公开数据集)。MT5 的 matrix 只能装数值,装不了文本,所以得先把整份文件读进字符串数组,再逐列编码后灌回矩阵。 ReadCsvEncode() 的思路是先探明列数:打开文件一路 FileReadString 直到行尾,统计 csv_columns,顺手把表头存进 csv_header[]。实测一份 14 列的数据集,单列读完后 toArr[] 里索引 0–13 是第一列、14–27 是第二列,顺序和原文件一致,之后按段切分就能拿到每列原始值。 LabelEncoder() 按「相同特征贴同标签」的原则工作,核心在 Classes():它扫一遍数组提取去重后的类,写入引用的 classes_arr[],还有个向量版变体可用。下面这段是列读取的骨架代码,注意它每次迭代重开一次 CSV 只取第 i+1 列,且 rows>=1 跳过了表头行。 外汇与贵金属市场波动剧烈、杠杆风险高,这类编码仅用于本地特征工程,不代表任何方向判断。

MQL5 / C++
class=class="str">"cmt">//--- Obtaining the columns
  matrix Matrix={};
class=class="str">"cmt">//--- Loading the entire Matrix to an Array
  class="type">int csv_columns=class="num">0, rows_total=class="num">0;
  class="type">int handle = CSVOpen(file_name,delimiter);
  if (handle != INVALID_HANDLE)
    {
     class="kw">while (!FileIsEnding(handle))
       {
        class="type">class="kw">string data = FileReadString(handle);
        csv_columns++;
class=class="str">"cmt">//---
        if (FileIsLineEnding(handle)) class="kw">break;
       }
    }    
  FileClose(handle);  
  ArrayResize(csv_header,csv_columns); 
class=class="str">"cmt">//---
  class="type">class="kw">string toArr[];
   class="type">int counter=class="num">0;
   for (class="type">int i=class="num">0; i<csv_columns; i++)
     {                
      if ((handle = CSVOpen(file_name,delimiter)) != INVALID_HANDLE)
       {    
        class="type">int column = class="num">0, rows=class="num">0;
        class="kw">while (!FileIsEnding(handle))
          {
           class="type">class="kw">string data = FileReadString(handle);
           column++;
 class=class="str">"cmt">//---      
           if (column==i+class="num">1)
             {                 
              if (rows>=class="num">1) class=class="str">"cmt">//Avoid the first column which contains the column&class="macro">#x27;s header
                {   
                 counter++;                
                 ArrayResize(toArr,counter); class=class="str">"cmt">//array size for all the columns 
                 toArr[counter-class="num">1]=data;
                }
              else csv_header[column-class="num">1]  =  data;
             }
 class=class="str">"cmt">//---
           if (FileIsLineEnding(handle))

「把 CSV 读进矩阵后打印出来看一眼」

上面这段是文件解析收尾的逻辑:内层循环每填满一行就把 rows 加一、column 归零;跳出后把有效行数(rows-1)累加到 rows_total,最后用 FileClose(handle) 关掉句柄,避免 MT5 里文件描述符泄漏。 紧跟着的日志是 matrix test 在 US500 的 D1 周期上跑出来的真实输出。从 [0] 到 [48] 共 5 行打印,每行 12 个单元格,拼起来是一个 5×12 的分类矩阵:天气(Sunny/Overcast/Rain)、温度(Hot/Mild/Cool)、湿度(High/Normal)、风力(Weak/Strong)和是否play(Yes/No)都被原样读进了矩阵。 你在 MT5 终端里用专家日志看这几行,能直接核对 CSV 解析有没有错位——比如 [12] 那行开头是 Overcast、Rain、Hot,若你本地文件里对应位置不是这三个值,就说明分隔符或换行处理有 bug。外汇和贵金属行情用类似方法灌进矩阵时,高风险在于历史数据缺口会让行数统计偏少,回测信号可能因此失真。

MQL5 / C++
            {
               rows++;
               column = class="num">0;
            }
         }
         rows_total += rows-class="num">1;
      }
      FileClose(handle);
   }
class=class="str">"cmt">//---

常见问题

自己写轻量工具函数更稳,重点补上标准库缺的按列操作和类型转换,避免引入多余依赖。
写个独立读取函数按行切分、跳过表头,把数值列转成 double 塞进矩阵,字符串列单独存数组。
可以,小布能按你给的字段把多资产快照编码进矩阵并打印,同时高亮偏离均值较多的行。
先按逗号切每行,把非数值字段捞进字符串数组,剩余转 double 拼成矩阵,编码时留好映射关系。
打印前设好输出精度并格式化每行,按原始列宽对齐,重点看首尾几行确认没读错位。