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数据 → Pandas清洗 → NumPy向量化 → 时间对齐 数据源:交易所API / CSV / 数据库 回测引擎层 订单簿模拟器 买卖盘口维护 撮合引擎 价格优先·时间优先 策略引擎 做市逻辑执行 风控与评估层 仓位限制 · 资金管理 · 订单频率控制 绩效评估:夏普比率 · 最大回撤 · 胜率 输出:回测报告 · 资金曲线 · 订单日志

这张图把整个工具链分成了四层。数据层负责把原始tick数据变成干净的DataFrame;回测引擎层模拟订单簿和撮合逻辑;风控层确保你的策略不会爆仓;最后输出报告。每一层都依赖下一层,缺一不可。

我的建议:刚开始别追求完美。先把数据层和简单的撮合逻辑跑通,哪怕只模拟一个交易对、一个做市策略。等基础通了,再慢慢加风控、加多品种。我第一个做市回测框架只用了200行代码,但跑通了之后,后面加功能就快多了。

好了,环境搭建和工具链选型就聊到这儿。记住:工具是死的,思路是活的。别纠结于选哪个框架,先动手把数据跑起来,你自然就知道该用什么了。


专注资料整理