4. 数据获取进阶:获取可转债日线行情数据

上一章我们搞定了基础数据,说白了就是拿到了可转债的「身份证信息」。但做回测嘛,光有身份证可不够,你得知道它每天在市场上是怎么「蹦跶」的——开盘多少、收盘多少、最高冲到哪、最低跌到哪、成交量又有多大。

这一章,我们就来啃这块硬骨头:获取可转债的日线行情数据

4.1 行情数据的「灵魂五要素」

做量化的人都知道,日线数据最核心的就是五个字段:

  • 开盘价:当天9:30第一笔成交价
  • 收盘价:下午3:00最后一笔成交价
  • 最高价:当天成交的最高价格
  • 最低价:当天成交的最低价格
  • 成交量:当天成交的总手数(注意,不是金额)

这五个字段,构成了我们所有技术指标计算的基础。你想想看,没有这些数据,什么MACD、KDJ、布林带,全都是空中楼阁。

核心观点:行情数据是回测的「血液」。数据质量直接决定回测结果的可靠性。我曾经因为一个数据源的收盘价用了复权数据,导致回测结果偏差了3个点,排查了整整两天。

4.2 数据源的选择与对比

我个人习惯用以下几个数据源,各有优劣:

数据源 优点 缺点 推荐指数
akshare 免费、更新快、接口简单 偶尔断流、数据偶有缺失 ⭐⭐⭐⭐
tushare 数据质量高、字段丰富 需要积分、高级接口收费 ⭐⭐⭐⭐
baostock 完全免费、历史数据全 更新有延迟、接口较老 ⭐⭐⭐
东方财富API 实时性强、数据稳定 需要逆向工程、维护成本高 ⭐⭐⭐

我建议初学者先用 akshare 上手,等跑通了整个流程,再考虑切换到更稳定的数据源。毕竟,先让轮子转起来比什么都重要。

4.3 实战:用akshare获取日线数据

好,我们直接上代码。这里我用akshare的 bond_convert_daily 接口来获取数据。

import akshare as ak
import pandas as pd
from datetime import datetime, timedelta

def get_convertible_bond_daily(symbol, start_date=None, end_date=None):
    """
    获取可转债日线行情数据
    
    参数:
        symbol: 可转债代码,如 '123456'
        start_date: 开始日期,格式 'YYYYMMDD'
        end_date: 结束日期,格式 'YYYYMMDD'
    
    返回:
        DataFrame,包含日期、开盘、收盘、最高、最低、成交量
    """
    # 默认取最近一年
    if end_date is None:
        end_date = datetime.now().strftime('%Y%m%d')
    if start_date is None:
        start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
    
    try:
        # 调用akshare接口
        df = ak.bond_convert_daily(symbol=symbol)
        
        # 重命名列,统一字段名
        df.rename(columns={
            'date': '日期',
            'open': '开盘价',
            'close': '收盘价',
            'high': '最高价',
            'low': '最低价',
            'volume': '成交量'
        }, inplace=True)
        
        # 日期过滤
        df['日期'] = pd.to_datetime(df['日期'])
        df = df[(df['日期'] >= start_date) & (df['日期'] <= end_date)]
        
        # 按日期升序排列
        df.sort_values('日期', inplace=True)
        df.reset_index(drop=True, inplace=True)
        
        return df
    
    except Exception as e:
        print(f"获取 {symbol} 数据失败: {e}")
        return pd.DataFrame()

# 测试一下
if __name__ == '__main__':
    # 以某只可转债为例
    df = get_convertible_bond_daily('123456', '20240101', '20241231')
    print(df.head())
    print(f"共获取 {len(df)} 条日线数据")

小技巧:akshare的接口返回的列名是英文的,我习惯统一转成中文,这样在后续分析时更直观。另外,日期列一定要转成datetime类型,否则排序和过滤会出问题。

4.4 数据清洗:别让脏数据毁了你的回测

数据拿到手,别急着用。我踩过的坑太多了,这里给你列几个常见的「脏数据」场景:

  • 缺失值:某天停牌,成交量直接为0,但价格可能沿用前一日
  • 异常值:开盘价突然跳空,或者成交量突然放大100倍
  • 重复数据:同一个交易日出现两条记录
  • 复权问题:可转债有转股、回售等事件,会影响价格连续性

我曾经因为没处理停牌日的缺失值,导致回测策略在停牌日「凭空」交易,结果回测收益虚高了5%。嗯,从那以后我再也不敢跳过数据清洗这一步了。

def clean_daily_data(df):
    """
    清洗日线数据
    """
    # 1. 去除重复行
    df = df.drop_duplicates(subset=['日期'])
    
    # 2. 处理缺失值:用前向填充
    df.fillna(method='ffill', inplace=True)
    
    # 3. 处理异常值:成交量不能为负数
    df['成交量'] = df['成交量'].clip(lower=0)
    
    # 4. 确保价格为正
    for col in ['开盘价', '收盘价', '最高价', '最低价']:
        df[col] = df[col].clip(lower=0.01)
    
    return df

# 清洗示例
df_clean = clean_daily_data(df)
print(f"清洗前 {len(df)} 条,清洗后 {len(df_clean)} 条")

4.5 数据存储:别每次都重新爬

每次跑回测都去实时拉数据,效率太低了。我建议把数据存到本地,用的时候直接读文件。

def save_daily_data(df, symbol, data_dir='./data'):
    """保存日线数据到CSV"""
    import os
    os.makedirs(data_dir, exist_ok=True)
    file_path = f"{data_dir}/{symbol}_daily.csv"
    df.to_csv(file_path, index=False, encoding='utf-8-sig')
    print(f"数据已保存至: {file_path}")

def load_daily_data(symbol, data_dir='./data'):
    """从本地加载日线数据"""
    file_path = f"{data_dir}/{symbol}_daily.csv"
    try:
        df = pd.read_csv(file_path)
        df['日期'] = pd.to_datetime(df['日期'])
        return df
    except FileNotFoundError:
        print(f"本地文件不存在: {file_path}")
        return pd.DataFrame()

注意:本地存储的数据要定期更新。我一般每周跑一次增量更新脚本,只拉取最近一周的数据追加到文件末尾。这样既保证了数据新鲜度,又不会浪费太多时间。

4.6 本章知识体系

为了让你更直观地理解本章的内容结构,我画了一张流程图:

日线行情数据获取流程 选择数据源 akshare / tushare 获取日线数据 开盘/收盘/最高/最低/量 数据清洗 去重/填充/异常处理 存储 核心数据字段 开盘价 收盘价 最高价 最低价 成交量 ⚠ 常见坑点 停牌日数据缺失 | 复权处理 | 数据源稳定性 建议:本地缓存 + 定期增量更新

4.7 避坑指南

最后,分享几个我实战中遇到的坑:

  • 数据源切换:不同数据源对同一只转债的收盘价可能有细微差异,回测时尽量固定一个数据源
  • 复权问题:可转债的复权比股票复杂,涉及转股、回售、强赎等事件。我建议直接用不复权数据,自己处理事件
  • 接口限流:akshare免费接口有频率限制,批量获取时记得加sleep
  • 日期对齐:交易日历要跟交易所保持一致,别在非交易日拉数据

一句话总结:数据获取是回测的基石,花80%的精力在数据上,剩下20%做策略,你会发现回测结果靠谱得多。

专注资料整理