再牛逼的选股策略,遇上股灾也得跪。你挑对了马,天上下刀子------照样死。
你花了三个月。
选股模型调了又调。
回测跑了一遍又一遍。
年化收益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。
跑一遍温度计。
看看温度。
温度高------减仓。
温度低------加仓。
温度中------不动。
就这。
不用天天盯盘。
不用追涨杀跌。
宏观因子是慢变量。你不需要每天看,但你需要每月看。
最后提醒一句------
宏观因子不是万能药 。
它能帮你避开大灾。
但它不能帮你选股。
选股是微观的事。
择时是宏观的事。
两条腿走路。
缺一条都走不远。
抬头看天,低头看路。天要下雨赶紧跑,路有黄金弯腰捡。