第 6 章 · 01 Oil Money 导论与 petrocurrency 神话 本节摘要:本节是整个石油货币大项目的起点。我们先把作者立下的"靶子"摆出来——市场分析师口口声声说"油价涨,石油货币就涨",但他们只验证了相关性,却悄悄回避了因果性。本节会讲清项目的核心思想武器:用非贸易伙伴货币当计价基准(工具变量思想),巧妙绕开构造贸易加权汇率的难题;并把项目里六个 py 脚本(NOK/RUB/CAD/COP/Trading backtest/oil production)与那份 59KB 的 README 的对应关系理顺。最后用一个工程小坑(Oil Money Trading backtest.py 文件名带空格无法 import)提醒读者:量化研究的第一个敌人,往往不是数学,而是空格。
本节摘要:本节是整个石油货币大项目的起点。我们先把作者立下的"靶子"摆出来——市场分析师口口声声说"油价涨,石油货币就涨",但他们只验证了相关性,却悄悄回避了因果性。本节会讲清项目的核心思想武器:用非贸易伙伴货币当计价基准(工具变量思想),巧妙绕开构造贸易加权汇率的难题;并把项目里六个 py 脚本(NOK/RUB/CAD/COP/Trading backtest/oil production)与那份 59KB 的 README 的对应关系理顺。最后用一个工程小坑(Oil Money Trading backtest.py 文件名带空格无法 import)提醒读者:量化研究的第一个敌人,往往不是数学,而是空格。
涉及源码:原项目
Oil Money project/README.md(59KB,整个仓库最长的一份文档),配套 5 个策略脚本——Oil Money NOK.py(约 547 行)、Oil Money RUB.py(约 215 行)、Oil Money CAD.py(约 381 行)、Oil Money COP.py(约 380 行)、Oil Money Trading backtest.py(约 293 行)。
⚠️ 注意:本节是第 6 章四节里最"文科"的一节——几乎没有公式,但概念密度极高。petrocurrency 神话、相关≠因果、工具变量、dollar effect 这四个词,会贯穿后面三节。如果你只读一节就跳到别的章节,请读这一节。
petrocurrency 字面意思是"石油货币",指那些出口收入高度依赖原油、且汇率自由浮动的产油国货币。最典型的代表是挪威克朗(NOK)、俄罗斯卢布(RUB)、加拿大元(CAD)、哥伦比亚比索(COP)。直觉上,油价上涨→这些国家出口赚得更多→本币升值;油价下跌→本币贬值。这条逻辑链看起来无懈可击,几乎所有财经媒体都这么写。
但作者在 README 的开头就立了一个靶:
Does this statement really hold? Prior to this article by Bloomberg (or many other similar research), market analysts test the correlation between petrocurrency and oil price, instead of the causality.
这是整个项目的总纲句。后面四个货币的所有折腾,都是在拷问这一句话。
这是统计学最基础、也最容易被遗忘的一条戒律。作者给出了两层批判:
💡 核心心法:为什么作者反复强调这一点?因为它直接决定了策略的根基。如果你误把"相关"当成"因果",你建立的模型在样本内 R² 漂亮,但样本外一遇新情况就崩。第 02 节 NOK 模型在 2017/11/15 的失效,以及第 03 节 CAD 的全面证伪,本质上都是这条戒律在不同货币上的演绎。
这是本项目最聪明的一笔,也是最容易看漏的一笔。
构建一个 petrocurrency 模型,最"正统"的做法是用贸易加权汇率(trade-weighted effective exchange rate)——按贸易伙伴权重加权出一个综合汇率。但作者在 README 里详细吐槽了它的不可行:
作者的替代方案极其优雅:换一个与目标国几乎不做生意的国家的货币来当计价基准(base currency)。
| 目标货币 | 计价基准 | 为什么选它 |
|---|---|---|
| NOK 挪威克朗 | 日元 JPY | 日本只占挪威出口不到 2%,与挪威经济几乎无关联 |
| RUB 俄罗斯卢布 | 澳元 AUD | 澳大利亚与俄罗斯几乎没有直接贸易 |
| CAD 加拿大元 | 澳元 AUD | 五眼联盟但双向贸易流量极小 |
| COP 哥伦比亚比索 | 澳元 AUD | 两个大宗商品出口国互相几乎不往来 |
为什么这样做有道理?因为这个基准货币外生于目标货币的石油基本面——它不会被油价、不会被目标国的贸易扰动"污染"。这正是计量经济学里**工具变量(instrumental variable)**思想的一个朴素化身:找一个对解释变量没有直接效应、但与被解释变量通过"石油渠道"间接关联的外生参照物。
如此一来,模型里的回归元就变成了"在 AUD(或 JPY)计价下的油价、EUR、GBP、USD……",被解释变量是"AUD(或 JPY)计价下的目标货币"。所有变量共享同一个外生计价单位,石油的真实驱动力就能被相对干净地分离出来。
⚠️ 重要说明:严格意义上的工具变量要求"外生性 + 相关性"两条都满足,且通常用于解决内生回归元的偏差。本项目这里只是借用了"找一个外生基准"的直觉,并不是教科书式 IV 估计。但这个直觉已经足够漂亮——它让作者完全绕开了构造贸易加权汇率的泥潭,又比直接用 USD 计价更不容易掉进 dollar effect 的陷阱(见第 03 节)。
README 末尾的 Further Reading 给出了 6 篇参考文献。这不是装饰,而是作者在为自己的结论做对照与背书。我们挑关键的说:
💡 核心心法:一个量化项目挂 6 篇文献、还特意挑一篇结论相反的对照,这种做派非常像写论文而不是写策略。但它正是 quantamental 的精髓——对基本面的尊重和对统计结论的怀疑必须同时在场。
本节没有复杂的算法,我们只做"项目地图"——把 README 和 5 个 py 脚本的对应关系理顺,方便后面三节定位。
Oil Money project/ ├── README.md # 59KB,全仓库最长,叙事与研究结论 ├── Oil Money NOK.py # 挪威克朗:多元回归 + Elastic Net + 协整 + 动量发现 ├── Oil Money RUB.py # 俄罗斯卢布:逐步回归 + 逐年 R² 衰减 ├── Oil Money CAD.py # 加拿大元:R²<5% + K-Means 找结构断点 ├── Oil Money COP.py # 哥伦比亚比索:正面确认 + 参数网格搜索 ├── Oil Money Trading backtest.py # 共用回测引擎:滚动 50 天 OLS + ±2σ 顺势 └── oil production/ ├── oil production choropleth.py # 全球产量 choropleth(地图) └── oil production cost curve.py # 全球产量成本曲线
每个"XX.py"脚本只负责一种货币的分析与可视化;真正通用的回测引擎只有一份,就是 Oil Money Trading backtest.py——NOK 和 COP 都 import 它。这是项目的模块化边界。
README 是按"四种货币 + 引言 + 讨论 + 文献"的结构展开的。我们把它和教程章节对齐:
| README 章节 | 核心结论 | 对应教程 |
|---|---|---|
| Intro | 立靶:相关≠因果;工具变量思想 | 本节(第 01 节) |
| Norwegian Krone | 模型失效即动量入场信号;α≈4% | 第 02 节 |
| Russian Ruble | 制裁逐年侵蚀石油解释力;DON'T TRADE IT | 第 03 节 |
| Canadian Dollar | 所有烃类 R²<5%;证伪;dollar effect | 第 03 节 |
| Colombian Peso | 唯一正面确认;pre-2017 R²≈80%;α≈4% | 第 04 节 |
| Discussion | 动量策略的市场中性;quantamental 思维 | 第 04 节 |
这是本节唯一一个"硬"工程知识点,但非常实用。Oil Money Trading backtest.py 这个文件名带了两个空格。Python 的 import 语句不支持带空格的模块名——import Oil Money Trading backtest 直接语法错误。
作者的解决方法是把文件重命名为 oil_money_trading_backtest.py(全小写、下划线、无空格),然后在 Oil Money NOK.py 和 Oil Money COP.py 里这样调用:
import oil_money_trading_backtest as om # 生成信号、跑回测、画图、算收益 signals = om.signal_generation(dataset, 'brent', 'nok', om.oil_money) p = om.portfolio(signals, 'nok') om.plot(signals, 'nok') om.profit(p, 'nok')
这就是为什么你在 NOK/COP 脚本里看到的导入语句是 import oil_money_trading_backtest as om,而硬盘上的原始文件名却带空格——重命名是 import 的前提。
⚠️ 重要说明:这个坑在 Windows 上特别容易踩,因为 Windows 用户习惯了文件名带空格(如
My Document)。Python 的 import 系统只认合法标识符(字母/数字/下划线,不能以数字开头)。一个实用准则:任何打算被 import 的 Python 文件,文件名一律用小写 + 下划线(snake_case)。这同时也是 PEP 8 推荐的模块命名风格。
立靶优于解法。整个项目最精彩的设计,不是任何一个公式,而是开篇那句"分析师只验证相关、回避因果"。一个好的量化项目,必须有一个可证伪的命题。没有靶子,所有结论都是自我感动。
工具变量的朴素化身。当构造理想指标(如贸易加权汇率)成本太高、争议太大时,找一个外生的参照物往往是更聪明的工程选择。本项目用非贸易伙伴货币当 base,就是这种思路的典范。
文献不是装饰,是对照实验。挂一篇结论相反的论文(Ferraro/Rogoff/Rossi)并解释为什么相反,比挂十篇支持你的论文更有说服力。
文件名要能 import。snake_case 不是审美偏好,是 import 的硬约束。
脚本分工。一个 py 一个货币(只做该货币的可视化与分析),共用一个回测引擎——这种"一货币一脚本 + 共享引擎"的结构,非常适合多标的的策略研究。
Oil Money Trading backtest.py 文件名带空格无法 import,必须重命名为 oil_money_trading_backtest.py 才能在 NOK/COP 脚本里 import oil_money_trading_backtest as om。下一节(第 02 节)进入项目的第一个货币——挪威克朗 NOK。我们会在那里看到全项目最高光的反直觉发现:"本想找统计套利,却意外发现模型失效的瞬间恰是动量入场点。"