4、回测框架搭建:事件驱动回测引擎设计、仓位管理模块、手续费与滑点模型、绩效评估指标体系

好,咱们直接进入正题。回测框架这东西,说白了就是你的策略实验室。没有它,你所有的想法都只是纸上谈兵。我个人习惯把回测框架拆成四个核心模块来搞:引擎、仓位、成本、评估。一个一个来。

4.1 事件驱动回测引擎设计

为什么用事件驱动?而不是简单的向量化回测?

向量化回测快,但太理想化了。它假设你可以在任意时刻以任意价格成交。现实吗?不现实。事件驱动回测模拟的是真实交易流程:行情来了 -> 处理信号 -> 下单 -> 成交确认 -> 更新持仓。每一步都有时间戳,有顺序。

我设计引擎的核心是一个事件队列。所有东西都是事件:Bar数据、成交回报、订单状态变更。引擎主循环就是不断从队列里取事件,然后分发给对应的处理器。

class EventEngine:
    def __init__(self):
        self.queue = deque()
        self.handlers = {}
    
    def register_handler(self, event_type, handler):
        self.handlers[event_type] = handler
    
    def run(self):
        while self.queue:
            event = self.queue.popleft()
            handler = self.handlers.get(event.type)
            if handler:
                handler(event)

嗯,这里要注意。事件队列的优先级很重要。比如成交回报的优先级应该高于新的行情数据,否则会出现用旧持仓计算新信号的逻辑错误。我在项目中遇到过这个问题,当时回测结果异常漂亮,实盘却一塌糊涂。查了两天才发现是事件顺序搞反了。

4.2 仓位管理模块

仓位管理,说白了就是每次开多少仓的问题。别小看这个,它直接决定了你的风险暴露。

我一般把仓位管理分成两类:

  • 固定比例:每次开仓用总资金的固定百分比。比如永远只用10%的资金开仓。
  • 动态调整:根据当前波动率或账户净值动态调整。比如波动率大了就减仓,净值回撤了就降杠杆。

我个人更倾向于动态调整。为什么?因为市场环境在变,固定比例太死板了。

举个例子,你设定每次开仓10%。当账户从100万亏到50万时,你还在开10%?那风险敞口其实变大了。动态调整会根据当前净值重新计算,更合理。

class PositionManager:
    def __init__(self, initial_capital, risk_per_trade=0.02):
        self.capital = initial_capital
        self.risk_per_trade = risk_per_trade
    
    def calculate_position_size(self, entry_price, stop_loss_price):
        risk_amount = self.capital * self.risk_per_trade
        risk_per_share = entry_price - stop_loss_price
        if risk_per_share <= 0:
            return 0
        return int(risk_amount / risk_per_share)

上面这个例子用的是风险百分比法。每次亏损不超过总资金的2%。你想想看,连续亏10次,也就亏了20%,还有翻身的机会。如果每次满仓干,亏两次就没了。

4.3 手续费与滑点模型

这个模块最容易被忽略,但恰恰是区分回测和实盘的关键。

手续费:包括佣金、印花税、过户费等。不同市场、不同券商都不一样。我建议直接按实际费率写死,别偷懒用估算值。

滑点:这个更复杂。滑点就是你下单的价格和实际成交价格之间的差异。流动性好的股票滑点小,小盘股滑点大。

我曾经犯过一个错误:回测时设了固定滑点1个tick,结果实盘时遇到极端行情,滑点直接到了5个tick。那一个月白干。

所以我现在用动态滑点模型

市场状态 滑点设置
正常行情 1个tick
高波动行情(波动率>95%分位) 3个tick
开盘/收盘前5分钟 2个tick
class SlippageModel:
    def __init__(self, base_slippage=0.0001):
        self.base_slippage = base_slippage
    
    def get_slippage(self, volatility, volume):
        # 波动率越高,滑点越大
        # 成交量越小,滑点越大
        slippage = self.base_slippage * (1 + volatility * 10)
        slippage *= (1 + 1 / (volume + 1))
        return slippage
避坑指南:手续费和滑点一定要在回测中同时考虑。只考虑手续费不考虑滑点,回测结果会偏乐观。两者加起来,年化收益可能直接打八折。

4.4 绩效评估指标体系

回测跑完了,怎么评价策略好不好?光看收益率太片面了。我一般看这几个指标:

  • 年化收益率:把总收益换算成年化。注意复利和单利的区别。
  • 最大回撤:从最高点到最低点的最大跌幅。这个指标比收益率更重要。回撤超过30%的策略,一般人拿不住。
  • 夏普比率:超额收益除以波动率。说白了就是每承担一单位风险能获得多少超额回报。大于1算及格,大于2算优秀。
  • 胜率与盈亏比:胜率高不一定赚钱,盈亏比高也不一定。两者要结合看。我见过胜率只有30%但盈亏比5:1的策略,长期下来收益惊人。
  • 卡玛比率:年化收益除以最大回撤。这个指标比夏普更直观,直接告诉你每亏一块钱能赚多少钱。
我的经验:不要只看一个指标。比如夏普比率高但最大回撤也大的策略,其实风险很大。我一般会建一个综合评分表,给每个指标打分,然后加权求和。这样能避免被单一指标误导。

最后,绩效评估一定要做样本外测试。把数据分成训练集和测试集,训练集上优化参数,测试集上验证效果。如果测试集上的表现远差于训练集,说明策略过拟合了。嗯,这个坑我踩过不止一次。

小技巧:回测报告里一定要包含交易明细。每一笔交易的入场时间、出场时间、盈亏、持仓周期。这样你才能知道哪些交易赚钱了,哪些亏钱了。光看汇总数据,你永远不知道问题出在哪。
回测框架核心模块 事件驱动引擎 仓位管理模块 成本模型 绩效评估体系 事件队列 · 事件分发 时间戳管理 · 订单处理 固定比例 · 动态调整 风险预算 · 头寸计算 手续费 · 滑点模型 动态滑点 · 成本计算 年化收益 · 最大回撤 · 夏普比率 胜率 · 盈亏比 · 卡玛比率

好了,回测框架的四个核心模块就这些。记住,框架是工具,不是目的。别在框架上花太多时间,把精力放在策略本身。但框架也不能太简陋,否则回测结果会误导你。

专注资料整理