用牛顿多项式给行情做插值
◍ 用牛顿多项式给行情做插值
在 MT5 的 MQL5 向导里,牛顿多项式是一类可被直接调用的插值工具,适合把离散的 K 线采样点连成平滑曲线,用来观察价格局部的多项式走向。它和拉格朗日插值本质等价,但递推结构更省算力,新增节点时不用重算全部系数。
实际写 EA 或指标时,若你有 5 个历史收盘价样本,牛顿前向差分可以把拟合阶数控制在 4 阶以内,避免高阶振荡。外汇与贵金属杠杆高、跳空频繁,这类插值只反映已有样本的数学延伸,对未知行情仅具概率性参考,不等于方向判断。
下面这段演示了用差分表构造牛顿前向多项式的核心循环,开 MT5 新建脚本粘进去就能跑,改 n 和 x 数组即可验证不同样本下的曲线形态。
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 个系数。
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 级回测请留意卡顿风险。
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 四舍五入成整数强度。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| |</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>: Cnewton(); ~Cnewton(); <span class="keyword">class="type">void</span> Set(<span class="keyword">vector</span> &W, <span class="keyword">vector</span> &X, <span class="keyword">vector</span> &Y); <span class="keyword">class="type">void</span> Get(<span class="keyword">vector</span> &W, <span class="keyword">vector</span> &X, <span class="keyword">class="type">class="kw">double</span> &XX, <span class="keyword">class="type">class="kw">double</span> &YY); }; <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> <span class="keyword">class="type">class="kw">double</span> _yy = <span class="number">class="num">0.0</span>; __N.Get(_w, _xx, _yy); m_high.Refresh(-<span class="number">class="num">1</span>); m_low.Refresh(-<span class="number">class="num">1</span>); m_close.Refresh(-<span class="number">class="num">1</span>); <span class="keyword">class="type">int</span> _i = StartIndex(); <span class="keyword">class="type">class="kw">double</span> _h = m_high.GetData(m_high.MaxIndex(_i,m_length)); <span class="keyword">class="type">class="kw">double</span> _l = m_low.GetData(m_low.MinIndex(_i,m_length)); <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> <span class="keyword">if</span>(_yy > _c) { _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>)); } <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. |</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> &sl, <span class="keyword">class="type">class="kw">double</span> &tp) { <span class="comment">class=class="str">"cmt">//--- check</span> ... <span class="comment">class=class="str">"cmt">//---</span> m_high.Refresh(-<span class="number">class="num">1</span>); m_low.Refresh(-<span class="number">class="num">1</span>); <span class="keyword">vector</span> _x, _y; _x.Init(m_length); _y.Init(m_length); <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < m_length; i++) { _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 上的符号切换频率,比盲设固定手数更不容易在趋势折返时被洗。
_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 跟这条信号更合拍。 外汇和贵金属杠杆高、相关性乱,单靠这条线很容易翻车;把它当保险的一环,而不是万能钥匙,才贴近实战。