1. 回测基础:vn.py回测引擎架构、回测与实盘差异、为什么需要模拟滑点与手续费

1.1 vn.py回测引擎到底长什么样?

说实话,我第一次接触vn.py的回测引擎时,第一反应是——这玩意儿比我想象的复杂多了。但用顺手之后,你会发现它的设计其实很优雅。

vn.py的回测引擎,核心就干三件事:喂数据、跑策略、算结果。但中间的过程,远比这三个词丰富。

我习惯把回测引擎拆成四个模块来看:

  • 数据模块:负责加载历史K线或Tick数据,按时间顺序喂给策略
  • 策略模块:你的交易逻辑就写在这里,比如金叉买入、死叉卖出
  • 撮合模块:这是最关键的——模拟订单怎么成交、以什么价格成交
  • 记录模块:记录每一笔交易、持仓变化、资金曲线

嗯,这里要注意,vn.py的默认撮合方式是市价撮合。什么意思?就是你发出买入指令,系统直接按下一根Bar的开盘价给你成交。这在理想世界里没问题,但实盘哪有这么便宜的事?

核心要点:回测引擎的撮合逻辑,直接决定了你的回测结果靠不靠谱。默认的市价撮合,说白了就是「理想状态」,和实盘差距很大。

1.2 回测和实盘,差在哪?

我经常跟团队里的人说一句话:「回测是天堂,实盘是地狱」。你想想看,回测时你面对的是已经走完的K线,所有数据都是确定的。但实盘呢?

我总结了一下,回测和实盘至少有这几点差异:

维度 回测 实盘
成交价格 按收盘价或开盘价成交 按买卖盘口撮合,有滑点
成交速度 瞬间成交 有网络延迟、排队延迟
手续费 可忽略或固定值 按交易所规则收取,复杂
市场冲击 不存在 大单会推动价格
数据质量 历史数据,无噪声 实时数据,有跳空、断线

说白了,回测是一个「无摩擦」的理想环境。而实盘处处是摩擦。如果你回测时不把这些摩擦考虑进去,那结果就是——回测赚得盆满钵满,实盘亏得底裤都不剩。

我曾经见过一个团队,回测年化收益50%,实盘跑了三个月直接腰斩。为什么?就是因为回测时没考虑滑点和手续费。你想想看,高频策略一天交易几十次,每次滑点一个tick,手续费再扣一点,利润早就被吃干净了。

1.3 为什么非要模拟滑点和手续费?

这个问题,我直接给你答案:不模拟滑点和手续费的回测,就是耍流氓。

咱们来拆解一下:

  • 滑点:你看到的价格和实际成交的价格之间的差值。比如你看到卖一价是10.00,你下单买入,结果成交在10.02。这0.02就是滑点。在流动性差的品种上,滑点可能大到让你怀疑人生。
  • 手续费:交易所和券商收的钱。别小看这千分之一、万分之几,高频策略跑下来,手续费可能比你的策略收益还高。

我个人习惯在回测时,至少设置双边滑点各1个tick,手续费按实盘标准上浮10%。为什么上浮?因为实盘有时候会有意外费用,比如撤单费、过夜费,留点余量总没错。

我的小技巧:如果你不确定滑点设多少,可以先跑一次无滑点回测,再跑一次带滑点的。对比一下收益差距。如果差距超过20%,说明你的策略对滑点非常敏感,实盘要格外小心。

1.4 vn.py回测引擎的核心流程

来,我画个图帮你理解vn.py回测引擎是怎么工作的。这个图我每次培训都会用,因为它把整个流程讲得很清楚。

vn.py 回测引擎核心流程 数据加载 历史K线 / Tick数据 策略运行 on_bar / on_tick 触发 订单撮合 模拟滑点 + 手续费 结果 记录 循环处理下一根Bar / 下一个Tick 关键说明: • 数据加载:支持本地CSV、数据库、RPC等多种数据源 • 策略运行:用户自定义的on_bar/on_tick逻辑在此执行 • 订单撮合:这是模拟滑点和手续费的关键环节,直接影响回测真实性 • 结果记录:生成交易明细、资金曲线、绩效报告

你看这个流程,其实就是一个事件循环。每来一根Bar或一个Tick,策略判断要不要交易,如果要交易,就送到撮合模块去模拟成交。成交时,滑点和手续费就在这里被加进去。

注意:很多新手会忽略一个细节——vn.py的默认撮合是不带滑点和手续费的。你需要手动在策略初始化时设置。如果不设置,你的回测结果会偏乐观,实盘时就会被打脸。

1.5 一个简单的滑点手续费设置示例

来,我直接给你看代码。这是我在vn.py里设置滑点和手续费的惯用写法:

from vnpy.app.cta_strategy import CtaTemplate
from vnpy.trader.constant import Direction, Offset

class MyStrategy(CtaTemplate):
    """我的策略 - 带滑点和手续费"""
    
    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)
        
        # 设置滑点(单位:tick)
        self.slip = 1  # 双边各滑1个tick
        
        # 设置手续费(万分之几)
        self.rate = 0.0003  # 万分之三
        
        # 设置最小交易量
        self.fixed_size = 1
        
    def on_bar(self, bar):
        """处理K线"""
        # 策略逻辑...
        pass
        
    def buy(self, price, volume):
        """买入 - 带滑点"""
        # 实际成交价 = 买入价 + 滑点
        slip_price = price + self.slip * self.pricetick
        return super().buy(slip_price, volume)
        
    def sell(self, price, volume):
        """卖出 - 带滑点"""
        # 实际成交价 = 卖出价 - 滑点
        slip_price = price - self.slip * self.pricetick
        return super().sell(slip_price, volume)

这段代码里,我手动在买入和卖出时加了滑点。手续费是在vn.py的底层自动计算的,你只需要设置好self.rate就行。

我的经验:滑点设置不要死板。对于流动性好的品种(比如股指期货),滑点设1个tick就够了。但对于流动性差的品种(比如某些商品期货),我建议设3-5个tick。我曾经在螺纹钢上吃过亏,回测设了1个tick,实盘滑了4个tick,那叫一个酸爽。

1.6 总结一下

这一章咱们聊了三个核心问题:

  • vn.py回测引擎的架构:数据加载 → 策略运行 → 订单撮合 → 结果记录,循环往复
  • 回测和实盘的差异:回测是理想环境,实盘有滑点、手续费、延迟、市场冲击
  • 为什么要模拟滑点和手续费:不模拟就是自欺欺人,实盘会让你怀疑人生

说白了,回测的目的不是为了证明你的策略有多牛,而是为了提前发现坑。滑点和手续费就是最大的两个坑。下一章咱们会深入讲vn.py里具体的滑点模拟方法,包括固定滑点、百分比滑点、还有更高级的限价单模拟。

嗯,今天就到这儿。记住一句话:回测不模拟滑点,实盘两行泪。