3、数据获取与预处理:获取A股行业指数数据、数据清洗与对齐、收益率计算、行业指数标准化处理

做行业轮动,第一步就是搞数据。

很多人一上来就写策略,结果数据源都没搞清楚。我见过太多人,回测跑得飞起,实盘一跑就崩——原因往往是数据没对齐,或者用了未来数据。嗯,这章咱们就把地基打牢。

3.1 数据源选择:Tushare vs AKShare

我个人习惯用Tushare做主力数据源。为什么?因为它的行业分类比较全,历史数据也干净。但Tushare有个门槛——你得注册拿token。如果你嫌麻烦,AKShare是个不错的替代方案,免费且无需注册。

不过要注意,AKShare的数据接口偶尔会变。我曾经在项目中遇到过,前一天还能跑的代码,第二天接口就改了名。所以我的建议是:生产环境用Tushare,研究环境用AKShare

核心观点:数据源没有绝对的好坏,关键是稳定性和可复现性。选一个你熟悉的,长期用下去。

3.2 获取行业指数数据

我们以申万一级行业为例。Tushare里对应的接口是index_daily,参数填上行业指数代码就行。

代码示例:

import tushare as ts

# 初始化
pro = ts.pro_api('你的token')

# 获取申万银行行业指数
df = pro.index_daily(ts_code='801780.SI', 
                     start_date='20200101', 
                     end_date='20231231')
print(df.head())

如果你用AKShare,代码长这样:

import akshare as ak

# 获取申万一级行业指数
df = ak.sw_index_daily(symbol="801780")
print(df.head())

这里有个坑:不同数据源的行业代码可能不一样。Tushare用.SI后缀,AKShare直接用数字代码。你想想看,如果混着用,代码写错了都不知道。

避坑指南:我曾经在项目中同时用了两个数据源,结果行业分类对不上。后来统一用Tushare的申万分类,才把问题解决。建议你从一开始就固定一个数据源。

3.3 数据清洗与对齐

拿到数据后,第一件事不是算收益率,而是清洗。

常见的脏数据包括:

  • 缺失值:某天停牌,数据是NaN
  • 异常值:价格突然跳空,可能是数据录入错误
  • 日期不连续:节假日、周末数据缺失

我的处理流程是这样的:

  1. 先检查日期是否连续。用pd.date_range生成完整日历,然后左连接。
  2. 缺失值用前向填充(ffill)。为什么?因为停牌期间,价格应该沿用上一个交易日的数据。
  3. 异常值用3倍标准差法剔除。如果某天涨跌幅超过均值±3σ,我一般会标记出来人工复核。

代码示例:

import pandas as pd

# 假设df是原始数据,包含date和close列
df['date'] = pd.to_datetime(df['date'])
df = df.set_index('date')

# 生成完整日历
full_calendar = pd.date_range(start='2020-01-01', 
                              end='2023-12-31', 
                              freq='B')  # 仅交易日

# 对齐
df = df.reindex(full_calendar)
df['close'] = df['close'].ffill()  # 前向填充

# 异常值处理
mean = df['close'].mean()
std = df['close'].std()
df = df[(df['close'] > mean - 3*std) & 
        (df['close'] < mean + 3*std)]

小技巧:对齐多个行业指数时,建议用pd.concat按列合并。这样能保证所有指数在同一时间轴上,方便后续计算。

3.4 收益率计算

收益率是行业轮动的核心输入。常用的有两种:

类型 公式 适用场景
简单收益率 (P_t - P_{t-1}) / P_{t-1} 短期信号,日频交易
对数收益率 ln(P_t / P_{t-1}) 长期分析,多期累加

我个人习惯用对数收益率。为什么?因为对数收益率具有可加性,计算多期累计收益时直接相加就行,不会出现复利计算的偏差。

代码示例:

# 计算对数收益率
df['log_return'] = np.log(df['close'] / df['close'].shift(1))

# 计算简单收益率
df['simple_return'] = df['close'].pct_change()

这里要注意:收益率计算时,shift(1)会引入NaN。第一行数据没有前值,所以收益率是空值。记得用dropna()去掉。

3.5 行业指数标准化处理

不同行业的价格水平差异很大。银行指数可能只有3000点,而食品饮料指数可能已经20000点了。直接比较绝对价格没有意义,必须标准化。

常用的标准化方法:

  • Z-score标准化:(x - μ) / σ,适用于正态分布的数据
  • Min-Max标准化:(x - min) / (max - min),将数据压缩到[0,1]区间
  • 百分位排名:将数据转换为0-100的排名值

在行业轮动中,我推荐用滚动Z-score。为什么?因为行业指数的均值和标准差是随时间变化的。用固定窗口(比如60天)计算滚动均值和标准差,能更好地捕捉近期趋势。

代码示例:

# 滚动Z-score标准化
window = 60
df['zscore'] = (df['log_return'] - 
                df['log_return'].rolling(window).mean()) / \
                df['log_return'].rolling(window).std()

关键点:标准化窗口的选择很重要。窗口太短(比如20天),信号会过于敏感;窗口太长(比如250天),信号反应迟钝。我一般用60天,也就是一个季度的交易日数。

3.6 本章知识体系

下面这张图,帮你理清数据获取与预处理的完整流程:

行业轮动数据预处理流程 数据获取 Tushare / AKShare 数据清洗 缺失值 / 异常值 / 对齐 收益率计算 对数收益率 / 简单收益率 标准化处理 滚动Z-score / Min-Max / 百分位排名 输出:标准化后的行业收益率矩阵 用于后续信号识别与交易执行

说白了,数据预处理就是四个步骤:获取 → 清洗 → 计算 → 标准化。每一步都有坑,但只要你按流程走,基本不会出大问题。

个人经验:我刚开始做行业轮动时,总想跳过数据清洗这一步。结果回测收益高得离谱,实盘却亏得一塌糊涂。后来才发现,原来是数据里混入了未来信息。从那以后,我每次跑策略前都会先做一遍完整的数据预处理流程。

嗯,数据部分就讲到这里。记住:数据质量决定了策略的上限。再好的模型,喂进去垃圾数据,出来的也是垃圾结果。


公众号:蓝海数据掘金营,微信deep3321