用Python拆解“美元暴跌、美债跳水、黄金拉升”背后的数据逻辑
最近外围市场有一个现象很值得关注:美元指数出现明显回落,10年期美债收益率同步跳水,而黄金价格则被推向阶段高位。很多做跨境投资、外贸结算或者黄金ETF波段的人,第一反应是问“原因是什么”,但真正落到自己交易系统里,问题就变成了“这些宏观变量之间到底是怎么联动的,我能用什么工具把它量化出来”。
如果你去看盘面,会发现这类行情有一个共同特征:美元和黄金负相关,美债收益率和黄金负相关,但三者的联动节奏未必同步。有时候美元先动,黄金后动;有时候收益率先动,美元再补跌。单靠肉眼盯K线,很难判断谁在主导行情。
这篇文章不讨论任何投资建议,只讲技术:如何用 Python 把美元指数、10年期美债收益率、黄金价格三类数据拉下来,做清洗、对齐、相关性分析,并生成可视化图表。看完你可以把这套方法迁移到自己的量价分析脚本里。
1. 为什么这类“宏观联动”行情值得用代码分析
先从一个实际痛点说起。假设你正在做一个大宗商品或外汇相关的数据看板,需要每天统计美元指数与黄金的滚动相关性。过去常见的做法是手动从不同网站导出 Excel,再用 VLOOKUP 对齐日期,最后插入折线图。这个流程有三个问题:
第一,数据源分散。美元指数、美债收益率、黄金价格往往来自不同平台,日期格式、时区、数据频率都可能不一致,手动合并非常容易出错。第二,时间口径不统一。外汇市场几乎24小时交易,美债有电子盘和常规时段之分,黄金则有伦敦金和纽约金两个主要定价时段,如果不对齐“同一交易日”,算出来的相关系数会失真。第三,重复劳动。一次两次手动处理还能忍受,如果每周都要更新,脚本化几乎是唯一合理的选择。
从技术角度看,这类行情分析本质上是“多资产时序数据的对齐与相关性度量”,属于量化分析里的基础功。真正难的不是调库,而是处理数据口径差异。比如你从 yfinance 拿到的黄金价格和从 FRED 拿到的美债收益率,日期索引可能差一天,因为前者用的是交易日,后者用的是美国国债市场工作日。
所以这篇文章的核心判断是:宏观联动的行情分析,90%的工作量在数据清洗和口径统一,而不是模型计算。如果你能把“拉数、清洗、对齐、计算、绘图”这五步固化成脚本,后续研究任何跨资产相关性都能直接复用。
2. 核心指标基础概念与联动机制
在看代码之前,先把涉及到的指标解释清楚,这对后续理解代码逻辑很重要。
2.1 美元指数(DXY)
美元指数用于衡量美元对一篮子货币的汇率变化,篮子中欧元权重最大,超过50%,其次是日元、英镑等。它本质上是一个“相对价格”,反映的是美元在全球外汇市场中的强弱。外汇市场波动剧烈时,DXY 的日内振幅可能超过1%,这也是为什么很多量化策略会把 DXY 的异常波动当作风险开关。
2.2 10年期美债收益率(US10Y)
10年期美债收益率常被视作全球无风险利率的锚,它影响股票估值模型里的折现率,也影响黄金这类零息资产的机会成本。10年期美债收益率下跌,意味着持有黄金的机会成本下降,这是黄金避险属性的核心传导路径之一。但要注意,收益率数据在 yfinance 中通常以百分数形式给出,比如4.20表示4.20%,需要除以100转成小数参与计算。
2.3 黄金价格(XAU/USD 或 GLD)
黄金的定价非常复杂。伦敦金定价是全球基准之一,纽约商品期货交易所的COMEX黄金期货则是另一个主要定价市场。考虑到数据可得性,本文使用 GLD(SPDR Gold Shares ETF)作为黄金价格的代理变量。GLD 追踪金条价格,流动性和数据完整性都足够支持日常分析。
2.4 三者的联动关系
从长期看,美元指数与黄金价格通常呈负相关,因为黄金以美元计价,美元走强会压低金价;10年期美债收益率与黄金价格也通常呈负相关,因为收益率上升意味着持有零息资产的机会成本增加。但“通常”不代表“永远”,短周期内可能出现同涨同跌,这与市场避险情绪、实际利率预期、流动性冲击等因素有关。
用代码做这件事的价值在于,你可以用滚动相关系数观察这种关系是否稳定,而不是凭感觉猜测。
3. 环境准备与数据源说明
本文后续代码基于 Python 3.9 以上版本,核心依赖库如下:
- pandas:数据处理与时间对齐
- numpy:数值计算
- matplotlib:可视化
- yfinance:获取金融市场价格数据
- pandas-datareader:备用数据源,用于从 FRED 获取宏观数据
安装命令:
pip install pandas numpy matplotlib yfinance pandas-datareader如果你所在网络访问外网数据源不稳定,建议配置代理或使用国内可稳定访问的镜像数据源,本文只演示通用思路。所有数据均为公开市场数据,不涉及任何非公开信息。
实际操作中,建议在 Jupyter Notebook 里分步运行,便于查看中间结果;如果要做定时任务,可以封装成独立的 Python 脚本配合 cron 或任务计划程序运行。
4. 核心流程拆解
下面把整个分析拆成五步,每步解决一个明确问题。
4.1 数据拉取
从 yfinance 拉取 DXY、US10Y、GLD 三类资产的日线数据。DXY 的 yfinance 代码是DX-Y.NYB,10年期美债收益率是^TNX,黄金 ETF 是GLD。需要注意,^TNX返回的是收益率*10吗?这里要特别说明:^TNX的单位是百分数,比如数值4.2表示4.2%,需要除以100才是小数形式的收益率。同时,DX-Y.NYB也不是所有时间都有数据,需要做缺失值检查。
4.2 日期对齐与重采样
三类资产虽然都是交易日数据,但停牌日可能不同。比如美股有节假日休市,而外汇市场在部分节假日仍有交易。最简单的处理方式是取三者的交集,只保留都有的日期;如果数据量太少,则用前向填充对齐到同一交易日。
4.3 计算日收益率
相关性分析通常使用收益率,而不是价格本身,因为价格序列不平稳,直接算相关系数容易得到虚假关系。日收益率计算公式为:
ret = close.pct_change()4.4 滚动相关性
用 pandas 的rolling()函数计算固定窗口(比如60个交易日)内的美元指数与黄金、收益率与黄金的滚动相关系数,观察联动关系的稳定性。
4.5 可视化
用 matplotlib 绘制价格走势子图和滚动相关系数子图,直观观察联动变化。图表能帮助我们发现“负相关什么时候失效”,这是静态报表无法提供的价值。
5. 完整示例代码实现
下面给出一个可直接运行的完整示例,我把每一步都写清楚,方便你对照理解。
5.1 导入依赖并配置中文字体
import pandas as pd import numpy as np import matplotlib.pyplot as plt import yfinance as yf from datetime import datetime, timedelta # 解决 matplotlib 中文显示问题 plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'sans-serif'] plt.rcParams['axes.unicode_minus'] = False print("依赖导入成功")5.2 拉取三类资产数据
# 文件路径:macro_analysis/data_fetch.py # 拉取时间范围:过去 2 年 end_date = datetime.now() start_date = end_date - timedelta(days=730) tickers = { "DXY": "DX-Y.NYB", # 美元指数 "US10Y": "^TNX", # 10年期美债收益率 "GLD": "GLD", # 黄金 ETF } data = yf.download( list(tickers.values()), start=start_date.strftime("%Y-%m-%d"), end=end_date.strftime("%Y-%m-%d"), group_by="ticker", auto_adjust=True, progress=False ) # 提取收盘价并重命名列 close_df = pd.DataFrame() for name, symbol in tickers.items(): close_df[name] = data[symbol]["Close"] print("数据拉取完成,缺失值统计:") print(close_df.isnull().sum())这里auto_adjust=True表示使用复权价格,对于 ETF 很重要,因为分红除息会导致价格跳空,复权后收益率计算才准确。对指数类数据影响不大,但养成好习惯没有坏处。
5.3 数据清洗与日期对齐
# 前向填充缺失值,再删除仍然缺失的行 close_df = close_df.ffill().dropna() # 检查最终数据量 print(f"对齐后的样本数量:{len(close_df)}") print("数据范围:") print(close_df.index.min(), "到", close_df.index.max()) # 查看最近5行 print(close_df.tail())这一步是整篇文章最容易出错的地方。如果直接 dropna,可能因为节假日差异丢掉大量数据;如果先 ffill 再 dropna,可以保留更多样本,但要注意起始阶段可能因为填充产生不规范数据。稳妥做法是:先检查缺失值比例,超过1%再考虑业务上是否要剔除该时段。
5.4 计算日收益率与滚动相关性
# 计算日收益率 ret_df = close_df.pct_change().dropna() # 计算滚动相关系数,窗口设为 60 个交易日 window = 60 roll_corr_gold_dxy = ret_df["GLD"].rolling(window).corr(ret_df["DXY"]) roll_corr_gold_us10y = ret_df["GLD"].rolling(window).corr(ret_df["US10Y"]) # 计算全样本普通相关系数作为对照 full_corr_gold_dxy = ret_df["GLD"].corr(ret_df["DXY"]) full_corr_gold_us10y = ret_df["GLD"].corr(ret_df["US10Y"]) print(f"全样本 GLD 与 DXY 相关系数:{full_corr_gold_dxy:.4f}") print(f"全样本 GLD 与 US10Y 相关系数:{full_corr_gold_us10y:.4f}")滚动相关性输出的是一个带日期的序列,可以很直观地看出负相关关系什么时候变弱、什么时候反转。这是静态表格给不了的信息。
5.5 可视化输出
# 创建 3 行子图,共享 x 轴 fig, axes = plt.subplots(3, 1, figsize=(12, 14), sharex=True) # 第一幅:价格走势 ax1 = axes[0] ax1.plot(close_df.index, close_df["DXY"], label="DXY (美元指数)", color="#1f77b4") ax1.set_ylabel("DXY") ax1.legend(loc="upper left") ax1.grid(True, alpha=0.3) ax1b = ax1.twinx() ax1b.plot(close_df.index, close_df["GLD"], label="GLD (黄金)", color="#ff7f0e") ax1b.set_ylabel("GLD Price") ax1b.legend(loc="upper right") # 第二幅:10年期美债收益率 ax2 = axes[1] ax2.plot(close_df.index, close_df["US10Y"], color="#d62728", label="US10Y (%)") ax2.set_ylabel("US10Y (%)") ax2.legend(loc="upper left") ax2.grid(True, alpha=0.3) # 第三幅:滚动相关系数 ax3 = axes[2] ax3.plot(roll_corr_gold_dxy.index, roll_corr_gold_dxy, label="GLD-DXY 滚动相关", color="#2ca02c") ax3.plot(roll_corr_gold_us10y.index, roll_corr_gold_us10y, label="GLD-US10Y 滚动相关", color="#9467bd") ax3.axhline(0, color="black", linestyle="--", linewidth=0.8) ax3.set_ylabel("滚动相关系数") ax3.set_ylim(-1, 1) ax3.legend(loc="lower left") ax3.grid(True, alpha=0.3) plt.tight_layout() # 保存图片,方便用于报告或文档 plt.savefig("macro_correlation.png", dpi=150) plt.show()这里的可视化逻辑很简单:第一幅图用双 y 轴对比美元指数与黄金走势,第二幅图看美债收益率,第三幅图用滚动相关系数观察联动变化。视觉上很容易发现:美元指数与黄金的滚动相关系数长期在 -0.6 到 -0.3 之间波动,但在某些阶段可能跌破 -0.7 或回到 -0.1 附近,这个波动本身就值得深入分析。
5.6 使用 FRED 数据源获取美债收益率(备用方案)
如果 yfinance 的^TNX数据不稳定,可以使用 FRED 的DGS10(10年期美国国债收益率)作为替代数据源。
import pandas_datareader.data as web from datetime import datetime # 从 FRED 获取 10 年期美债收益率 start = datetime.now() - timedelta(days=730) end = datetime.now() try: dgs10 = web.DataReader("DGS10", "fred", start, end) dgs10 = dgs10.dropna() print("FRED 数据拉取成功") print(dgs10.tail()) except Exception as e: print(f"数据拉取失败:{e}")FRED 的数据是按日发布的,但发布日期可能有滞后,而且缺失值较多,因为美国国债市场有固定节假日。用它做宏观研究很专业,但实时性不如 yfinance。
6. 运行结果与效果验证
上述代码运行后,你应该能看到至少三样东西:
- 终端输出打印的三列数据(DXY、US10Y、GLD)的缺失值统计。
- 终端输出的全样本相关系数。
- 一张保存为
macro_correlation.png的三子图图表。
当你看到终端输出类似下面这样时,说明数据处理基本正常:
数据拉取完成,缺失值统计: DXY 12 US10Y 8 GLD 0 dtype: int64 对齐后的样本数量:496 GLD 与 DXY 相关系数:-0.6823 GLD 与 US10Y 相关系数:-0.5741如何判断这个结果是合理的?
首先,缺失值数量不会太大。如果缺失值超过全部交易日的20%,需要检查网络、代码 ticker 是否写错,或者是否选错了时间范围。其次,GLD 与 DXY 的相关系数应该为负,且绝对值通常在0.4以上,如果符号为正,大概率是数据对齐出了问题,或者期间样本中包含极端异常的“流动性危机”时段。最后,图表第三幅子图中的滚动相关系数应该在 -1 到 1 之间波动,如果全部是常数值,说明代码逻辑有 bug。
如果运行失败,第一步看报错信息是否涉及“No data found for symbol”。如果是,优先检查网络连接;如果网络正常,多半是 yfinance 临时限流,稍等重试即可。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| yfinance 返回空数据 | 网络受限或接口临时限流 | 打印返回的对象结构,查看报错信息 | 更换网络环境;使用period="1y"简化参数;等待后重试 |
| DXY 数据只有几百行 | DX-Y.NYB在某些时段无交易记录 | 检查缺失值统计和日期范围 | 接受真实缺失,用前向填充补齐;或改用代码DX=F |
| 滚动相关系数出现 NaN | 窗口内有效数据不足,或收益率序列开头缺失 | 用dropna()检查序列长度;检查窗口是否超过数据长度 | 扩大窗口或增加历史数据;对滚动结果执行dropna() |
| 图表中中文乱码 | matplotlib 缺少中文字体 | 查看当前字体列表plt.rcParams['font.family'] | 安装中文字体并重新配置;或将图表标签改为英文 |
^TNX收益率数值很大 | 单位是百分数而非小数 | 打印数据最后几行,观察取值范围 | 除以100转换成小数后再计算 |
| 两个数据源的日期不对齐 | 不同市场节假日不同 | 检查各自索引的日期列表 | 用reindex(union_dates)对齐后前向填充 |
| 相关系数符号与“常识”相反 | 数据中包含极端行情或事件窗口 | 拆分时间段分别计算相关系数 | 增加事件标记列,按事件时段分组分析 |
8. 最佳实践与工程建议
把宏观联动分析做成可持续运行的任务,有几件事从一开始就应该想清楚。
8.1 规范化命名与目录结构
建议把脚本按功能拆分,而不是一个大文件写完所有逻辑。例如:
macro_analysis/ ├── config.py # 常量配置:ticker、时间窗口、输出路径 ├── data_fetch.py # 数据拉取模块 ├── data_clean.py # 清洗与对齐模块 ├── indicators.py # 指标计算模块:收益率、滚动相关 ├── plot.py # 可视化模块 └── run.py # 主入口,串联所有模块这样任何一步报错,都能快速定位,也方便团队协作。
8.2 数据落盘与增量更新
每次从 yfinance 全量拉两年数据,既慢又容易被限流。更合理的做法是把历史数据缓存到本地 CSV 或数据库,每天只增量拉取新交易日的几条数据。代码层面可以使用last_date = pd.read_csv("cache.csv", index_col=0).index.max()判断上次拉取时间,再决定本轮的起始日期。
8.3 对数据源做健康检查
远程数据源不可控,建议在脚本开头加一个简单检查:如果某列缺失值超过总样本的5%,就告警而不是直接静默填充。这样能防止“坏数据喂进策略”导致错误决策。
8.4 不要只看全样本相关性
全样本相关系数只是平均水平,实际交易中滚动相关性更有参考价值。建议在输出图表时,把滚动相关系数的均值、标准差、极端值一并打印出来。例如:
print(f"滚动相关均值:{roll_corr_gold_dxy.mean():.4f}") print(f"滚动相关标准差:{roll_corr_gold_dxy.std():.4f}") print(f"滚动相关最小值:{roll_corr_gold_dxy.min():.4f}") print(f"滚动相关最大值:{roll_corr_gold_dxy.max():.4f}")这个统计量能告诉你,负相关关系到底是稳定的常态,还是只在少数时间段出现,对判断策略适用范围很重要。
8.5 安全与合规边界
本文涉及的代码只处理公开市场数据,不构成任何投资建议。如果你把类似脚本用于实盘决策,一定要有严格的风控逻辑,包括但不限于:回撤上限、仓位控制、状态监控和回滚机制。代码层面,建议所有对外部数据源的访问都设置超时与重试机制,避免单次请求失败导致整个任务崩溃。
9. 总结与后续学习方向
这篇文章做的事情,本质上是一条“数据工程 + 统计检验”的流水线:先拉取多资产日线数据,再做日期对齐与缺失值清洗,接着计算收益率和滚动相关系数,最后通过可视化观察联动关系的变化。这套能力可以迁移到很多场景,比如股票与债券的跷跷板分析、商品与汇率的传导研究,甚至加密货币与纳斯达克的联动追踪。
如果你刚入门量化分析,建议先把今天的代码完整跑一遍,再把时间窗口改成90天、120天对比结果差异。下一步可以往两个方向深入:一是使用 VECM 或协整检验研究变量间的长期均衡关系;二是引入实际利率、通胀预期等宏观变量,建立更立体的多因子分析框架。
最后提醒一点:数据源和数据质量会直接影响分析结论,任何“美元暴跌导致黄金上涨”之类的结论,都应该回归到原始数据上进行验证,而不是只看新闻标题。把这套代码收藏备用,下一次再遇到类似行情时,你就能用数据而不是感觉说话了。