5.3 宏观因子:抬头看天

再牛逼的选股策略,遇上股灾也得跪。你挑对了马,天上下刀子------照样死。

你花了三个月。

选股模型调了又调。

回测跑了一遍又一遍。

年化收益25%。

夏普比率1.8。

完美。

然后------

2015年6月来了。

千股跌停。

你的模型说:买入。

你的账户说:操你妈。

为什么?

因为你没看天。

个股策略是术,宏观环境是道。术再精,逆不了道。

一、什么是宏观因子

说白了------

大势。

GDP增速。

CPI。

M2。

利率。

社融。

PMI。

这些玩意儿,

你一个人改不了。

任何一只个股改不了。

任何一家公司改不了。

但它能决定------

你的策略是赚钱还是亏钱。

打个比方。

个股策略是船。

宏观因子是海。

风平浪静,破船也能到岸。

惊涛骇浪,航母也得翻。

你永远在跟宏观作对,只是你不知道而已。

二、六大宏观指标,逐个扒皮

1. GDP增速

GDP------国内生产总值。

通俗讲:

一个国家赚了多少。

增速高------经济热。

企业赚钱。

股市涨。

增速低------经济冷。

企业亏钱。

股市跌。

简单粗暴。

但有个问题------

GDP是滞后指标

它告诉你"上季度怎么了"。

不告诉你"下季度会怎样"。

所以------

光看GDP,等于看着后视镜开车。

2. CPI

居民消费价格指数。

就是物价。

CPI温和上涨(1%-3%)------

经济健康。

通胀可控。

利好股市。

CPI太高(>5%)------

通胀失控。

央行加息。

股市挨锤。

CPI为负------

通缩。

大家不消费。

经济凉了。

3. M2

广义货币供应量。

就是------

市场上有多少钱。

M2增速高------

放水。

钱多。

资产价格涨。

股市涨。

M2增速低------

收水。

钱少。

资产价格跌。

股市跌。

A股有句老话:

M2是A股的亲妈。放水涨,收水跌,就这么简单。

4. 利率

十年期国债收益率。

无风险利率。

利率低------

存银行没意思。

钱往股市跑。

股市涨。

利率高------

存银行躺赚。

钱从股市跑。

股市跌。

而且------

利率是所有资产定价的锚。

DCF模型里,分母就是利率。

利率升,估值降。

利率降,估值升。

反比关系。

铁律。

5. 社融

社会融资规模。

就是实体经济从金融体系借了多少钱。

社融高------

企业在借钱。

要扩张。

经济好。

股市涨。

社融低------

企业不借钱。

收缩。

经济差。

股市跌。

社融是领先指标

比GDP有用。

6. PMI

采购经理指数。

就是问一堆采购经理:

"你们公司这个月怎么样?"

PMI > 50------扩张。

PMI < 50------收缩。

也是领先指标。

每月月初就出。

比GDP快得多。

三、宏观温度计:给经济量个体温

六个指标。

各说各的话。

GDP说:还行。

CPI说:要涨。

M2说:在放水。

利率说:要加息。

到底听谁的?

全听。

做个综合评分。

我管它叫------

宏观温度计。

逻辑很简单:

六个指标,每个打0-100分。

加权平均。

得到一个综合温度。

温度高------经济热。

满仓干。

温度低------经济冷。

减仓保命。

温度中等------半仓。

别浪。

代码来了。

// python

安装依赖:pip install akshare pandas numpy matplotlib

import akshare as ak
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

plt.rcParams'font.sans-serif' = 'SimHei'
plt.rcParams'axes.unicode_minus' = False

============================================

第一步:获取宏观数据

============================================

1. GDP增速(季度)

def get_gdp():
"""获取GDP同比增速"""
df = ak.macro_china_gdp()
df'季度' = pd.to_datetime(df'季度')
df = df.sort_values('季度').reset_index(drop=True)

取"累计同比增长"列(即GDP同比增速)

df'gdp_yoy' = df'累计同比增长'.astype(float)
return df\['季度', 'gdp_yoy'].dropna()

2. CPI同比(月度)

def get_cpi():
"""获取CPI同比"""
df = ak.macro_china_cpi_monthly()
df'月份' = pd.to_datetime(df'月份')
df = df.sort_values('月份').reset_index(drop=True)
df'cpi_yoy' = df'全国-同比'.astype(float)
return df\['月份', 'cpi_yoy'].dropna()

3. M2增速(月度)

def get_m2():
"""获取M2同比增速"""
df = ak.macro_china_m2_yearly()
df'月份' = pd.to_datetime(df'月份')
df = df.sort_values('月份').reset_index(drop=True)
df'm2_yoy' = df'同比'.astype(float)
return df\['月份', 'm2_yoy'].dropna()

4. 十年期国债收益率(月度取均值)

def get_rate():
"""获取十年期国债收益率"""
df = ak.bond_china_yield(start_date="20100101")
df'日期' = pd.to_datetime(df'日期')

取"中国国债收益率10年"

df_10y = df\['日期', '中国国债收益率10年'].copy()
df_10y'中国国债收益率10年' = df_10y'中国国债收益率10年'.astype(float)
df_10y'年月' = df_10y'日期'.dt.to_period('M')
df_monthly = df_10y.groupby('年月')'中国国债收益率10年'.mean().reset_index()
df_monthly'月份' = df_monthly'年月'.dt.to_timestamp()
df_monthly'rate_10y' = df_monthly'中国国债收益率10年'
return df_monthly\['月份', 'rate_10y'].dropna()

5. 社融(月度)

def get_social_financing():
"""获取社会融资规模"""
df = ak.macro_china_shrzgm()

该接口返回的列名可能有变化,做兼容处理

df = df.iloc:36 # 取最近36个月
df.columns = '月份', '社融规模' + list(df.columns2:)
df'月份' = pd.to_datetime(df'月份', errors='coerce')
df'社融规模' = df'社融规模'.astype(float)
df = df.sort_values('月份').reset_index(drop=True)

计算同比增速

df'sf_yoy' = df'社融规模'.pct_change(12) * 100
return df\['月份', '社融规模', 'sf_yoy'].dropna()

6. PMI(月度)

def get_pmi():
"""获取制造业PMI"""
df = ak.macro_china_pmi()
df'月份' = pd.to_datetime(df'月份')
df = df.sort_values('月份').reset_index(drop=True)
df'pmi' = df'制造业-指数'.astype(float)
return df\['月份', 'pmi'].dropna()

============================================

第二步:各指标打分(0-100)

============================================

def score_gdp(val):
"""GDP增速打分:越高越好"""

GDP增速 4%-10% 为正常区间

if val >= 8: return 90
elif val >= 6: return 70
elif val >= 4: return 50
elif val >= 2: return 30
else: return 10

def score_cpi(val):
"""CPI打分:温和通胀最好"""

CPI 1%-3% 最优

if 1 <= val <= 3: return 80
elif 0 <= val < 1: return 50
elif 3 < val <= 5: return 40
elif val > 5: return 10 # 恶性通胀
else: return 20 # 通缩

def score_m2(val):
"""M2增速打分:适度放水好"""

M2增速 8%-12% 为适度

if 8 <= val <= 12: return 75
elif val > 12: return 85 # 大放水,短期利好
elif 5 <= val < 8: return 45
else: return 20 # 严重收紧

def score_rate(val):
"""利率打分:越低越好(对股市)"""

利率 2%-4% 为正常区间

if val <= 2.5: return 85
elif val <= 3.0: return 70
elif val <= 3.5: return 50
elif val <= 4.0: return 30
else: return 15

def score_sf(val):
"""社融增速打分:越高越好"""
if val >= 15: return 90
elif val >= 10: return 70
elif val >= 5: return 50
elif val >= 0: return 30
else: return 10

def score_pmi(val):
"""PMI打分:50以上扩张"""
if val >= 52: return 85
elif val >= 50: return 65
elif val >= 48: return 40
else: return 15

============================================

第三步:构建宏观温度计

============================================

def build_temperature():
"""
构建宏观温度综合评分
返回:按月对齐的各指标分数 + 综合温度
"""

获取数据

gdp_df = get_gdp()
cpi_df = get_cpi()
m2_df = get_m2()
rate_df = get_rate()
sf_df = get_social_financing()
pmi_df = get_pmi()

以月度为主时间轴

all_months = sorted(set(
cpi_df'月份'.tolist() +
m2_df'月份'.tolist() +
rate_df'月份'.tolist() +
pmi_df'月份'.tolist()
))

results = \[\]
for month in all_months:
row = {'月份': month}

CPI

cpi_row = cpi_dfcpi_df\['月份' == month]
if not cpi_row.empty:
row'cpi' = cpi_row'cpi_yoy'.values0
row'cpi_score' = score_cpi(row'cpi')
else:
row'cpi_score' = np.nan

M2

m2_row = m2_dfm2_df\['月份' == month]
if not m2_row.empty:
row'm2' = m2_row'm2_yoy'.values0
row'm2_score' = score_m2(row'm2')
else:
row'm2_score' = np.nan

利率

rate_row = rate_dfrate_df\['月份' == month]
if not rate_row.empty:
row'rate' = rate_row'rate_10y'.values0
row'rate_score' = score_rate(row'rate')
else:
row'rate_score' = np.nan

PMI

pmi_row = pmi_dfpmi_df\['月份' == month]
if not pmi_row.empty:
row'pmi' = pmi_row'pmi'.values0
row'pmi_score' = score_pmi(row'pmi')
else:
row'pmi_score' = np.nan

GDP(季度数据,映射到最近月份)

gdp_row = gdp_dfgdp_df\['季度'.dt.to_period('Q') == month.to_period('Q')]
if not gdp_row.empty:
row'gdp' = gdp_row'gdp_yoy'.values-1
row'gdp_score' = score_gdp(row'gdp')
else:
row'gdp_score' = np.nan

社融

sf_row = sf_dfsf_df\['月份' == month]
if not sf_row.empty:
row'sf_yoy' = sf_row'sf_yoy'.values0
row'sf_score' = score_sf(row'sf_yoy')
else:
row'sf_score' = np.nan

results.append(row)

df = pd.DataFrame(results)

综合温度:加权平均

权重:M2 25%, 社融 20%, PMI 20%, 利率 15%, CPI 10%, GDP 10%

weights = {
'm2_score': 0.25,
'sf_score': 0.20,
'pmi_score': 0.20,
'rate_score': 0.15,
'cpi_score': 0.10,
'gdp_score': 0.10,
}

score_cols = list(weights.keys())
weight_vals = np.array(list(weights.values()))

df'temperature' = (
dfscore_cols.fillna(50) * weight_vals
).sum(axis=1)

return df

============================================

运行示例

============================================

if name == 'main':
print("正在获取宏观数据,请稍候...")
temp_df = build_temperature()

print("\n最近12个月宏观温度:")
print(temp_df\['月份', 'temperature'].tail(12).to_string(index=False))

画图

fig, ax = plt.subplots(figsize=(14, 6))
ax.plot(temp_df'月份', temp_df'temperature', 'r-', linewidth=1.5)
ax.axhline(y=70, color='g', linestyle='--', alpha=0.5, label='过热线(70)')
ax.axhline(y=40, color='b', linestyle='--', alpha=0.5, label='过冷线(40)')
ax.fill_between(temp_df'月份', 70, 100, alpha=0.1, color='red')
ax.fill_between(temp_df'月份', 0, 40, alpha=0.1, color='blue')
ax.set_title('宏观温度计', fontsize=16)
ax.set_ylabel('综合温度', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('macro_temperature.png', dpi=150)
plt.show()
print("\n图片已保存:macro_temperature.png")

跑一下。

你就能看到一条温度曲线。

红的时候------经济过热。

蓝的时候------经济过冷。

中间------正常。

温度计不会告诉你买什么,但会告诉你该不该买。

四、温度→仓位:该上几成仓

温度有了。

然后呢?

调仓位。

逻辑极简单:

|----------|----------|-----------|
| 温度区间 | 经济状态 | 建议仓位 |
| ≥ 75 | 过热 | 50%(开始防守) |
| 60-75 | 偏热 | 70% |
| 40-60 | 中性 | 80% |
| 25-40 | 偏冷 | 50% |
| < 25 | 极冷 | 30%(极端防守) |

等等------

过热反而减仓?

对。

物极必反。

经济过热------央行加息。

加息------杀估值。

2007年,GDP增速14%。

A股见顶6124。

然后呢?

跌到1664。

最热闹的时候,往往是最危险的时候。

反过来------

极冷的时候,反而不能空仓。

为什么?

因为极冷之后------

央行放水。

政策托底。

经济触底反弹。

2008年底。

2014年中。

2019年初。

都是极冷之后暴涨。

代码:

// python

安装依赖:pip install akshare pandas numpy

import akshare as ak
import pandas as pd
import numpy as np

============================================

宏观温度 → 仓位调整策略

============================================

def temperature_to_position(temperature):
"""
根据宏观温度返回建议仓位比例
temperature: 0-100 的综合温度
返回: 建议仓位 (0-1)
"""
if temperature >= 75:
return 0.50 # 过热,防守
elif temperature >= 60:
return 0.70 # 偏热
elif temperature >= 40:
return 0.80 # 中性,标准仓位
elif temperature >= 25:
return 0.50 # 偏冷,谨慎
else:
return 0.30 # 极冷,极端防守

def macro_position_strategy(start_date='20150101', end_date='20241231'):
"""
宏观仓位策略回测
对比:纯持有沪深300 vs 宏观调仓
"""

获取沪深300指数

hs300 = ak.stock_zh_index_daily(symbol="sh000300")
hs300'date' = pd.to_datetime(hs300'date')
hs300 = hs300 (hs300\['date' >= start_date) &
(hs300'date' <= end_date)
].copy()
hs300 = hs300.sort_values('date').reset_index(drop=True)
hs300'hs300_ret' = hs300'close'.pct_change()

获取宏观数据(简化版,用月度数据)

cpi_df = ak.macro_china_cpi_monthly()
cpi_df'月份' = pd.to_datetime(cpi_df'月份')
cpi_df'cpi' = cpi_df'全国-同比'.astype(float)

m2_df = ak.macro_china_m2_yearly()
m2_df'月份' = pd.to_datetime(m2_df'月份')
m2_df'm2' = m2_df'同比'.astype(float)

pmi_df = ak.macro_china_pmi()
pmi_df'月份' = pd.to_datetime(pmi_df'月份')
pmi_df'pmi' = pmi_df'制造业-指数'.astype(float)

按月构建温度

hs300'年月' = hs300'date'.dt.to_period('M')

positions = \[\]
for ym in hs300'年月'.unique():
month_ts = ym.to_timestamp()

CPI分数

cpi_val = cpi_df cpi_df\['月份'.dt.to_period('M') == ym
]'cpi'.values
cpi_s = score_cpi(cpi_val0) if len(cpi_val) > 0 else 50

M2分数

m2_val = m2_df m2_df\['月份'.dt.to_period('M') == ym
]'m2'.values
m2_s = score_m2(m2_val0) if len(m2_val) > 0 else 50

PMI分数

pmi_val = pmi_df pmi_df\['月份'.dt.to_period('M') == ym
]'pmi'.values
pmi_s = score_pmi(pmi_val0) if len(pmi_val) > 0 else 50

简化温度:三个指标等权

temp = cpi_s * 0.33 + m2_s * 0.34 + pmi_s * 0.33
pos = temperature_to_position(temp)
positions.append({'年月': ym, 'temperature': temp, 'position': pos})

pos_df = pd.DataFrame(positions)

合并

result = hs300.merge(pos_df, on='年月', how='left')
result'position' = result'position'.ffill().fillna(0.8)

计算策略收益

result'strategy_ret' = result'hs300_ret' * result'position'.shift(1)
result'strategy_ret'.iloc0 = 0

累计收益

result'buy_hold_cum' = (1 + result'hs300_ret'.fillna(0)).cumprod()
result'strategy_cum' = (1 + result'strategy_ret'.fillna(0)).cumprod()

输出结果

total_days = (result'date'.iloc-1 - result'date'.iloc0).days
years = total_days / 365.25

bh_total = result'buy_hold_cum'.iloc-1 - 1
st_total = result'strategy_cum'.iloc-1 - 1

bh_annual = (1 + bh_total) ** (1/years) - 1
st_annual = (1 + st_total) ** (1/years) - 1

最大回撤

bh_peak = result'buy_hold_cum'.cummax()
st_peak = result'strategy_cum'.cummax()
bh_mdd = ((result'buy_hold_cum' - bh_peak) / bh_peak).min()
st_mdd = ((result'strategy_cum' - st_peak) / st_peak).min()

print(f"回测区间:{start_date} ~ {end_date}")
print(f"{'='*50}")
print(f"沪深300买入持有:总收益 {bh_total:.1%},年化 {bh_annual:.1%},最大回撤 {bh_mdd:.1%}")
print(f"宏观调仓策略: 总收益 {st_total:.1%},年化 {st_annual:.1%},最大回撤 {st_mdd:.1%}")
print(f"{'='*50}")
print(f"超额收益(年化):{st_annual - bh_annual:.1%}")
print(f"回撤改善:{st_mdd - bh_mdd:.1%}")

return result

复用打分函数(从上面的温度计代码复制)

def score_cpi(val):
if 1 <= val <= 3: return 80
elif 0 <= val < 1: return 50
elif 3 < val <= 5: return 40
elif val > 5: return 10
else: return 20

def score_m2(val):
if 8 <= val <= 12: return 75
elif val > 12: return 85
elif 5 <= val < 8: return 45
else: return 20

def score_pmi(val):
if val >= 52: return 85
elif val >= 50: return 65
elif val >= 48: return 40
else: return 15

============================================

运行

============================================

if name == 'main':
result = macro_position_strategy()

看出门道没?

宏观调仓不一定比死拿赚得多。

但------

回撤小。

回撤小,就意味着------

你睡得着觉。

赚钱的策略千千万,能让你拿住的才是好策略。

五、三次系统性风险复盘

不讲故事。

上数据。

2015年:杠杆牛崩盘

背景:

• 2014年下半年到2015年上半年,M2增速飙到13%+

• 融资融券余额从4000亿干到2.2万亿

• 宏观温度:75+,过热

2015年6月------

证监会查场外配资。

崩了。

沪深300:一个月跌40%。

千股跌停。

国家队救市。

如果你当时宏观温度到了75以上减仓到50%------

你的回撤比死扛的人少一半。

2018年:贸易战

背景:

• 2017年底金融去杠杆

• M2增速从11%掉到8%

• 社融增速断崖下跌

• 利率飙升到3.9%

宏观温度:从60直接掉到35。

2018年全年:

沪深300跌25%。

如果你在社融断崖的时候就把仓位降到50%------

又躲过一劫。

2020年:新冠

背景:

• 2019年底PMI连续在荣枯线附近

• 经济本就偏弱

• 宏观温度:40左右

2020年1月底------新冠爆发。

沪深300:两周跌12%。

但------

2月底央行大幅放水。

M2增速从8.8%飙到11.1%。

宏观温度快速回升。

如果你在M2飙升的时候加仓------

3月到7月,沪深300涨了25%。

别人恐惧你贪婪------前提是央行在放水。

三次复盘,一次结论:

宏观因子不是让你赚更多,是让你亏更少。

亏更少,就是赚更多。

复利的本质------

少亏。

六、实操指南

说了这么多。

怎么用?

每月花十分钟。

1号到5号之间------

宏观数据基本都出了。

打开AKShare。

跑一遍温度计。

看看温度。

温度高------减仓。

温度低------加仓。

温度中------不动。

就这。

不用天天盯盘。

不用追涨杀跌。

宏观因子是慢变量。你不需要每天看,但你需要每月看。

最后提醒一句------

宏观因子不是万能药

它能帮你避开大灾。

但它不能帮你选股。

选股是微观的事。

择时是宏观的事。

两条腿走路。

缺一条都走不远。

抬头看天,低头看路。天要下雨赶紧跑,路有黄金弯腰捡。

相关推荐
LuminousCPP1 小时前
数据结构-排序(五):计数排序与排序专题阶段总结|从外部归并到线性排序,再看算法边界与选型
c语言·数据结构·笔记·算法·排序算法
流云枫1 小时前
003-上下文窗口:物理限制、KV Cache 与工程应对策略
算法
盟道科技1 小时前
小程序订阅消息大规模投递实践:频控令牌池、Redis限流与失败补偿设计
分布式·算法·小程序
工具派1 小时前
视频场景检测是怎么找到切镜头位置的?三种思路与实测
算法·计算机视觉·视频
钓鱼的肝1 小时前
csp-j-s总结(1)
c++·笔记·算法·青少年编程
流云枫1 小时前
001-Transformer架构与Self-Attention机制深度解析
算法
XZ-0700011 小时前
week4-2-坐标轴
python
YHL1 小时前
🔍 结构化输出:从 `JSON.parse` 到 `withStructuredOutput` 的三级火箭
前端·深度学习
The Chosen One9851 小时前
OS第二章随手记(2.1)
linux·运维·服务器·笔记