股票交易中的非线性回归模型(基础篇)
📘

股票交易中的非线性回归模型(基础篇)

第 1/3 篇

◍ 在 MT5 里给价格跑一条非线性回归线

MT5 自带的标准回归工具多是线性的,面对带曲率的外汇或贵金属走势容易滞后。非线性回归把价格当作因变量、把 bar 索引当作自变量,用多项式拟合,能更贴着波段拐弯。 下面这段代码在 MT5 主图画一条二阶多项式拟合曲线,样本取最近 100 根 K 线。外汇与贵金属杠杆高、跳空频繁,拟合线只描述已发生结构,不代表后续必沿该曲率运行,拐头概率需结合量价确认。 把 Period 从 100 改成 30,曲线会更敏感但噪声变大;改成 200 则更平滑却容易漏掉短线机会。开 MT5 新建指标脚本粘入即可验证。

MQL5 / C++
class="type">int Period = class="num">100;
class="type">class="kw">double x[class="num">100], y[class="num">100], coeff[class="num">3];
for(class="type">int i=class="num">0;i<Period;i++){
   x[i] = i;
   y[i] = iClose(_Symbol,_Period,i);
}
class=class="str">"cmt">// 二阶拟合: y = a + b*x + c*x^class="num">2
PolyFit(x,y,Period,class="num">2,coeff);
for(class="type">int i=class="num">0;i<Period;i++){
   class="type">class="kw">double fit = coeff[class="num">0]+coeff[class="num">1]*x[i]+coeff[class="num">2]*x[i]*x[i];
   PlotIndexSetDouble(class="num">0,i,fit);
}

「从线性指标到分形现实的挫败」

盯着回归系统的回测报告看了一整晚,窗外湿雪,咖啡凉透,还是绕不开那个问题:用 RSI、Stochastic、MACD 这类上世纪七十年代的计算器逻辑,去拟合今天这个充满非线性冲击的市场,本身就很荒谬。 过去三年我把能试的都试了,从最朴素的线性回归到卷积网络。分类任务勉强出了点东西,回归任务始终没跑通。训练集上干干净净,实盘一上就亏。第一个卷积网络训练集 R² 标到 1.00%,结果实盘两周账户回撤 30%,典型过拟合。 翻文献才撞见 Mandelbrot 早就讲过的市场分形属性——我们却一直拿线性尺子去量海岸线,量得越细越长。后来把经典技术分析和微分方程、自适应比率这类非线性动力学绑在一起,用一台普通 Acer 笔记本加 VPS 接 MT5 跑实验,模型就这么来的。 这不是圣杯,外汇和贵金属市场高波动、黑天鹅频发,不存在稳赢结构。它只是够聪明、能干活,又不至于复杂到遇一次极端行情就崩。

把欧元美元的价格呼吸写进方程

2022年起断断续续打磨的这套价格模型,最初是想把欧元兑美元图表里「平稳流动—剧烈波动—节奏交替」的现象用数学抓住。作者跑图时觉得市场像在呼吸,H1周期上波动期与平静期交替尤其像正弦波,于是把线性、二次、记忆、动量、周期、基准几项拼进同一个方程。 方程前两项负责当前走势:线性项对应价格平稳移动,二次项类似汽车加速,市场一旦提速就由它接手。三四项翻旧账,x_t2及其平方当作市场的「记忆与惯性」,思路接近道氏理论里价格水平会被记住的说法。 动量项只取 x_t1 减 x_t2,简单却在趋势狂热时成为主力驱动;正弦项本是偶然加入,却让模型在H1上咬住了起伏节奏;最后的常数项是一张安全网,压住预测不让它偏离现实太远。作者试过指数、对数等复杂函数,反而把模型搞脏,最后退回简单实用。 回测观察里几个系数区间值得记:线性项 coeffs[0] 常落在 0.3–0.4(对最近变动的即时反应),coeffs[1] 常近 0.7(稍旧价格影响更大);二次项在平静市仅 0.01–0.02,欧元美元日线趋势增强时主导权上升;动量项平静期 0.2–0.3,突破前常跳到 0.5,可当行情启动的旁证。外汇与贵金属波动剧烈,系数区间仅为历史样本现象,实盘仍属高风险。 系数选定交给 Nelder-Mead 优化,那是下一段的坑。下面直接看方程骨架,每行都已标了它在模型里的角色。

MQL5 / C++
<span class="keyword">def</span> equation(self, x_prev, coeffs):
&nbsp;&nbsp;&nbsp;&nbsp;x_t1, x_t2 = x_prev[<span class="number">class="num">0</span>], x_prev[<span class="number">class="num">1</span>]
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> (coeffs[<span class="number">class="num">0</span>] * x_t1 +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># trend</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;coeffs[<span class="number">class="num">1</span>] * x_t1**<span class="number">class="num">2</span> +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"># acceleration</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;coeffs[<span class="number">class="num">2</span>] * x_t2 +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># market memory</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;coeffs[<span class="number">class="num">3</span>] * x_t2**<span class="number">class="num">2</span> +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"># inertia</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;coeffs[<span class="number">class="num">4</span>] * (x_t1 - x_t2) + <span class="comment"># impulse</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;coeffs[<span class="number">class="num">5</span>] * np.sin(x_t1) +&nbsp;&nbsp;<span class="comment"># market rhythm</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;coeffs[<span class="number">class="num">6</span>])&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"># basic level</span>

◍ 常数项与七比率的锚定逻辑

常数项在模型里远不只是凑平差的摆设。高波动段它把预测值拽回实际价格附近,避免输出发散;低波动段则负责把整体价位估得更准。实盘观察里,常数项数值常跟着趋势强度走——趋势越猛,它越贴近 0。 我试过往方程里硬塞新项、换复杂函数,结果回测表现反而塌了。市场像在提醒:别叠不必要的复杂度。现在这版只留七个比率,不多不少,各自在预测链里有固定工位。 七比率的权重不是手拍的,靠 Nelder-Mead 单纯形法在样本上搜最优。看它迭代寻优的过程,会联想到混沌系统里初值敏感的那套味儿。这部分留到下节拆。

「无导数探测怎样驯服价格模型」

梯度下降和遗传算法在汇率模型调参上先后翻车。梯度下降跑一周只换来参数飞向无穷或卡在局部极小;遗传算法整夜跑完一周历史,结果飘得像读茶渣。后来换上 1965 年的 Nelder-Mead 单纯形法,不靠导数,只靠在解空间里巧妙探点,反而和市场跳起了舞,平稳滑向最优。 损失函数最后退回最朴素的均方误差(MSE)。开发者常犯的错是有效了就拼命加惩罚项、塞进 MAPE 等花活,直到彻底跑不动;斧头一样的 MSE 反而够用。下面这段 Python 风格的实现把每轮 MSE、R² 和系数存进进度列表,方便肉眼看收敛。

MQL5 / C++
def loss_function(self, coeffs, X_train, y_train):
    y_pred = np.array([self.equation(x, coeffs) for x in X_train])
    mse = np.mean((y_pred - y_train)**class="num">2)
    r2 = r2_score(y_train, y_pred)
    # Save progress for analysis
    self.optimization_progress.append({
        &class="macro">#x27;mse&class="macro">#x27;: mse,
        &class="macro">#x27;r2&class="macro">#x27;: r2,
        &class="macro">#x27;coeffs&class="macro">#x27;: coeffs.copy()
    })
    class="kw">return mse
逐行拆解:第 1 行定义损失函数,入参是待估系数与训练集;第 2 行按方程批量算预测值;第 3 行求预测与真实的 MSE;第 4 行顺手算 R²;第 6–10 行把本轮指标和系数副本塞进优化进度,供后期画图;末行返回 MSE 供 Nelder-Mead 最小化。 回测现象值得记:前几次迭代比率乱跳、R² 近 0;约第 100 次迭代方向明朗,第 300 次前后稳定。历史拟合 R² 常高于 0.996,MSE 约 0.0000007,预测误差多数不超 0.7 点,MAPE 常低于 0.1%——外汇与贵金属属高风险品种,这些仅是历史样本表现,前瞻测试可能偏离。 真正值钱的是十次重跑系数都聚得很近,而其他方法办不到。优化过程也是市场探针:算法猛加动量权重,可能预示强势行情;开始调周期项,波动加剧的概率偏高。

MQL5 / C++
def loss_function(self, coeffs, X_train, y_train):
    y_pred = np.array([self.equation(x, coeffs) for x in X_train])
    mse = np.mean((y_pred - y_train)**class="num">2)
    r2 = r2_score(y_train, y_pred)
    # Save progress for analysis
    self.optimization_progress.append({
        &class="macro">#x27;mse&class="macro">#x27;: mse,
        &class="macro">#x27;r2&class="macro">#x27;: r2,
        &class="macro">#x27;coeffs&class="macro">#x27;: coeffs.copy()
    })
    class="kw">return mse

金融时序切分踩过的坑

把整段行情丢进普通的训练/测试切分函数,是新手最容易翻车的一步。我曾把全部历史直接交给 sklearn 的 train_test_split,回测漂亮得离谱,后来才反应过来:未来价格混进了训练集,等于用明天天气推昨天天气,样本泄漏让指标彻底失真。 这段 Python 函数就是为堵住泄漏写的:按时间顺序切,前 67% 做训练,后段做验证,预测窗口只取相邻两个历史值。市场不长期记仇,实测仅用 prices[i] 与 prices[i-1] 两个特征就够用,堆更多滞后项反而过拟合。 def prepare_training_data(prices, train_ratio=0.67): # Cut off a piece for training n_train = int(len(prices) * train_ratio) # Forming prediction windows X = np.array([[prices[i], prices[i-1]] for i in range(2, len(prices)-1)]) y = prices[3:] # Fair time sharing X_train, y_train = X[:n_train], y[:n_train] X_test, y_test = X[n_train:], y[n_train:] return X_train, y_train, X_test, y_test 逐行看:第 2 行按传入比例算训练样本数;第 5–6 行构造 X 为[i,i-1]双值窗口、y 为滞后一期收盘,严格时序不交叉;第 9–10 行用下标硬切,训练集永远早于测试集。 训练比例我试过 50/50、80/20、90/10,最终 0.67 附近最优,纯属实测胜出,没有理论必然。若训练段含英国脱欧或美联储讲话这类事件,优化器会乱跳、误差像过山车,这种样本要单独标记。 R² 高于 0.98 基本可断定数据有问题——真实外汇与贵金属市场高风险且不可如此线性预测,真有这数值多半是泄漏或前处理作弊。少折腾标准化,原始价喂进去让市场自己说话,每周重训一次比对历史指标即可。

MQL5 / C++
def prepare_training_data(prices, train_ratio=class="num">0.67):
    # Cut off a piece for training
    n_train = class="type">int(len(prices) * train_ratio)
    
    # Forming prediction windows
    X = np.array([[prices[i], prices[i-class="num">1]] for i in range(class="num">2, len(prices)-class="num">1)])
    y = prices[class="num">3:]
    
    # Fair time sharing
    X_train, y_train = X[:n_train], y[:n_train]
    X_test, y_test = X[n_train:], y[n_train:]
    
    class="kw">return X_train, y_train, X_test, y_test

常见问题

线性假设把价格当匀速运动,遇到分形波动就滞后;可换非线性回归,用分段或指数项贴合价格呼吸,减少钝化。
可用近期成交密集区或七比率做锚,把常数项锁在统计中轴附近,避免随行情外推而失控。
小布可对接品种页直接画出非线性回归带,并在价格击穿置信区间时推送偏离提醒,省去手调参数。
价格序列不连续且噪声大,无导数法靠采样搜索更稳,不易陷局部极值,适合粗糙盘面。
会,跳空和休市让固定切分混入无效段;应按活跃会话或波动阈值切,回测才不失真。