4. 滑点与手续费模型验证:模拟滑点计算、手续费扣减逻辑、滑点参数敏感性测试
回测系统里,滑点和手续费这两个东西,看着不起眼,但往往是压死策略的最后一根稻草。我见过太多人,策略在回测里曲线漂亮得不行,一上实盘就崩。为什么?说白了,就是回测时把滑点和手续费想得太理想了。
今天我们就来聊聊,怎么验证你的回测系统在这两块没偷懒。我个人习惯是把它们拆成三个层面来测:滑点计算对不对、手续费扣得准不准、参数变一变策略还扛不扛得住。
4.1 滑点计算模型验证
滑点这东西,本质上是理想成交价和实际成交价之间的差值。回测里怎么模拟?常见的有三种方式,我一个个说。
4.1.1 固定滑点模型
最简单粗暴的方式。比如你设定每笔交易固定滑1个tick,或者固定滑多少金额。代码实现起来很直接:
def apply_fixed_slippage(price, slippage_ticks, tick_size):
"""
固定滑点模型
:param price: 理想成交价
:param slippage_ticks: 滑点tick数
:param tick_size: 最小变动价位
:return: 实际成交价
"""
direction = 1 if is_buy else -1 # 买入向上滑,卖出向下滑
return price + direction * slippage_ticks * tick_size
嗯,这里要注意。固定滑点模型有个坑——它不随市场波动变化。我在项目中遇到过,用固定滑点测高频策略,回测结果看起来还行,实盘时市场一波动,滑点直接翻倍,策略就跪了。所以固定滑点只适合做基准测试,别太当真。
4.1.2 比例滑点模型
按成交金额的一定比例算滑点。这个更贴近实际,因为大单子滑点比例通常更高。
def apply_percentage_slippage(price, volume, slippage_rate):
"""
比例滑点模型
:param slippage_rate: 滑点比例,如0.001表示千分之一
"""
slippage = price * volume * slippage_rate
return price + (slippage / volume) # 折算到每股
你想想看,这个模型其实隐含了一个假设:滑点和成交量成正比。但真实市场里,小单子可能没滑点,大单子滑点是非线性的。所以比例模型更适合中低频策略,高频策略用这个会低估滑点。
4.1.3 基于订单簿的滑点模拟
这个就高级一些了。如果你有L2数据,可以模拟订单簿的消耗过程。我习惯的做法是这样的:
def simulate_order_book_slippage(order_book, volume, side):
"""
基于订单簿模拟滑点
:param order_book: 订单簿数据,包含价格和挂单量
:param volume: 要成交的数量
:param side: 'buy' 或 'sell'
"""
total_cost = 0
remaining = volume
levels = order_book['asks'] if side == 'buy' else order_book['bids']
for price, qty in levels:
if remaining <= 0:
break
trade_qty = min(remaining, qty)
total_cost += trade_qty * price
remaining -= trade_qty
avg_price = total_cost / volume
return avg_price
4.2 手续费扣减逻辑验证
手续费这块,不同市场、不同券商差别很大。但核心逻辑就几个:按成交金额收、按笔数收、或者两者结合。
4.2.1 常见手续费模型
| 类型 | 计算公式 | 适用场景 |
|---|---|---|
| 按金额比例 | fee = trade_value * rate | A股、期货 |
| 按笔数固定 | fee = fixed_per_trade | 部分美股券商 |
| 混合模式 | fee = max(fixed, trade_value * rate) | 部分加密货币交易所 |
我曾经踩过一个坑:回测时只算了买入的手续费,忘了卖出也要扣。结果策略看起来赚了,实际一算,手续费把利润吃掉了大半。所以验证的时候,一定要把买卖两边的费用都算进去。
4.2.2 手续费验证的边界情况
验证手续费逻辑,我一般会测这几个边界:
- 零成交额:如果某笔交易成交额为0,手续费应该是0,不能出现负数
- 最小收费:很多券商有最低手续费,比如每笔最低5元。这个要测
- 大额交易:有些交易所对大额交易有费率优惠,回测里要能体现
- 撤单费用:部分市场撤单也要收费,别忘了
4.3 滑点参数敏感性测试
这个环节,说白了就是看看你的策略对滑点有多敏感。有些策略滑点大一点就死,有些则无所谓。你不测一下,心里没底。
4.3.1 敏感性测试方法
我常用的方法是:固定其他参数,只改变滑点大小,观察策略表现的变化。
def slippage_sensitivity_test(strategy, base_slippage, slippage_range):
"""
滑点敏感性测试
:param slippage_range: 滑点变化范围,如[0.5, 1.0, 1.5, 2.0]倍基准滑点
"""
results = []
for multiplier in slippage_range:
current_slippage = base_slippage * multiplier
# 运行回测
metrics = run_backtest(strategy, slippage=current_slippage)
results.append({
'multiplier': multiplier,
'sharpe': metrics['sharpe_ratio'],
'max_drawdown': metrics['max_drawdown'],
'total_return': metrics['total_return']
})
return results
跑完之后,我会画一张敏感性曲线图。横轴是滑点倍数,纵轴是夏普比率或最大回撤。如果曲线斜率很陡,说明策略对滑点敏感,实盘时要格外小心。
4.3.2 敏感性分析的关键指标
我个人习惯关注这三个指标的变化:
- 夏普比率拐点:滑点大到什么程度,夏普比率开始急剧下降?这个点就是策略的滑点容忍上限
- 盈亏平衡滑点:滑点大到什么程度,策略从盈利变成亏损?这个值决定了策略的生存空间
- 最大回撤变化率:滑点增加1%,最大回撤增加多少?这个能看出策略的风险暴露程度
4.4 综合验证框架
说了这么多,最后给一个我实际项目中用的验证框架。它把滑点和手续费验证整合在一起,形成一个闭环。
这个框架的核心思路是:先跑原始回测,然后依次经过滑点计算、手续费扣减、敏感性测试三个环节,最后输出验证报告。如果报告显示策略在合理滑点范围内表现稳定,那就可以放心上实盘了。
好了,滑点和手续费验证这块就聊到这儿。记住一句话:回测里省下的滑点和手续费,实盘里都会加倍还给你。验证工作做得越细,实盘翻车的概率就越低。