矩阵分解基础知识·综合运用
🧮

矩阵分解基础知识·综合运用

(3/3)· 收尾篇带你把旋转、缩放与三维变换的矩阵直接落进代码,绕开易错的手工展开

偏理论 第 3/3 篇
很多交易者写指标时习惯把矩阵硬拆成标量公式,以为这样更直观。一旦涉及多维数据或三维变换,手工展开不仅冗长还容易写错符号。直接用矩阵表达,代码反而更短、更接近数学原意。

用矩阵乘法替代标量分解的旋转缩放

矩阵分解和标量分解最大的坑在于顺序:标量乘因子随便换位置结果不变,矩阵乘不行,左乘右乘出来是两码事。本小节只演示最小可用版本——用两个 2x2 矩阵相乘完成箭头的旋转与缩放,足够你跑通逻辑,但别直接当生产代码用。 核心计算在第 12 行 MatrixA_x_MatrixB:A 是 2x2 变换矩阵,B 是按列排的坐标点,结果写进 R。规则很硬——A 的列数必须等于 B 的行数,否则编译都过不了。展开就是 R11=(A11×B11)+(A12×B12),R12=(A21×B11)+(A22×B12),逐点算。 第 21 行 Arrow 函数里玩的就是这套:M_1 装旋转(cos/sin 角度),先 M_1×M_2 把图像转了;第 38~39 行清掉 M_1 再填缩放,第 40 行用新矩阵乘已旋转数据,Sx、Sy 都设成 size 就等于整体放大。外汇贵金属图表上叠加这类自定义图形属高风险辅助,信号仅供参照。 FillPolygon 读不懂矩阵结构,所以第 48 行用 for 循环把 R 拆回坐标点再画。下一篇会换更顺手的写法,这一版先让你在 MT5 里按 F5 看箭头转起来。

MQL5 / C++
class="num">01. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">02. class="macro">#class="kw">property copyright "Daniel Jose"
class="num">03. class="macro">#class="kw">property indicator_chart_window
class="num">04. class="macro">#class="kw">property indicator_plots class="num">0
class="num">05. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">06. class="macro">#include <Canvas\Canvas.mqh>
class="num">07. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">08. CCanvas canvas;
class="num">09. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">10. class="macro">#define _ToRadians(A) (A * (M_PI / class="num">180.0))
class="num">11. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">12. class="type">void MatrixA_x_MatrixB(const class="type">class="kw">double &A[][], const class="type">class="kw">double &B[][], class="type">class="kw">double &R[][], const class="type">int nDim)
class="num">13. {
class="num">14.    for (class="type">int c = class="num">0, size = (class="type">int)(B.Size() / nDim); c < size; c++)
class="num">15.    {
class="num">16.        R[c][class="num">0] = (A[class="num">0][class="num">0] * B[c][class="num">0]) + (A[class="num">0][class="num">1] * B[c][class="num">1]);
class="num">17.        R[c][class="num">1] = (A[class="num">1][class="num">0] * B[c][class="num">0]) + (A[class="num">1][class="num">1] * B[c][class="num">1]);
class="num">18.    }
class="num">19. }
class="num">20. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">21. class="type">void Arrow(const class="type">int x, const class="type">int y, const class="type">class="kw">ushort angle, const class="type">uchar size = class="num">100)
class="num">22. {
class="num">23.    class="type">class="kw">double M_1[class="num">2][class="num">2]{
class="num">24.                    cos(_ToRadians(angle)), sin(_ToRadians(angle)),

◍ 用矩阵乘法在MT5画布上摆一个箭头

这段逻辑核心是把一个箭头的局部坐标先做旋转、再做缩放,最后平移到画布中心。M_2 里存的是四个顶点相对锚点的偏移:横轴 0/1.5/1.0/1.5,纵轴 0/-0.75/0/0.75,构成一个朝右的箭头轮廓。 旋转靠 M_1 里的 sin/cos 组合,角度通过 _ToRadians 转弧度后填入 2x2 矩阵。第 37 行先乘出旋转后的 M_3,随后把 M_1 清零并只留对角线 size 值(第 39 行),等于施加一个均匀缩放,再乘一次得到 M_2 的最终屏幕坐标。 循环里把 M_2 的浮点结果加上基准 x、y 并强转 int,写进 dx/dy 数组。canvas.FillPolygon 用 clrPurple 填满箭头,FillCircle 在锚点画一个半径 5 的 clrRed 圆点——这是肉眼校验旋转中心是否正确的落点。 OnInit 里先取图表像素宽高(CHART_WIDTH_IN_PIXELS / CHART_HEIGHT_IN_PIXELS),CreateBitmapLabel 建一块同名 "BL" 的画布铺满窗口,Erase 成白底,再 Arrow(px/2, py/2, 160) 把 size=160 的箭头钉在屏幕正中。改 160 或第 62 行坐标,能直接看到箭头大小和位置变化;外汇与贵金属图表叠加此类自绘图形仍属高风险辅助,参数误用可能掩盖真实价格结构。

MQL5 / C++
class="num">25.                 -sin(_ToRadians(angle)), cos(_ToRadians(angle))
class="num">26.             },
class="num">27.     M_2[][class="num">2] {
class="num">28.              class="num">0.0,  class="num">0.0,
class="num">29.              class="num">1.5, -class="num">0.75,
class="num">30.              class="num">1.0,  class="num">0.0,
class="num">31.              class="num">1.5,  class="num">0.75
class="num">32.             },
class="num">33.     M_3[M_2.Size() / class="num">2][class="num">2];
class="num">34.
class="num">35.    class="type">int dx[M_2.Size() / class="num">2], dy[M_2.Size() / class="num">2];
class="num">36.    
class="num">37.    MatrixA_x_MatrixB(M_1, M_2, M_3, class="num">2);
class="num">38.    ZeroMemory(M_1);
class="num">39.    M_1[class="num">0][class="num">0] = M_1[class="num">1][class="num">1] = size;
class="num">40.    MatrixA_x_MatrixB(M_1, M_3, M_2, class="num">2);
class="num">41.
class="num">42.    for (class="type">int c = class="num">0; c < (class="type">int)M_2.Size() / class="num">2; c++)
class="num">43.    {
class="num">44.       dx[c] = x + (class="type">int) M_2[c][class="num">0];
class="num">45.       dy[c] = y + (class="type">int) M_2[c][class="num">1];
class="num">46.    }
class="num">47.
class="num">48.    canvas.FillPolygon(dx, dy, ColorToARGB(clrPurple, class="num">255));
class="num">49.    canvas.FillCircle(x, y, class="num">5, ColorToARGB(clrRed, class="num">255));
class="num">50. }
class="num">51. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">52. class="type">int OnInit()
class="num">53. {   
class="num">54.    class="type">int px, py;
class="num">55.    
class="num">56.    px = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0);
class="num">57.    py = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0);
class="num">58.
class="num">59.    canvas.CreateBitmapLabel("BL", class="num">0, class="num">0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
class="num">60.    canvas.Erase(ColorToARGB(clrWhite, class="num">255));
class="num">61.        
class="num">62.    Arrow(px / class="num">2, py / class="num">2, class="num">160);
class="num">63.
class="num">64.    canvas.Update(true);
class="num">65.    
class="num">66.    class="kw">return INIT_SUCCEEDED;
class="num">67. }

「指标生命周期里的两个收口函数」

写完绘图逻辑后,指标能不能干净退出、MT5 主图会不会留垃圾,全看 OnCalculate 和 OnDeinit 怎么写。 OnCalculate 这里直接 return rates_total,意思是每次新柱触发都按全部柱数重算,不去做增量优化。对轻量画布指标来说 CPU 占用可接受,但如果你后面接了复杂循环,这个写法会拖慢刷新。 [CODE]

  • //+------------------------------------------------------------------+
  • int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
  • {
  • return rates_total;
  • }
  • //+------------------------------------------------------------------+
  • void OnDeinit(const int reason)
  • {
  • canvas.Destroy();
  • }
  • //+------------------------------------------------------------------+

[/CODE] 逐行看:第 69 行是 MT5 指标必有的计算入口,四个参数由终端传入,price[] 是收盘价序列引用;第 71 行返回 rates_total 告诉终端已处理柱数;第 74 行 OnDeinit 在指标移除或品种切换时触发,reason 是退出原因码;第 76 行 canvas.Destroy() 释放 CCanvas 对象,不写这句,图表上可能残留上次画的线框。 开 MT5 新建指标,把这段原样贴进 OnInit 配套文件里,加载到 XAUUSD 的 M5 图,切换周期再切回来,若没报错且画布消失,说明销毁逻辑通了。外汇和贵金属波动剧烈,这类自定义指标仅作辅助参考,实盘前务必在模拟盘验证稳定性。

MQL5 / C++
class="num">68. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">69. class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[])
class="num">70. {
class="num">71.    class="kw">return rates_total;
class="num">72. }
class="num">73. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">74. class="type">void OnDeinit(const class="type">int reason)
class="num">75. {
class="num">76.    canvas.Destroy();
class="num">77. }
class="num">78. class=class="str">"cmt">//+------------------------------------------------------------------+

下一篇换个矩阵模型接着拆

这一节里作者把前面三篇用到的矩阵计算定位成「能讲清原理的最简实现」,但明说它并不是做各类因子分解的最优载体。也就是说,你现在在 MT5 里跑通的那套 App1~App3(分别 1.87KB / 1.97KB / 2.62KB)只是入门壳,碰到复杂数据分解会露怯。 作者放话很快会发新文,换一种更贴合矩阵表达的模型,并且给可直接用的数据分解代码。外汇和贵金属行情的高频噪声本就容易被简陋分解误判,等新模型出来,建议把旧 mq5 的信号逻辑对照着重写一遍。 东西就到这,等下一篇新模型挂出来,直接下 ZIP 跑差异就行。

把重复劳动交给小布
这些矩阵变换的诊断与可视化,小布盯盘的 AIGC 已内置,打开对应品种页即可看到旋转与缩放的实时分解,你专注决策就好。

常见问题

可用二维数组配合自定义乘加函数,或借助结构体封装行列操作;重点是把数学式原样映射,而非先转标量。
可以,把多品种报价视为多维向量,用矩阵做缩放归一,能快速生成横向可比视图,但外汇贵金属波动高风险,结果仅作参考。
旋转、剪切、透视可合成一个变换矩阵连续作用,标量形式需层层套公式,矩阵只需一次乘法,概率上更不易漏项。
能,小布内置的 AIGC 可解析品种页里的计算逻辑,标出可能维度不匹配的位置,省去你逐行比对。
标量写法对人眼更易读,矩阵直接进代码显得陌生;但处理批量数组时,矩阵版本倾向更简洁且维护成本低。