4. 数据获取与预处理:使用Python获取股票/ETF数据,清洗与标准化
做量化交易,数据就是你的弹药库。弹药不行,枪法再好也白搭。
我见过太多人,策略逻辑写得漂漂亮亮,回测曲线美如画,一上实盘就崩。为什么?数据源有问题,或者预处理没做好。说白了,垃圾进,垃圾出。
这一章,我们就来聊聊怎么用Python把数据搞到手,再把它收拾得服服帖帖。
4.1 数据源的选择:别在源头就输了
我个人习惯,数据源至少要备两个。一个主用,一个备用。为什么?因为任何数据服务商都可能抽风。我曾经在周五晚上跑回测,结果某数据源直接挂了,整个周末的计划全泡汤。
目前国内常用的数据源,我列个表给你看:
| 数据源 | 类型 | 优点 | 缺点 |
|---|---|---|---|
| Tushare | 免费/付费 | 数据全,社区活跃 | 免费版有积分限制 |
| AkShare | 免费 | 开源,接口丰富 | 依赖网络爬虫,稳定性一般 |
| Baostock | 免费 | 无需注册,上手快 | 数据更新有延迟 |
| JoinQuant / RiceQuant | 平台内置 | 本地化好,回测方便 | 数据导出有限制 |
对于双低策略,我们主要需要两类数据:可转债数据和正股/ETF数据。可转债数据包括价格、转股价值、溢价率等;正股数据就是常规的日线行情。
4.2 用Python获取数据:代码走起
我们以AkShare为例,演示怎么获取可转债和ETF的数据。AkShare的接口设计得挺人性化,你想想看,一行代码就能拉数据。
先安装库:
pip install akshare pandas numpy
获取可转债实时行情:
import akshare as ak
import pandas as pd
# 获取所有可转债的实时数据
bond_zh_cov_df = ak.bond_zh_cov()
print(bond_zh_cov_df.head())
获取ETF日线数据(以沪深300ETF为例):
# 获取沪深300ETF的历史日线数据
# 代码:510300.SH
etf_df = ak.fund_etf_hist_em(symbol="510300", period="daily",
start_date="20230101", end_date="20231231",
adjust="qfq")
print(etf_df.head())
获取可转债的转股价值、溢价率等关键字段:
# 获取可转债的详细数据,包含转股价值、溢价率等
bond_cov_df = ak.bond_cov_all()
# 筛选我们需要的字段
bond_cov_df = bond_cov_df[['债券代码', '债券名称', '现价', '转股价',
'正股价', '转股价值', '溢价率']]
print(bond_cov_df.head())
4.3 数据清洗:把脏活累活干漂亮
数据拿到手,别急着用。先看看有没有坑。
我遇到过最离谱的一次,某只可转债的转股价居然是0。你想想看,转股价为0意味着什么?意味着可以零成本转股,这明显是数据错误。如果不处理,你的双低评分直接崩掉。
常见的清洗步骤:
- 处理缺失值:用前向填充或删除
- 处理异常值:价格不能为负,溢价率不能太离谱
- 去重:同一只债券在同一天出现多次
- 数据类型转换:把字符串转成数值
代码示例:
# 1. 检查缺失值
print(bond_cov_df.isnull().sum())
# 2. 删除转股价为0或缺失的行
bond_cov_df = bond_cov_df[bond_cov_df['转股价'] > 0]
# 3. 删除现价异常的行(比如现价小于0.1元)
bond_cov_df = bond_cov_df[bond_cov_df['现价'] > 0.1]
# 4. 重置索引
bond_cov_df = bond_cov_df.reset_index(drop=True)
# 5. 确保数值列是float类型
bond_cov_df['现价'] = pd.to_numeric(bond_cov_df['现价'], errors='coerce')
bond_cov_df['转股价'] = pd.to_numeric(bond_cov_df['转股价'], errors='coerce')
4.4 数据标准化:让不同品种站在同一起跑线
双低策略的核心是「双低」——低价格、低溢价率。但这两个指标的数值范围差很多。价格可能是80到200,溢价率可能是-5%到50%。直接相加,价格会主导评分。
所以,我们需要做标准化。
常用的标准化方法:
- Min-Max标准化:缩放到[0,1]区间
- Z-score标准化:均值为0,标准差为1
- 排名标准化:用百分位排名代替原始值
我个人偏好排名标准化。为什么?因为它对异常值不敏感。你想想看,如果某只可转债价格突然飙到500,Min-Max会把其他所有债券的价格都压到接近0,这显然不合理。
代码实现:
# 排名标准化函数
def rank_normalize(series):
return series.rank(pct=True)
# 对现价和溢价率做排名标准化
bond_cov_df['价格排名'] = rank_normalize(bond_cov_df['现价'])
bond_cov_df['溢价率排名'] = rank_normalize(bond_cov_df['溢价率'])
# 计算双低得分(等权)
bond_cov_df['双低得分'] = bond_cov_df['价格排名'] + bond_cov_df['溢价率排名']
# 按得分排序,得分越低越好
bond_cov_df = bond_cov_df.sort_values('双低得分')
print(bond_cov_df[['债券代码', '债券名称', '现价', '溢价率', '双低得分']].head(10))
4.5 数据合并与对齐:别让时间错位
双低策略需要同时用到可转债数据和正股数据。这两类数据的更新频率可能不一样。可转债数据可能每天更新一次,正股数据也是每天更新。但万一哪天停牌了呢?或者数据源漏了一天?
所以,合并数据时一定要做时间对齐。
# 假设我们有可转债日线数据和正股日线数据
# 以日期为索引,做inner join
merged_df = pd.merge(bond_daily_df, stock_daily_df,
on='date', how='inner', suffixes=('_bond', '_stock'))
# 检查合并后的数据是否有缺失
print(merged_df.isnull().sum())
# 如果某一天正股数据缺失,可以考虑用前一天的收盘价填充
merged_df = merged_df.fillna(method='ffill')
4.6 本章小结:数据准备的核心逻辑
数据获取与预处理,说白了就是三个字:准、全、稳。
- 准:数据来源可靠,清洗到位
- 全:字段齐全,时间跨度够长
- 稳:流程自动化,可重复执行
下面这张图,是我自己总结的数据处理流程,你可以参考:
嗯,数据准备这块,说难不难,说简单也不简单。关键是要养成好习惯——每次拿到数据,先做一遍完整的清洗和标准化流程。别偷懒,偷懒的代价就是回测结果不可信。
记住一句话:数据质量决定了策略的上限。模型再牛,数据不行,一切都是白搭。
公众号:蓝海资料掘金营,微信deep3321