第 8 章 · 01 Monte Carlo:用反例证伪 SDE 预测股价


文档摘要

第 8 章 · 01 Monte Carlo:用反例证伪 SDE 预测股价 本节摘要:这是全书最后一个高潮,也是最尖锐的一节。我们正面拷问一个被 LinkedIn 数据科学博客反复兜售的命题——"股价是 Wiener 过程,可以用蒙特卡洛模拟预测"。本节先讲清几何布朗运动(GBM)怎么把对数收益拆成 、每步叠 、 ;再讲项目怎么跑 N 次模拟、挑与历史标准差最小的 best fit 外推。然后是两个"教科书级反例":GE 2018 全年跌 57.9%,500 次模拟最低只跌到 10.99(实际 6.71);NVDA 2008 跌 75.6%,500 次模拟最低 6.086(实际 5.9)——号称擅长胖尾的 MC 连尾部量级都测不准。

第 8 章 · 01 Monte Carlo:用反例证伪 SDE 预测股价

本节摘要:这是全书最后一个高潮,也是最尖锐的一节。我们正面拷问一个被 LinkedIn 数据科学博客反复兜售的命题——"股价是 Wiener 过程,可以用蒙特卡洛模拟预测"。本节先讲清几何布朗运动(GBM)怎么把对数收益拆成 drift=μ-σ²/2、每步叠 sde=drift+σ·gauss(0,1)price*=exp(sde);再讲项目怎么跑 N 次模拟、挑与历史标准差最小的 best fit 外推。然后是两个"教科书级反例":GE 2018 全年跌 57.9%,500 次模拟最低只跌到 10.99(实际 6.71);NVDA 2008 跌 75.6%,500 次模拟最低 6.086(实际 5.9)——号称擅长胖尾的 MC 连尾部量级都测不准。最后用 500→1500 次的方向准确率测试证明大数定律在此失效(预测方向与掷硬币无异)。这是一份极好的批判性思维教学案例:用过去预测未来,过去未发生的事无法预测。

涉及源码:原项目 Monte Carlo project/Monte Carlo backtest.py(386 行,含 200 色硬编码色梯)与 Monte Carlo project/README.md(7KB,论证体);数据通过 fix_yahoo_finance 拉取 yfinance 的 GE(2016-01-15 至 2019-01-15)与 NVDA(2006-01-15 至 2009-01-15)日线。

⚠️ 注意:本节通篇在"证伪"一个流行做法,但证伪不等于否定一切。蒙特卡洛在期权定价(用大数定律逼近期望)、VaR 计算(在已知分布假设下估算分位数)等领域是严肃工具。作者真正反对的是"拿 MC 去预测真实股价的方向或尾部"——这种用法的根基(经验分布 + Wiener 连续性)在真实市场里不成立。读完本节请带着"什么场景能用、什么场景不能用"的分辨力离开,而不是把 MC 一棍子打死。

金融/数学原理

一、开篇立调:xkcd 的曲线拟合漫画

作者在 README 最顶端,在所有文字之前,先放了一张 xkcd 的"Curve-Fitting"漫画。

图: xkcd 曲线拟合漫画,立调"house of cards"

这张漫画画的是:有人拿到一串散点数据,先用线性回归,再用二次、三次、指数、对数、甚至一个奇怪的"龙形曲线"去拟合——每一种拟合都自圆其说,但本质都是对同一组数据的过度诠释。作者把它放在最前面,是在提前给读者打一针免疫:本节要讲的蒙特卡洛股价预测,也是同一类"house of cards(纸牌屋)"——看起来拟合得极漂亮,一推就倒。

💡 交易员视角:为什么作者这么尖刻?因为 LinkedIn 上有大量"数据科学博客",教人用决策树/随机森林预测股价、用反向传播神经网络预测债券价格。作者引 Bloomberg 的报道指出:机器学习在实盘交易中的失败率约为 90%。绝大多数这类文章把"在历史数据上拟合得好看"误当成"能预测未来"。这张漫画就是对这些文章的总嘲讽。

二、被反驳的命题:股价是 Wiener 过程

Wiener 过程(布朗运动)是连续时间随机过程的基石。它的核心假设有三条:

  1. 增量独立:不同时间段的增量互不影响(弱式有效市场假说的数学化身)。
  2. 增量正态:任意时间段内的增量服从均值为 0、方差与时间长度成正比的正态分布。
  3. 路径连续:过程在时间上是连续的,没有跳跃。

把股价对数化之后,前三条假设可以拼成几何布朗运动(GBM),这正是 Black-Scholes 期权定价公式的底层模型。被作者点名的那篇 LinkedIn 博客的逻辑链是:

"股价可以看作 Wiener 过程 → Wiener 过程可以用 SDE 描述 → SDE 可以用蒙特卡洛模拟 → 所以我们能用 MC 预测股价。"

听起来很顺,但作者立刻指出一个最朴素的破绽:Wiener 过程要求价格在时间上连续,而真实市场是会收盘的。从今天收盘到明天开盘之间存在隔夜波动(overnight volatility),这本身就是一次"跳跃"。所以"股价是 Wiener 过程"这个前提,从根上就不成立。

但作者也承认,隔夜波动还不是最大的问题。最大的问题是:就算我们接受了 Wiener 假设,MC 真的能预测股价吗? 这才是本节要用两个反例回答的问题。

三、几何布朗运动(GBM)与 SDE 离散化

GBM 把股价的对数增量建模成一个带漂移的布朗运动。离散化到每一步(比如每个交易日),它的递推式是:

log_return = log(P_t / P_{t-1}) drift = mean(log_return) - var(log_return) / 2 sde = drift + std(log_return) * gauss(0, 1) P_t = P_{t-1} * exp(sde)

这里有几个关键点要讲清楚:

1. 为什么 drift 要减去 σ²/2? 这是 Itô 引理(Itô's lemma)的产物。我们要建模的是价格 P,但 GBM 真正假设的是对数收益服从正态分布。从 dP = μP dt + σP dW 用 Itô 引理推到 d(log P),会出现一个 -½σ²dt 的修正项。所以如果你直接用样本均值 mean(log_return) 当漂移,会高估价格的长期增长——必须扣掉半个方差,才能得到对数价格的无偏漂移。这是从离散数据反推连续模型时必须做的"凸性修正"。

2. 为什么用标准正态 gauss(0,1) 而不是经验分布? 严格 GBM 假设增量正态。代码里用 Python 标准库的 random.gauss(0,1) 抽标准正态样本,再乘以历史波动率 std(log_return),就还原出"以历史波动率为尺度"的正态增量。这是 GBM 的"教科书实现"。但作者在代码注释里坦白了更深一层的问题:我们的伪随机数本质上来自经验分布——我们用过去算出的 μ 和 σ 去生成未来的可能路径,这等于"用过去预测未来"。这是后面证伪的核心论据。

3. 为什么用 exp 而不是线性叠加? 因为价格必须为正。如果直接 P_t = P_{t-1} + sde,模拟路径可能跌破零;用 exp 把对数空间映射回价格空间,天然保证 P>0。这是 GBM 相对于算术布朗运动的根本优势。

四、best fit 选择:标准差最小化

项目的核心玩法不是"模拟一条路径",而是"模拟 N 条路径,挑一条最贴合历史的当预测"。挑选标准很简单——把每条模拟路径在训练期内的取值与真实收盘价逐点相减,算标准差,标准差最小的那条胜出:

std = np.std(simulated_path[:len(train)] - train['Close'])

直观上这像是在 N 条随机路径里找一条"长得最像历史的"。读到这一步你应该已经有警觉:这不就是过拟合吗? 从 N 条路径里挑一条最贴合历史的,本质上是对历史做选择偏差(selection bias)——这条"best fit"对未来没有任何信息量,因为它只是"恰好历史长得像"的那一条,而不是"机制上最可能"的那一条。这个批判我们留到代码精读后的反直觉发现里展开。

算法与代码精读

代码结构很干净,三个核心函数 + 一个 main:

函数 行数 职责
monte_carlo(data, testsize, simulation) 212-261 跑 N 次模拟、选 best fit、返回路径字典与最佳索引
plot(df, forecast_horizon, d, pick, ticker) 267-312 画两张图:训练期拟合 + 训练/测试对照
test(df, ticker, simu_start, simu_end, simu_delta) 322-362 测模拟次数 vs 方向准确率,画水平条形图
main() 371-382 拉 GE 数据,依次调用上面三个函数

文件顶部还有一段约 160 行的硬编码色梯 colorlist(200 个十六进制颜色),纯粹是为了给最后那张条形图上渐变色。这是项目里最"原始"的工程痕迹——换成 matplotlib.cm 一行就能搞定,但作者选择了手撸色梯。

一、monte_carlo:模拟 + 选 best fit

核心循环在 232-245 行:

232 for counter in range(simulation): 233 d[counter]=[df['Close'].iloc[0]] 234 235 # we dont just forecast the future 236 # we need to compare the forecast with the historical data as well 237 for i in range(len(df)+forecast_horizon-1): 238 239 # we use standard normal distribution to generate pseudo random number 240 sde=drift+returnn.std()*rd.gauss(0,1) 241 temp=d[counter][-1]*np.exp(sde) 242 243 d[counter].append(temp.item())

注意 238 行那个循环范围 len(df)+forecast_horizon-1:模拟路径不只画未来,而是从训练起点开始一直画到测试终点。为什么?因为后面选 best fit 要拿整条路径和真实历史比标准差——只有让模拟路径覆盖训练期,才能算"哪条最贴合历史"。这是个很巧妙的设计,也埋下了"过拟合"的伏笔。

选 best fit 在 251-259 行:

251 std=float('inf') 252 pick=0 253 for counter in range(simulation): 254 255 temp=np.std(np.subtract( 256 d[counter][:len(df)],df['Close'])) 257 if temp<std: 258 std=temp 259 pick=counter

注意 256 行的 d[counter][:len(df)]——只拿训练期那段与真实收盘价比标准差。pick 就是 N 条路径里"训练期贴合度最高"的那条的索引。

二、plot:两张图揭示"house of cards"

plot 函数画两张图,是本节视觉冲击的核心。

第一张图(272-288 行):训练期内的所有模拟路径,高亮 best fit。 把所有路径用 alpha=0.05 画成几乎透明的灰色背景,best fit 用蓝色粗线、真实价格用红色粗线叠在上面。这张图的视觉效果会让你以为"哇,拟合得真好"。

图: GE 训练期模拟,best fit 极贴合

第二张图(294-312 行):把 best fit 延伸到测试期,与真实价格对照。 这张图加了一条黑色虚线竖线分隔训练/测试,左侧标 "Training",右侧标 "Testing"。这张图就是"纸牌屋倒塌"的现场——best fit 在训练期贴得完美,一过虚线就完全跑偏。

图: GE 训练 vs 测试对照,house of cards 倒塌

作者在 292-293 行的注释里写得很直白:

292 #the figure reveals why monte carlo simulation in trading is house of cards 293 #it is merely illusion that monte carlo simulation can forecast any asset price or direction

三、test:大数定律失效的现场

test 函数是本节最有方法论价值的一环。它不满足于"GE 一个反例",而是系统检验:增加模拟次数,能提升方向预测准确率吗?

逻辑在 337-346 行:

337 for i in np.arange(simu_start,simu_end+1,simu_delta): 338 print(i) 339 340 forecast_horizon,d,pick=monte_carlo(df,simulation=i,**kwargs) 341 342 actual_return=np.sign( 343 df['Close'].iloc[len(df)-forecast_horizon]-df['Close'].iloc[-1]) 344 345 best_fitted_return=np.sign(d[pick][len(df)-forecast_horizon]-d[pick][-1]) 346 table.at[i,'Prediction']=np.where(actual_return==best_fitted_return,1,-1)

注意它只看方向(sign),不看价格幅度。如果测试期末相对训练期末是涨的,actual_return 就是 +1;best fit 同样是涨,best_fitted_return 也是 +1;两者对齐就记 1(成功),否则记 -1(失败)。默认参数是 500 次起步、1500 次封顶、步长 50,所以一共跑 21 档。

最后用 colorlist 那段硬编码色梯画水平条形图,x 轴只有两个刻度:Failure(-1)和 Success(+1)。

图: GE 模拟次数 vs 方向准确率,呈掷硬币状

结论触目惊心:准确率与模拟次数完全无关,21 档结果像掷硬币一样在 ±1 之间随机跳动。即使你把模拟次数加到 150000,大数定律在这里也不会救你——因为问题不在样本量,而在方法本身。

⚠️ 重要说明:这里"大数定律失效"是工程口语,不是说数学定理错了。大数定律保证的是"在真实分布下、样本量足够大时,样本均值收敛到期望"。但这里有两个致命问题:一是我们用的不是真实分布而是经验分布(用过去 σ 估的);二是 best fit 是从 N 条路径里事后挑出来的,本身就破坏了"独立同分布抽样"的前提。所以不是大数定律失效,而是它的前提根本没满足。

🎯 反直觉发现

本节的反直觉不是"一个小反转",而是三连击——每一个都足以单独写一篇批判文章。

🎯 反例 1:GE 2018,拟合极美外推全错

GE 是 2018 年道琼斯成分股里表现最差的,全年跌 57.9%(长期 M&A 失败的代价)。作者特意挑它,理由是:如果 MC 真的擅长预测胖尾,它就该在这种极端下跌上展示实力

50/50 切分训练/测试后:训练期内 best fit 几乎完美贴合 GE 的真实收盘价(看第一张图,蓝色线和红色线几乎重合);但一进测试期,best fit 缓慢爬升,真实 GE 却像石头坠入深海——方向完全相反。500 次模拟里预测的最低价是 10.99 美元,而 GE 实际跌到的最低价是 6.71 美元。差了将近 40%。

这就是"house of cards"最直白的演示:训练期拟合度越高,反而越容易让你误以为模型有效

🎯 反例 2:NVDA 2008,胖尾量级都测不准

如果觉得 GE 是孤例,作者又上了 NVDA(英伟达)2006-2009 的数据。2008 年金融危机 NVDA 跌了 75.6%——这是风险量化真正的"主场"。同样 50/50 切分,best fit 同样在测试期失效:500 次模拟预测的最低价是 6.086 美元,实际最低价是 5.9 美元。

听起来 6.086 和 5.9 差不多?注意这是500 次模拟里最悲观的那条的最低点——也就是说,即便你跑 500 次、挑出跌得最凶的那条路径,它预测的"最坏情况"依然比真实情况乐观。号称擅长捕捉黑天鹅的 MC,在真正的黑天鹅面前连量级都测不准。风险管理场景下用 MC 算 VaR,本质就是这种"系统性低估尾部"的陷阱。

🎯 大数定律的"失效"

第三个反直觉是方法论层面的:test 函数证明,把模拟次数从 500 加到 1500,方向预测准确率毫无改善,呈纯随机状。这戳破了很多 MC 拥护者的最后退路——"你模拟得不够多"。作者在 README 里写得很尖刻:你可以试 150000 次,我没有那个算力(老实说是没耐心),但我保证结果一样。

批判观点的完整逻辑链

把上面三个反例串起来,作者的批判逻辑是:

  1. 随机数来自经验分布——用过去的 μ、σ 生成未来的可能路径,等于"用过去预测未来"。过去从未发生的事(比如 2008 那种级别的崩盘,在 2006-2007 的样本里根本看不到),你的模拟根本生成不出来。作者用了一句很妙的比喻:"这就像麻瓜(muggles)试图理解魔法世界。"
  2. best fit 是选择偏差——从 N 条路径里挑一条最贴合历史的,本质上是对历史过拟合。这条"best fit"对未来没有信息量,它只是"恰好这一轮随机数长得像历史"。
  3. Wiener 连续性前提不成立——市场会收盘,隔夜波动就是一次跳跃,这违背 Wiener 过程"路径连续"的核心假设。
  4. 风险管理用途也被否定——即便退一步只算 VaR,MC 在 GE/NVDA 上都系统性低估尾部。作者的原话是:"除非你喝 Kool-Aid(被洗脑),否则别信。" Kool-Aid 是个英语典故,指琼斯镇惨案里被洗脑喝下毒饮料的信徒,引申为"盲目相信"。
  5. 行业实证——Bloomberg 报道机器学习实盘失败率约 90%,作者引用自己认识的 risk quant 朋友圈"没人真用 MC"。

关键技巧

  1. 立靶子比写代码更重要。本节最精彩的设计是 README 开篇那张 xkcd 漫画 + "house of cards" 这句话。在写任何一行模拟代码之前,作者已经把"这是个纸牌屋"的态度亮明了。一个好的批判性研究,必须先有可证伪的命题和鲜明的立场。

  2. 用最极端的反例证伪。证伪一个"能预测股价"的命题,不要用平平淡淡的样本——要用 GE 2018(跌 57.9%)、NVDA 2008(跌 75.6%)这种最极端的案例。如果 MC 在这种它"本该擅长"的胖尾场景都失败,日常场景就更不用谈了。这是证伪的最高效路径。

  3. 方向准确率 > 价格幅度test 函数只看涨跌方向、不看价格幅度,这是个很聪明的设计。因为 MC 的拥护者总可以说"价格预测本来就不准,看个方向就行"——那就直接用方向打脸。如果连方向都像掷硬币,价格幅度就更不用谈。

  4. 控制变量法:扫模拟次数。不满足于"一个次数下的失败",系统扫 500→1500 步长 50,证明准确率与次数无关。这堵死了"你模拟得不够多"的退路。这是工程化的证伪——把对手所有可能的辩词都提前堵死。

  5. 诚实承认前提。作者没有偷懒说"MC 完全没用",而是坦白它在期权定价等领域是严肃工具——他反对的只是"拿 MC 预测真实股价方向/尾部"。这种精准划定批判边界的做法,比一刀切的否定更有说服力。

  6. 可视化即论证。两张图(训练期拟合 + 训练/测试对照)的对比,比任何文字都更有冲击力。第一张图让你以为"这模型真行",第二张图立刻打脸——这种"先扬后抑"的可视化叙事,是数据沟通的高级技巧。

本节要点回顾

  1. 被反驳的命题:LinkedIn 数据科学博客常见的"股价是 Wiener 过程,可用 MC 预测"——前提(连续性)就不成立(市场收盘有隔夜跳跃)。
  2. GBM 实现:drift=mean(log_return)-var(log_return)/2(Itô 凸性修正),sde=drift+std·gauss(0,1),price*=exp(sde);用标准正态抽样、保证价格为正。
  3. best fit 选择:从 N 条模拟路径里挑训练期标准差最小的那条外推——本质是选择偏差/对历史过拟合。
  4. 反例 1(GE 2018 跌 57.9%):训练期拟合极漂亮,测试期方向完全反向;500 次模拟最低 10.99,实际最低 6.71。
  5. 反例 2(NVDA 2008 跌 75.6%):号称擅长胖尾的 MC 连尾部量级都测不准;500 次模拟最低 6.086,实际最低 5.9。
  6. 大数定律"失效":500→1500 次方向准确率与掷硬币无异,与模拟次数无关——前提是用的经验分布 + best fit 的事后选择破坏了大数定律的 i.i.d. 前提。
  7. 核心批判:随机数来自经验分布(用过去预测未来,过去未发生的事无法预测,"麻瓜理解魔法世界");best fit 是选择偏差;Wiener 连续性前提不成立;连风险管理(VaR)用途都系统性低估尾部。
  8. 诚实边界:证伪≠全盘否定——MC 在期权定价等领域是严肃工具,作者只反对"拿 MC 预测真实股价方向/尾部"。

下一节是全书的最后一节。我们会先简单交代 Ore Money 这个"未完成占位项目"的现状(只有数据骨架和一个气泡图脚本,没有策略),然后做整本教程的回顾——从一个量化研究者"轻信指标→学会检验→融合基本面→批判反思"的完整旅程,落点在全书的核心理念:"identification(识别)远比 estimation(估计)重要"。


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