4、环境搭建与工具链:Python环境、Pandas、NumPy、Backtrader/自定义框架选型
做量化回测,说白了就是跟数据打交道。环境搭不好,后面全是坑。我见过太多人花了两周写策略,结果发现是Pandas版本不对导致数据对齐出了问题——那种感觉,就像你跑完马拉松发现终点线画错了位置。
这一章,咱们就把地基打牢。我会把Python环境、核心库、回测框架选型这些事,掰开揉碎了讲清楚。
4.1 Python环境:别用系统自带的Python
我个人习惯用Miniconda。为什么不是Anaconda?因为Anaconda太臃肿了,装一堆你用不上的包。做市策略回测,轻装上阵才是王道。
我的推荐配置:
- Python 3.9+(3.10也行,但别用3.12,有些库还没适配)
- Miniconda 或 Python venv
- pip + conda 混合管理
安装步骤其实很简单:
# 创建虚拟环境
conda create -n mm_strategy python=3.9
# 激活环境
conda activate mm_strategy
# 安装核心依赖
pip install pandas numpy matplotlib seaborn
嗯,这里要注意:千万别在base环境里装东西。我曾经因为图省事,直接在base里装了一堆包,结果项目依赖全乱了,回测结果怎么都对不上。后来花了整整一天排查,才发现是numpy版本冲突导致的浮点精度问题。
4.2 Pandas:你的数据瑞士军刀
做市策略回测,Pandas是绝对的核心。说白了,你就是在跟DataFrame谈恋爱——它好你就好,它乱你就崩。
我建议你重点掌握这几个操作:
- 时间序列索引:做市数据是按tick或秒级来的,时间索引必须精确
- 滚动窗口计算:比如计算过去N笔交易的买卖价差均值
- 数据对齐:不同交易所的数据时间戳可能差几毫秒,对齐不好就出大问题
避坑指南:我曾经在回测中发现策略收益异常高,查了半天才发现是Pandas的merge操作把未来数据带进来了。记住:merge时一定要用sort=False,并且检查时间戳是否严格递增。
import pandas as pd
# 加载tick数据
df = pd.read_csv('btc_tick.csv', parse_dates=['timestamp'])
df.set_index('timestamp', inplace=True)
# 计算过去100笔交易的价差
df['spread'] = df['ask_price'] - df['bid_price']
df['spread_ma'] = df['spread'].rolling(100).mean()
# 检查时间戳是否严格递增
assert df.index.is_monotonic_increasing, "时间戳不是严格递增的!"
4.3 NumPy:性能的基石
Pandas底层就是NumPy。你想想看,做市策略每秒可能要处理上千笔订单,如果每笔都用Python原生循环,那速度慢得你想哭。
NumPy的向量化操作,说白了就是把循环交给C语言去跑。我刚开始做回测时,用for循环算一个简单的价差均值,跑了3分钟。改成NumPy的np.mean后,0.3秒就搞定了。
核心技巧:
- 能用
np.where就别用if-else - 能用
np.vectorize就别用for - 矩阵运算优先用
np.dot或@操作符
import numpy as np
# 模拟订单簿数据
bid_prices = np.array([100.1, 100.2, 100.3, 100.0])
ask_prices = np.array([100.5, 100.6, 100.7, 100.8])
# 向量化计算价差
spreads = ask_prices - bid_prices
print(f"平均价差: {np.mean(spreads):.4f}")
# 条件筛选:只保留价差小于0.5的
valid_mask = spreads < 0.5
valid_bids = bid_prices[valid_mask]
4.4 回测框架选型:Backtrader vs 自定义
这是个大问题。我见过有人用Backtrader做高频回测,结果性能惨不忍睹;也有人非要自己造轮子,结果写了三个月还没跑通。
我的建议很简单:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 日频/小时频策略 | Backtrader | 开箱即用,社区成熟 |
| 分钟级策略 | Backtrader + 自定义数据源 | 需要优化数据加载 |
| 秒级/tick级策略 | 自定义框架 | Backtrader性能瓶颈明显 |
| 做市策略(高频) | 自定义框架 | 需要精细控制订单簿和撮合逻辑 |
注意:Backtrader的默认撮合逻辑是"下一根K线成交",这对做市策略来说是致命的。做市策略需要模拟限价单的实时成交,而不是等K线走完。所以,如果你要做高频做市,别犹豫,直接上自定义框架。
我自己做做市策略回测时,用的是自定义框架。为什么?因为Backtrader的订单簿模型太简单了,它没法模拟"部分成交"、"排队等待"这些真实场景。你想想看,做市策略的核心就是跟订单簿打交道,如果连这个都模拟不准,回测结果还有什么意义?
4.5 自定义框架的核心组件
如果你决定自己写框架,至少需要这几个模块:
- 数据引擎:加载tick数据,处理时间戳对齐
- 订单簿模拟器:维护买卖盘口,处理订单插入、撤销、成交
- 撮合引擎:按价格优先、时间优先原则撮合
- 策略引擎:执行你的做市逻辑,生成订单
- 风控模块:检查仓位、资金、订单频率等限制
嗯,这里有个小技巧:先把数据引擎写好。我刚开始做的时候,一上来就写策略逻辑,结果数据加载就花了半天。后来学乖了,先把数据管道跑通,再写策略——这样调试起来快得多。
class OrderBookSimulator:
"""简单的订单簿模拟器"""
def __init__(self):
self.bids = [] # 买单列表 [(价格, 数量, 时间戳)]
self.asks = [] # 卖单列表
def add_order(self, side, price, qty, timestamp):
if side == 'buy':
self.bids.append((price, qty, timestamp))
self.bids.sort(key=lambda x: (-x[0], x[2])) # 价格降序,时间升序
else:
self.asks.append((price, qty, timestamp))
self.asks.sort(key=lambda x: (x[0], x[2])) # 价格升序,时间升序
def match(self):
"""撮合逻辑:检查是否有可成交的订单"""
while self.bids and self.asks:
best_bid = self.bids[0]
best_ask = self.asks[0]
if best_bid[0] >= best_ask[0]:
# 成交!处理成交逻辑
trade_price = best_ask[0]
trade_qty = min(best_bid[1], best_ask[1])
# ... 更新订单簿
else:
break
4.6 工具链全景图
说了这么多,咱们用一张图来总结整个工具链的结构:
这张图把整个工具链分成了四层。数据层负责把原始tick数据变成干净的DataFrame;回测引擎层模拟订单簿和撮合逻辑;风控层确保你的策略不会爆仓;最后输出报告。每一层都依赖下一层,缺一不可。
我的建议:刚开始别追求完美。先把数据层和简单的撮合逻辑跑通,哪怕只模拟一个交易对、一个做市策略。等基础通了,再慢慢加风控、加多品种。我第一个做市回测框架只用了200行代码,但跑通了之后,后面加功能就快多了。
好了,环境搭建和工具链选型就聊到这儿。记住:工具是死的,思路是活的。别纠结于选哪个框架,先动手把数据跑起来,你自然就知道该用什么了。