2、回测框架搭建:Python环境准备、Pandas数据处理、回测引擎核心逻辑
做市策略这东西,光看理论是没用的。你得让它跑起来,跑在真实的历史数据上,才能知道它到底是骡子是马。这一章,我们就来亲手搭一个回测框架。
我个人习惯把回测框架拆成三块:环境底座、数据管道、引擎核心。一块一块来,别急。
2.1 Python环境准备:别在工具上翻车
说实话,我见过太多人花了两小时调策略,结果发现是Python版本不对。嗯,这种坑我踩过不止一次。
你需要的东西其实不多:
- Python 3.8+(我推荐3.10,稳定且兼容性好)
- Pandas 1.5+(数据处理的主力)
- NumPy(数值计算,Pandas的底层依赖)
- Matplotlib(画图看回测曲线用)
装起来很简单:
pip install pandas numpy matplotlib
小技巧: 我建议你用虚拟环境。每个项目一个独立环境,互不干扰。曾经有一次我同时做两个项目,一个要Pandas 1.3,一个要1.5,结果互相覆盖,折腾了一下午。
python -m venv backtest_env
source backtest_env/bin/activate # Mac/Linux
backtest_env\Scripts\activate # Windows
2.2 Pandas数据处理:你的数据就是你的命
做市策略回测,数据质量直接决定结果可信度。说白了,垃圾数据进,垃圾结果出。
我们通常需要这样的数据结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| timestamp | datetime | 时间戳,精确到秒或毫秒 |
| bid_price | float | 买一价 |
| ask_price | float | 卖一价 |
| bid_volume | float | 买一量 |
| ask_volume | float | 卖一量 |
| mid_price | float | 中间价(计算得出) |
| spread | float | 买卖价差(计算得出) |
加载数据并做预处理,我一般这么写:
import pandas as pd
import numpy as np
def load_and_prepare_data(filepath):
df = pd.read_csv(filepath, parse_dates=['timestamp'])
df.set_index('timestamp', inplace=True)
# 计算衍生字段
df['mid_price'] = (df['bid_price'] + df['ask_price']) / 2
df['spread'] = df['ask_price'] - df['bid_price']
# 去掉空值
df.dropna(inplace=True)
return df
注意: 我曾经遇到过数据里时间戳不连续的情况,回测结果完全偏了。后来养成了习惯——加载数据后先检查时间间隔是否均匀。用
df.index.diff().value_counts() 看一眼,心里有底。
2.3 回测引擎核心逻辑:让策略动起来
回测引擎说白了就是一个循环——遍历每一笔行情,判断要不要下单,然后记录结果。你想想看,这不就是模拟一个交易员坐在屏幕前盯盘吗?
核心逻辑就三步:
- 接收行情:拿到当前时刻的买卖盘口数据
- 策略决策:根据当前持仓和行情,决定是否挂单、撤单
- 记录状态:更新持仓、资金、盈亏
我习惯把引擎封装成一个类,这样复用起来方便:
class BacktestEngine:
def __init__(self, data, initial_capital=100000):
self.data = data
self.capital = initial_capital
self.position = 0 # 当前持仓数量
self.trades = [] # 交易记录
self.equity_curve = [] # 权益曲线
def run(self, strategy_func):
for idx, row in self.data.iterrows():
# 1. 策略决策
action, price, volume = strategy_func(
row, self.position, self.capital
)
# 2. 执行交易
if action == 'buy':
cost = price * volume
if cost <= self.capital:
self.capital -= cost
self.position += volume
self.trades.append({
'time': idx, 'type': 'buy',
'price': price, 'volume': volume
})
elif action == 'sell':
if volume <= self.position:
self.capital += price * volume
self.position -= volume
self.trades.append({
'time': idx, 'type': 'sell',
'price': price, 'volume': volume
})
# 3. 记录权益
current_equity = self.capital + self.position * row['mid_price']
self.equity_curve.append(current_equity)
return self.calculate_performance()
核心要点: 回测引擎的「真实感」取决于你如何处理滑点和成交概率。做市策略尤其敏感——你挂单不一定能成交。我一般会在引擎里加一个「成交概率模型」,比如价差在1个tick以内时成交概率80%,超过2个tick就降到30%。这样回测结果更贴近实盘。
2.4 知识体系一览
为了让你对整个框架有个直观印象,我画了一张图:
你看,整个框架其实不复杂。环境搭好,数据准备好,引擎写个循环,就齐活了。但真正让回测有价值的,是你在每个环节里加入的「真实感」——比如滑点、成交概率、手续费。这些细节,才是区分业余和专业的分水岭。
我的经验: 刚开始做回测时,我总想一次性把引擎写得特别完美。结果写了三天还没跑通。后来我学乖了——先写一个最简版本,能跑通就行。然后一点点加功能。这叫「渐进式开发」,效率高得多。
好了,环境搭好了,数据也准备好了,引擎的核心逻辑也清楚了。接下来就是让策略真正跑起来,看看它到底能赚多少、亏多少。这部分,我们留到下一节细聊。
公众号:蓝海资料掘金营,微信deep3321