1. 项目概述
这两年不管是做量化的、做资产配置的、还是单纯研究宏观策略的朋友,多多少少都绕不开一个问题——宏观经济数据和A股大盘之间到底存不存在可量化的联动关系?CPI涨跌、PPI波动、M2增速、PMI荣枯线,这些数字每天都被各方解读,但落到实证层面,用Python把宏观数据和沪深指数放在一起跑回归、算相关性、看领先滞后关系的完整案例,其实并不多见。
这个项目的核心思路,就是搭一条从数据采集、清洗、标准化、到相关性分析和回归建模的完整流程,用真实的历史数据验证宏观指标对沪深指数的解释力度。整个项目不依赖昂贵的Wind终端,全部用开源数据源加Python常用库就能跑通。项目最终产出的不是一篇论文式的严肃研究,而是一套可复用的分析框架——换一批指标、换一个指数,代码逻辑完全可以直接迁移。
适合来参考这个项目的人分三类:一是刚学完Python基础语法,想找一个完整的、带业务含义的数据分析练手项目的人;二是做宏观策略研究,想系统梳理经济指标与股市关系的研究岗新人;三是想为自己的简历增加一个有深度、可量化、能落地的数据分析项目案例的求职者。如果你属于其中任何一类,这篇文章值得认真看完,文末我还会把实操过程中踩过的坑一一列出来。
2. 分析思路与框架设计
2.1 为什么选这些宏观经济指标
研究宏观与股市的关系,第一步不是写代码,而是想清楚选哪些指标。我见过不少人上来就直接用tushare拉二十个经济指标塞进回归模型里,结果多重共线性爆表,模型解释一团乱,最后只能草草收场。
真正靠谱的做法是按经济学传导逻辑来挑选。我这次选用了四类指标:
第一类是通胀类指标,包括CPI同比和PPI同比。CPI反映居民端消费价格变化,直接影响货币政策取向和消费板块盈利预期;PPI反映工业生产端的出厂价格变化,与周期性行业的利润高度相关。两者在逻辑上对股市的传导路径是清晰的:PPI上行通常意味着工业品需求旺盛或供给收缩,利好上游资源类企业,但PPI过高又会引发加息担忧,对成长股形成压制。
第二类是增长类指标,主要是PMI(采购经理人指数)。PMI是月度发布的最早的宏观经济先行指标,50是荣枯线,高于50说明制造业处于扩张区间。股市交易的是预期,PMI这类先行指标的理论价值天然高于GDP等滞后指标。
第三类是货币类指标,选M2同比增速和社会融资规模存量同比。M2是广义货币供应量,代表整个社会的货币投放量,流动性充裕与否对资产价格的影响是最直接的“放水”逻辑。社融则是实体经济的融资需求,代表信用扩张的意愿和实际力度。
第四类是利率类指标,选10年期国债收益率。它代表市场无风险利率的水平,理论上与股市估值呈反向关系——利率上行时,股债性价比的天平会向债券倾斜,股市估值中枢被动下移。
这四类指标正好覆盖“增长、通胀、流动性与货币、利率”四个维度,逻辑上互有补充但又不过度重叠。选指标的时候我给自己定了一个标准:每个指标都必须有一条从“发布→传导→影响股市”的清晰故事线,说不清传导机制的指标不进模型。
2.2 指数与时间窗口的选择逻辑
指数方面,我同时选取了上证指数(000001.SH)和沪深300(000300.SH)。上证指数覆盖全市场,代表“大盘整体”的感受;沪深300代表A股核心资产的走势,机构资金和外资更关注的是沪深300。两个指数放在一起对比,可以识别出宏观指标对宽基指数和核心资产的不同影响差异。
时间窗口选的是2015年1月到2023年12月,整整9年。这个区间很关键,它完整覆盖了2015年牛市泡沫与股灾、2016年熔断、2017年蓝筹行情、2018年贸易摩擦熊市、2019年至2021年初的核心资产牛市、2021年至2022年的结构性调整、2022年至2023年的震荡磨底。行情周期足够完整,宏观环境上则经历了宽松、收紧、再宽松、通胀抬头等多种状态,样本多样性对实证研究来说非常重要——只拿一段单边行情的样本做分析,结论没有任何参考价值。
2.3 技术栈选型与版本说明
整个项目用到的库如下:
- Python 3.9以上,建议用Anaconda安装,不用一步步配环境,省心很多
- pandas:数据处理的核心库,所有清洗、对齐、计算都在pandas里完成
- numpy:数学计算底层支持
- matplotlib和seaborn:可视化绘图,一个管基础图形,一个管统计图形
- statsmodels:回归建模,相比sklearn,statsmodels输出的回归摘要更接近计量经济学论文风格,有系数显著性检验、R方、F统计量等全套指标
- scipy:统计检验支持
- tushare或akshare:数据源,后面单独讲
版本上只要不是太老的版本基本都能跑通,我本地用的是pandas 2.0.3和statsmodels 0.14.0,py3.10环境,兼容性没问题。
3. 数据获取与预处理
3.1 数据源对比与选择
数据分析项目里,数据源是第一个分水岭。数据错了,后面模型再漂亮都是白搭。
我这次对比了三个方案:
tushare:国内量化圈最常用的数据接口,提供股票、基金、期货、宏观等数据。有积分门槛,高积分享受更高频的数据和更大额度的调用,普通注册用户也能拿到日线级别的基本数据,宏观数据用pro接口拉取,300积分就能解锁大部分内容。社区活跃,遇到问题容易找到解决方案。
akshare:完全免费、不需要注册token,接口直接爬取公开网页数据。好处是零门槛,坏处是接口变动频繁,今天能跑的代码可能下周就失效,数据稳定性略差。
Wind和Choice:金融终端级别的数据质量,数据标准统一,带数据字典和校验文档,但年费几万块,个人项目和经济型学习者没必要上。
我个人建议:做这个项目用tushare pro作为主数据源,具体接口为:
- 沪深指数日线行情:index_daily
- CPI同比、PPI同比、PMI、M2同比、社融存量同比:这些在tushare的宏观数据接口里都有,CPI和PPI走cn_cpi和cn_ppi,PMI走cn_pmi,M2和社融走cn_m2和cn_sf
如果注册后积分不够,可以改用akshare,接口文档里搜索macro_china相关字段也能拿到同样的宏观数据,只是格式需要自己调。
3.2 数据对齐与缺失值处理
宏观数据和指数数据的频率天然不同:宏观指标按月发布,指数行情每天都有。要让两者能一起分析,统一频率是第一步。
我的处理方式是“宏观数据月频化对齐指数月末收盘价”,具体来说:
把每个宏观指标都重采样成月度序列,然后取每个交易日对应月份的宏观指标值。代码逻辑大概是:
# 宏观数据月频转日频对齐 macro_monthly['year_month'] = macro_monthly.index.to_period('M') index_daily['year_month'] = index_daily.index.to_period('M') # 用merge把宏观指标对齐到每日行情上 df = pd.merge(index_daily, macro_monthly, on='year_month', how='left')这里有个关键细节:对齐的时候只能向前填充,不能向后填充。为什么?宏观经济指标的公布有滞后性。比如3月份的CPI数据要到4月上旬才公布,那么在3月份的每一个交易日,市场参与者看到的其实是2月份的CPI数据。直接取当月值再做分析,就犯了“未来函数”的错误——把当时市场上根本看不到的信息当成已知信息来建模,结果必然虚高。
所以正确的做法是:宏观数据做一期shift(滞后一个月)再做对齐。这个细节是我在复盘时发现的,也是很多入门项目最常踩的坑。数据对齐之后进行缺失值处理——因为merge之后是左连接,遇上月初前几天可能没有数据,或者宏观数据本身的公布日期与发布时间错位,会留有空挡。我的处理原则很粗暴:连续缺失超过3个交易日就删除对应月份的分析样本,缺失1到2天用前后相邻的数据做线性插值。实证研究中样本量本来就不大,宁可少一点,也别用拍脑袋造出来的数据。
3.3 标准化与平稳化处理
原始数据直接进模型是不行的。
看几个原始序列的量级:指数点位是3000到5000的范围,M2同比是8%到15%的范围,PMI在49到52之间波动。不同量纲的数据放在同一模型里,回归系数的大小直接受数量级影响,没法横向比较。所以我先对连续型变量做了z-score标准化:
$$x_{standard} = \frac{x - \mu}{\sigma}$$
标准化之后,每个指标都变成均值为0、标准差为1的序列,回归系数的大小就可以直接比较相对重要性了。
另外还有个重要问题:原序列不平稳。经济数据和股市点位通常是带趋势的序列,直接做回归可能出现“伪回归”——其实两个序列只是各自随时间上涨,根本没有内在联系,但回归出来的系数依然显著。检验平稳性的标准工具是ADF单位根检验。
实操中我的处理是:宏观指标和指数点位都用“同比变化率”或“对数收益率”做变换,这样既能让序列平稳,又保留了业务含义。指数用对数收益率,宏观指标用同比值,ADF检验的p值都小于0.05,平稳性过关。
4. 探索性数据分析
4.1 相关性矩阵初探
数据预处理完成之后,先不急着建模,用seaborn画一张相关性热力图,把所有变量之间的两两相关性一次性展示出来。
import seaborn as sns import matplotlib.pyplot as plt corr_matrix = df[['sh_return', 'hs300_return', 'cpi_yoy', 'ppi_yoy', 'pmi', 'm2_yoy', 'sf_yoy', 'bond_yield']].corr() plt.figure(figsize=(10, 8)) sns.heatmap(corr_matrix, annot=True, cmap='RdBu_r', center=0, fmt='.2f') plt.title('Macro Indicators vs Index Returns Correlation Matrix') plt.tight_layout() plt.show()第一遍跑出来的结果很有意思,但也很有迷惑性。PPI同比与沪深300月收益率的同期相关系数只有0.12左右,M2同比与上证指数收益率的相关系数是0.08,全都不显著。如果只看这个结果,好像宏观指标对股市几乎没有解释力。
但先别急着下结论——同期相关性不显著不代表没有关系,恰恰有可能是“关系存在但存在时滞”。宏观政策从出台到影响企业盈利,再到反映到股价上,中间有传导链条和时间差。比如2015年央行多次降息降准,M2增速在年中见顶,但股市的高点出现在6月,宏观流动性的拐点和市场拐点并不完全同步。这种领先滞后关系用同期相关性是看不出来的,需要做滞后相关分析。
4.2 走势图里的故事
我画了双轴折线图来对比上证指数和各宏观指标的走势,这一步虽然不产生任何统计量,但对理解数据非常有帮助。
PPI和周期股的走势对比最有叙事感。2016年到2017年供给侧结构性改革推进,PPI同比从-5%左右一路拉升到7%以上,同期沪深300走出了一波蓝筹行情;2021年PPI冲高到13.5%的历史高位,但沪深300反而在年初见顶后一路走弱。这说明什么?PPI上行初期对周期板块是利润改善的利好,但当PPI过高引发通胀预期和货币政策收紧担忧时,对整体估值的压制作用会超过盈利改善的利好。
M2增速和股市的关系也值得琢磨。2015年M2增速高点在12%左右,股市泡沫也差不多同步见顶;2020年M2增速短暂冲高到11.1%,随后回落,同期核心资产的估值顶出现在2021年春节前后。这背后是流动性“放水→资产价格上行→边际收紧→估值回落”的完整周期。
这些图形分析虽然不能直接产出量化结论,但帮助我建立了一个直觉:宏观指标和股市的关系大概率是非线性的、时变的、有滞后的。带着这个直觉再去做建模,至少不会跑出来什么结果都硬解释。
4.3 滞后相关性扫描
为了验证“宏观指标领先股市”这个假设,我写了一个滞后相关性分析,分别计算各宏观指标在领先1个月、3个月、6个月、12个月的情况下与指数收益率的相关系数:
def lag_corr_analysis(df, macro_cols, target_col, max_lag=12): results = [] for col in macro_cols: for lag in range(1, max_lag + 1): corr = df[col].corr(df[target_col].shift(-lag)) results.append({'indicator': col, 'lag_months': lag, 'correlation': corr}) return pd.DataFrame(results)有意思的结果出来了:PMI领先6个月的相关系数达到了0.3左右,显著性明显强于同期相关性;10年期国债收益率的变动领先市场约9到12个月出现负相关性,符合利率周期与股市估值周期逆向运动的逻辑。
这组结果说明一个核心问题:宏观指标对股市的影响不是即时反馈,而是有传导时滞的。分析宏观和股市的关系,必须把“领先、同步、滞后”的结构考虑进去,这本身就是这个项目最有价值的增量信息。
5. 实证建模与结果分析
5.1 多元线性回归模型的搭建
探索性分析给了方向后,我开始搭建正式的实证模型。
模型1是同期多元回归:
$$IndexReturn_t = \beta_0 + \beta_1 CPI_t + \beta_2 PPI_t + \beta_3 PMI_t + \beta_4 M2_t + \beta_5 SF_t + \beta_6 Yield_t + \varepsilon_t$$
模型2加入领先项,把宏观指标滞后6期再放进模型:
$$IndexReturn_t = \beta_0 + \beta_1 CPI_{t-6} + \beta_2 PPI_{t-6} + \beta_3 PMI_{t-6} + \beta_4 M2_{t-6} + \beta_5 SF_{t-6} + \beta_6 Yield_{t-6} + \varepsilon_t$$
用statsmodels跑回归时要注意模型的稳健标准误设置,我直接用了HC1异方差稳健标准误,避免因为金融数据的异方差性导致t统计量虚高:
import statsmodels.api as sm X = df[['cpi_yoy', 'ppi_yoy', 'pmi', 'm2_yoy', 'sf_yoy', 'bond_yield']] y = df['hs300_return'] X = sm.add_constant(X) model = sm.OLS(y, X).fit(cov_type='HC1') print(model.summary())同期模型的结果并不意外:整体模型的F统计量不显著,R方只有0.06左右,各系数的t值也没有一个超过2。这说明当期宏观经济数据对当期股市收益率几乎没有解释能力——市场永远在交易预期而不是交易当下。
滞后6个月的模型有了质的改善:整体模型的R方上升到0.18,在金融数据里这个解释力度已经不算低了。PMI的系数为正且在5%水平上显著,PPI系数为正但在10%水平边缘显著,国债收益率的系数为负且显著。方向符合经济逻辑:PMI回升预示企业盈利预期改善,推升指数;利率上行压制估值,拉低指数。
5.2 多重共线性检验
几个宏观指标之间本身就有交叉影响,比如PPI和CPI同属价格指标,M2和社融都反映流动性状况。变量之间如果高度相关,会出现“系数估计不稳定、符号与经济学常识相悖”的问题。
我算了VIF(方差膨胀因子),规则是VIF大于10就说明该变量与其他变量存在严重共线性:
from statsmodels.stats.outliers_influence import variance_inflation_factor X_vif = X.drop('const', axis=1) vif_data = pd.DataFrame() vif_data['variable'] = X_vif.columns vif_data['VIF'] = [variance_inflation_factor(X_vif.values, i) for i in range(X_vif.shape[1])]跑出来的结果,社融存量同比的VIF超过15,M2的VIF在9附近,两个流动性指标的信息重叠度过高。我做了取舍:去掉社融,保留M2同比。因为M2代表央行的货币投放意愿,含义更纯粹;社融混入了信贷需求端的波动,和M2高度相关但噪音更多。
调整之后,模型里保留CPI、PPI、PMI、M2同比、10年期国债收益率五个核心指标。VIF全部降到5以下,回归系数的符号也稳定了,不再出现系数翻转的异常现象。
5.3 稳健性检验与结果解读
为了让结论更有说服力,我做了几组稳健性检验。
第一个是把样本区间分成两段,2015到2019一段,2020到2023一段,分别跑回归。分段之后PMI领先项的显著性在2020年之后明显增强,结构性牛市阶段宏观因子对市场的解释力更强,熊市和震荡市里市场更受风险偏好和资金面主导。这个现象本身就是值得在结论里讨论的内容。
第二个是替换被解释变量,把沪深300收益率换成上证指数收益率再跑一遍,系数的正负方向和显著性基本保持不变,说明结果对指数选择不敏感。
最终模型的结论可以归纳为三条:
- PMI对沪深300未来6个月的收益率有显著正向影响,PMI每上升1个百分点(标准差标准化后),未来半年沪深300月均收益率大约提升0.3个百分点,对应年化约3.6个百分点。
- 10年期国债收益率对指数6个月后的收益率有显著负向影响,利率上行阶段股市估值承压,这在样本期内反复得到验证。
- M2增速的影响在不同时段不稳定,单纯看货币供应量增速和股市涨跌的关系容易误判,需要结合货币政策的边际方向来判断——M2增速上行早期利好估值,持续高位反而可能预示政策收紧,转折点才是关键。
5.4 数据可视化呈现
分析的结果要能“一眼看懂”,可视化这关不可省。我做了一张核心图——PMI领先6个月的滞后曲线与沪深300对数收益率走势对比:
fig, ax1 = plt.subplots(figsize=(14, 6)) ax1.plot(lagged_pmi.index, lagged_pmi.values, color='#d62728', linestyle='--', label='PMI (t-6)') ax1.set_xlabel('Date') ax1.set_ylabel('PMI', color='#d62728') ax1.tick_params(axis='y', labelcolor='#d62728') ax1.legend(loc='upper left') ax2 = ax1.twinx() ax2.plot(hs300.index, hs300.values, color='#1f77b4', label='HS300 Log Return MA3') ax2.set_ylabel('HS300 Log Return', color='#1f77b4') ax2.tick_params(axis='y', labelcolor='#1f77b4') ax2.legend(loc='upper right') plt.title('PMI Leading 6 Months vs HS300 Returns') plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('pmi_hs300_lag6.png', dpi=150)把PMI序列整体向前平移6个月之后再和沪深300走势叠加,能明显看到两边的波动方向多数时候是同步的,PMI的拐点经常比市场早半年左右出现。这种可视化的说服力比任何回归表格都直观。
6. 实操踩坑与排查经验
6.1 数据频率对齐的坑
这是整个项目里最折磨人的环节。我最初直接用merge把月频宏观数据和日频指数数据合并在一起,结果发现出现大量缺失值。原因是宏观数据的发布时间不是标准的自然月末,有的指标因为假期调整在月中发布。而merge用月份字段匹配时,日频数据的月份和月频数据的索引对不上。
解决思路是用PeriodIndex对齐月份,然后ffill填充,再用merge_asof做时间点上的最近匹配。这个方案处理完,数据整齐多了。merge_asof是pandas里一个很好的工具,专门处理带有时间差的拼接。
6.2 未来函数的致命陷阱
如果某个宏观指标的发布日期滞后一个月,那么当月的全部交易日都应该使用上个月的指标值,而不是当月值。我一开始用当期值分析,跑出来的PMI与指数相关系数是0.02,毫无解释力。后来把数据整体shift一个月,相关系数立刻跳到了0.2以上。前后差别之大,让人心惊。金融实证分析中,数据时间口径稍微弄错,结论就可能完全相反。
6.3 样本量的问题
月度频率的样本量9年一共只有108个月,这是天然限制。加入6个滞后变量做回归时,自由度下降很快,模型很容易过拟合。我的建议是宁可牺牲一些变量数量,也不要让样本量被过度消耗。这个项目最终保留5个解释变量加1个常数项,勉强算是够用。
6.4 环境配置的经验
我遇到过在云服务器上装tushare,跑代码的时候提示缺少SSL证书的坑。解决方法是安装certifi库,然后设置环境变量。Windows上市和Mac上直接pip install pandas numpy matplotlib seaborn statsmodels scipy tushare一条命令搞定,Anaconda里自带大部分库,只需要补装tushare国内接口。
新的机器跑statsmodels的OLS回归时还容易遇到LAPACK库缺失的低级报错,直接在conda环境里重新安装numpy和scipy基本能解决。
7. 项目扩展方向
7.1 加入市场情绪变量
宏观指标属于基本面维度,但股市短期定价很大程度上由情绪主导。可以引入融资融券余额、换手率、北向资金净流入等情绪指标做对照模型,看看在宏观因子的基础上加入情绪因子后模型的解释力能提升多少。
7.2 换用机器学习模型
线性回归能抓变量间的线性关系,但这些宏观因子和股市之间大概率存在非线性。XGBoost、随机森林、或者带L1惩罚的Lasso回归都可以尝试。Lasso能自动筛选重要特征,尤其适合样本量有限但变量较多的场景。滚动时间窗口训练测试,既能扩大有效样本,又能观察因子重要性的时变特征。
7.3 分批对比不同市场周期
2015年的“杠杆牛”、2018年的“政策底”、2020年的“疫情牛”和2022年的“弱复苏”,背后的宏观驱动逻辑完全不一样。可以把样本分开建模,然后对比不同市场阶段里宏观因子的显著性和符号差异,这也是很多Quant Researcher在实际工作中常用的做法。
7.4 预测模型落地
做预测可以直接在现有框架上改。把沪深300未来3个月的收益率当作标签,用当前可得的宏观数据训练模型,再用滚动预测的方式回测。这个方向落地价值很强——一个能稳定跑赢随机基准的宏观择时模型,本身就是很好的策略雏形。需要提醒的是,预测一定有失败概率,回测里的表现不等于实盘表现,谨慎对待高收益率的结果。
8. 个人心得与建议
整个项目从构思到跑通,我最想分享的经验是:数据分析项目的关键节点从来不是代码本身,而是每一步背后的业务决策。
选指标的时候要想清楚传导逻辑,处理数据的时候要盯紧时间口径,建模型之前先检验平稳性,跑完回归要回头看看符号和经济学常识是否一致。代码只是把这些判断执行出来的工具,真正值钱的是你做的每一个假设和选择。
如果你正准备拿这个项目去面试,建议重点关注三块内容:一是数据预处理的细节,尤其是滞后期和未来函数的处理逻辑,面试官很容易问这个;二是多重共线性的识别与处理,这体现你对统计模型的深层理解;三是滞后分析的设计思路,这体现你解决问题的能力。
一个能跑通、能讲清楚、能经得起追问的数据分析项目,从实际价值来说,比三个半懂不懂的课程项目更有说服力。希望我整理的这些思路和踩坑记录,能帮你绕过那些我绕过的弯路,让你的实证分析之路顺畅一些。