股票交易中的非线性回归模型(基础篇)
◍ 在 MT5 里给价格跑一条非线性回归线
MT5 自带的标准回归工具多是线性的,面对带曲率的外汇或贵金属走势容易滞后。非线性回归把价格当作因变量、把 bar 索引当作自变量,用多项式拟合,能更贴着波段拐弯。 下面这段代码在 MT5 主图画一条二阶多项式拟合曲线,样本取最近 100 根 K 线。外汇与贵金属杠杆高、跳空频繁,拟合线只描述已发生结构,不代表后续必沿该曲率运行,拐头概率需结合量价确认。 把 Period 从 100 改成 30,曲线会更敏感但噪声变大;改成 200 则更平滑却容易漏掉短线机会。开 MT5 新建指标脚本粘入即可验证。
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 优化,那是下一段的坑。下面直接看方程骨架,每行都已标了它在模型里的角色。
<span class="keyword">def</span> equation(self, x_prev, coeffs): x_t1, x_t2 = x_prev[<span class="number">class="num">0</span>], x_prev[<span class="number">class="num">1</span>] <span class="keyword">class="kw">return</span> (coeffs[<span class="number">class="num">0</span>] * x_t1 + <span class="comment"># trend</span> coeffs[<span class="number">class="num">1</span>] * x_t1**<span class="number">class="num">2</span> + <span class="comment"># acceleration</span> coeffs[<span class="number">class="num">2</span>] * x_t2 + <span class="comment"># market memory</span> coeffs[<span class="number">class="num">3</span>] * x_t2**<span class="number">class="num">2</span> + <span class="comment"># inertia</span> coeffs[<span class="number">class="num">4</span>] * (x_t1 - x_t2) + <span class="comment"># impulse</span> coeffs[<span class="number">class="num">5</span>] * np.sin(x_t1) + <span class="comment"># market rhythm</span> coeffs[<span class="number">class="num">6</span>]) <span class="comment"># basic level</span>
◍ 常数项与七比率的锚定逻辑
常数项在模型里远不只是凑平差的摆设。高波动段它把预测值拽回实际价格附近,避免输出发散;低波动段则负责把整体价位估得更准。实盘观察里,常数项数值常跟着趋势强度走——趋势越猛,它越贴近 0。 我试过往方程里硬塞新项、换复杂函数,结果回测表现反而塌了。市场像在提醒:别叠不必要的复杂度。现在这版只留七个比率,不多不少,各自在预测链里有固定工位。 七比率的权重不是手拍的,靠 Nelder-Mead 单纯形法在样本上搜最优。看它迭代寻优的过程,会联想到混沌系统里初值敏感的那套味儿。这部分留到下节拆。
「无导数探测怎样驯服价格模型」
梯度下降和遗传算法在汇率模型调参上先后翻车。梯度下降跑一周只换来参数飞向无穷或卡在局部极小;遗传算法整夜跑完一周历史,结果飘得像读茶渣。后来换上 1965 年的 Nelder-Mead 单纯形法,不靠导数,只靠在解空间里巧妙探点,反而和市场跳起了舞,平稳滑向最优。 损失函数最后退回最朴素的均方误差(MSE)。开发者常犯的错是有效了就拼命加惩罚项、塞进 MAPE 等花活,直到彻底跑不动;斧头一样的 MSE 反而够用。下面这段 Python 风格的实现把每轮 MSE、R² 和系数存进进度列表,方便肉眼看收敛。
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
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 基本可断定数据有问题——真实外汇与贵金属市场高风险且不可如此线性预测,真有这数值多半是泄漏或前处理作弊。少折腾标准化,原始价喂进去让市场自己说话,每周重训一次比对历史指标即可。
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