1. 为什么需要自己搭建量化回测系统
第一次接触期货量化交易时,我用过不少现成的回测平台。这些平台确实方便,点点鼠标就能跑策略,但用久了发现三个致命问题:
- 回测结果和实盘差距大,滑点计算不透明
- 策略逻辑被限制在平台提供的函数里
- 历史数据质量存疑,尤其高频数据经常错位
最崩溃的一次是,在某个平台回测年化60%的策略,实盘跑一个月亏了20%。后来拆解发现,他们的手续费计算漏掉了平今仓的高额费用。这件事让我下定决心从零搭建回测系统,虽然前期投入大,但能100%掌控每个环节。
2. 核心架构设计
2.1 数据层实现要点
期货数据有三大坑要避开:
- 主力合约换月时的价格跳空
- 夜盘和日盘的交易时间分割
- 不同交易所的tick数据格式差异
我的解决方案是构建双层数据仓库:
python复制class DataWarehouse:
def __init__(self):
self.raw_data = {} # 存储原始tick数据
self.continuous_data = {} # 连续主力合约数据
def create_continuous(self, symbol):
# 基于持仓量自动拼接主力合约
# 处理换月时的价差调整
pass
重要提示:一定要保存原始tick时间戳!很多策略在回测时用1分钟K线,实盘却用tick触发,这会导致巨大偏差。
2.2 事件驱动引擎
传统向量化回测的问题在于:
- 无法模拟实际订单排队
- 不能处理部分成交
- 忽略市场深度影响
事件驱动引擎的核心结构:
python复制class EventEngine:
def __init__(self):
self.event_queue = PriorityQueue() # 时间优先队列
self.handlers = defaultdict(list)
def process(self):
while not self.event_queue.empty():
event = self.event_queue.get()
for handler in self.handlers[event.type]:
handler(event) # 触发策略逻辑
关键事件类型包括:
- MarketEvent:新的tick数据到达
- SignalEvent:策略产生交易信号
- OrderEvent:委托单提交
- FillEvent:成交回报
3. 回测核心实现细节
3.1 手续费计算陷阱
期货手续费有几种计算方式:
- 按固定金额(如原油6元/手)
- 按成交金额比例(如股指0.000023)
- 平今仓惩罚性收费(如螺纹钢平今收万分之3)
必须实现精确计算:
python复制def calculate_commission(exchange, symbol, action, volume, price):
if 'SHFE' in exchange and action == '平今':
return price * volume * 0.0003
# 其他交易所规则...
3.2 滑点模型设计
实测有效的三种滑点模型:
- 固定滑点:每笔交易固定损失N个最小变动价位
- 比例滑点:按价格的一定比例计算
- 动态滑点:根据当时的买卖盘口深度计算
建议用动态滑点:
python复制def get_dynamic_slippage(order, market_data):
if order.direction == 'BUY':
return market_data['ask1'] + spread * 0.5
else:
return market_data['bid1'] - spread * 0.5
4. 策略开发实战案例
4.1 均线突破策略实现
看似简单的策略藏着很多魔鬼细节:
python复制class MAStrategy:
def __init__(self, short_window=5, long_window=20):
self.short_ma = []
self.long_ma = []
def on_tick(self, tick):
# 使用收盘价会导致未来函数
# 应该用当前tick的last_price
self.short_ma.append(tick.last_price)
self.long_ma.append(tick.last_price)
if len(self.short_ma) > window:
short_avg = sum(self.short_ma[-short_window:])/short_window
long_avg = sum(self.long_ma[-long_window:])/long_window
# 突破信号处理
if short_avg > long_avg and not self.position:
self.send_order('BUY')
4.2 策略评估指标
除了常见的年化收益/最大回撤,期货策略要特别关注:
- 盈亏比(Profit Factor):总盈利/总亏损
- 胜率调整后的夏普比率
- 单笔最大盈利与最大亏损比
- 连续亏损次数分布
计算示例:
python复制def analyze(results):
pf = sum(r for r in results if r>0) / abs(sum(r for r in results if r<0))
sharpe = (annual_return - risk_free) / annual_volatility
max_consecutive = max(len(list(g)) for k,g in groupby(results, lambda x:x<0) if k)
5. 性能优化技巧
5.1 向量化计算加速
用numpy替代循环:
python复制# 慢速实现
ma_values = []
for i in range(len(prices)-window):
ma_values.append(sum(prices[i:i+window])/window)
# 快速实现
ma_values = np.convolve(prices, np.ones(window)/window, 'valid')
5.2 多进程回测
Python的multiprocessing模块实测:
python复制def parallel_backtest(strategies):
with Pool(processes=4) as pool:
results = pool.map(run_strategy, strategies)
return results
6. 常见踩坑记录
-
未来函数陷阱:
- 使用当前K线的收盘价计算信号
- 解决方案:所有计算必须基于已完成的K线
-
手续费漏算:
- 忘记考虑平今仓的高额费用
- 解决方案:建立交易所-品种-交易动作的费率矩阵
-
数据透视偏差:
- 用后复权价格回测农产品期货
- 解决方案:使用实际交易价格+换月调整
-
滑点低估:
- 大单量策略用固定滑点模型
- 解决方案:根据订单量动态调整滑点
这套系统我迭代了两年多,最大的收获不是策略本身,而是对市场微观结构的理解。现在看任何策略,第一反应不是收益率多高,而是思考:
- 这个信号在流动性的哪个环节赚钱?
- 滑点会吃掉多少预期收益?
- 极端行情下订单能否成交?
最后分享一个血泪教训:永远不要在回测系统里写import random来模拟随机事件,这会导致每次回测结果不一致。应该用固定随机种子:
python复制import numpy as np
np.random.seed(42) # 宇宙的终极答案