矩阵分解基础知识·进阶篇
🧮

矩阵分解基础知识·进阶篇

(2/3)· 跳过标量转换的笨办法,在代码里原生跑矩阵运算,处理多维数据更轻

进阶 第 2/3 篇
把矩阵硬拆成标量公式再写进 EA,是多数人的默认习惯。遇到旋转、缩放这类变换,标量写法反而更绕。本篇示范如何直接在 MQL5 里用矩阵原生表达,少写一半转换代码。

「画布上旋转的箭头怎么写出来」

想在 MT5 主图里用代码画一个能按角度旋转的箭头,核心是先建一块 CCanvas 画布,再用多边形顶点绕原点旋转后平移到目标坐标。下面这段是某自定义指标的开头骨架,indicator_plots 设成 0 说明它不输出常规 plot,纯靠画布绘图。 #include <Canvas\Canvas.mqh> CCanvas canvas; #define _ToRadians(A) (A * (M_PI / 180.0)) 上面三行把画布对象和角度转弧度宏备好,M_PI 是 MT5 内置常量,除以 180.0 把角度制转弧度制供 cos/sin 使用。 void Arrow(const int x, const int y, const ushort angle) { int ax[] = {0, 150, 100, 150}, ay[] = {0, -75, 0, 75}; int dx[ax.Size()], dy[ax.Size()]; for (int c = 0; c < (int)ax.Size(); c++) { dx[c] = (int)((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x; dy[c] = (int)((ax[c] * (-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y; } canvas.FillPolygon(dx, dy, ColorToARGB(clrBlue, 255)); canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255)); } Arrow 函数里 ax/ay 是箭头四个顶点相对原点的像素偏移,长 150、宽 150 的菱形箭簇。循环里用标准二维旋转矩阵把每个点绕原点转 angle 度,再平移到 (x,y),FillPolygon 填蓝色实心箭头,FillCircle 在轴心画半径 5 的红色圆点。 OnInit 里先用 ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) 和 CHART_HEIGHT_IN_PIXELS 抓主图像素宽高,存进 px、py,后续 canvas.Create 就要用这两个值定画布尺寸。外汇与贵金属图表叠加自绘图形存在重绘延迟风险,参数改动后建议在 EURUSD 的 M1 上实测刷新率。

MQL5 / C++
class="macro">#include <Canvas\Canvas.mqh>
CCanvas canvas;
class="macro">#define _ToRadians(A) (A * (M_PI / class="num">180.0))
class="type">void Arrow(const class="type">int x, const class="type">int y, const class="type">class="kw">ushort angle)
{
  class="type">int ax[] = {class="num">0, class="num">150, class="num">100, class="num">150},
      ay[] = {class="num">0, -class="num">75,   class="num">0,  class="num">75};
  class="type">int dx[ax.Size()], dy[ax.Size()];
  for (class="type">int c = class="num">0; c < (class="type">int)ax.Size(); c++)
  {
    dx[c] = (class="type">int)((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
    dy[c] = (class="type">int)((ax[c] * (-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
  }
  canvas.FillPolygon(dx, dy, ColorToARGB(clrBlue, class="num">255));
  canvas.FillCircle(x, y, class="num">5, ColorToARGB(clrRed, class="num">255));
}
class="type">int OnInit()
{   
  class="type">int px, py;
  px = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0);
  py = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0);

在报价窗口里画一支会转的箭头

这段自定义指标的核心不在算价格,而是把图形直接丢进主图。初始化时先用 CreateBitmapLabel 在坐标 (px, py) 建一块画布,像素格式走 ARGB_NORMALIZE,随后 Erase 填白底,整块画布尺寸由 px、py 决定。 Arrow(px/2, py/2, 60) 把箭头中心放在画布正中,旋转角 60 度。箭头本体是个四顶点多边形:ax 取 {0,150,100,150}、ay 取 {0,-75,0,75},经二维旋转矩阵换算后 FillPolygon 涂蓝,圆心再用 FillCircle 涂红半径 5,红点就是旋转轴心。 OnCalculate 直接 return rates_total,指标不参与序列计算,纯做显示;OnDeinit 里 canvas.Destroy() 释放资源,避免重加载时残留位图标签。外汇与贵金属图表加载此类指标属低风险显示层改动,但误写坐标可能让图形跑出可视区。 想验证就开 MT5 新建指标,把下面代码整段贴入,编译后拖上 EURUSD 小时图,应能看到一支蓝色箭头压在红点上,改 angle 参数即可实时变向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_plots class="num">0

class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Arrow(const class="type">int x, const class="type">int y, const class="type">class="kw">ushort angle)
{
   class="type">int ax[] = {class="num">0, class="num">150, class="num">100, class="num">150},
       ay[] = {class="num">0, -class="num">75,   class="num">0,   class="num">75};
   class="type">int dx[], dy[];

   ArrayResize(dx, ax.Size());
   ArrayResize(dy, ax.Size());

   for (class="type">int c = class="num">0; c < (class="type">int)ax.Size(); c++)
   {
      dx[c] = (class="type">int)((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
      dy[c] = (class="type">int)((ax[c] * (-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
   }

   canvas.FillPolygon(dx, dy, ColorToARGB(clrBlue, class="num">255));
   canvas.FillCircle(x, y, class="num">5, ColorToARGB(clrRed, class="num">255));

   ArrayFree(dx);
   ArrayFree(dy);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

canvas.CreateBitmapLabel("BL", class="num">0, class="num">0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
canvas.Erase(ColorToARGB(clrWhite, class="num">255));

Arrow(px / class="num">2, py / class="num">2, class="num">60);

canvas.Update(true);

class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
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="kw">return rates_total;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
   canvas.Destroy();
}
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用 Canvas 在图表上画一个会转向的箭头

想在 MT5 图表上脱离 ObjectCreate 那套老接口、直接像素级画图,CCanvas 是最干脆的路子。上面这段把箭头封装成了 Arrow() 函数,角度用 ushort 传、尺寸默认 100,调用时给个中心坐标就能落笔。 箭头顶点存在 ax/ay 两个数组里,原始比例是 {0,1.5,1,1.5} 和 {0,-0.75,0,0.75},乘完 size 再做旋转矩阵变换。_ToRadians 宏把角度转弧度,cos/sin 一套下来,dx/dy 就是旋转后的屏幕坐标。 OnInit 里先抓图表像素宽高:ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) 和 CHART_HEIGHT_IN_PIXELS 返回的就是当前窗口的 px/py。CreateBitmapLabel 建一块名为 BL 的画布,Erase 填白底,然后在正中心 (px/2, py/2) 画一个朝 60 度方向的绿箭头,红点压在圆心。 开 MT5 新建 EA 把这段贴进 OnInit,编译挂上图表就能看到正中浮着一个绿箭头加红点。把第 40 行 Angle 参数从 60 改成 0 或 120,箭头朝向立刻变,外汇和贵金属图表叠加这类标记时需注意视觉遮挡带来的误读风险。

MQL5 / C++
class="macro">#include <Canvas\Canvas.mqh>
CCanvas canvas;
class="macro">#define _ToRadians(A) (A * (M_PI / class="num">180.0))
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="type">class="kw">double ax[] = {class="num">0.0,  class="num">1.5, class="num">1.0, class="num">1.5},
          ay[] = {class="num">0.0, -.class="num">75, class="num">0.0, .class="num">75};
   class="type">int dx[ax.Size()], dy[ax.Size()];
   
   for (class="type">int c = class="num">0; c < (class="type">int)ax.Size(); c++)
   {
      ax[c] *= size;
      ay[c] *= size;
      dx[c] = (class="type">int) ((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
      dy[c] = (class="type">int) ((ax[c] *(-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
   }

   canvas.FillPolygon(dx, dy, ColorToARGB(clrGreen, class="num">255));
   canvas.FillCircle(x, y, class="num">5, ColorToARGB(clrRed, class="num">255));
}
class="type">int OnInit()
{   
   class="type">int px, py;
   
   px = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0);
   py = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0);

   canvas.CreateBitmapLabel("BL", class="num">0, class="num">0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
   canvas.Erase(ColorToARGB(clrWhite, class="num">255));
      
   Arrow(px / class="num">2, py / class="num">2, class="num">60);

   canvas.Update(true);
   
   class="kw">return INIT_SUCCEEDED;
}

「指标卸载时清理画布对象」

这段收尾代码处理的是指标生命周期的最后一步。OnCalculate 直接返回 rates_total,说明本例不依赖逐根重算逻辑,只是占位让 MT5 正常加载指标线程。 真正的重点在 OnDeinit:当指标从图表移除或品种切换时,canvas.Destroy() 会显式销毁之前用 Canvas 类创建的位图对象。若不主动销毁,MT5 可能在反复加载后残留图形资源,导致图表刷新异常。 外汇与贵金属市场波动剧烈、高风险突出,任何自定义指标都应保证卸载干净;开 MT5 把这段挂到自己的 Canvas 指标里,切换周期试几次就能验证资源是否泄漏。

MQL5 / C++
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="kw">return rates_total;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
   canvas.Destroy();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
把重复劳动交给小布
这些矩阵运算的诊断与模板生成,小布盯盘的 AIGC 已内置,打开对应品种页即可看到示例结构,你只需补入自己的参数。

常见问题

通常用 double 类型的动态数组嵌套,或一维数组配合下标换算;若维度固定也可用静态数组,编译期就能定大小。
倾向省掉 60% 以上的逐项乘法,尤其是连续做旋转加缩放时,矩阵连乘一次搞定,标量要展开几十行。
可以,描述你的变换维度与用途,AIGC 会给出可直接粘贴的矩阵声明与乘法函数,省去手敲下标。
主要是可读性错觉,标量展开更像常规公式;但处理批量数据或图形变换时,矩阵形式其实更贴近问题本身。