3. 数据获取与处理:让ETF数据乖乖听话

做量化投资,数据就是你的弹药库。没有数据,再好的策略也是空中楼阁。这一章,咱们就聊聊怎么把ETF的历史数据搞到手,再把它收拾得服服帖帖。

说实话,我刚开始做量化那会儿,花在数据清洗上的时间比写策略还多。你想想看,拿到的数据经常是缺胳膊少腿的,不处理干净,策略跑出来的结果你敢信吗?

核心观点:数据质量决定了策略的天花板。垃圾数据进,垃圾策略出。

3.1 获取ETF历史行情数据

获取数据的方式有很多,我挑几个最常用的说说。

3.1.1 从Tushare获取数据

Tushare是国内用得最多的金融数据接口之一。我个人习惯用它来拉日线数据,稳定且字段齐全。

import tushare as ts

# 设置token(需要去官网注册)
ts.set_token('你的token')

pro = ts.pro_api()

# 获取沪深300ETF(510300.SH)的历史日线数据
df = pro.fund_daily(
    ts_code='510300.SH',
    start_date='20200101',
    end_date='20231231',
    fields='trade_date,open,high,low,close,vol'
)

print(df.head())

这里要注意,Tushare的token需要去官网申请。我记得第一次用的时候,忘了设置token,结果一直报错,折腾了半天才发现问题。

3.1.2 从AKShare获取数据

AKShare是另一个好用的开源库,完全免费,数据源覆盖广。我比较喜欢它的ETF实时行情接口。

import akshare as ak

# 获取ETF实时行情
etf_spot = ak.fund_etf_spot_em()
print(etf_spot.head())

# 获取历史数据
etf_hist = ak.fund_etf_hist_em(
    symbol="510300",
    period="daily",
    start_date="20200101",
    end_date="20231231",
    adjust="qfq"  # 前复权
)
print(etf_hist.head())

小技巧:AKShare的接口名经常更新,建议定期查看官方文档。我一般每季度检查一次,避免接口失效影响策略运行。

3.1.3 从本地CSV文件读取

有时候数据源不稳定,我会把数据存到本地CSV。这样跑策略时不用每次都去拉数据,速度快很多。

import pandas as pd

# 从CSV读取数据
df = pd.read_csv('etf_510300.csv', parse_dates=['trade_date'])
print(df.info())

3.2 数据清洗与预处理

拿到原始数据后,第一件事就是清洗。我见过太多人直接拿原始数据跑策略,结果回测曲线漂亮得不行,实盘一跑就崩。为什么?数据里有坑啊。

3.2.1 处理缺失值

ETF数据偶尔会有缺失,比如停牌日。处理方式要看情况:

  • 前向填充:用上一个交易日的数据填充,适合价格数据
  • 删除缺失行:如果缺失不多,直接删掉
  • 插值法:用前后数据做线性插值
# 前向填充
df['close'].fillna(method='ffill', inplace=True)

# 删除缺失行
df.dropna(subset=['close'], inplace=True)

# 线性插值
df['close'].interpolate(method='linear', inplace=True)

注意:千万不要用均值填充价格数据!我曾经犯过这个错,结果策略信号全乱了。价格序列有趋势性,均值填充会破坏这种结构。

3.2.2 处理异常值

异常值通常来自数据录入错误或极端行情。我一般用两种方法检测:

  • 3σ原则:超过均值±3倍标准差的值视为异常
  • IQR方法:超过Q1-1.5*IQR或Q3+1.5*IQR的值视为异常
# 用IQR方法检测异常值
Q1 = df['close'].quantile(0.25)
Q3 = df['close'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 标记异常值
df['is_outlier'] = (df['close'] < lower_bound) | (df['close'] > upper_bound)

# 查看异常值
print(df[df['is_outlier']])

3.2.3 复权处理

ETF有分红、拆合,这些都会影响价格连续性。做量化必须用复权数据。

  • 前复权:调整历史价格,保持当前价格不变
  • 后复权:调整当前价格,保持历史价格不变

我个人习惯用前复权,因为这样计算收益率时更直观。AKShare的adjust="qfq"参数就是前复权。

3.3 数据可视化基础

数据可视化不是为了好看,是为了发现问题。一张图胜过千行数据,这话一点不假。

3.3.1 绘制K线图

K线图是ETF分析的基础。我用mplfinance库,简单好用。

import mplfinance as mpf

# 准备数据
df_plot = df.set_index('trade_date')
df_plot = df_plot[['open', 'high', 'low', 'close', 'vol']]

# 绘制K线图
mpf.plot(
    df_plot,
    type='candle',
    volume=True,
    style='charles',
    title='沪深300ETF K线图',
    ylabel='价格',
    ylabel_lower='成交量'
)

3.3.2 绘制均线

均线是最常用的技术指标。我一般会叠加5日、20日、60日均线。

# 计算均线
df['ma5'] = df['close'].rolling(window=5).mean()
df['ma20'] = df['close'].rolling(window=20).mean()
df['ma60'] = df['close'].rolling(window=60).mean()

# 绘制
mpf.plot(
    df_plot,
    type='candle',
    mav=(5, 20, 60),
    volume=True,
    style='charles',
    title='沪深300ETF 均线图'
)

3.3.3 绘制收益率分布

了解收益率的分布特征,对风险管理很重要。

import matplotlib.pyplot as plt

# 计算日收益率
df['return'] = df['close'].pct_change()

# 绘制直方图
plt.figure(figsize=(10, 6))
plt.hist(df['return'].dropna(), bins=50, alpha=0.7, color='steelblue')
plt.title('日收益率分布')
plt.xlabel('收益率')
plt.ylabel('频次')
plt.grid(True, alpha=0.3)
plt.show()

3.4 常用数据源介绍

选数据源,说白了就是看三点:数据质量、更新频率、费用。我整理了一个对比表:

数据源 特点 费用 适用场景
Tushare 数据全面,接口稳定 部分免费,高级功能收费 专业量化研究
AKShare 完全免费,更新快 免费 个人学习和研究
东方财富 实时行情,数据量大 免费 实时监控
Wind 机构级数据,质量最高 收费(较贵) 专业机构
本地CSV 离线可用,速度快 免费 策略回测

我的建议:刚开始做量化,用AKShare就够了。等策略成熟了,再考虑Tushare或Wind。别一上来就花冤枉钱。

3.5 本章知识体系

下面这张图,把本章的核心逻辑串起来了。你一看就明白:

数据获取与处理知识体系 数据源 数据获取 数据处理 Tushare AKShare 东方财富 Wind / 本地CSV API接口调用 CSV文件读取 数据库查询 缺失值处理 异常值检测 复权处理 数据可视化 K线图 均线图 / 分布图 数据获取 → 数据清洗 → 数据可视化 → 策略开发

这张图把整个流程串起来了。从数据源开始,到获取数据,再到清洗处理,最后可视化展示。每一步都环环相扣,缺一不可。

好了,数据这块就聊到这儿。记住一句话:数据是你的朋友,不是你的敌人。花时间把数据处理好,后面的策略开发会顺畅很多。

专注资料整理