第二章:数据来源与采集陷阱

做量化交易的朋友,尤其是搞另类数据的,都绕不开一个坎——数据怎么来?

我见过太多人,策略回测跑得漂亮,一上实盘就崩。为什么?数据源就有问题。今天咱们就聊聊数据采集里那些坑。我个人习惯是,先搞清楚数据怎么死的,再想怎么活。

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时间戳作为基准,然后统一转换成目标时区。

对齐方法:

  • 使用pandasresample方法,将高频数据降采样到低频
  • 使用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 知识体系总览

下面这张图,是我自己总结的数据采集陷阱全景图。你可以把它当成一个检查清单。

数据采集陷阱全景图 数据采集陷阱 API调用限制 网页爬虫反爬 数据频率不一致 数据断点与缺失 频率限制 总量限制 并发限制 User-Agent检测 IP封禁 验证码/动态加载 时间戳格式不同 节假日不一致 降采样/对齐 网络中断 数据源缺失 停牌/节假日 核心原则:先验证数据完整性,再跑回测

2.6 实战检查清单

最后,我整理了一个检查清单。每次开始回测前,我都会过一遍。

检查项 具体内容 工具/方法
API限制 是否有限流?并发数多少? 查看API文档,写限流器
反爬措施 是否有验证码?是否动态加载? F12分析网络请求,用Selenium
频率对齐 所有数据是否在同一时间轴? pandas resample/merge_asof
缺失值 是否有缺失?缺失比例多少? isnull()检查,ffill/插值
数据源验证 数据是否来自可靠来源? 交叉验证,对比多个数据源

好了,这一章的内容就到这里。数据采集是回测的基石,地基不稳,楼盖得再高也是白搭。下一章,咱们聊聊数据清洗里的那些坑。


公众号:蓝海数据掘金营,微信deep3321