数据获取与清洗(上):使用yfinance获取历史数据、使用ib_insync连接盈透证券获取实时数据、数据格式标准化

做量化交易,尤其是高频策略,第一步就是搞定数据。没有数据,再牛的算法也是空中楼阁。我个人习惯把数据获取分成两条线:一条是历史数据,用来回测;另一条是实时数据,用来实盘。今天咱们就先把这两条线打通,顺便把数据格式统一一下。

一、用yfinance拿历史数据——简单粗暴但够用

yfinance这个库,说白了就是雅虎财经的非官方Python接口。我刚开始做美股回测时,第一个用的就是它。为什么?因为免费、简单、不用注册API Key。你想想看,对于快速验证策略想法来说,这简直是神器。

我的经验:yfinance适合拿日线、周线级别的历史数据。如果你要做分钟级甚至tick级的高频回测,它的数据精度就不太够了。我曾经用它做5分钟级别的回测,结果发现数据有缺失,后来换成了IB的实时数据。

安装很简单:

pip install yfinance

基本用法:

import yfinance as yf

# 下载苹果股票从2023年1月到2024年1月的日线数据
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01')

print(data.head())

输出结果长这样:

Date Open High Low Close Volume
2023-01-03 130.28 131.74 128.12 129.93 74651200
2023-01-04 128.90 130.39 127.31 128.67 64857600

嗯,这里要注意:yfinance返回的数据是Pandas DataFrame格式,索引是日期。但它的列名是大写的,而且Open/High/Low/Close/Volume这些字段名跟我们后面要用的IB数据不太一样。所以标准化这一步必不可少。

二、用ib_insync连接盈透证券——实时数据的正确打开方式

如果你要做实盘交易,或者需要更精细的分钟级、tick级数据,那就得用盈透证券(Interactive Brokers)了。ib_insync这个库,我个人觉得是连接IB最好用的Python库,没有之一。

为什么?因为它把IB复杂的API封装成了异步模式,用起来特别顺手。我当年第一次用IB原生API时,被那些回调函数搞得头大。后来换了ib_insync,整个世界清净了。

避坑指南:我曾经在连接IB时忘记启动TWS(Trader Workstation)或IB Gateway,结果程序一直报错。记住:ib_insync是通过socket连接本地运行的TWS或Gateway的,所以必须先启动客户端。

安装:

pip install ib_insync

连接并获取实时数据:

from ib_insync import *

# 创建连接
ib = IB()
ib.connect('127.0.0.1', 7497, clientId=1)  # 7497是TWS的端口,7496是Gateway的

# 定义合约
contract = Stock('AAPL', 'SMART', 'USD')

# 请求历史数据(5分钟K线)
bars = ib.reqHistoricalData(
    contract,
    endDateTime='',
    durationStr='2 D',
    barSizeSetting='5 mins',
    whatToShow='TRADES',
    useRTH=True,
    formatDate=1
)

# 转成DataFrame
df = util.df(bars)
print(df.head())

输出结果:

date open high low close volume
2024-01-02 09:30:00 185.64 186.23 185.12 185.89 1234567

看到区别了吗?IB返回的列名是小写的,而且时间格式是带时区的。这就是为什么我们需要做数据格式标准化。

三、数据格式标准化——统一战场

做量化交易最怕什么?数据格式不统一。你从yfinance拿到的数据是大写列名,从IB拿到的是小写列名,从其他数据源拿到的可能又是另一种格式。我见过最离谱的一次,有个同事把Open拼成了"OPen",结果回测跑出来全是错的。

所以,我建议所有数据进来后,先做标准化处理。标准格式如下:

  • datetime:统一为UTC时间,列名用'datetime'
  • open, high, low, close:统一小写
  • volume:统一小写
  • symbol:添加股票代码列

标准化函数示例:

def standardize_data(df, source='yfinance'):
    """
    将不同来源的数据标准化为统一格式
    """
    df = df.copy()
    
    if source == 'yfinance':
        # yfinance的列名是大写的
        df.columns = [col.lower() for col in df.columns]
        # 重置索引,把日期变成普通列
        df = df.reset_index()
        df.rename(columns={'date': 'datetime'}, inplace=True)
        
    elif source == 'ib':
        # IB的列名已经是小写,但时间列名是'date'
        df.rename(columns={'date': 'datetime'}, inplace=True)
    
    # 统一添加symbol列
    if 'symbol' not in df.columns:
        df['symbol'] = 'AAPL'  # 这里可以根据实际情况修改
    
    # 确保列顺序一致
    cols = ['datetime', 'symbol', 'open', 'high', 'low', 'close', 'volume']
    df = df[cols]
    
    return df

# 使用示例
yf_data = yf.download('AAPL', start='2023-01-01', end='2023-01-10')
yf_std = standardize_data(yf_data, source='yfinance')
print(yf_std.head())
核心要点:数据标准化不是可选项,而是必选项。我见过太多策略因为数据格式问题导致回测结果失真。统一格式后,你的回测引擎只需要处理一种数据格式,代码简洁、bug少。

四、本章知识体系总览

下面这张图,是我做数据获取与清洗时的核心流程。你看一眼就能明白整个逻辑:

数据获取与清洗核心流程 yfinance 历史数据(日线/周线) ib_insync 实时数据(分钟/tick) 数据标准化 统一列名/时间/格式 标准化DataFrame datetime | symbol | open | high | low | close | volume 回测引擎输入 实盘策略输入

这张图的核心逻辑很简单:不管数据从哪来,最终都要经过标准化处理,变成统一的格式。这样你的回测引擎和实盘策略才能无缝对接。

五、一些实用技巧

  • 数据缓存:yfinance每次请求都有延迟,我建议把下载的数据存成CSV或Parquet格式,避免重复下载。特别是做回测时,数据量大了网络请求很慢。
  • IB连接重试:IB的连接有时会断开,尤其是长时间运行。我习惯在代码里加一个重试机制,每隔30秒检查一次连接状态。
  • 时区处理:美股数据默认是美东时间,但你的策略可能用UTC。记得在标准化时统一时区,不然回测结果会差几个小时。
我的小习惯:每次拿到新数据,我都会先打印前5行和后5行,看看有没有异常值。比如某天的开盘价是0,或者成交量突然少了好几个数量级。这种数据直接扔掉,别留着坑自己。

好了,数据获取与清洗的上半部分就到这里。记住:数据质量决定了策略的天花板。你花再多时间在数据清洗上都不为过。下一节我们会继续深入,聊聊数据清洗中的那些坑——缺失值处理、异常值检测、复权数据等等。


专注资料整理