2、数据获取与预处理:获取正股与可转债行情数据、数据对齐与清洗、计算日收益率

做量化交易,数据就是你的弹药库。弹药不行,枪法再好也白搭。

这一章,咱们就聊聊怎么把正股和可转债的行情数据搞到手,然后对齐、清洗,最后算出日收益率。听起来步骤多,其实核心就三件事:拿数据、对齐、算收益

2.1 数据源的选择与获取

我个人习惯用 akshare 这个库来拿数据。它是开源的,接口稳定,而且覆盖了A股和可转债的日线数据。当然,你也可以用 tushare、baostock 或者直接从万得、聚宽拉数据。但 akshare 对个人开发者最友好,不用申请 token,直接 pip install 就能用。

小提示: 如果你用 akshare,记得更新到最新版。老版本有些接口已经废弃了,我踩过这个坑。

获取正股和可转债的日线数据,代码其实很简单:

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)]
警告: 千万别用 fillna(method='ffill') 来填充缺失的收盘价。你以为是在补数据,其实是在造虚假的联动关系。我见过有人这么干,回测曲线漂亮得不行,实盘直接亏哭。

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 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个检查清单:

数据获取与预处理流程 获取数据 akshare / tushare 时间对齐 pd.merge inner 数据清洗 去空 / 去重 / 去异常 计算收益率 pct_change() 最终数据集 date + ret_stock + ret_bond 联动分析 后续章节展开 核心原则:数据干净,分析才靠谱 数据源要稳定 对齐要严格 清洗要彻底

2.6 避坑指南

最后,分享几个我实战中踩过的坑:

  • 复权问题:正股数据默认是后复权,可转债没有复权一说。如果你直接拿价格算收益率,遇到分红送股,收益率会突变。我的做法是:正股用后复权价格,可转债用实际价格,两者不混用。
  • 交易日历:A股和可转债的交易日历完全一致,但如果你同时做港股或美股,就得用 exchange_calendar 来对齐。别想当然。
  • 数据量:我建议至少拉3年的数据。太短了,样本不够;太长了,市场结构可能变了。3年是个折中。

本章核心产出:一个包含 date、ret_stock、ret_bond 三列的 DataFrame。这是后续所有联动分析的基础。数据没处理好,后面全是白费功夫。

好了,数据到手,清洗完毕,收益率也算好了。下一章,咱们就拿着这份干净数据,开始真正的联动分析。


蓝海数据掘金营,专注资料整理