神经网络实践:最小二乘法(基础篇)
📘

神经网络实践:最小二乘法(基础篇)

第 1/3 篇

「用最小二乘法给价格曲线找最优拟合」

在 MT5 里做机器学习入门,最小二乘法(OLS)是最容易落地的起点:它不依赖复杂框架,只用线性代数求残差平方和最小的系数,给一段价格序列配一条最优拟合直线。 实际加载 EURUSD 的 H1 收盘价,取最近 200 根_bar 做一元线性回归,斜率代表单位时间的加权价格漂移。若斜率为正且显著,价格倾向沿拟合线缓慢抬升;为负则倾向下沉——但这只是概率描述,外汇与贵金属杠杆高,信号失效时回撤可能远超样本内表现。 下面这段代码在 MT5 脚本里直接跑,能算出拟合直线与残差,你可以改 bars_count 看不同窗口下的斜率变化。

MQL5 / C++
class="type">int bars_count = class="num">200;
class="type">class="kw">double x[], y[], coeff[class="num">2];
ArrayResize(x, bars_count);
ArrayResize(y, bars_count);
for(class="type">int i=class="num">0; i<bars_count; i++) {
   x[i] = i;
   y[i] = iClose("EURUSD", PERIOD_H1, bars_count - class="num">1 - i);
}
MathLinearRegression(x, y, coeff);
Print("slope=", coeff[class="num">1], " intercept=", coeff[class="num">0]);

从正切迷思到正割现实

多数交易者刚接触神经网络在 MT5 里的实践时,直觉会奔着正切函数去——毕竟 tan 在指标平滑里太常见。但上一篇文章已经点破:实际落地用的底层三角运算是正割(sec),不是正切。 这一转向一开始容易让人犯懵,因为教科书里的 sec 很少被拿来做实时计算。可一旦你打开 MetaEditor 把公式敲进去跑一遍,会发现它的推导链条比想象中干净,没有多余变换来搅局。 外汇与贵金属市场高杠杆、高波动,任何数学工具都只是概率辅助,正割函数本身不预示方向,只帮你把非线性关系摊平来看。

◍ 数组索引颠倒导致圆圈画反了

在 MT5 自定义画布里用循环批量画点,最容易踩的坑不是算错坐标,而是数组索引在自增时悄悄换了奇偶。原始代码第 33 行用 A[c1++] 连续取两次,本意是 (x偏移, y偏移) 成对取出,但实测绘制出来的小圆圈在 X、Y 方向上整体颠倒。 问题出在 global.x 加上的是奇数索引值,而 global.y 加上的却是偶数索引值——正好和数组声明顺序反了。数组 A 声明为 {-100,150, -80,50, 30,-80, 100,-120},前四个点理论偏移应是 (-100,150)、(-80,50),实际却读成了 (150,-100)、(50,-80) 这类镜像值。 为了看清过程,可把代码改成第 37 行那样,把取到的值赋给 vx、vy 再画,并在界面输出括号里的数值。你会发现画布上圆点的位置虽然修正了显示,但括号里打印的 vx、vy 相对数组声明仍然是颠倒的,这说明索引消费顺序和内存布局预期不一致。 把取数逻辑显式拆成先读 x 再读 y 并各自对齐奇偶位后,方括号内打印值才和数组声明完全一致。外汇与贵金属图表脚本涉及这类底层绘制时波动风险高,建议直接开 MT5 把下面两段代码分别跑一遍比对。

MQL5 / C++
class="type">void Func_01(class="type">void)
{
   class="type">int A[] {
         -class="num">100,  class="num">150,
          -class="num">80,   class="num">50,
           class="num">30,  -class="num">80,
          class="num">100, -class="num">120
          };

   canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, class="num">255));
   canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, class="num">255));

   for (class="type">uint c0 = class="num">0, c1 = class="num">0; c1 < A.Size(); c0++)
      canvas.FillCircle(global.x + A[c1++], global.y + A[c1++], class="num">5, ColorToARGB(clrRed, class="num">255));
}

class="type">void Func_01(class="type">void)
{
   class="type">int A[]={
         -class="num">100,  class="num">150,
          -class="num">80,   class="num">50,
           class="num">30,  -class="num">80,
          class="num">100, -class="num">120
          };

   class="type">int vx, vy;
   class="type">class="kw">string s = "";

   canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, class="num">255));
   canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, class="num">255));

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

「用坐标数组在画布上标点」

这段逻辑把一组写死的二维偏移量直接画到 MT5 的 Canvas 上,适合做坐标映射的底层验证。数组 A 里每两个元素是一对 (x, y) 偏移,共 4 对:(-100,150)、(-80,50)、(30,-80)、(100,-120),循环里 c1 每次自增两次取点。 画布先以 global 为中心拉出一条皇家蓝的十字参考线,线长由 _SizeLine 控制;随后对每个点用红色实心圆(半径 5)标出,并把坐标文本拼进字符串 s。 最后 TextOut 在中心点左移 200、下移 _SizeLine+70 的位置输出全部坐标对,黑色显示。把 A 改成你自己的点位序列,就能在图表上直观看到相对中心的分布,外汇与贵金属品种请留意点差和滑点带来的坐标偏移风险。

MQL5 / C++
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">string s = "";
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.    for (class="type">uint c0 = class="num">0, c1 = class="num">0; c1 < A.Size(); c0++)
class="num">36.    {
class="num">37.       vx = A[c1++];
class="num">38.       vy = A[c1++];
class="num">39.       canvas.FillCircle(global.x + vx, global.y + vy, class="num">5, ColorToARGB(clrRed, class="num">255));
class="num">40.       s += StringFormat("[ %d <> %d ] ", vx, vy);
class="num">41.    }
class="num">42.    canvas.TextOut(global.x - class="num">200, global.y + _SizeLine + class="num">70, s, ColorToARGB(clrBlack));
class="num">43. }

把斜率搜索压成一个求和值

前面用四条紫色竖线标出了各离散点到目标直线的距离,第 41 行用 ly = (vx * MathTan(_ToRadians(global.Angle))) - vy 算长度,第 43 行把每个 ly 拼成字符串打印。你会看到动画里出现负的线长,这阶段不必纠结,先弄清我们在手动调角度找「最陡但不让紫线变长」的斜率。 手动调只适合四个点。若数组里有成百上千个点,挨个看长度是否增长会累死人。办法是把所有紫色线长度相加,只盯这一个总和:一旦它开始上升,说明斜率已经越过最优点。 代码里把 A 数组写死成 4 对坐标(22–27 行),NewAngle 用 0.2 的 step 改角度(52 行)。你在 MT5 里把 A 换成自己的样本点,跑起来边按箭头边看总和,就能定位理想斜率。外汇和贵金属波动大,这类几何拟合只作辅助判势,实盘仍属高风险。

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">string s = "";
class="num">31.     class="type">class="kw">double ly;
class="num">32.
class="num">33.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">34.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, class="num">255));
class="num">35.
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 = (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.         s += StringFormat("sy%d = %.4f  | ", c0, ly);
class="num">44.     }
class="num">45.     canvas.TextOut(global.x - class="num">200, global.y + _SizeLine + class="num">50, s, ColorToARGB(clrBlack));
class="num">46. }
class="num">47. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">48. class="type">void NewAngle(class="kw">const class="type">char direct, class="kw">const class="type">class="kw">double step = class="num">0.2)
class="num">49. {
class="num">50.     canvas.Erase(ColorToARGB(clrWhite, class="num">255));
class="num">51.
class="num">52.     global.Angle = (MathAbs(global.Angle + (step * direct)) < class="num">90 ? global.Angle + (step * direct) : global.Angle);
class="num">53.     canvas.TextOut(global.x + _SizeLine + class="num">50, global.y, StringFormat("%.2f", MathAbs(global.Angle)), ColorToARGB(clrBlack));

常见问题

把拟合窗口调短(如20根)对比长窗口(如60根),看拐点出现位置差;窗口越短对近期价格反应越快,但噪声也越多,实盘建议先用30根试。
正切假设斜率连续平滑,真实价格常跳变,正割式分段拟合更贴近。把曲线按波段切段分别拟合,比硬套一条正切线更准。
可以。小布盯盘的AIGC分析能直接对打开的品种做拟合诊断,自动标出斜率反转和价格偏离区,你只需看结论不用手写数组。
检查坐标数组是从0还是末尾开始存点,把循环索引倒过来或reverse数组即可;改完在画布上重绘验证圆圈是否落在价格点上。
预存x、y的和与平方和,斜率=(nΣxy−ΣxΣy)/(nΣx²−(Σx)²),只算一次求和就能出斜率,不用每轮重扫全数组。