2. 回测系统搭建:回测框架选型、数据源获取与本地数据库搭建
做量化交易,说白了就是跟历史数据打交道。你想想看,没有一套靠谱的回测系统,策略好坏全靠猜,那跟赌博有什么区别?
这一章,我带你亲手搭一套回测基础设施。别怕,没那么玄乎。我们分三步走:选框架、找数据、建库房。
2.1 回测框架选型:Backtrader vs Zipline
选框架这事儿,我踩过不少坑。先说结论:个人项目、中小型策略,无脑选 Backtrader。为什么?
| 对比维度 | Backtrader | Zipline |
|---|---|---|
| 上手难度 | 低,文档清晰,社区活跃 | 中高,依赖复杂,配置繁琐 |
| 数据接口 | 灵活,支持自定义数据源 | 强依赖 Bundle,扩展稍麻烦 |
| 性能 | 单线程,适合日频/小时频 | 支持多标的,但内存占用高 |
| 实盘对接 | 有现成 Broker API 封装 | 需要自己写适配层 |
| 维护状态 | 持续更新,Python 3 友好 | Quantopian 关闭后,社区维护 |
我个人习惯用 Backtrader。记得有一次,我用 Zipline 跑一个多因子策略,光是装依赖就折腾了一下午,最后还因为数据格式不对报错。换成 Backtrader,半小时就搞定了。
核心观点:框架只是工具,别在选型上内耗。Backtrader 足够覆盖 90% 的回测场景。等你真遇到性能瓶颈了,再考虑换框架也不迟。
2.2 数据源获取:Tushare vs JoinQuant
数据是回测的血液。没有干净的数据,再牛的策略也是白搭。
国内常用的数据源,我主要用两个:Tushare 和 JoinQuant(聚宽)。
Tushare
- 优点:数据全,从股票、基金到期货、期权,应有尽有。积分制,免费用户也能拿到不少数据。
- 缺点:接口调用频率有限制,高频数据需要付费积分。数据偶尔有缺失,需要自己清洗。
- 适用场景:个人研究、低频策略、需要多品种数据的场景。
JoinQuant(聚宽)
- 优点:数据质量高,自带回测平台,可以直接在线跑策略。API 设计简洁,对新手友好。
- 缺点:本地使用需要下载数据,离线支持不如 Tushare 灵活。免费版有数据量限制。
- 适用场景:快速原型验证、在线回测、不想折腾本地环境的用户。
我的建议:如果你刚开始,先用 Tushare 的免费接口把流程跑通。等需要更高质量的数据时,再考虑 JoinQuant 或付费数据源。别一开始就追求完美,先动起来。
2.3 本地数据库搭建:SQLite
数据拿到手,总不能每次都去远程拉吧?效率低不说,万一接口挂了,你连回测都跑不了。
所以,本地数据库是必须的。我推荐 SQLite。为什么?
- 零配置:不需要安装服务器,一个文件就是一个数据库。
- 轻量级:适合单机回测,读写速度足够快。
- Python 原生支持:标准库自带
sqlite3,无需额外依赖。
下面是一个简单的建表示例,用来存储日线数据:
import sqlite3
# 连接数据库(如果文件不存在,会自动创建)
conn = sqlite3.connect('backtest.db')
cursor = conn.cursor()
# 创建日线数据表
cursor.execute('''
CREATE TABLE IF NOT EXISTS daily_data (
code TEXT NOT NULL, -- 股票代码
date TEXT NOT NULL, -- 交易日期
open REAL, -- 开盘价
high REAL, -- 最高价
low REAL, -- 最低价
close REAL, -- 收盘价
volume INTEGER, -- 成交量
amount REAL, -- 成交额
PRIMARY KEY (code, date) -- 联合主键,防止重复数据
)
''')
conn.commit()
conn.close()
print("数据库和表创建成功!")
注意:SQLite 不支持并发写入。如果你有多进程同时写入的需求,建议换成 PostgreSQL。不过对于个人回测,单线程写入完全够用。
我曾经犯过一个低级错误:把数据全部存在 CSV 文件里,每次回测都要全量加载。结果数据量一大,内存直接爆了。换成 SQLite 后,按日期范围查询,速度提升了不止一个量级。
2.4 知识体系总览
为了让你更直观地理解这三部分的关系,我画了一张流程图:
2.5 实战:把 Tushare 数据存入 SQLite
光说不练假把式。下面是一个完整的示例,演示如何从 Tushare 获取数据并存入 SQLite:
import tushare as ts
import sqlite3
from datetime import datetime, timedelta
# 设置 Tushare Token(需要先在 tushare.pro 注册)
ts.set_token('你的token')
pro = ts.pro_api()
# 连接 SQLite
conn = sqlite3.connect('backtest.db')
cursor = conn.cursor()
# 获取股票日线数据
def fetch_and_store(code, start_date, end_date):
df = pro.daily(ts_code=code, start_date=start_date, end_date=end_date)
if df.empty:
print(f"未获取到 {code} 的数据")
return
# 插入数据
for _, row in df.iterrows():
cursor.execute('''
INSERT OR REPLACE INTO daily_data
(code, date, open, high, low, close, volume, amount)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
row['ts_code'],
row['trade_date'],
row['open'],
row['high'],
row['low'],
row['close'],
row['vol'],
row['amount']
))
conn.commit()
print(f"{code} 数据已存入,共 {len(df)} 条记录")
# 示例:获取贵州茅台最近30天数据
fetch_and_store('000001.SZ', '20240101', '20240130')
conn.close()
小技巧:第一次全量拉取历史数据时,建议分批处理。比如每次拉取一年数据,避免接口超时。我一般会写个循环,从上市日期开始,逐年拉取。
嗯,到这里,回测系统的基础设施就搭好了。你有了数据源,有了本地数据库,也有了回测框架。接下来,就可以开始写策略了。
记住,这套流程我用了好几年,虽然简单,但足够稳定。别小看这些基础工作,它们决定了你后续所有策略研究的效率。