第一章 数据获取与清洗:用yfinance搞定股票/加密货币数据

做量化交易,说白了就是跟数据打交道。我见过太多人一上来就写策略,结果数据源就有问题,折腾半天全白费。今天咱们就从最基础的开始——怎么用yfinance把数据拿下来,再把它收拾得干干净净。

1.1 为什么选yfinance?

市面上能拿金融数据的库不少,但yfinance是我个人最常用的。原因很简单:免费、不用注册、支持股票和加密货币。我刚开始做量化那会儿,为了拿数据还得去各个交易所申请API,麻烦得要命。yfinance直接省掉了这些破事。

核心优势:

  • 支持美股、港股、A股(通过特定代码)
  • 支持比特币、以太坊等主流加密货币
  • 历史数据最多可拉取10年以上
  • 自动处理股票分割和股息调整

1.2 安装与基本用法

先装库,这个不用多说:

pip install yfinance pandas numpy

拿数据就几行代码的事:

import yfinance as yf
import pandas as pd

# 获取苹果股票数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
print(aapl.head())

嗯,这里要注意一点。yfinance返回的数据默认是DataFrame,列名是Open、High、Low、Close、Volume、Adj Close。我刚开始用的时候,老是把Adj Close和Close搞混,后来才明白——Adj Close是复权后的收盘价,考虑了分红和拆股。做回测时一定要用Adj Close,不然结果会偏。

1.3 加密货币数据获取

拿加密货币也差不多,就是代码写法有点区别:

# 获取比特币数据
btc = yf.download('BTC-USD', start='2020-01-01', end='2023-12-31')

# 获取以太坊数据
eth = yf.download('ETH-USD', start='2020-01-01', end='2023-12-31')

为什么加个USD?因为yfinance把加密货币当成货币对来处理。BTC-USD就是比特币对美元的价格。我有个朋友一开始写的是'BTC',结果啥也没拿到,折腾了半天才发现少了个后缀。

1.4 处理缺失值——避坑指南

拿到的数据很少是完美的。尤其是加密货币,周末和节假日经常没数据。我曾经因为没处理缺失值,导致策略在回测时出现了奇怪的跳空,差点以为发现了什么套利机会。

常见的缺失值处理方法:

方法 适用场景 代码示例
向前填充 非交易时间的数据缺失 df.fillna(method='ffill')
向后填充 数据开头缺失 df.fillna(method='bfill')
插值法 少量缺失且数据平滑 df.interpolate()
直接删除 缺失比例极小 df.dropna()

注意:千万别在回测时用未来数据填充!比如用后一天的收盘价填充前一天的缺失值,这属于典型的前瞻偏差。我见过有人因为这个在实盘时亏得底裤都不剩。

1.5 异常值检测与处理

异常值这东西,说白了就是数据里的「刺头」。比如某天比特币突然涨了50%,然后又跌回去,这明显是数据错误。我常用的方法是Z-score和IQR:

from scipy import stats
import numpy as np

# Z-score方法
z_scores = np.abs(stats.zscore(df['Close']))
outliers = df[z_scores > 3]  # 超过3个标准差视为异常

# IQR方法
Q1 = df['Close'].quantile(0.25)
Q3 = df['Close'].quantile(0.75)
IQR = Q3 - Q1
outliers = df[(df['Close'] < Q1 - 1.5*IQR) | (df['Close'] > Q3 + 1.5*IQR)]

我个人习惯用IQR方法,因为金融数据往往不是正态分布,Z-score的假设不太成立。处理异常值时,我一般直接删除,或者用前后几天的均值替代。

1.6 重采样与时间对齐

做情绪拐点分析,经常需要把不同频率的数据对齐。比如你有日线数据,但想跟小时级别的情绪指标做对比。这时候就需要重采样:

# 日线转周线
weekly = df.resample('W').agg({
    'Open': 'first',
    'High': 'max',
    'Low': 'min',
    'Close': 'last',
    'Volume': 'sum'
})

# 日线转小时线(需要先有分钟数据)
hourly = df.resample('H').last()

时间对齐这块,我踩过最大的坑就是时区问题。yfinance默认返回的是UTC时间,但A股是北京时间,美股是美东时间。不对齐的话,你会发现数据对不上。解决办法很简单:

# 统一转换为北京时间
df.index = df.index.tz_convert('Asia/Shanghai')

# 或者去掉时区信息
df.index = df.index.tz_localize(None)

1.7 完整的数据清洗流程

说了这么多,咱们来个完整的例子。这是我做项目时常用的流程:

def clean_data(ticker, start, end):
    # 1. 获取数据
    df = yf.download(ticker, start=start, end=end)
    
    # 2. 处理缺失值
    df = df.fillna(method='ffill').fillna(method='bfill')
    
    # 3. 检测异常值(用IQR方法)
    for col in ['Open', 'High', 'Low', 'Close']:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        lower = Q1 - 3*IQR
        upper = Q3 + 3*IQR
        df[col] = df[col].clip(lower, upper)
    
    # 4. 只保留交易时间(去掉周末)
    df = df[df.index.dayofweek < 5]
    
    return df

# 使用示例
aapl_clean = clean_data('AAPL', '2020-01-01', '2023-12-31')

小技巧:数据清洗完记得保存一份副本。我每次都会把原始数据和清洗后的数据分开存,万一发现清洗逻辑有问题,还能回退重来。

1.8 本章知识体系

下面这张图是我自己总结的数据处理流程,你照着这个思路走,基本不会出大问题:

数据获取与清洗流程 1. 数据获取 yfinance.download() 2. 缺失值处理 ffill / bfill / interpolate 3. 异常值检测 Z-score / IQR 4. 重采样 resample() / asfreq() 5. 时间对齐 tz_convert() / tz_localize() 6. 保存数据 to_csv() / to_parquet() 每个步骤都可能需要根据实际情况调整参数 建议先在小样本上测试,再批量处理

数据清洗这步看着简单,但真做起来坑不少。我建议你拿到数据后,先画个图看看整体走势,再检查有没有明显的异常点。别急着跑策略,数据干净了,后面的分析才有意义。

专注资料整理