2. 行业分类标准与数据获取:申万、中信、GICS行业分类
做行业轮动,第一步就是搞清楚「行业」到底怎么划分的。
你可能觉得这很简单——不就是把股票分分类吗?但我在实际项目中踩过不少坑。有一次,我按申万一级行业做轮动策略,回测效果特别好,结果实盘时发现某只股票被重新分类了,整个持仓逻辑全乱了。嗯,从那以后,我每次做策略前都会先花时间研究行业分类标准。
2.1 三大主流行业分类标准
目前国内量化圈用得最多的,就是申万、中信和GICS这三套标准。我分别说说它们的特点。
2.4.1 申万行业分类
申万宏源出的这套分类,是国内量化界的「默认配置」。我个人习惯用它做A股行业轮动,原因很简单:更新及时,而且跟国内市场的贴合度最高。
- 层级结构:一级(28个)、二级(104个)、三级(227个)
- 更新频率:每年6月和12月各调整一次
- 适用场景:A股行业轮动策略、因子测试
重要提示:申万2021年做过一次大改版,把一级行业从28个调整到了31个。如果你用的是历史数据,一定要注意版本对齐。我见过有人拿新版分类去回测2018年的数据,结果对不上号。
2.4.2 中信行业分类
中信证券的分类体系,在机构投资者中认可度很高。说白了,它跟申万最大的区别在于:中信更偏向「投资视角」,而申万更偏向「产业视角」。
- 层级结构:一级(29个)、二级(120个)、三级(300+个)
- 更新频率:不定期调整,通常跟随市场变化
- 适用场景:机构组合管理、行业配置研究
我的经验:如果你做的是偏中长期的行业轮动,中信分类可能更合适。因为它的调整频率低,分类稳定性更好,不会因为短期市场变化频繁变动。
2.4.3 GICS行业分类
GICS是MSCI和标普联合推出的全球标准。做跨市场研究时,你绕不开它。比如你要对比A股和港股的消费板块,用GICS就比用申万方便得多。
- 层级结构:11个行业、24个行业组、69个行业、158个子行业
- 更新频率:每年3月调整
- 适用场景:跨市场比较、全球资产配置
2.2 三种分类的对比
我整理了一张对比表,方便你快速了解差异:
| 维度 | 申万 | 中信 | GICS |
|---|---|---|---|
| 一级行业数 | 31(新版) | 29 | 11 |
| 更新频率 | 半年一次 | 不定期 | 每年一次 |
| 适用市场 | A股为主 | A股为主 | 全球市场 |
| 数据可得性 | 高(免费) | 中(需付费) | 高(免费) |
| 机构使用率 | 高 | 中高 | 中 |
2.3 用Python获取行业指数数据
理论说完了,咱们直接上代码。我个人最常用的数据源是Tushare和AkShare,前者数据质量高,后者免费且覆盖全。
2.3.1 通过Tushare获取申万行业指数
import tushare as ts
# 设置token(你需要先在tushare官网注册)
pro = ts.pro_api('your_token_here')
# 获取申万一级行业列表
industry_list = pro.index_classify(level='L1', src='SW')
print(industry_list.head())
# 获取某个行业的历史行情
# 比如:食品饮料(申万一级代码:801120)
df = pro.index_daily(ts_code='801120.SI',
start_date='20200101',
end_date='20231231')
print(df.head())
注意:Tushare的申万行业指数代码格式是「6位数字.SI」。我曾经因为忘了加.SI后缀,折腾了半天才发现数据拉不下来。
2.3.2 通过AkShare获取行业指数
AkShare的好处是免费,而且不用注册。我平时做快速验证时特别喜欢用它。
import akshare as ak
# 获取申万行业指数实时行情
sw_index = ak.stock_sw_index_spot()
print(sw_index.head())
# 获取历史数据
# 以食品饮料为例
sw_history = ak.stock_sw_index_daily(symbol="801120")
print(sw_history.tail())
2.3.3 批量获取多个行业数据
做行业轮动时,你通常需要同时跟踪几十个行业。我一般这样写:
import pandas as pd
import akshare as ak
from datetime import datetime, timedelta
# 定义要跟踪的行业列表
industries = {
'食品饮料': '801120',
'医药生物': '801150',
'电子': '801080',
'银行': '801780',
'非银金融': '801790'
}
# 批量获取数据
def get_industry_data(industry_code, start_date, end_date):
try:
df = ak.stock_sw_index_daily(symbol=industry_code)
df = df[(df['date'] >= start_date) & (df['date'] <= end_date)]
return df
except:
return None
# 设置时间范围
end = datetime.now().strftime('%Y-%m-%d')
start = (datetime.now() - timedelta(days=365)).strftime('%Y-%m-%d')
# 循环获取
all_data = {}
for name, code in industries.items():
data = get_industry_data(code, start, end)
if data is not None:
all_data[name] = data
print(f"{name}: 获取成功,共{len(data)}条数据")
else:
print(f"{name}: 获取失败")
2.4 数据清洗与对齐
数据拿到手后,千万别直接拿去用。我吃过这个亏——有一次回测收益特别高,结果发现是因为数据里混入了停牌日的空值,收益率计算全错了。
这里分享几个我常用的清洗步骤:
- 处理缺失值:用前向填充法,或者直接删除
- 对齐日期:不同行业可能有不同的交易日,需要统一
- 计算收益率:用对数收益率,而不是简单收益率
- 去极值:用MAD方法或百分位法
def clean_industry_data(df):
# 1. 处理缺失值
df = df.fillna(method='ffill')
# 2. 计算对数收益率
df['log_return'] = np.log(df['close'] / df['close'].shift(1))
# 3. 去极值(MAD方法)
median = df['log_return'].median()
mad = (df['log_return'] - median).abs().median()
upper = median + 3 * mad
lower = median - 3 * mad
df['log_return'] = df['log_return'].clip(lower, upper)
return df.dropna()
2.5 知识体系总览
下面这张图,帮你理清本章的核心逻辑:
我的建议:刚开始做行业轮动时,先用申万一级行业练手。等熟悉了整个流程,再尝试中信或GICS。一口吃不成胖子,量化投资更是如此。