📘

用牛顿多项式给行情做插值

◍ 用牛顿多项式给行情做插值

在 MT5 的 MQL5 向导里,牛顿多项式是一类可被直接调用的插值工具,适合把离散的 K 线采样点连成平滑曲线,用来观察价格局部的多项式走向。它和拉格朗日插值本质等价,但递推结构更省算力,新增节点时不用重算全部系数。 实际写 EA 或指标时,若你有 5 个历史收盘价样本,牛顿前向差分可以把拟合阶数控制在 4 阶以内,避免高阶振荡。外汇与贵金属杠杆高、跳空频繁,这类插值只反映已有样本的数学延伸,对未知行情仅具概率性参考,不等于方向判断。 下面这段演示了用差分表构造牛顿前向多项式的核心循环,开 MT5 新建脚本粘进去就能跑,改 nx 数组即可验证不同样本下的曲线形态。

MQL5 / C++
class="type">class="kw">double NewtonForward(class="type">class="kw">double x[], class="type">class="kw">double y[], class="type">int n, class="type">class="kw">double x0, class="type">class="kw">double h) {
   class="type">class="kw">double diff[class="num">10][class="num">10];
   for(class="type">int i=class="num">0;i<n;i++) diff[i][class="num">0]=y[i];
   for(class="type">int j=class="num">1;j<n;j++)
      for(class="type">int i=class="num">0;i<n-j;i++)
         diff[i][j]=diff[i+class="num">1][j-class="num">1]-diff[i][j-class="num">1];
   class="type">class="kw">double u=(x0-x[class="num">0])/h, res=y[class="num">0], term=class="num">1;
   for(class="type">int j=class="num">1;j<n;j++) {
      term*= (u-j+class="num">1)/j;
      res+= term*diff[class="num">0][j];
   }
   class="kw">return res;
}

「用牛顿插值替你画出价格轨迹」

在外汇和贵金属这种高流动性市场里,传统滞后指标常让交易者错过进场窗口。多项式插值提供一种轻量替代:它用一条明确方程把过去观测和未来预测绑在一起,零存储需求,不像某些神经网络每次阶段训练后还要存大量权重和偏差。 牛顿插值多项式 N(x) 的核心是差商与基准多项式。差商是对唯一 x 值反复做除法,数据点数量决定 x 最高指数:2 个点得线性 y=mx+c,3 个点最高指数为 2。以 (1,2)、(3,4)、(5,6) 为例,第一系数 (4-2)/(3-1)=1,第二系数 (6-4)/(5-3)=1,第三系数经极值作分母除得 0,最终 y=2+1*(x-1)+0*(x-1)*(x-3)=x+1。 换一组真实报价看威力:取 7 个柱索引与 EURUSD 类价格——0:1.25590、1:1.26370、2:1.25890、3:1.25395、4:1.25785、5:1.26565、6:1.26175,展开 8 列差商表后方程升到 6 次:y=1.2559+0.0078*(x-0)-0.0063*(x-0)*(x-1)+…。若序列设为逆向,下一个索引是 -1;正向则是 8,代进去就可能估出下一根 K 线价格。 外汇与贵金属波动剧烈、杠杆风险高,插值只是数学拟合,对跳空和突发事件无免疫,预测倾向失效。开 MT5 用下方代码算差商,先拿 3 点试,再塞 7 点报价,感受指数膨胀速度。

用差商写出可伸缩的牛顿插值系数函数

在 MT5 里落地牛顿前向插值,核心只缺两件东西:一个按差商递推算系数的函数,一个拿系数和 x 去估 y 的函数。现有 MQL5 标准库里没现成类能直接跑这套思路,得自己写。 所谓“可伸展”,是指函数不绑定采样点数量。3 个点用的差商步骤和 4 个点不同,写死就废了;正确做法是喂进去 n 个样本,吐出 n-1 个系数。下面这段 Set 方法用两个嵌套 for 循环加两个整数跟踪 x、y 索引来实现,效率未必最优,但能跑通。 代码里 _x_scale 从 1 起逐轮加 1,_y_scale 从 X.Size()-1 起逐轮减 1,外层 i 走 n-1 次,内层 ii 走剩余可用对。遇到两个相同 X 值会直接 printf 报错并返回,避免除以零。W[0] 一般在外面单独赋 Y[0],W[i+1] 取每轮 _w[i][0] 作为第 i 阶差商。 别把正态当圣经:这段代码没做边界外的外推约束,拿去估样本区间外的贵金属价格时误差可能陡增,外汇与贵金属交易本身高风险,回测前先在小样本上手算核对前 3 个系数。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| INPUT PARAMETERS                                                |
class=class="str">"cmt">//|    X   -   vector with x values of sampled data                 |
class=class="str">"cmt">//|    Y   -   vector with y values of sampled data                 |
class=class="str">"cmt">//| OUTPUT PARAMETERS                                               |
class=class="str">"cmt">//|    W   -   vector with coefficients.                            |                
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Cnewton::Set(vector &W, vector &X, vector &Y)
{  vector _w[];
   ArrayResize(_w, class="type">int(X.Size() - class="num">1));
   class="type">int _x_scale = class="num">1;
   class="type">int _y_scale = class="type">int(X.Size() - class="num">1);
   for(class="type">int i = class="num">0; i < class="type">int(X.Size() - class="num">1); i++)
   {  _w[i].Init(_y_scale);
      for(class="type">int ii = class="num">0; ii < _y_scale; ii++)
      {  if(X[ii + _x_scale] != X[ii])
         {  if(i == class="num">0)
            {  _w[i][ii] = (Y[ii + class="num">1] - Y[ii]) / (X[ii + _x_scale] - X[ii]);
            }
            else if(i > class="num">0)
            {  _w[i][ii] = (_w[i - class="num">1][ii + class="num">1] - _w[i - class="num">1][ii]) / (X[ii + _x_scale] - X[ii]);
            }
         }
         else
         {  printf(__FUNCSIG__ + " ERR!, identical X value: " + DoubleToString(X[ii + _x_scale]) + ", at: " + IntegerToString(ii + _x_scale) + ", and: " + IntegerToString(ii));
            class="kw">return;
         }
      }
      _x_scale++;
      _y_scale--;
      W[i + class="num">1] = _w[i][class="num">0];
      if(_y_scale <= class="num">0)
      {  break;
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 牛顿插值里 W X XX 到底怎么喂

做价格行为拟合时,经常要用已知采样点外推某个未知 x 对应的 y。这段 Cnewton::Get 就是干这事的核心:把预先算好的系数向量 W、采样横坐标 X、待查横坐标 XX 传进去,YY 带回插值结果。 W[0] 直接作为基准值赋给 YY,之后从 i=1 开始循环,每一步用 W[i] 乘上 (XX - X[0])…(XX - X[i-1]) 的连乘项,再累加进 YY。也就是说,第 i 阶项依赖前面所有采样点的横坐标差,阶数越高计算量线性叠加。 在 MT5 里实跑时,若 W.Size() 是 10,那最内层循环最多跑 9 次,总乘算约 45 次——样本点从 5 个加到 20 个,CPU 占用可能翻 16 倍,外汇和贵金属 tick 级回测请留意卡顿风险。

MQL5 / C++
class="type">void Cnewton::Get(vector &W, vector &X, class="type">class="kw">double &XX, class="type">class="kw">double &YY)
{  YY = W[class="num">0];
   for(class="type">int i = class="num">1; i < class="type">int(W.Size()); i++)
   {  class="type">class="kw">double _y = W[i];
      for(class="type">int ii = class="num">0; ii < i; ii++)
      {  _y *= (XX - X[ii]);
      }
      YY += _y;
   }
}

「牛顿多项式在信号、止损与仓位上的三种落地」

把牛顿插值类接进 MT5 的 EA 框架,最实用的三处切口是信号生成、尾随停止和资金管理。核心都依赖同一个捆绑类:用 x 向量装柱线索引、y 向量装目标序列,调 Set 拿到系数,再喂下一个 x 给 Get 预测下一个 y。 信号侧直接用收盘价填 y。预测出下一根收盘价后,必须把涨跌映射成 0–100 的整数强度:把预测变化除以当前(最高-最低)区间,负值在多头函数里自动归零,正值在空头函数里归零。唯一可调的是回溯长度 m_length,在 EURJPY、H1、2023 全年优化这个参数,净值曲线全年跑下来有可见但普通的回报。 尾随停止改用柱线区间填 y。预测出下一根区间后,取一部分乘可优化参数 m_stop_level 当作止损偏移,再加最小停止距离避開经纪商报错。同样品种周期下,仅优化多项式长度配动量震荡信号,结果乏善可陈;换移动平均尾随因多优化了参数反而“更好”,说明配对信号差异很大。 资金管理把区间变化当波动指标:预测区间扩大就按比例缩固定保证金仓位,否则不动。仅优化回溯周期、不挂尾随、用动量信号,对比内置“规模优化”类(按亏损递减因子缩仓),多项式方案在历史回测里明显占优。外汇与贵金属杠杆高,这类回测优势不代表实盘概率,开 MT5 自测前先控仓位。 下面这段是信号强度归一化的关键代码,逐行看:_xx 设为 -1 即 m_length+1 的下一个索引;调 Get 拿预测价 _yy;刷新高低收缓存;取回溯内最高 _h、最低 _l、现价 _c;若预测价高于现价,把 (预测-现价)/真实区间 乘 100 四舍五入成整数强度。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> Cnewton
{
<span class="keyword">class="kw">private</span>:
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cnewton();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;~Cnewton();
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Set(<span class="keyword">vector</span> &amp;W, <span class="keyword">vector</span> &amp;X, <span class="keyword">vector</span> &amp;Y);
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Get(<span class="keyword">vector</span> &amp;W, <span class="keyword">vector</span> &amp;X, <span class="keyword">class="type">class="kw">double</span> &amp;XX, <span class="keyword">class="type">class="kw">double</span> &amp;YY);
};
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> _xx = -<span class="number">class="num">1.0</span>;<span class="comment">class=class="str">"cmt">//m_length + class="num">1.0,</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> _yy = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp; __N.Get(_w, _xx, _yy);
&nbsp;&nbsp; m_high.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; m_low.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; m_close.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">class="type">int</span> _i = StartIndex();
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> _h = m_high.GetData(m_high.MaxIndex(_i,m_length));
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> _l = m_low.GetData(m_low.MinIndex(_i,m_length));
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> _c = m_close.GetData(<span class="number">class="num">0</span>);
<span class="comment">class=class="str">"cmt">//</span>
&nbsp;&nbsp; <span class="keyword">if</span>(_yy &gt; _c)
&nbsp;&nbsp; {&nbsp;&nbsp;_result = <span class="keyword">class="type">int</span>(<span class="functions">round</span>(((_yy - _c) / (<span class="functions">fmax</span>(_h, <span class="functions">fmax</span>(_yy, _c)) - <span class="functions">fmin</span>(<span class="functions">fmin</span>(_yy, _c), _l))) * <span class="number">class="num">100.0</span>));
&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Checking trailing stop and/or profit for class="type">long position.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">bool</span> CTrailingNP::CheckTrailingStopLong(CPositionInfo *position, <span class="keyword">class="type">class="kw">double</span> &amp;sl, <span class="keyword">class="type">class="kw">double</span> &amp;tp)
{
<span class="comment">class=class="str">"cmt">//--- check</span>
&nbsp;&nbsp; ...
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; m_high.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; m_low.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">vector</span> _x, _y;
&nbsp;&nbsp; _x.Init(m_length);
&nbsp;&nbsp; _y.Init(m_length);
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; m_length; i++)
&nbsp;&nbsp; {&nbsp;&nbsp;_x[i] = i;

用波动衰减斜率压仓位的实现细节

这段逻辑把近期高低点振幅的差分送进一个线性拟合(__N 是带权最小二乘的封装),取 x=-1 处的预测值 _yy 作为「波动还在放大还是收敛」的代理变量。若 _yy>0.0,说明按当前样本外推,下一根 K 线的振幅差倾向继续走阔,此时反而减仓。 减仓公式里 _range 由「当前真实波幅 + 衰减步长(m_decrease_factor*Point) + _yy」构成,lot 被 NormalizeDouble 到 2 位小数:lot*(1.0-(_yy/_range))。也就是说 _yy 占 _range 的比重越大,开仓手数压得越狠,最大可降到接近 0 手(受经纪商下限约束)。 多头 trailing 那段另算:delta = m_stop_level*_yy + Point*StopsLevel(),只有 price-base > delta 才把 sl 设为 price-delta,否则 sl 保持 EMPTY_VALUE 不移动。外汇与贵金属杠杆高,这类基于波动的止损距离可能瞬间放大数倍点值,实盘前务必在 MT5 策略测试器用真实点差跑一遍。 直接把上面 CMoneySizeOptimized::Optimize 的循环抄进 EA,把 m_length 设成 20、m_decrease_factor 从 0.1 起步调,观察 __N.Get 返回的 _yy 在 EURUSD 15M 上的符号切换频率,比盲设固定手数更不容易在趋势折返时被洗。

MQL5 / C++
  _y[i] = (m_high.GetData(StartIndex()+i)-m_low.GetData(StartIndex()+i));
  }
  vector _w;
  _w.Init(m_length);
  _w[class="num">0] = _y[class="num">0];
  __N.Set(_w, _x, _y);
  class="type">class="kw">double _xx = -class="num">1.0;
  class="type">class="kw">double _yy = class="num">0.0;
  __N.Get(_w, _x, _xx, _yy);
class=class="str">"cmt">//---
  ...
class=class="str">"cmt">//---
  class="kw">return(sl != EMPTY_VALUE);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checking trailing stop and/or profit for class="type">long position.            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingNP::CheckTrailingStopLong(CPositionInfo *position, class="type">class="kw">double &sl, class="type">class="kw">double &tp)
{
class=class="str">"cmt">//--- check
  
  ...
class=class="str">"cmt">//---
  sl = EMPTY_VALUE;
  tp = EMPTY_VALUE;
  delta = (m_stop_level * _yy) + (m_symbol.Point() * m_symbol.StopsLevel());
class=class="str">"cmt">//---
  if(price - base > delta)
  {  sl = price - delta;
  }
class=class="str">"cmt">//---
  class="kw">return(sl != EMPTY_VALUE);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Optimizing lot size for open.                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CMoneySizeOptimized::Optimize(class="type">class="kw">double lots)
{  class="type">class="kw">double lot = lots;
class=class="str">"cmt">//--- class="num">0 factor means no optimization
  if(m_decrease_factor > class="num">0)
  {  m_high.Refresh(-class="num">1);
     m_low.Refresh(-class="num">1);
     vector _x, _y;
     _x.Init(m_length);
     _y.Init(m_length);
     for(class="type">int i = class="num">0; i < m_length; i++)
     {  _x[i] = i;
        _y[i] = (m_high.GetData(StartIndex() + i) - m_low.GetData(StartIndex() + i)) - (m_high.GetData(StartIndex() + i + class="num">1) - m_low.GetData(StartIndex() + i + class="num">1));
     }
     vector _w;
     _w.Init(m_length);
     _w[class="num">0] = _y[class="num">0];
     __N.Set(_w, _x, _y);
     class="type">class="kw">double _xx = -class="num">1.0;
     class="type">class="kw">double _yy = class="num">0.0;
     __N.Get(_w, _x, _xx, _yy);
     class=class="str">"cmt">//---
     if(_yy > class="num">0.0)
     {  class="type">class="kw">double _range = (m_high.GetData(StartIndex()) - m_low.GetData(StartIndex()));
        _range += (m_decrease_factor*m_symbol.Point());
        _range += _yy;
        lot = NormalizeDouble(lot*(class="num">1.0-(_yy/_range)), class="num">2);
     }
  }
class=class="str">"cmt">//--- normalize and check limits
...
class=class="str">"cmt">//---
  class="kw">return(lot);
}

◍ 把这条线请下神坛

牛顿多项式在样本内能把几个数据点拟合成曲线,但测试报告已经暴露短板:它抓不住白噪声,本质是过度拟合了采样里的每一次摆动,而不是底层形态。样本外表现拉胯,多半由此而来。 金融序列的统计属性一直在漂移,均值方差不老实,急涨急跌反而是常态;平滑多项式天生难描这种非线性。更别说它接不进情绪和基本面数据。 MQL5 向导可以把多个信号塞进一个 EA,所以拿牛顿信号配一个甚至几个过滤器,才有概率改善出场质量。尾随和资金管理模块不支持这种配对,只能自己多跑几轮测试,看哪类 trailing 和 money 跟这条信号更合拍。 外汇和贵金属杠杆高、相关性乱,单靠这条线很容易翻车;把它当保险的一环,而不是万能钥匙,才贴近实战。

常见问题

插值只还原已知样本点间的走势,对未发生的跳空无预测力;建议用近期 20~50 根 K 线做局部插值,避免长周期失真。
用递推差商表,每来一个新样本只算最后一列并缓存历史阶差商,可在 O(n) 内扩展,不必从头构建。
可以,小布已内置插值轨迹与波动衰减诊断,打开对应品种页即可看到拟合曲线和仓位参考,不用手搓函数。
按斜率绝对值映射仓位系数,贵金属波动大建议上限取 0.5 倍基准,外汇可到 0.8;均属高风险,需先回测。
插值止损来自拟合轨迹切线而非成交密集区,适合趋势市;震荡市应叠加结构位,别单独信一条线。