神经网络实践:最小二乘法(基础篇)
「用最小二乘法给价格曲线找最优拟合」
在 MT5 里做机器学习入门,最小二乘法(OLS)是最容易落地的起点:它不依赖复杂框架,只用线性代数求残差平方和最小的系数,给一段价格序列配一条最优拟合直线。
实际加载 EURUSD 的 H1 收盘价,取最近 200 根_bar 做一元线性回归,斜率代表单位时间的加权价格漂移。若斜率为正且显著,价格倾向沿拟合线缓慢抬升;为负则倾向下沉——但这只是概率描述,外汇与贵金属杠杆高,信号失效时回撤可能远超样本内表现。
下面这段代码在 MT5 脚本里直接跑,能算出拟合直线与残差,你可以改 bars_count 看不同窗口下的斜率变化。
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 把下面两段代码分别跑一遍比对。
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 改成你自己的点位序列,就能在图表上直观看到相对中心的分布,外汇与贵金属品种请留意点差和滑点带来的坐标偏移风险。
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 换成自己的样本点,跑起来边按箭头边看总和,就能定位理想斜率。外汇和贵金属波动大,这类几何拟合只作辅助判势,实盘仍属高风险。
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));