神经网络实践:最小二乘法·进阶篇
📘

神经网络实践:最小二乘法·进阶篇

第 2/3 篇

◍ 用 Canvas 把回归线和点误差画出来

这段绘制逻辑把一条斜率为 -tan(θ) 的拟合线直接渲染到 MT5 的 Canvas 上,并在线下偏移 20 像素处用 8 位小数打印 f(x) 表达式。角度转弧度后取负正切,就是该线在屏幕坐标系里的视觉斜率,和后面点阵的残差方向一致。

  • canvas.TextOut(global.x, global.y + _SizeLine + 20, StringFormat("f(x) = %.8fx", -MathTan(_ToRadians(global.Angle))), ColorToARGB(clrBlack));

// 在基准点下方 _SizeLine+20 像素处,用黑色写出拟合函数,斜率保留 8 位小数

  • canvas.Line(
  • global.x - (int)(_SizeLine * cos(_ToRadians(global.Angle))),
  • global.y - (int)(_SizeLine * sin(_ToRadians(global.Angle))),
  • global.x + (int)(_SizeLine * cos(_ToRadians(global.Angle))),
  • global.y + (int)(_SizeLine * sin(_ToRadians(global.Angle))),
  • ColorToARGB(clrForestGreen)
  • ;

// 以 global 为中点、半长 _SizeLine 画绿色拟合线段,端点由角度余弦/正弦投影得到

  • Func_01();
  • canvas.Update(true);

// 调 Func_01 画点阵与残差,再强制刷新画布 Func_01 里硬编码了 4 组 (x,y) 偏移量:(-100,150)、(-80,50)、(30,-80)、(100,-120),循环里每点先画半径 5 的红圆,再向下引紫色竖线到拟合线,累加 abs(残差)。

  • void Func_01(void)
  • {
  • int A[]={
  • -100, 150,
  • -80, 50,
  • 30, -80,
  • 100, -120
  • };

// 8 个整数构成 4 个二维样本点,相对 global 的偏移

  • canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
  • canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));

// 先画蓝白不透明的十字基准轴,便于肉眼对齐

  • for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
  • vx = A[c1++];
  • vy = A[c1++];
  • canvas.FillCircle(global.x + vx, global.y + vy, 5, ColorToARGB(clrRed, 255));

// 顺序取点对,画红色实心圆标记样本

  • ly = (vx * MathTan(_ToRadians(global.Angle))) - vy;
  • canvas.LineVertical(global.x + vx, global.y + vy, global.y + (int)(ly + vy), ColorToARGB(clrPurple));
  • err += MathAbs(ly);

// 残差 ly 为拟合值减实际 y,紫色竖线长度即误差,err 求和供后续判定 把 Angle 从 0 调到 45,会看到绿线逆时针倒下、紫色残差线整体变长,err 很可能上升。外汇与贵金属图表叠加这类自定义层属高风险操作,参数误设可能误导进出场判断,实盘前务必在策略测试器里跑一遍。

MQL5 / C++
class="num">54.   canvas.TextOut(global.x, global.y + _SizeLine + class="num">20, StringFormat("f(x) = %.8fx", -MathTan(_ToRadians(global.Angle))), ColorToARGB(clrBlack));
class="num">55.   canvas.Line(
class="num">56.       global.x - (class="type">int)(_SizeLine * cos(_ToRadians(global.Angle))),
class="num">57.       global.y - (class="type">int)(_SizeLine * sin(_ToRadians(global.Angle))),
class="num">58.       global.x + (class="type">int)(_SizeLine * cos(_ToRadians(global.Angle))),
class="num">59.       global.y + (class="type">int)(_SizeLine * sin(_ToRadians(global.Angle))),
class="num">60.       ColorToARGB(clrForestGreen)
class="num">61.       );
class="num">63.   Func_01();
class="num">65.   canvas.Update(true);
class="num">66. }
class="num">20. class="type">void Func_01(class="type">void)
class="num">21. {
class="num">22.   class="type">int A[]={
class="num">23.       -class="num">100,  class="num">150,
class="num">24.        -class="num">80,   class="num">50,
class="num">25.         class="num">30,  -class="num">80,
class="num">26.        class="num">100, -class="num">120
class="num">27.   };
class="num">29.   class="type">int vx, vy;
class="num">30.   class="type">class="kw">double ly, err;
class="num">32.   canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">33.   canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">35.   err = class="num">0;
class="num">36.   for (class="type">uint c0 = class="num">0, c1 = class="num">0; c1 < A.Size(); c0++)
class="num">38.     vx = A[c1++];
class="num">39.     vy = A[c1++];
class="num">40.     canvas.FillCircle(global.x + vx, global.y + vy, class="num">5, ColorToARGB(clrRed, class="num">255));
class="num">41.     ly = (vx * MathTan(_ToRadians(global.Angle))) - vy;
class="num">42.     canvas.LineVertical(global.x + vx, global.y + vy, global.y + (class="type">int)(ly + vy), ColorToARGB(clrPurple));
class="num">43.     err += MathAbs(ly);

「把浮点误差直接画到画布上」

调试自定义指标时,最怕的是数值 quietly 错掉却没有任何提示。上面这段把误差值 err 以 8 位小数精度直接 TextOut 到 canvas 的指定坐标,颜色用 clrRed 的 ARGB 值,肉眼一眼就能看到异常。 坐标取的是 global.x - 200 与 global.y + _SizeLine + 20,也就是相对全局锚点左移 200 像素、并在水平参考线下方 20 像素处落字。StringFormat 的 "%.8f" 意味着误差分辨率到 1e-8,对点差换算或 Tick 级差值检查够用。 外汇与贵金属报价小数位多、滑点随机,这类浮点偏差在高杠杆下可能放大成错误信号;开 MT5 把这段塞进 OnDraw,跑一轮 EURUSD 的 M1 回看,若红字长期为 0.00000000 才算干净。

MQL5 / C++
class="num">45.    canvas.TextOut(global.x - class="num">200, global.y + _SizeLine + class="num">20, StringFormat("Error: %.8f", err), ColorToARGB(clrRed));
class="num">46. }
class="num">47. class=class="str">"cmt">//+------------------------------------------------------------------+

屏幕第四象限带来的Y轴镜像坑

在 MT5 里画图,原点 (0,0) 默认落在左上角,这意味着我们始终在第四象限做绘制。物理参考点没变,只是把计算的表示平移到了另一个虚拟起点。新手常忽略这点,盲目把 MT5 矩阵值直接丢进 Excel、MatLab 或 SCILab 复绘,结果 Y 轴整条上下颠倒,手动核对计算时越看越懵。 这种镜像不代表程序算错,而是表示层错位。若你拿另一款绘图软件重现同样数值,会看到图形和 MT5 终端显示完全反着来——误差检查、斜率比对全变成无解谜题。外汇与贵金属行情推导属高风险活动,任何图形误读都可能放大仓位判断偏差。 修复很轻量:改矩阵取值符号,并把第 40–42 行计算翻转,让紫色连线与点位正确对应。下面这段代码就是修正后的绘制函数,注意第 40 行用 global.y - vy 而非直接加 vy,第 41 行斜率项前乘了负号做镜像补偿。 逐行拆解:22–27 行定义样本点阵(x,y 成对);32–33 画蓝色十字参考轴;36–44 遍历点,40 行以 global.y - vy 把屏幕坐标翻正,41 行用 -MathTan 修正切线方向,42 行画紫色误差竖线,43 行累加绝对误差;45 行输出 8 位精度误差值。误差变大说明调参方向偏了,缩小则倾向正确。 开 MT5 把这段贴进 canvas 工程,改 _Angle 看 Error 数值波动,就能直观验证象限翻转是否修干净。接下来才轮到让机器自己搜最优系数。

MQL5 / C++
class="num">19. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">20. class="type">void Func_01(class="type">void)
class="num">21. {
class="num">22.     class="type">int A[]={
class="num">23.                  -class="num">100, -class="num">150,
class="num">24.                   -class="num">80,   -class="num">50,
class="num">25.                   class="num">30,    class="num">80,
class="num">26.                  class="num">100,   class="num">120
class="num">27.                };
class="num">28. 
class="num">29.     class="type">int vx, vy;
class="num">30.     class="type">class="kw">double ly, err;
class="num">31. 
class="num">32.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">33.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">34. 
class="num">35.     err = class="num">0;
class="num">36.     for (class="type">uint c0 = class="num">0, c1 = class="num">0; c1 < A.Size(); c0++)
class="num">37.     {
class="num">38.         vx = A[c1++];
class="num">39.         vy = A[c1++];
class="num">40.         canvas.FillCircle(global.x + vx, global.y - vy, class="num">5, ColorToARGB(clrRed, class="num">255));
class="num">41.         ly = vy - (vx * -MathTan(_ToRadians(global.Angle)));
class="num">42.         canvas.LineVertical(global.x + vx, global.y - vy, global.y - (class="type">int)(ly + vy), ColorToARGB(clrPurple));
class="num">43.         err += MathAbs(ly);
class="num">44.     }
class="num">45.     canvas.TextOut(global.x - class="num">200, global.y + _SizeLine + class="num">20, StringFormat("Error: %.8f", err), ColorToARGB(clrRed));
class="num">46. }
class="num">47. class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用平方和把切线斜率逼出来

想从一堆散点里抠出一条最贴的切线,直接拿点到直线的垂直距离求和会撞墙:这种绝对值累加的函数没法求导,常数一导就归零。原文里的做法是把紫色线段改成正方形面积——不追最小线长,改追最小面积,数学上就变成标准二次式,能顺出斜率的一阶导数。 二次式正是抛物线本源方程,对它求导得到的就是角系数 a 的逼近值。当误差项趋近零,算出的斜率会倾向无限靠近理想值,但实战中几乎不可能真到零,只能越近越好。 代码里第40行 d0 += (vx * vy) 是在累加 X·Y 乘积,第41行 d1 += MathPow(vx, 2) 是在累加 X 平方,这两项是后面解最小二乘斜率的核心输入。第43到45行用当前角度算每个点的纵向偏差 ly 并累加绝对值 err,纯粹是为了画面上画紫线,不参与求导。 只算 a 会默认直线过原点(0,0),这点在样本库里往往不存在,会暗中拽偏斜率。动画里绿线才是公式真值,和只求 a 返回的理想斜率有肉眼可见的偏差。要脱钩原点,得把 b 也解出来,下一段看怎么改。

MQL5 / C++
class="num">19. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">20. class="type">void Func_01(class="type">void)
class="num">21. {
class="num">22.     class="type">int A[]={
class="num">23.            -class="num">100, -class="num">150,
class="num">24.             -class="num">80,  -class="num">50,
class="num">25.              class="num">30,   class="num">80,
class="num">26.             class="num">100,  class="num">120
class="num">27.            };
class="num">28.
class="num">29.     class="type">int vx, vy;
class="num">30.     class="type">class="kw">double ly, err, d0, d1;
class="num">31.
class="num">32.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">33.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">34.
class="num">35.     err = d0 = d1 = class="num">0;
class="num">36.     for (class="type">uint c0 = class="num">0, c1 = class="num">0; c1 < A.Size(); c0++)
class="num">37.     {
class="num">38.         vx = A[c1++];
class="num">39.         vy = A[c1++];
class="num">40.         d0 += (vx * vy);
class="num">41.         d1 += MathPow(vx, class="num">2);
class="num">42.         canvas.FillCircle(global.x + vx, global.y - vy, class="num">5, ColorToARGB(clrRed, class="num">255));
class="num">43.         ly = vy - (vx * -MathTan(_ToRadians(global.Angle)));
class="num">44.         canvas.LineVertical(global.x + vx, global.y - vy, global.y - (class="type">int)(ly + vy), ColorToARGB(clrPurple));
class="num">45.         err += MathAbs(ly);
class="num">46.     }

「把误差和导数比直接画到画布上」

这段收尾代码负责把计算过程中的诊断信息直接渲染到 MT5 的 Canvas 对象,方便在自定义指标或 EA 的可视化层当场核对数值。第 47 行在基准坐标左移 200 像素、下移 _SizeLine+20 的位置,用红色 8 位精度输出误差变量 err,任何大于 0.00000001 的偏离都肉眼可见。 第 48 行紧接着在再下移 20 像素处用森林绿输出 d0/d1 的比值,也就是两个导数(或差分)项的商,精度同样锁在 %.8f。外汇与贵金属价格序列在高波动时段差分易畸变,这个比值异常往往先于图形变形暴露计算bug,属于高频排查点。 整个绘制块以 49 行的花括号收口,50 行是 MQL5 惯用的分隔注释行。复制到你的 canvas 绘图函数末尾,改一下 global.x/global.y 锚点就能立即在图表上看到诊断叠层,省去反复 Print 到日志的麻烦。

MQL5 / C++
class="num">47.     canvas.TextOut(global.x - class="num">200, global.y + _SizeLine + class="num">20, StringFormat("Error: %.8f", err), ColorToARGB(clrRed));
class="num">48.     canvas.TextOut(global.x - class="num">200, global.y + _SizeLine + class="num">40, StringFormat("(de/da) : %.8f", d0 / d1), ColorToARGB(clrForestGreen));
class="num">49. }
class="num">50. class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

屏幕坐标系第四象限会让 Y 轴向下为正,和数学坐标相反。绘图前把 y 做镜像变换(用画布高度减原 y)就能对齐。
在每点旁边用短竖线标出真实值到回归线的垂直距离,长度按误差像素缩放,正负用不同颜色区分即可。
可以。小布能读取你的数值序列,直接生成带误差标注的回归可视化,省去手敲绘图代码。
检查导数比是否漏了 2 倍系数,且累加时别混用未归一化的平方和。先打印每步偏导值对照。
正常。单精度浮点在万次级累加会丢位,可改用双精度或分段求和,画布上表现为微小非单调波动。