
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
2025 年 4 月 3 日,美国宣布对中国进口商品加征大规模关税,SPY(标普 500 ETF)当天暴跌 4.8%,次日再跌 6%。财经新闻的标题千篇一律:「地缘政治不确定性冲击市场」。
而在更早的 2024 年 8 月 5 日,日元套息交易(Yen Carry Trade)大规模平仓,SPY 单日下跌 3%,恐慌指数相关产品 VIXY 飙升。新闻标题还是那一句:「地缘政治不确定性冲击市场」。
两次事件被贴上了同一个标签。但如果你真的把数据拉出来看,会发现它们几乎没有任何共同点:关税冲击中黄金大涨,日元平仓时黄金反而下跌;日元平仓时债券被疯抢,关税冲击时债券却和股票一起被抛售。
同一个标签,完全不同的市场。
本文将带你用 Python 对三次地缘政治冲击事件做一次"法医式"拆解:什么资产先动、期权市场在现货下跌前定价了什么、新闻情绪又在说什么。这是一个非常好的量化金融 + 数据分析实战案例,适合正在学习 Python 的你。
分析覆盖三次事件:
资产篮子围绕一个问题构建:哪些工具最能揭示市场如何"解读"一次冲击?
这 11 个资产共同构成每次事件的"指纹"。数据通过行情数据服务商的历史 EOD(日终)API 获取,每个事件取前后各 30 天的窗口。
import requests
import pandas as pd
# 数据服务商的 API 地址和密钥(请替换为你自己使用的服务)
BASE_URL = '你的行情数据API地址'
api_key = '你的API密钥'
# 定义三次事件:日期、标签、冲击类型
events = {
'oct7_attack': {
'date': '2023-10-07',
'label': '哈马斯袭击以色列(2023 年 10 月)',
'shock_type': 'confidence', # 信心冲击
},
'yen_carry_unwind': {
'date': '2024-08-05',
'label': '日元套息平仓(2024 年 8 月)',
'shock_type': 'liquidity', # 流动性冲击
},
'tariff_shock': {
'date': '2025-04-03',
'label': '美中关税冲击(2025 年 4 月)',
'shock_type': 'structural', # 结构性冲击
}
}
# 11 个资产构成的观察篮子
assets = {
'spy': 'SPY.US', 'qqq': 'QQQ.US', 'iwm': 'IWM.US',
'xle': 'XLE.US', 'xlf': 'XLF.US', 'ita': 'ITA.US',
'xlk': 'XLK.US', 'gld': 'GLD.US', 'tlt': 'TLT.US',
'uup': 'UUP.US', 'vixy': 'VIXY.US'
}
def fetch_prices(ticker, start, end):
"""拉取单个资产的复权收盘价"""
params = {'from': start, 'to': end,
'api_token': api_key, 'fmt': 'json'}
r = requests.get(f'{BASE_URL}/eod/{ticker}', params=params)
df = pd.DataFrame(r.json())
df['date'] = pd.to_datetime(df['date'])
# 使用复权收盘价(adjusted_close),自动处理拆股和分红的影响
df = df.set_index('date')[['adjusted_close']]
df.columns = [ticker.split('.')[0].lower()]
return df
def fetch_event_prices(event_date, lookback=30, lookahead=30):
"""以事件日为中心,取前后各 30 天的所有资产价格"""
start = (pd.Timestamp(event_date) - pd.Timedelta(days=lookback)).strftime('%Y-%m-%d')
end = (pd.Timestamp(event_date) + pd.Timedelta(days=lookahead)).strftime('%Y-%m-%d')
frames = [fetch_prices(t, start, end) for t in assets.values()]
return pd.concat(frames, axis=1)
# 为三次事件各生成一个 DataFrame
event_prices = {name: fetch_event_prices(e['date']) for name, e in events.items()}小提示:这里使用复权收盘价(adjusted close)而不是普通收盘价,是为了消除拆股和分红造成的价格"假跳水"。这是做历史回测时最常见的易错点之一。
要横向比较三次事件,需要一个统一的度量方法。思路分三步:把所有资产价格在事件日归一化为 100;切出事件前后的窗口;按 T+1(事件次日)涨跌幅的绝对值排名,找出"最先重定价"的资产。
def normalize_to_event(df, event_date):
"""将所有资产价格在事件日归一化为 100,方便跨资产比较"""
event_date = pd.Timestamp(event_date)
# 事件日如果是周末,自动顺延到第一个交易日(锚定日)
anchor = df.index[df.index >= event_date][0]
normalized = df.div(df.loc[anchor]) * 100
return normalized, anchor
def repricing_leaderboard(window_df, anchor):
"""计算事件后的累计收益,并按 T+1 涨跌幅绝对值排名"""
anchor_idx = window_df.index.get_loc(anchor)
post_event = window_df.iloc[anchor_idx:]
# 相对锚定日的累计收益率(百分比)
cumulative_returns = (post_event / post_event.iloc[0] - 1) * 100
# 取 T+1 当天的变动幅度,按绝对值降序排列
t1_moves = cumulative_returns.iloc[1].abs().sort_values(ascending=False)
return cumulative_returns, t1_moves三次事件的 T+1 排行榜结果非常有意思:
VIXY 在三次事件中都排第一,这符合常识——波动率永远是重定价最快的。但剥开 VIXY 往下看,三张榜单完全不同。这本身就是关键信息:T+1 排行榜揭示了市场到底在为什么定价。
一个细节:10 月 7 日袭击发生在周六,第一个交易日是 10 月 9 日(周一),所以锚定日要顺延。处理事件研究时,"事件日不一定是交易日"是必须考虑的工程问题。
价格数据告诉你发生了什么,期权数据告诉你市场愿意花多少钱来防范它。
这里的偏度(Skew)指标定义很直观:
当这个差值上升,说明市场在为下行保护支付溢价——这就是被量化了的恐惧。
def compute_skew(df, spot):
"""计算每日的 IV 偏度:虚值 Put 的平均 IV - 平值 Call 的平均 IV"""
df = df.copy()
# moneyness(价值状态)= 行权价 / 现货价
df['moneyness'] = df['strike'] / spot
for expiry in sorted(df['exp_date'].unique()):
sub = df[df['exp_date'] == expiry]
# 筛选虚值认沽:行权价在现货的 90%~97% 之间
otm_puts = sub[(sub['type'] == 'put') & (sub['moneyness'].between(0.90, 0.97))]
# 筛选平值认购:行权价在现货的 97%~103% 之间
atm_calls = sub[(sub['type'] == 'call') & (sub['moneyness'].between(0.97, 1.03))]
if otm_puts.empty or atm_calls.empty:
continue
daily_skew = []
# 按交易日分组,逐日计算偏度
for date, puts in otm_puts.groupby('tradetime'):
calls = atm_calls[atm_calls['tradetime'] == date]
if calls.empty:
continue
skew = puts['volatility'].mean() - calls['volatility'].mean()
daily_skew.append({'date': date, 'skew': skew})
if daily_skew:
return pd.DataFrame(daily_skew).set_index('date').sort_index()
return pd.DataFrame()偏度数据揭示了本次分析中最关键的两个发现:
发现一:日元平仓前 6 天,期权市场已经在"买保险"。 2024 年 7 月 30 日,偏度飙升至 0.087,是整个事件前窗口的最高读数。而触发平仓的日本央行加息决议是 7 月 31 日才公布的——也就是说,期权市场在触发事件公开之前一天,就已经在为下行风险大举定价。有人(很可能是很多人)在新闻见报前就在抢购 SPY 的认沽保护。
发现二:关税冲击前一天,偏度居然是负的。 2025 年 4 月 2 日,偏度读数为 -0.023——平值 Call 比虚值 Put 还贵,期权市场不仅没在定价下跌,反而在定价上涨。第二天,SPY 就迎来了 2025 年最大单日跌幅。
对第二个发现有两种合理解释:一是市场整个 3 月都把关税当作谈判筹码来定价,到 4 月初集体认定"会达成协议";二是期权市场根本没有掌握信息——4 月 2 日宣布的关税比绝大多数参与者预期的更严厉、更直接。
无论哪种解释,结论都一样:偏度衡量的是参与者愿意为保护付多少钱。如果市场集体认定某个风险不值得定价,偏度就不会警告你。而这个集体判断,可能是错的。
偏度信号有个弱点:它可能因为与地缘政治无关的原因而飙升。为了更稳健,再引入第二个独立信号:SPY 与 GLD 的 10 日滚动相关性。正常情况下,股票和黄金的相关性较弱或为负;当压力积累时,这种关系会瓦解。
两个信号先做 Z-score 标准化(消除量纲差异),相关性信号取反(相关性下降 = 压力上升),然后取平均得到复合分数,超过 1.0 即触发压力警报。
def build_composite(skew_df, event_prices_df):
"""构建复合压力指标:IV 偏度 + SPY/GLD 相关性瓦解"""
prices = event_prices_df[['spy', 'gld']].copy()
# 计算 SPY 与 GLD 的 10 日滚动相关性
prices['corr'] = prices['spy'].rolling(10).corr(prices['gld'])
def zscore(s):
# Z-score 标准化:让两个信号在同一尺度上比较
return (s - s.mean()) / s.std()
skew_z = zscore(skew_df['skew'])
# 相关性信号取反:相关性下降意味着压力上升
corr_z = zscore(prices['corr'].dropna()) * -1
combined = pd.concat(
[skew_z.rename('skew_z'), corr_z.rename('corr_z')], axis=1)
# 复合分数 = 两个标准化信号的均值
combined['composite'] = combined.mean(axis=1)
# 超过 1.0 触发压力警报
combined['stress_flag'] = combined['composite'] > 1.0
return combined结果如何?三次事件中只有两天被打上压力标记:
两次都是事前预警。但关税事件里也暴露了致命短板:4 月 2 日(崩盘前一天),偏度分量跌到 -2.824 的极端负值,把复合分数拽到了 -0.817——指标实际上在说"没有压力",紧接着就是全年最大单日跌幅。两个信号里只有相关性分量做对了,二中取一不是一个可靠的复合指标,这是该方法论的诚实边界。
最后一层数据是新闻情绪:基于财经新闻生成的每日标准化分数,范围从 -1(极度负面)到 +1(极度正面)。
结果令人警醒:
把三次事件放在一起,市场行为的分类学就清晰了:
| 大涨,避险首选 | |||
理解这三类指纹的逻辑:
三次事件在新闻里共用同一个标签,数据却给了它们三个截然不同的名字。这次分析有几条值得记住的结论:
对学 Python 的同学来说,这也是一个很完整的数据分析工程范本:API 分页拉取、复权价处理、事件窗口切片、归一化、滚动相关性、Z-score 标准化、多信号合成——每一步都是量化分析中的高频基本功。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐