2. 回测系统搭建:Python环境准备、数据源获取(Tick级数据)、回测引擎架构设计
做抢单策略,最怕什么?
怕回测结果漂亮,实盘一跑就崩。我见过太多人,拿着日线级别的回测曲线,兴冲冲杀进高频战场,结果被Tick级的数据噪声打得鼻青脸肿。说白了,抢单策略的回测,必须从Tick级数据开始。
这一章,咱们就把回测系统的地基打牢。我会把我在搭建这套系统时踩过的坑、总结的经验,一股脑倒出来。
2.1 Python环境准备:别在工具上翻车
我个人习惯用Python 3.9+,太老的版本有些库不支持,太新的又怕兼容性问题。嗯,这里要注意,别用Anaconda自带的那个Python,版本经常落后。
核心依赖清单
- 数据处理:pandas(必须2.0+)、numpy
- 高性能计算:numba、cython(可选)
- 数据存储:h5py(HDF5格式)、pyarrow(Parquet格式)
- 时间处理:arrow、datetime
- 回测框架:自己写(推荐),或者用backtrader(改起来费劲)
我曾经在项目里直接用pip install一把梭,结果numba和pandas版本冲突,跑个回测报错半小时。后来学乖了,用虚拟环境隔离。
# 创建虚拟环境
python -m venv tick_backtest_env
# 激活(Windows)
tick_backtest_env\Scripts\activate
# 激活(Mac/Linux)
source tick_backtest_env/bin/activate
# 安装核心库
pip install pandas==2.0.3 numpy==1.24.3 numba==0.58.1 h5py==3.9.0 pyarrow==12.0.0
小技巧:把依赖写进requirements.txt,团队协作时直接pip install -r requirements.txt,省心。
2.2 数据源获取:Tick级数据,贵但值得
抢单策略的命根子就是数据。日线、分钟线?那都是给趋势策略玩的。咱们要的是每一笔成交的细节——价格、数量、时间戳、买卖方向。
数据来源,我主要用这几个:
| 数据源 | 特点 | 成本 | 推荐指数 |
|---|---|---|---|
| 交易所官方API | 最原始、最准确,但需要自己攒 | 免费(但有流量限制) | ⭐⭐⭐⭐ |
| 第三方数据商(如Wind、聚宽) | 清洗过,但可能有延迟 | 贵(年费几万起) | ⭐⭐⭐ |
| 开源数据集(如Binance历史数据) | 免费,但质量参差不齐 | 免费 | ⭐⭐ |
我个人建议,起步阶段用交易所的WebSocket接口自己攒数据。虽然麻烦,但你能确保数据是原汁原味的。我刚开始做的时候,图省事买了第三方数据,结果回测时发现时间戳被四舍五入到了毫秒级,抢单策略直接废了。
数据格式,我推荐用Parquet。为什么?
- 压缩率高,Tick级数据一天几个GB,Parquet能压到几百MB
- 读取速度快,pandas直接读,不用转格式
- 支持列式存储,你只取需要的字段就行
import pandas as pd
import pyarrow.parquet as pq
# 读取Tick数据
df = pd.read_parquet('btc_usdt_tick_20240101.parquet')
# 看看数据结构
print(df.head())
# 输出示例:
# timestamp price volume side
# 0 2024-01-01 00:00:00.123456 42350.2 0.0015 buy
# 1 2024-01-01 00:00:00.124001 42350.1 0.0020 sell
# 2 2024-01-01 00:00:00.124500 42350.3 0.0018 buy
注意:Tick数据的时间戳一定要精确到微秒级(6位小数)。毫秒级(3位小数)对于抢单策略来说,太粗糙了。我曾经因为数据商只提供毫秒级时间戳,导致回测里出现了大量「同时成交」的假象,策略看起来无敌,实盘一跑就亏。
2.3 回测引擎架构设计:别把引擎做成「铁板一块」
回测引擎,说白了就是一个模拟器。你把历史数据喂进去,它模拟订单簿的变化,然后让你的策略在模拟环境里跑。
我见过很多人的回测引擎,代码全写在一个文件里,几千行,改个参数都要找半天。你想想看,这哪是引擎,分明是炸弹。
好的架构,应该是模块化的。我习惯分成三层:
- 数据层:负责读取、清洗、对齐Tick数据
- 引擎层:负责驱动时间推进、撮合订单、更新订单簿
- 策略层:负责接收市场数据、生成交易信号、管理仓位
下面这张图,是我自己设计的回测引擎核心流程。你看一眼,心里就有数了。
引擎层的核心,是时间推进机制。说白了,就是按时间顺序,一个一个Tick地处理。每个Tick进来,先更新订单簿,然后问策略:「现在这个行情,你要不要下单?」
代码骨架大概是这样的:
class BacktestEngine:
def __init__(self, data_path, strategy):
self.data = self._load_data(data_path)
self.strategy = strategy
self.order_book = OrderBook()
self.positions = {}
self.equity_curve = []
def _load_data(self, path):
# 读取Parquet文件
df = pd.read_parquet(path)
# 按时间排序,确保顺序正确
df = df.sort_values('timestamp')
return df
def run(self):
for idx, tick in self.data.iterrows():
# 1. 更新订单簿
self.order_book.update(tick)
# 2. 检查是否有未成交订单
self._check_pending_orders(tick)
# 3. 调用策略,生成信号
signal = self.strategy.on_tick(tick, self.order_book)
# 4. 执行信号
if signal:
self._execute_order(signal, tick)
# 5. 记录权益曲线
self._record_equity(tick)
def _execute_order(self, signal, tick):
# 这里要处理滑点、手续费等
# 我习惯在回测里加一个「滑点模型」
fill_price = tick.price + signal.direction * self.slippage
# ... 执行逻辑
核心要点:
- 每个Tick都要更新订单簿,不能跳过
- 滑点模型必须加,抢单策略对滑点极其敏感
- 记录每个Tick的权益变化,方便后续分析
2.4 避坑指南:我踩过的那些坑
做回测系统,有几个坑是绕不开的。我一个个说。
坑一:未来函数
这是最常见的坑。比如你在回测里用了「当根K线收盘价」来判断开仓,但实际交易中,收盘价是走完才知道的。Tick级回测里,这个问题更隐蔽——你可能不小心用了「下一个Tick」的数据。
我曾经在写订单簿更新逻辑时,把当前Tick的成交价当成了下一个Tick的买入价,结果回测收益翻倍,实盘直接腰斩。后来我加了一条铁律:每个Tick只能使用它之前的数据。
坑二:数据对齐
如果你同时回测多个品种,一定要确保它们的时间戳是对齐的。我见过有人把BTC和ETH的Tick数据直接拼在一起,结果因为两个交易所的时间戳差了0.1秒,策略在回测里频繁套利,实盘根本不存在这种机会。
坑三:性能瓶颈
Tick级数据量巨大,一天可能上百万条。如果用纯Python循环,一个月的回测可能要跑几个小时。我的解决方案是用numba加速核心循环,或者用Cython把撮合逻辑编译成C代码。
from numba import jit
@jit(nopython=True)
def match_order(order_book, tick):
# 用numba加速撮合逻辑
# 速度能提升10倍以上
pass
小技巧:先用小数据量(比如1天的Tick)调试逻辑,确认没问题了,再跑全量数据。别一上来就跑一个月,等半天发现有个bug,心态会崩。
好了,回测系统的地基就打到这里。数据准备好了,引擎搭起来了,下一步就是往里面填策略逻辑了。记住,回测系统是你的实验室,实验设备越精密,实验结果越可信。