第三章 数据获取:多资产历史行情数据的获取与清洗(含API对接)

做套利策略,数据就是你的眼睛。眼睛要是花了,策略再牛也白搭。

我刚开始做量化那会儿,就吃过数据的大亏。当时跑回测发现收益曲线漂亮得不像话,结果一查,是某只股票的复权数据没处理好,凭空多出来20%的收益。嗯,从那以后,我对数据清洗这件事就特别较真。

3.1 数据源的选择与评估

市面上数据源不少,但质量参差不齐。我个人习惯把数据源分成三类:

类型 代表 优点 缺点
专业金融终端 Wind、Bloomberg 数据全面、质量高 贵,一年几万到几十万
开源数据平台 AKShare、Tushare 免费、社区活跃 稳定性一般,有调用限制
交易所直连 CTP、FIX 实时、无中间环节 开发成本高,维护麻烦
我的建议: 初期用开源平台就够了。等策略跑通了、资金量上来了,再考虑付费数据源。别一上来就花冤枉钱。

3.2 API对接实战:以AKShare为例

AKShare是我用得比较多的开源库。它封装了国内主流交易所的数据接口,用起来很方便。咱们直接看代码:

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

# 获取期货主力连续合约数据
def fetch_futures_data(symbol, start_date, end_date):
    """
    获取期货历史行情
    symbol: 合约代码,如 'RB' 代表螺纹钢
    """
    try:
        df = ak.futures_main_sina(symbol=symbol, 
                                  start_date=start_date,
                                  end_date=end_date)
        return df
    except Exception as e:
        print(f"数据获取失败: {e}")
        return None

# 获取股票日线数据
def fetch_stock_data(stock_code, start_date, end_date):
    """
    获取A股日线数据
    stock_code: 如 '000001' 代表平安银行
    """
    df = ak.stock_zh_a_hist(symbol=stock_code,
                            period="daily",
                            start_date=start_date,
                            end_date=end_date,
                            adjust="qfq")  # 前复权
    return df

# 实战调用
if __name__ == "__main__":
    # 获取螺纹钢和热卷的数据,用于跨品种套利
    rb_data = fetch_futures_data("RB", "20230101", "20231231")
    hc_data = fetch_futures_data("HC", "20230101", "20231231")
    
    print(f"螺纹钢数据量: {len(rb_data)} 条")
    print(f"热卷数据量: {len(hc_data)} 条")
注意: API调用频率有限制。我曾经一次性请求太多数据,直接被封了IP。建议每次请求间隔至少0.5秒,或者用try-except做异常处理。

3.3 数据清洗:从原始数据到可用数据

原始数据拿回来,基本不能直接用。你想想看,交易所的数据里什么幺蛾子都有:空值、重复值、异常跳空……

我总结了一套清洗流程,咱们一步步来:

3.3.1 缺失值处理

def handle_missing_data(df):
    """
    处理缺失值
    """
    # 检查缺失情况
    missing_count = df.isnull().sum()
    print(f"缺失值统计:\n{missing_count}")
    
    # 前向填充(用上一个有效值填充)
    df = df.fillna(method='ffill')
    
    # 如果开头还有缺失,用后向填充
    df = df.fillna(method='bfill')
    
    return df

3.3.2 异常值检测

套利交易最怕的就是异常跳空。我一般用两种方法:

  • 标准差法: 超过3倍标准差的数据直接剔除
  • 百分位法: 低于1%或高于99%分位数的数据标记为异常
def detect_outliers(df, column='close', method='std'):
    """
    异常值检测
    """
    if method == 'std':
        mean = df[column].mean()
        std = df[column].std()
        upper = mean + 3 * std
        lower = mean - 3 * std
    elif method == 'percentile':
        upper = df[column].quantile(0.99)
        lower = df[column].quantile(0.01)
    
    outliers = df[(df[column] > upper) | (df[column] < lower)]
    print(f"检测到 {len(outliers)} 个异常值")
    
    return outliers

3.3.3 时间对齐

做套利,时间对齐是重中之重。不同品种的交易时间可能不一样,比如股指期货和ETF的收盘时间就差15分钟。

def align_time_series(df1, df2, freq='1min'):
    """
    将两个时间序列对齐到相同的时间频率
    """
    # 设置时间索引
    df1.set_index('datetime', inplace=True)
    df2.set_index('datetime', inplace=True)
    
    # 重采样到统一频率
    df1_resampled = df1.resample(freq).last()
    df2_resampled = df2.resample(freq).last()
    
    # 合并,只保留共同的时间段
    aligned = pd.merge(df1_resampled, df2_resampled, 
                       left_index=True, right_index=True,
                       suffixes=('_asset1', '_asset2'))
    
    # 删除任何一方缺失的时间点
    aligned.dropna(inplace=True)
    
    return aligned

3.4 数据存储与版本管理

数据拿到手、洗干净了,怎么存也是个学问。我踩过的坑是:一开始用CSV存,结果数据量大了之后,读取速度慢得让人抓狂。

现在我的方案是分层存储:

层级 存储方式 用途
原始层 Parquet + 压缩 保存原始API数据,方便回溯
清洗层 HDF5 清洗后的数据,用于日常分析
特征层 内存/Redis 计算好的特征,供策略实时调用
核心原则: 原始数据永远不改。每次清洗都生成新文件,保留完整的处理链条。这样出了问题,能快速定位是数据问题还是策略问题。

3.5 数据质量监控

数据不是洗一次就完事了。每天跑策略之前,我都会跑一遍质量检查:

def data_quality_check(df):
    """
    每日数据质量检查
    """
    issues = []
    
    # 检查数据量
    if len(df) < expected_count:
        issues.append(f"数据量不足: 期望{expected_count}, 实际{len(df)}")
    
    # 检查时间连续性
    time_diff = df.index.to_series().diff()
    if time_diff.max() > pd.Timedelta(minutes=5):
        issues.append("存在时间断点")
    
    # 检查价格合理性
    if df['close'].min() <= 0:
        issues.append("存在非正价格")
    
    if issues:
        print("数据质量问题:")
        for issue in issues:
            print(f"  - {issue}")
        return False
    else:
        print("数据质量检查通过")
        return True
一个小技巧: 把质量检查结果发到企业微信或钉钉群里。这样每天早上打开手机,就知道今天的数据能不能用。我用了两年了,省心不少。

3.6 数据获取与清洗的整体流程

说了这么多,咱们用一张图把整个流程串起来:

多资产数据获取与清洗流程 数据源选择 API对接获取数据 原始数据存储(Parquet) 数据清洗(缺失值/异常值/时间对齐) 清洗数据存储(HDF5) 每日质量监控 Wind/ Bloomberg AKShare/ Tushare 交易所直连 注意频率限制 异常重试机制 前向/后向填充 3倍标准差剔除 时间对齐重采样

这张图就是我每天跑数据的标准流程。说白了,数据工作就是个体力活,但体力活做扎实了,策略才能站得住脚。

最后说一句: 数据清洗花的时间,永远值得。我见过太多人花80%的时间写策略,结果被数据坑了。反过来,把数据基础打牢,策略反而跑得顺风顺水。

专注资料整理