矩阵分解基础知识·进阶篇
(2/3)· 跳过标量转换的笨办法,在代码里原生跑矩阵运算,处理多维数据更轻
「画布上旋转的箭头怎么写出来」
想在 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 上实测刷新率。
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 参数即可实时变向。
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,箭头朝向立刻变,外汇和贵金属图表叠加这类标记时需注意视觉遮挡带来的误读风险。
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 指标里,切换周期试几次就能验证资源是否泄漏。
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">//+------------------------------------------------------------------+