矩阵分解基础知识·综合运用
(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 看箭头转起来。
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 行坐标,能直接看到箭头大小和位置变化;外汇与贵金属图表叠加此类自绘图形仍属高风险辅助,参数误用可能掩盖真实价格结构。
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 图,切换周期再切回来,若没报错且画布消失,说明销毁逻辑通了。外汇和贵金属波动剧烈,这类自定义指标仅作辅助参考,实盘前务必在模拟盘验证稳定性。
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 跑差异就行。