3. 数据获取基础:从 Yahoo Finance 获取历史行情、从 CSV 文件读取数据、API 接口调用规范
数据获取是量化回测的起点。原始数据的质量直接决定回测结果的可靠性。本章聚焦三种最常用的数据获取方式:Yahoo Finance 在线接口、本地 CSV 文件读取以及通用 API 调用规范。掌握这些基础,你就能为后续清洗与回测建立稳定的数据管道。
💡 核心原则:无论使用哪种数据源,始终在获取阶段完成 字段对齐(统一列名)、类型转换(日期、数值)和 缺失标记(NaN 处理),避免将脏数据带入回测逻辑。
3.1 从 Yahoo Finance 获取历史行情
Yahoo Finance 提供免费、稳定的历史日线数据,是回测入门最常用的数据源。Python 中通过 yfinance 库即可快速获取。
3.1.1 安装与基本调用
# 安装(如未安装)
# pip install yfinance
import yfinance as yf
# 获取单只股票历史行情
ticker = "AAPL"
data = yf.download(ticker, start="2023-01-01", end="2023-12-31")
print(data.head())
3.1.2 返回数据结构
yf.download() 返回 pandas.DataFrame,包含以下标准列:
| 列名 | 含义 | 数据类型 | 常见问题 |
|---|---|---|---|
| Open | 开盘价 | float64 | 除权后可能为 0 或负值 |
| High | 最高价 | float64 | 需检查是否 ≥ Open 和 Close |
| Low | 最低价 | float64 | 需检查是否 ≤ Open 和 Close |
| Close | 收盘价(未复权) | float64 | 回测前通常需复权 |
| Volume | 成交量 | int64 | 可能为 0(停牌日) |
| Adj Close | 复权收盘价 | float64 | Yahoo 自动调整,推荐用于回测 |
⚠️ 常见陷阱:Yahoo Finance 数据在 拆股、分红 时,
Open/High/Low/Close 不会自动调整,只有 Adj Close 是复权后的。如果直接用 Close 计算收益率,会导致严重偏差。建议始终使用 Adj Close 进行策略计算。
3.1.3 批量获取与异常处理
tickers = ["AAPL", "MSFT", "GOOGL"]
data = yf.download(tickers, start="2023-01-01", end="2023-12-31")
# 处理缺失数据(如某只股票某日停牌)
data = data.dropna(how='any') # 删除任意列为 NaN 的行
print(data.shape)
3.2 从 CSV 文件读取数据
本地 CSV 文件是离线回测、历史数据备份、机构数据交换的通用格式。读取时需注意编码、分隔符、日期解析等细节。
3.2.1 标准读取方式
import pandas as pd
# 标准读取
df = pd.read_csv("historical_data.csv",
parse_dates=["Date"],
index_col="Date",
encoding="utf-8")
# 查看基本信息
print(df.info())
print(df.head())
3.2.2 常见 CSV 格式问题与解决方案
| 问题类型 | 表现 | 解决方案 |
|---|---|---|
| 编码错误 | UnicodeDecodeError,中文乱码 | 指定 encoding='gbk' 或 'utf-8-sig' |
| 分隔符不一致 | 数据未正确分列 | 使用 sep=';' 或 sep='\t' 匹配实际分隔符 |
| 日期格式不统一 | 2023/01/01 与 2023-01-01 混用 | 使用 parse_dates 参数自动推断 |
| 表头缺失 | 第一行即为数据 | 设置 header=None,手动指定列名 |
| 空值标记多样 | 空字符串、"NA"、"null"、"None" | 使用 na_values=['', 'NA', 'null', 'None'] |
| 千位分隔符 | "1,234.56" 无法直接转为 float | 设置 thousands=',' |
3.2.3 读取后字段对齐
# 统一列名为小写 + 下划线
df.columns = [col.lower().replace(' ', '_') for col in df.columns]
# 确保包含必要字段
required_cols = ['open', 'high', 'low', 'close', 'volume']
for col in required_cols:
if col not in df.columns:
raise ValueError(f"缺少必要字段: {col}")
# 类型强制转换
df['close'] = pd.to_numeric(df['close'], errors='coerce')
📌 最佳实践:建议将 CSV 读取封装为函数,统一处理编码、列名映射、类型转换,形成可复用的数据加载模块。后续章节的清洗流程将基于此模块展开。
3.3 API 接口调用规范
除 Yahoo Finance 外,量化交易常对接专业数据服务商(如 Wind、Bloomberg、Tushare、AKShare)。统一的 API 调用规范能降低维护成本,提高代码可读性。
3.3.1 通用 API 调用模板
import requests
import time
from typing import Dict, Optional
def fetch_market_data(symbol: str,
start_date: str,
end_date: str,
api_key: str,
base_url: str = "https://api.example.com") -> Optional[Dict]:
"""
通用市场数据 API 调用模板
参数:
symbol: 股票代码
start_date: 起始日期 (YYYY-MM-DD)
end_date: 结束日期 (YYYY-MM-DD)
api_key: API 密钥
base_url: API 基础地址
返回:
解析后的 JSON 数据,失败返回 None
"""
headers = {
"Authorization": f"Bearer {api_key}",
"User-Agent": "QuantBacktest/1.0"
}
params = {
"symbol": symbol,
"start": start_date,
"end": end_date,
"format": "json"
}
try:
response = requests.get(base_url + "/history",
headers=headers,
params=params,
timeout=10)
response.raise_for_status() # 触发 HTTP 错误
return response.json()
except requests.exceptions.RequestException as e:
print(f"API 请求失败: {e}")
return None
finally:
time.sleep(0.5) # 限速,避免被封
3.3.2 API 调用规范清单
- 认证方式:优先使用 API Key 放在 Header 中,避免 URL 明文传输。
- 限速与重试:每次请求后至少等待 0.5 秒;遇到 429 状态码时指数退避重试。
- 错误处理:区分网络错误(超时、断连)与业务错误(参数非法、无权限),分别记录日志。
- 数据缓存:对同一参数组合的请求结果缓存到本地 CSV/Parquet,减少重复调用。
- 字段映射:将 API 返回的字段名(如
trade_date)统一映射为内部标准名(如date)。
3.4 三种数据获取方式对比
| 特性 | Yahoo Finance (yfinance) | CSV 文件读取 | 通用 API 调用 |
|---|---|---|---|
| 数据实时性 | 延迟 15-20 分钟 | 取决于文件更新时间 | 通常实时或 T+1 |
| 数据范围 | 全球主要交易所 | 仅限文件包含的数据 | 取决于服务商覆盖 |
| 使用成本 | 免费 | 免费(本地存储) | 通常按量收费 |
| 稳定性 | 中等(可能被限流) | 高(本地文件) | 高(商业 SLA) |
| 清洗复杂度 | 低(字段标准) | 中(需处理格式差异) | 中高(需字段映射) |
| 适用场景 | 快速原型、个人研究 | 离线回测、数据备份 | 生产环境、机构级回测 |
📌 本章小结:数据获取是回测管道的入口。Yahoo Finance 适合快速获取标准行情,CSV 读取适合离线与批量处理,通用 API 规范则保障生产环境的可靠性。无论选择哪种方式,获取后立即进行字段对齐、类型转换、缺失标记是避免脏数据扩散的关键习惯。下一章将深入讲解数据清洗的核心操作——缺失值与异常值处理。