5、策略回测框架搭建:Python环境配置、数据获取与清洗
做市策略能不能赚钱,不能光靠拍脑袋。你得跑回测。
回测框架,说白了就是一台时光机。你把历史数据喂进去,让策略在过去的市场里跑一遍,看看它表现如何。我见过太多人,策略逻辑写得天花乱坠,一跑回测就露馅。嗯,框架搭得稳,后面才能少踩坑。
5.1 环境配置:别让工具拖后腿
我个人习惯用 Anaconda 来管理 Python 环境。为什么?省心。你想想看,做量化交易要装一堆库:pandas、numpy、matplotlib、scipy……手动装容易版本冲突,Anaconda 一键搞定。
核心依赖库清单:
- pandas:数据处理的核心,没有它寸步难行
- numpy:数值计算,回测里算收益率、波动率全靠它
- matplotlib / plotly:可视化,看曲线、看分布
- scipy:统计检验、优化算法
- statsmodels:时间序列分析,做平稳性检验
安装命令很简单,一行搞定:
conda create -n market_making python=3.9
conda activate market_making
pip install pandas numpy matplotlib scipy statsmodels
我的小习惯:每次新建项目,我都会用 conda env export > environment.yml 把环境锁住。这样换机器、换同事,一秒钟复现环境。曾经有个项目,就因为环境不一致,回测结果差了 20%,排查了三天……
5.2 数据获取:从哪里来,到哪里去
做市策略对数据的要求很高。你想想看,做市商赚的是买卖价差,数据精度不够,回测就是自欺欺人。
我个人推荐的数据源:
- Level 2 行情数据:包含逐笔成交、十档盘口,做市策略必备
- 分钟级 K 线:用于计算波动率、流动性指标
- 订单簿快照:记录每个时间点的买卖挂单情况
数据获取的代码框架:
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def fetch_tick_data(symbol, start_date, end_date):
"""
获取逐笔成交数据
实际项目中会连接数据库或API
"""
# 这里模拟数据加载
# 真实场景:从 ClickHouse / InfluxDB 读取
df = pd.read_parquet(f'data/{symbol}_tick.parquet')
# 过滤时间范围
mask = (df['timestamp'] >= start_date) & (df['timestamp'] <= end_date)
return df.loc[mask].reset_index(drop=True)
def fetch_orderbook_snapshot(symbol, date):
"""
获取订单簿快照
做市策略的核心输入
"""
# 实际项目中,订单簿数据量很大
# 建议按天分片存储
return pd.read_parquet(f'data/{symbol}_orderbook_{date}.parquet')
避坑指南:我曾经遇到过数据时间戳不对齐的问题。交易所的时钟和本地时钟有偏差,回测时看起来是同一时刻,实际差了 200 毫秒。做市策略对时间敏感,200ms 足以让策略误判。解决方案:统一使用交易所时间戳,不要用本地时间。
5.3 数据清洗:脏数据是回测的毒药
数据拿到手,别急着跑策略。先洗一洗。
我总结了一套「四步清洗法」:
- 去重:同一笔成交被记录两次,收益率直接翻倍
- 去异常:价格超过 3 个标准差,大概率是错误数据
- 补缺失:做市策略不能有空窗期,需要前向填充
- 对齐时间:不同数据源的时间戳统一到毫秒级
代码实现:
def clean_tick_data(df):
"""
清洗逐笔成交数据
"""
# 第一步:去重
df = df.drop_duplicates(subset=['timestamp', 'price', 'volume'])
# 第二步:去异常
# 做市策略对价格敏感,异常价格会导致虚假盈亏
mean_price = df['price'].mean()
std_price = df['price'].std()
df = df[(df['price'] > mean_price - 3*std_price) &
(df['price'] < mean_price + 3*std_price)]
# 第三步:排序并重置索引
df = df.sort_values('timestamp').reset_index(drop=True)
# 第四步:检查时间连续性
# 如果两个 tick 之间间隔超过 5 秒,标记为异常
df['time_diff'] = df['timestamp'].diff().dt.total_seconds()
df['gap_flag'] = df['time_diff'] > 5
return df
5.4 核心数据结构:回测的骨架
数据清洗完了,得组织成回测能用的结构。我个人习惯用 OHLCV + 订单簿 的组合。
| 字段 | 类型 | 说明 |
|---|---|---|
| timestamp | datetime | 时间戳,精确到毫秒 |
| open | float | 开盘价 |
| high | float | 最高价 |
| low | float | 最低价 |
| close | float | 收盘价 |
| volume | int | 成交量 |
| bid_price_1 | float | 买一价 |
| bid_volume_1 | int | 买一量 |
| ask_price_1 | float | 卖一价 |
| ask_volume_1 | int | 卖一量 |
经验之谈:做市策略回测,千万别只用 K 线数据。K 线是聚合后的,丢失了盘口深度信息。我早期犯过这个错,回测曲线漂亮得很,实盘一跑就亏。后来加上订单簿数据,回测和实盘的拟合度从 60% 提升到了 85%。
5.5 知识体系总览
下面这张图,是我对本章内容的总结。你把它存下来,后面每学一章,都可以往这张图上加内容。
框架搭好了,数据也洗干净了。下一步就是写回测引擎的核心逻辑——订单管理、撮合规则、资金计算。这些内容,我们下一章再聊。