2、数据获取与预处理:获取正股与可转债行情数据、数据对齐与清洗、计算日收益率
做量化交易,数据就是你的弹药库。弹药不行,枪法再好也白搭。
这一章,咱们就聊聊怎么把正股和可转债的行情数据搞到手,然后对齐、清洗,最后算出日收益率。听起来步骤多,其实核心就三件事:拿数据、对齐、算收益。
2.1 数据源的选择与获取
我个人习惯用 akshare 这个库来拿数据。它是开源的,接口稳定,而且覆盖了A股和可转债的日线数据。当然,你也可以用 tushare、baostock 或者直接从万得、聚宽拉数据。但 akshare 对个人开发者最友好,不用申请 token,直接 pip install 就能用。
获取正股和可转债的日线数据,代码其实很简单:
import akshare as ak
import pandas as pd
# 获取正股日线数据(以贵州茅台为例)
stock_df = ak.stock_zh_a_hist(symbol="600519", period="daily",
start_date="20240101", end_date="20241231")
# 获取可转债日线数据(以茅台转债为例)
bond_df = ak.bond_zh_cov_hist(symbol="110053", period="daily",
start_date="20240101", end_date="20241231")
你看,两行代码就把数据拉下来了。但这里有个坑——字段名不一致。正股返回的列名是“日期”、“开盘”、“收盘”,可转债返回的可能是“date”、“open”、“close”。嗯,这里要注意,后面清洗的时候得统一。
2.2 数据对齐:时间轴必须一致
正股和可转债的交易时间基本一致,但偶尔会有差异。比如某天正股正常交易,可转债却停牌了。或者反过来。这时候就需要做时间轴对齐。
我一般用 pd.merge 做内连接,只保留两者都有数据的日期。为什么?因为我们要算联动,缺了任何一方都没法算。
# 先统一列名
stock_df.rename(columns={'日期': 'date', '收盘': 'close'}, inplace=True)
bond_df.rename(columns={'date': 'date', 'close': 'close'}, inplace=True)
# 只保留日期列和收盘价
stock_close = stock_df[['date', 'close']].copy()
bond_close = bond_df[['date', 'close']].copy()
# 内连接对齐
merged = pd.merge(stock_close, bond_close, on='date', how='inner', suffixes=('_stock', '_bond'))
对齐之后,数据行数可能会少一些。别慌,这是正常的。我曾经遇到过一个极端情况:某只可转债因为连续停牌,一个月只有3天有数据。这时候强行对齐,样本量太少,模型根本跑不动。我的建议是:如果对齐后数据少于60个交易日,直接放弃这只转债,换一只。
2.3 数据清洗:脏数据必须处理
数据清洗是体力活,但也是保命活。常见的脏数据有:
- 缺失值:某天收盘价为 NaN
- 异常值:价格突然跳涨 100% 又跳回(通常是复权问题)
- 重复行:同一天出现两条记录
我的清洗流程是这样的:
# 检查缺失值
print(merged.isnull().sum())
# 删除缺失值(或者用前向填充,但我不建议)
merged.dropna(inplace=True)
# 检查重复行
print(merged.duplicated(subset='date').sum())
merged.drop_duplicates(subset='date', inplace=True)
# 检查异常值:价格不能为负,也不能是零
merged = merged[(merged['close_stock'] > 0) & (merged['close_bond'] > 0)]
2.4 计算日收益率
收益率是量化分析的基础。我们算的是简单日收益率,公式很简单:
R_t = (P_t - P_{t-1}) / P_{t-1}
用 pandas 的 pct_change() 一行搞定:
merged['ret_stock'] = merged['close_stock'].pct_change()
merged['ret_bond'] = merged['close_bond'].pct_change()
# 第一天的收益率是 NaN,删掉
merged.dropna(inplace=True)
为什么要算收益率而不是直接用价格?因为价格是非平稳的,收益率相对平稳。你想想看,茅台从100涨到200,和从200涨到400,虽然价格翻倍,但收益率都是100%。用收益率做联动分析,才能排除绝对价格的影响。
2.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个检查清单:
2.6 避坑指南
最后,分享几个我实战中踩过的坑:
- 复权问题:正股数据默认是后复权,可转债没有复权一说。如果你直接拿价格算收益率,遇到分红送股,收益率会突变。我的做法是:正股用后复权价格,可转债用实际价格,两者不混用。
- 交易日历:A股和可转债的交易日历完全一致,但如果你同时做港股或美股,就得用
exchange_calendar来对齐。别想当然。 - 数据量:我建议至少拉3年的数据。太短了,样本不够;太长了,市场结构可能变了。3年是个折中。
本章核心产出:一个包含 date、ret_stock、ret_bond 三列的 DataFrame。这是后续所有联动分析的基础。数据没处理好,后面全是白费功夫。
好了,数据到手,清洗完毕,收益率也算好了。下一章,咱们就拿着这份干净数据,开始真正的联动分析。