第二章:数据获取与清洗——情绪因子的“原材料”处理
做量化投资,尤其是情绪因子这块,我有个深刻的体会:数据质量决定了策略的天花板。你模型再漂亮,代码再优雅,拿到的数据是脏的、错的、缺失的,那结果就是垃圾进垃圾出。这一章,我们就来聊聊怎么把市场情绪数据这个“原材料”洗干净、处理好。
2.1 情绪数据有哪些?从哪拿?
情绪因子不是凭空捏造的,它藏在市场的交易行为里。我个人习惯把情绪数据分成三类:
- 交易活跃度类:换手率、成交量、成交额。说白了,就是看市场是不是“热”了。
- 波动与风险类:波动率、振幅、日内最大回撤。市场情绪激动时,波动率通常会飙升。
- 杠杆与资金类:融资融券余额、沪港通/深港通资金流向。这些数据能反映“聪明钱”的态度。
数据源方面,我常用的有:
- Tushare:国内比较全的免费数据接口,换手率、融资融券都能拿。
- Wind / 聚宽:机构常用,数据质量高,但需要付费。
- Yahoo Finance / Alpha Vantage:做全球市场时用,但A股数据不太全。
我的小建议:刚开始做研究时,先用Tushare免费版跑通流程。等策略稳定了,再考虑付费数据源。别一上来就花冤枉钱。
2.2 数据获取实战:用Python拉取换手率与融资融券
嗯,这里直接上代码。我以Tushare为例,展示怎么获取沪深300成分股的日度换手率和融资融券余额。
import tushare as ts
import pandas as pd
import datetime
# 设置token(你需要在tushare官网注册获取)
ts.set_token('你的token')
pro = ts.pro_api()
# 获取交易日历
start_date = '2020-01-01'
end_date = '2023-12-31'
# 1. 获取沪深300成分股列表
hs300 = pro.index_weight(index_code='000300.SH', start_date=start_date, end_date=end_date)
stock_list = hs300['con_code'].unique().tolist()
# 2. 获取换手率数据(日度)
def get_turnover_data(stock, start, end):
df = pro.daily_basic(ts_code=stock, start_date=start, end_date=end,
fields='ts_code,trade_date,turnover_rate')
return df
# 3. 获取融资融券余额(日度)
def get_margin_data(stock, start, end):
df = pro.margin_detail(ts_code=stock, start_date=start, end_date=end,
fields='ts_code,trade_date,margin_balance')
return df
# 示例:获取第一只股票的数据
sample_stock = stock_list[0]
turnover_df = get_turnover_data(sample_stock, start_date, end_date)
margin_df = get_margin_data(sample_stock, start_date, end_date)
print(f"换手率数据样例:\n{turnover_df.head()}")
print(f"融资融券数据样例:\n{margin_df.head()}")
这段代码跑起来后,你会得到类似这样的表格:
| ts_code | trade_date | turnover_rate |
|---|---|---|
| 000001.SZ | 2020-01-02 | 0.85 |
| 000001.SZ | 2020-01-03 | 0.92 |
注意:Tushare的免费版有调用频率限制(每分钟200次)。如果你要拉全量数据,建议加个time.sleep(0.5)控制节奏。我曾经因为没加限制,直接被封了半小时。
2.3 数据清洗:那些坑,我替你踩过了
数据拿到手,别急着用。先看看有没有这些问题:
- 缺失值:有些股票停牌了,当天换手率是NaN。融资融券数据也可能因为节假日缺失。
- 异常值:换手率突然飙到50%以上?可能是数据错误,也可能是极端行情(比如2015年股灾)。
- 重复值:同一个交易日,同一个股票,出现了两条记录。这通常是接口抽风导致的。
- 数据对齐问题:不同数据源的日期格式可能不一样,有的用'2020-01-02',有的用'20200102'。
我的清洗流程一般是这样的:
def clean_emotion_data(df):
# 1. 去重
df = df.drop_duplicates(subset=['ts_code', 'trade_date'])
# 2. 处理缺失值
# 对于换手率,如果缺失,用前一天的数值填充(市场情绪有惯性)
df['turnover_rate'] = df.groupby('ts_code')['turnover_rate'].ffill()
# 对于融资融券余额,如果缺失,用0填充(表示没有融资交易)
df['margin_balance'] = df['margin_balance'].fillna(0)
# 3. 处理异常值
# 换手率超过99%分位数的,用99%分位数替换(防止极端值干扰)
for stock in df['ts_code'].unique():
mask = df['ts_code'] == stock
upper = df.loc[mask, 'turnover_rate'].quantile(0.99)
df.loc[mask & (df['turnover_rate'] > upper), 'turnover_rate'] = upper
# 4. 日期格式统一
df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
return df
# 应用清洗函数
clean_df = clean_emotion_data(turnover_df)
print(f"清洗后数据量:{len(clean_df)},原始数据量:{len(turnover_df)}")
关键点:清洗不是“一刀切”。比如缺失值填充,换手率用前向填充是因为情绪有延续性;但融资余额用0填充,是因为没有融资交易本身就是一种信息(市场不看好)。不同字段,处理逻辑不同。
2.4 数据预处理:让情绪因子“可计算”
数据洗干净了,接下来要做两件事:标准化和对齐。
标准化:不同股票的换手率绝对值差异很大(小盘股换手率天生高,大盘股低)。直接拿原始值做因子,会出问题。我一般用Z-score标准化:
from scipy import stats
def standardize_factor(df, factor_col):
# 按股票分组,对因子做Z-score标准化
df[factor_col + '_zscore'] = df.groupby('ts_code')[factor_col].transform(
lambda x: stats.zscore(x, nan_policy='omit')
)
return df
clean_df = standardize_factor(clean_df, 'turnover_rate')
对齐:把不同频率的数据对齐到统一的时间轴上。比如换手率是日频,融资融券余额也是日频,但有些数据可能是周频。对齐时,我习惯用“向前填充”或“向后填充”,具体看业务逻辑。
避坑指南:我曾经在回测时发现,某个月的因子表现特别好,后来一查,是因为数据对齐时用了“未来数据”——用当天的融资余额去预测当天的收益率。这属于典型的“未来函数”,回测结果全是假的。切记:对齐时只能用历史数据。
2.5 本章知识体系总览
说了这么多,我画了一张图帮你梳理整个流程。从数据源到最终可用的情绪因子,每一步都有讲究。
你看,整个流程其实不复杂,但每一步都容易踩坑。数据获取时要注意接口限制,清洗时要区分不同字段的特性,预处理时要警惕未来函数。把这些基础打牢了,后面做因子分析和回测才能站得住脚。
一句话总结:情绪因子的质量,90%取决于数据清洗和预处理。别嫌麻烦,这一步值得花时间。