第 6 章 · 02 NOK:从统计套利到动量信号


文档摘要

第 6 章 · 02 NOK:从统计套利到动量信号 本节摘要:本节是整个石油货币大项目的最高光时刻。作者本想在挪威克朗(NOK)和布伦特原油(Brent)之间做一笔统计套利——用多元回归算出 NOK 的"公允价",价格偏离就反向开仓赌回归。结果多元回归 R² 漂亮,但模型在 2017/11/15 突然失效,价格一举突破 2σ 止损线。意外的是,正是模型失效的那个瞬间,成了趋势跟随(动量)策略的最佳入场点——价格朝突破方向又猛冲了一个月。作者用一句金句概括:"We started off seeking statistical arbitrage, but ended up discovering a momentum trading entry signal.

第 6 章 · 02 NOK:从统计套利到动量信号

本节摘要:本节是整个石油货币大项目的最高光时刻。作者本想在挪威克朗(NOK)和布伦特原油(Brent)之间做一笔统计套利——用多元回归算出 NOK 的"公允价",价格偏离就反向开仓赌回归。结果多元回归 R² 漂亮,但模型在 2017/11/15 突然失效,价格一举突破 2σ 止损线。意外的是,正是模型失效的那个瞬间,成了趋势跟随(动量)策略的最佳入场点——价格朝突破方向又猛冲了一个月。作者用一句金句概括:"We started off seeking statistical arbitrage, but ended up discovering a momentum trading entry signal." 本节会逐行精读多元回归、Elastic Net、协整检验、以及那个把"模型失效"封装成"动量入场信号"的滚动 50 天 OLS 策略。

涉及源码:原项目 Oil Money NOK.py(约 547 行,本项目最长脚本)、Oil Money Trading backtest.py(约 293 行,共用回测引擎)。

⚠️ 注意:本节代码量大,但思路只有一条主线——从"反向赌回归"到"顺势赌突破"的思维翻转。读的时候请始终盯住这个翻转点:它发生在价格突破 ±2σ 的那一瞬间。

金融/数学原理

一、为什么选挪威克朗

挪威是全球最大的产油国之一,且汇率自由浮动。沙特、伊朗、委内瑞拉虽然也是产油大国,但汇率钉住美元(pegged),套利空间被央行挤干。所以候选名单上只剩挪威、俄罗斯、加拿大。

按第 01 节的工具变量思想,NOK 的回归元全部用日元 JPY 计价:EURJPY、GBPJPY、USDJPY、Brent(in JPY),被解释变量是 NOKJPY。日本只占挪威出口不到 2%,是干净的外生基准。

作者一开始其实是怀疑 NOK 是不是真石油货币的——挪威 60% 的 GDP 来自服务业,主权基金规模全球第一,理论上对石油的依赖没那么强。结果回归跑出来狠狠打脸:Brent 对 NOK 的影响高度显著,R² 还很高。NOK 确实还是石油货币。

二、多元线性回归与多重共线性

模型形式:

NOKJPY = β0 + β1·USDJPY + β2·GBPJPY + β3·EURJPY + β4·BrentJPY + ε

这是最朴素的普通最小二乘(OLS)。训练区间是 2013-04-25 到 2017-04-25,测试区间是 2017-04-25 到 2018-04-25。

样本内 R² 很高,T 检验、F 检验都显著。但 OLS 摘要里有一个红灯:条件数(condition number)飙升。条件数是设计矩阵 X 的最大奇异值与最小奇异值之比,它衡量的是回归元之间是否近似线性相关。条件数大,意味着存在严重的多重共线性(multicollinearity)。

为什么 NOK 模型会有多重共线性?原因很直观:

  • Brent 与 USD 负相关:原油期货以美元计价,美元升贬值会反向带动油价。
  • GBP 与 EUR 相关:脱欧公投前,英镑与欧元之间存在协整关系,长期同涨同跌。

多重共线性的危害不在于 R² 本身(它会很高),而在于系数估计极不稳定——稍微动一下样本,β 的符号和大小都可能剧变。这样的模型做样本外预测会很脆。

三、Elastic Net:L1 + L2 正则化解共线性

作者的应对是 Elastic Net。它把 Lasso(L1)和 Ridge(L2)两种正则化拼在一起:

Loss = RSS + λ·[ α·Σ|β_j| + (1−α)/2·Σβ_j² ] └──────┘ └────────────────────────┘ 残差平方和 L1(变量选择) + L2(系数收缩)
  • L1 部分会把不重要的回归元系数压到精确为 0,起到变量选择的作用;
  • L2 部分会让高度相关的回归元系数"分摊",避免某一个独大;
  • 混合项让 Elastic Net 既能选变量、又能稳定共线变量,是多重共线性的标准解药。

本项目用的是 sklearn.linear_model.ElasticNetCV,在给定的 α(对应公式里的 λ)和 l1_ratio(对应公式里的 α)网格里做交叉验证自动选参:

m = en(alphas=[0.0001, 0.0005, 0.001, 0.01, 0.1, 1, 10], l1_ratio=[.01, .1, .5, .9, .99], max_iter=5000).fit(x0[x0.index<'2017-04-25'], y)

得到的弹性网系数估计为:[0.00388958, 0.01992038, 0.02823187, 0.00050092](分别对应 USD/GBP/EUR/Brent)。可以看到 EUR 的系数最大(0.028),印证了挪威处于 EEA、与欧盟经济深度绑定的直觉;Brent 的系数虽小(0.0005),但因为 Brent 价格本身量级大,实际影响力依然显著。

四、NOK 的两层分解:EUR 主导长期,Brent 主导短期

把 NOKJPY 看成一个随机过程,可以分解为两层:

频率 主导变量 解释
长期趋势 EUR 挪威在 EEA,经济与欧盟深度绑定
短期扰动 Brent 石油价格波动直接驱动短线

这个分解很重要——它解释了为什么模型会在某个特定日期"失效":当短期扰动(Brent)的冲击大到把长期趋势(EUR)的锚也拽偏时,模型给出的"公允价"就不再有意义,价格会朝突破方向持续运行。这正是动量信号的来源。

五、协整检验:Engle-Granger 两步法

为了验证 NOK 与 EUR 的长期关系,作者尝试了协整检验。statsmodels 当时还没有 Johansen 检验(多维),只能用 Engle-Granger 两步法:

  1. 第一步:对 NOKJPY 与 EURJPY 做 OLS,得到残差序列。
  2. 第二步:对残差做 ADF(Augmented Dickey-Fuller)单位根检验。如果残差平稳,说明两者协整(存在长期均衡关系)。

结果 ADF 统计量 -2.56,对应 p 值 0.10,连 90% 置信区间都没达到——协整关系没有被正式确认。但可视化上两者走势明显相关(回归 R² 高达 73%),所以作者用"rule of thumb"宣布 EUR 仍是 NOK 长期趋势的主导。这其实是量化研究里很诚实的一种处理:统计检验没过,但经济直觉和可视化都支持,那就如实记录,不要硬包装成显著

算法与代码精读

一、统计套利版策略:1σ 触发 + 2σ 止损的反向开仓

这是 Oil Money NOK.py 里最初的版本,逻辑像布林带:用 Elastic Net 算出拟合值(fitted)和残差的标准差 σ,然后:

  • 价格 > fitted + 1σ:做空(赌回归);
  • 价格 < fitted − 1σ:做多(赌回归);
  • 价格 > fitted + 2σ 或 < fitted − 2σ:止损清仓,模型视为失效。

关键代码(Oil Money NOK.py:214-270)用 signals['cumsum'] 控制只触发首次信号,然后用 break 在触及 2σ 止损线时退出整个信号生成循环:

upper = np.std(df['sk_residual'][df.index<'2017-04-25']) # 1σ 阈值 # ... signals['stop profit'] = signals['fitted'] + 2*upper # 上轨 2σ signals['stop loss'] = signals['fitted'] + 2*lower # 下轨 2σ # ... for j in range(len(signals)): if signals['nok'].iloc[j] > signals['upper'].iloc[j]: signals.iloc[j, index] = -1 # 突破上轨做空 # ...触及 2σ 止损线则 break 退出整个循环

这个版本在样本内来回振荡时确实能赚一点小钱。但作者发现了一件怪事:从 2017/11/15 开始,NOKJPY 一路向上突破 2σ 止损线,如果不止损反而顺势加仓,后面整整一个月都在大涨。止损反而是把最赚的那一段给切掉了。

图: NOK 模型

二、模型失效点:2017/11/15

Oil Money NOK.py:288-298 在图上标了一个黄色五角星——"Potential Exit Point of Momentum Trading",并用一条黑色虚线 plt.axvline('2017/11/15') 标出模型失效日。

为什么是这一天?作者在 README 里给了几个候选解释(都没有定论,这正是 quantamental 的诚实):

  • 沙特和伊朗在那天宣布延长减产协议,推高油价;
  • 特朗普当选总统后倾向弱美元,推高大宗商品价格;
  • 北海炼能扩张等基本面变化。

关键不是"为什么",而是"发生了什么":模型破裂意味着基本面发生了投资者预期的根本变化,这种变化会持续相当长一段时间——而这正是 CTA(商品交易顾问)赚钱的方式

图: NOK 持仓与失效点

三、共用回测引擎:oil_money_trading_backtest.py

真正把"模型失效即信号"封装成可重复策略的,是这份共用脚本。它的核心是 signal_generation 函数(Oil Money Trading backtest.py:55-178),逻辑如下:

对每一个新数据点 i: ① 如果当前有持仓 holding != 0: - 持仓天数 counter > 10(默认)→ 清仓,模型重置 - |当前价 − 入场价| ≥ 0.5(默认 stop)→ 止盈/止损清仓 - 否则 counter += 1 ② 如果没有持仓 holding == 0: - 若模型未训练(trained=False):用过去 50 个点做 OLS 若 R² > 0.7,则 trained=True,算出 σ,铺设 ±1σ/±2σ 置信带 - 若模型已训练: 价格 > +2σ → 做多(breakout 上轨) 价格 < −2σ → 做空(breakout 下轨) 一旦入场:把所有置信带 reset 回 forecast 线(避免视觉混淆)

这里有四个关键参数,都做成函数参数:

def signal_generation(dataset, x, y, method, holding_threshold=10, # 持仓上限(天) stop=0.5, # 止盈止损点数 rsquared_threshold=0.7, # R² 有效性门槛 train_len=50): # 滚动训练窗口

🎯 反直觉发现(全项目最高光):请特别注意上面 ② 里的入场规则——价格突破 +2σ 是做多、突破 −2σ 是做空。这和第一版的统计套利完全相反!第一版里"突破上轨"是做空赌回归,这里"突破上轨"是做多赌延续。换句话说,作者把同一个 ±2σ 触发器,从反转信号翻成了顺势信号

这就是 README 里那句金句的代码化身:

We started off seeking statistical arbitrage, but ended up discovering a momentum trading entry signal.

模型失效(价格突破 2σ)的瞬间,本来是统计套利的"出逃点",却被作者重新定义为趋势跟随的入场点。一次思维翻转,把一个失败策略变成了盈利策略。

为什么 reset 置信带(Oil Money Trading backtest.py:163-175)这一步不能省?因为入场后,模型其实已经"破裂"了,继续画 ±2σ 会让看图的人误以为模型仍然有效、误以为"价格还在置信带里为什么不开仓"。把置信带塌缩回 forecast 线,是在视觉上明确告诉读者:从这里开始,模型不再给预测,我们在跟趋势走

四、参数网格搜索:holding × stop 双循环

作者对默认参数不满意——只赚 2%,还不如存银行吃挪威央行 0.75% 的无风险利率。于是做了一次暴力搜索(Oil Money NOK.py:478-489):

dic = {} for holdingt in range(5, 20): # 持仓 5~19 天 for stopp in np.arange(0.3, 1.1, 0.05): # 止损 0.30~1.05 点 signals = om.signal_generation(dataset, 'brent', 'nok', om.oil_money, holding_threshold=holdingt, stop=stopp) p = om.portfolio(signals, 'nok') dic[holdingt, stopp] = p['asset'].iloc[-1] / p['asset'].iloc[0] - 1

双循环跑完,把所有参数组合的收益率收集到 profile DataFrame,然后用两种图可视化:

  1. 收益分布直方图(oil-nok profit distribution.png):平均 2%,但极值能到 −6% 和 +6%。
  2. 收益热力图(oil-nok profit heatmap.png,用 sns.heatmap):横轴止损点数、纵轴持仓天数、颜色代表收益率。

图: NOK 收益热力图

热力图给出的结论非常清晰,也是本节第二个反直觉发现:收益率与持仓天数强相关,与止损止盈点数几乎无关。最优持仓天数是 9 天,止损可以在 0.6~1.05 的宽区间里任选。

这背后的直觉是:这是一个动量策略,动量的强弱取决于"趋势能持续多久",而不是"单次波动有多大"。所以持仓周期(捕捉趋势)是关键,止损(控制单次波动)反而不痛不痒。

图: NOK 收益分布

关键技巧

  1. 条件数是多重共线性的免费预警。OLS 摘要里那一行 Cond. No. 经常被忽略,但它一旦飙升(>30 就要警惕,>100 基本确认),就要切到 Elastic Net/Ridge。这是看 OLS 摘要的肌肉记忆。

  2. L1 做变量选择、L2 做系数收缩。Elastic Net 的混合正则项让它比纯 Lasso(共线时挑一个扔其他)和纯 Ridge(全留但不归零)都更适合金融数据——金融变量本来就高度共线。

  3. 置信带 reset 回 forecast 线。这个细节看似只是画图,实则是策略逻辑的一部分:它标志着"模型破裂,进入趋势跟随模式"。视觉诚实也是策略诚实。

  4. 把失效点重新定义为入场点。这是本节最值钱的一招。当一个统计模型的预测被价格击穿时,第一反应不要是"模型错了赶紧修",而是问一句"这个击穿本身是不是信号?"。

  5. 参数搜索看热力图,不看平均值。平均值 2% 看着寒酸,但热力图告诉你极值能到 6%,而且对哪个参数敏感一目了然。直方图 + 热力图是参数搜索的标准可视化组合。

  6. 引用 Nick Patterson 的话做策略祛魅。冷战的密码学家、文艺复兴雇员,他说"Renaissance 用的就是简单一元回归,简单但有效,前提是知道如何避开那些坑"。这不是炫耀复杂模型,而是提醒:识别(找对变量)远比估计(调对参数)重要。本项目的 ±2σ 突破看似简陋,但因为识别对了"模型失效=趋势起点",反而有效。

本节要点回顾

  1. NOK 是石油货币:多元回归 + Elastic Net 都确认 Brent 对 NOK 影响显著,R² 很高。
  2. 多重共线性:Brent-USD 负相关、GBP-EUR 协整导致条件数飙升,用 ElasticNetCV(L1+L2)解决。
  3. 两层分解:EUR 主导长期趋势,Brent 主导短期扰动。模型失效往往发生在 Brent 短期冲击拽偏 EUR 长期锚的时候。
  4. 协整检验未通过:Engle-Granger 两步法的 ADF p 值 0.10,没到 90% 置信,但可视化支持,作者如实记录。
  5. 🎯 反直觉发现:模型失效(价格突破 2σ)的瞬间恰是动量入场点。从"反向赌回归"翻转为"顺势赌突破",把失败策略变盈利策略。
  6. 共用回测引擎:滚动 50 天 OLS → R²>0.7 才算有效 → ±2σ 触发顺势多空 → 持仓上限 10 天 + 止盈止损 0.5 点 → 清仓后用最新 50 点重新校准。
  7. 参数搜索结论:收益与持仓天数强相关、与止损几乎无关;NOK 最优持仓 9 天

下一节(第 03 节)把同样的方法套到 RUB 和 CAD 上——结果一个被制裁摧毁,一个被 dollar effect 揭穿。两个都是"证伪"案例,但证伪本身比确认更有教学意义。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U