第二章:数据来源与采集陷阱
做量化交易的朋友,尤其是搞另类数据的,都绕不开一个坎——数据怎么来?
我见过太多人,策略回测跑得漂亮,一上实盘就崩。为什么?数据源就有问题。今天咱们就聊聊数据采集里那些坑。我个人习惯是,先搞清楚数据怎么死的,再想怎么活。
2.1 API调用限制:你以为无限量,其实有限额
很多数据提供商都有API接口。听起来很美,对吧?但实际用起来,限制多得让你头疼。
常见的限制类型:
- 频率限制:每秒最多10次请求,多了直接封IP
- 总量限制:每天最多100万次调用,超了要加钱
- 并发限制:同时只能开5个连接
我在项目中遇到过一件事。有个团队爬取某财经网站的实时行情,用了多线程并发。结果对方服务器直接返回503,连着三天数据全断。后来一查,是并发数超了,被人家防火墙拦了。
⚠️ 避坑指南:我曾经因为没看API文档的限流说明,导致整个回测数据少了两周。后来学乖了,每次调用前先查文档,再写个限流器。
解决方案:
- 使用
time.sleep()控制请求间隔 - 用
requests.Session复用连接 - 设置重试机制,比如指数退避
import time
import requests
from requests.adapters import HTTPAdapter
session = requests.Session()
session.mount('https://', HTTPAdapter(max_retries=3))
def fetch_data(url, params, max_retries=5):
for i in range(max_retries):
try:
resp = session.get(url, params=params, timeout=10)
if resp.status_code == 200:
return resp.json()
elif resp.status_code == 429:
wait = 2 ** i
print(f"被限流了,等待{wait}秒")
time.sleep(wait)
else:
print(f"请求失败,状态码:{resp.status_code}")
return None
except Exception as e:
print(f"请求异常:{e}")
time.sleep(1)
return None
2.2 网页爬虫反爬:道高一尺,魔高一丈
很多另类数据藏在网页里。你想爬?人家早准备好了反爬手段。
常见的反爬手段:
- User-Agent检测:非浏览器请求直接拒绝
- IP封禁:短时间大量请求,直接拉黑
- 验证码:每次请求都要输入验证码
- 动态加载:数据通过JavaScript渲染,直接请求拿不到
说白了,反爬就是一场猫鼠游戏。你想想看,人家网站也要保护自己的数据资产。
💡 个人经验:我建议先用浏览器的开发者工具(F12)看看网络请求。很多时候,数据是通过XHR接口返回的JSON,直接模拟那个接口就行,不用解析HTML。
应对策略:
- 使用
fake_useragent库随机切换User-Agent - 使用代理IP池,比如付费的代理服务
- 对于动态加载页面,用Selenium或Playwright模拟浏览器
- 设置合理的请求间隔,别太暴力
from fake_useragent import UserAgent
import requests
ua = UserAgent()
headers = {'User-Agent': ua.random}
resp = requests.get('https://example.com/data', headers=headers)
2.3 数据频率不一致:日线、分钟线、Tick数据,怎么对齐?
做回测时,最头疼的问题之一就是数据频率不一致。
比如,你有一个日线级别的股票数据,还有一个分钟级别的舆情数据。怎么把它们对齐?
常见问题:
- 日线数据只有收盘价,分钟数据有开盘、最高、最低、收盘
- 不同数据源的时间戳格式不同,比如有的用UTC,有的用北京时间
- 节假日、休市日,不同市场不一样
我记得有一次,回测一个策略,收益率高得离谱。后来一查,是分钟数据和日线数据没对齐,导致信号提前了。嗯,这里要注意,数据对齐是回测的生命线。
🔑 核心原则:所有数据必须对齐到同一个时间轴。我习惯用UTC时间戳作为基准,然后统一转换成目标时区。
对齐方法:
- 使用
pandas的resample方法,将高频数据降采样到低频 - 使用
merge_asof进行非精确时间对齐 - 手动处理节假日,比如用
exchange_calendars库
import pandas as pd
# 假设df_min是分钟数据,df_daily是日线数据
# 将分钟数据降采样到日线
df_min_daily = df_min.resample('D').agg({
'price': 'last',
'volume': 'sum'
})
# 使用merge_asof进行非精确对齐
aligned = pd.merge_asof(df_daily, df_min_daily,
left_index=True, right_index=True,
direction='backward')
2.4 数据断点与缺失:回测的隐形杀手
数据断点和缺失,是回测中最隐蔽的陷阱。你想想看,如果某一天的数据缺失了,回测会怎么处理?
常见原因:
- 网络中断,导致数据没采集到
- 数据源本身就有缺失,比如某些股票停牌
- 节假日、非交易日,数据自然为空
我在项目中遇到过,回测一个高频策略,收益率曲线特别平滑。后来发现,是因为数据缺失导致信号没触发,反而躲过了亏损。这种“假”收益,实盘时根本不存在。
⚠️ 避坑指南:我曾经因为没处理数据缺失,导致回测结果比实盘高20%。后来每次回测前,我都会先检查数据完整性。
处理方法:
- 使用
isnull()检查缺失值 - 对于少量缺失,用前向填充(ffill)或插值
- 对于大量缺失,直接删除该时间段
- 记录缺失日志,方便后续排查
import pandas as pd
# 检查缺失值
missing = df.isnull().sum()
print(f"缺失值统计:\n{missing}")
# 前向填充
df.fillna(method='ffill', inplace=True)
# 如果某天缺失超过50%,删除该天
threshold = len(df.columns) * 0.5
df.dropna(thresh=threshold, inplace=True)
2.5 知识体系总览
下面这张图,是我自己总结的数据采集陷阱全景图。你可以把它当成一个检查清单。
2.6 实战检查清单
最后,我整理了一个检查清单。每次开始回测前,我都会过一遍。
| 检查项 | 具体内容 | 工具/方法 |
|---|---|---|
| API限制 | 是否有限流?并发数多少? | 查看API文档,写限流器 |
| 反爬措施 | 是否有验证码?是否动态加载? | F12分析网络请求,用Selenium |
| 频率对齐 | 所有数据是否在同一时间轴? | pandas resample/merge_asof |
| 缺失值 | 是否有缺失?缺失比例多少? | isnull()检查,ffill/插值 |
| 数据源验证 | 数据是否来自可靠来源? | 交叉验证,对比多个数据源 |
好了,这一章的内容就到这里。数据采集是回测的基石,地基不稳,楼盖得再高也是白搭。下一章,咱们聊聊数据清洗里的那些坑。
公众号:蓝海数据掘金营,微信deep3321