trading/doc_03_event_driven_backtes...

28 KiB
Raw Permalink Blame History

事件驱动回测引擎详解

quant_event_driven_backtest_demo.py 学习文档

目标读者:零基础量化入门者
配套文件quant_event_driven_backtest_demo.py
前置知识:建议先阅读前两篇文档
系列位置:第 3 篇 — 事件驱动回测篇


目录

  1. 为什么需要事件驱动回测?
  2. 整体架构总览
  3. 事件类详解 (Event Classes)
  4. 数据处理器 (DataHandler)
  5. 策略引擎 (Strategy)
  6. 组合管理器 (Portfolio)
  7. 模拟券商 (SimulatedBroker)
  8. 回测引擎主循环 (BacktestEngine)
  9. 绩效分析
  10. 事件驱动 vs 向量化:结果差异解析
  11. 术语速查表

1. 为什么需要事件驱动回测?

在上一篇文档中,我们用向量化回测验证了策略。向量化回测速度极快,适合快速探索,但它有一个根本局限:

向量化回测是"上帝视角" —— 所有数据同时可见,一次性计算完毕。
真实交易是"时间顺序" —— 只有当前这根 K 线,下一根是未知的。

1.1 向量化回测无法模拟的真实问题

现实场景 向量化的处理 事件驱动的处理
订单类型 (Order Types) 只能假设"立即以当日收盘价成交" 可以模拟市价单/限价单/止损单
成交延迟 (Fill Delay) 信号日 = 成交日(不现实) 信号日 T下一日 T+1 开盘才成交
资金限制 (Capital Constraints) 隐性假设可以买任意多 按实际现金余额决定买多少股
整数股 (Integer Shares) 往往假设可以买分数股 强制取整(现实中必须整数)
实时风控 (Real-time Risk Check) 无法在"每笔交易"时检查 每个 OrderEvent 前都可以检查
多策略组合 (Multi-Strategy) 难以优雅处理 每个策略独立处理 MarketEvent
实盘无缝切换 无法复用代码 策略代码在回测和实盘中完全相同

1.2 核心优势:回测代码 = 实盘代码

事件驱动的最大价值是:你的策略类 (Strategy)、组合管理器 (Portfolio) 在回测中和实盘中运行的是完全相同的代码
切换到实盘只需要将 DataHandler(历史数据)换成实时行情 APISimulatedBroker(模拟券商)换成真实券商 API。

回测模式:   DataHandler(历史CSV) → 策略 → 组合 → SimulatedBroker(模拟)
实盘模式:   MarketDataFeed(API)  → 策略 → 组合 → RealBroker(真实券商API)
                                    ↑ 这两行代码完全不变 ↑

2. 整体架构总览

2.1 系统的核心:事件队列

整个系统围绕一个事件队列 (Event Queue) 运转。所有组件都通过这个队列通信,彼此之间完全解耦 (Decoupled)——没有任何组件直接调用另一个组件,它们只是往队列里放事件,或者从队列里取事件。

                    ┌─────────────────────────────────┐
                    │     事件队列 (Event Queue)        │
                    │   先进先出 (FIFO deque)           │
                    │   ← 放入事件     取出事件 →       │
                    └─────────────────────────────────┘
                              ↑↑↑↑↑↑↑
          ┌──────────┬─────────┴──────────┬──────────┐
          │          │                    │          │
    DataHandler   Strategy            Portfolio    SimulatedBroker
    数据处理器    策略引擎             组合管理器    模拟券商
          │          │                    │          │
      放入 Market  放入 Signal         放入 Order   放入 Fill
      Event        Event               Event        Event

2.2 单个交易日的事件流

每天,系统会经历以下事件序列:

⏰ 时钟滴答:新的一个交易日开始
        │
        ▼
[1] DataHandler.stream_next()
    → 将今天的 OHLCV 数据封装成 MarketEvent 放入队列
        │
        ▼
[2] SimulatedBroker.fill_pending()
    → 以今天的开盘价成交昨天挂的待处理订单
    → 成交后产生 FillEvent 放入队列
        │
        ▼
[3] 处理队列中的所有事件:
        │
        ├─ MarketEvent  →  Strategy.on_market()
        │                  策略看到今天的价格 → 可能产生 SignalEvent
        │
        ├─ SignalEvent  →  Portfolio.on_signal()
        │                  组合管理器决定买多少 → 产生 OrderEvent
        │
        ├─ OrderEvent   →  SimulatedBroker.on_order()
        │                  券商接收订单 → 加入待处理队列(明天开盘成交)
        │
        └─ FillEvent    →  Portfolio.on_fill()
                           更新现金余额和持仓记录
        │
        ▼
[4] Portfolio.record_equity()
    → 记录今天收盘时的账户净值快照
        │
        ▼
⏰ 时钟滴答:下一个交易日开始(回到步骤 1

为什么第 2 步(成交待处理订单)在第 3 步(处理队列)之前?

这是防止前视偏差的关键设计:

  • 今天T日策略看到价格后产生信号挂了一个订单
  • 这个订单在今天不成交(因为今天的收盘价就是信号触发的那根 K 线)
  • 明天T+1 日)开盘时,才以开盘价成交这个订单
  • 这正确模拟了现实中"当天看到信号,明天才能买到"的情况

3. 事件类详解 (Event Classes)

代码定义了 4 种事件类,像快递单据一样,每类事件携带不同的信息。

3.1 MarketEvent市场事件

触发时机DataHandler 每天推送新的 K 线数据时
携带信息OHLCV 数据(开高低收量)

@dataclass
class MarketEvent:
    symbol : str           # 股票代码   例: "000001.SZ"
    date   : pd.Timestamp  # 日期       例: 2024-01-15
    open   : float         # 开盘价 (Opening Price)
    high   : float         # 最高价 (High Price)
    low    : float         # 最低价 (Low Price)
    close  : float         # 收盘价 (Closing Price)
    volume : float         # 成交量 (Volume)

OHLCV 是什么?
一根 K 线Candlestick / Bar记录了某个时间段内的价格运动全貌

  • O (Open 开盘价):这个时间段第一笔成交价
  • H (High 最高价):这个时间段最高成交价
  • L (Low 最低价):这个时间段最低成交价
  • C (Close 收盘价):这个时间段最后一笔成交价
  • V (Volume 成交量):这个时间段总成交股数

3.2 SignalEvent信号事件

触发时机:策略判断应该开仓/平仓时
携带信息:交易方向、信号强度

@dataclass
class SignalEvent:
    symbol    : str        # 股票代码
    date      : pd.Timestamp
    direction : Direction  # LONG做多/ SHORT做空/ EXIT平仓
    strength  : float      # 信号强度 0~1用于仓位管理
                           # 1.0 = 满仓0.5 = 半仓
    strategy  : str        # 产生信号的策略名称(多策略时有用)

关键区别SignalEvent 是"建议",不是"命令"。
Portfolio 收到 SignalEvent 后,还会根据风控规则决定是否执行、执行多少。

3.3 OrderEvent订单事件

触发时机Portfolio 决定下单后
携带信息:具体下单量、订单类型

@dataclass
class OrderEvent:
    symbol      : str        # 股票代码
    date        : pd.Timestamp
    order_type  : OrderType  # MARKET市价单/ LIMIT限价单/ STOP止损单
    direction   : Direction  # LONG / SHORT / EXIT
    quantity    : int        # 下单数量(股数,整数!)
    limit_price : float      # 限价单价格(仅限价单用)
    stop_price  : float      # 止损价格(仅止损单用)

三种订单类型

类型 中文 说明 优点 缺点
MARKET 市价单 立即以当前最优价成交 100% 成交 价格不确定,大单可能有较大滑点
LIMIT 限价单 只在指定价格或更好时成交 价格确定 可能无法成交(市场未到你的价格)
STOP 止损单 价格到达止损价时转为市价单 控制最大亏损 跳空时实际成交价可能比止损价更差

本 Demo 只实现了市价单(最简单),限价单和止损单留作扩展练习。

3.4 FillEvent成交事件

触发时机:模拟券商确认订单已成交后
携带信息:实际成交价(含滑点)、佣金

@dataclass
class FillEvent:
    symbol     : str
    date       : pd.Timestamp
    direction  : Direction    # LONG / SHORT / EXIT
    quantity   : int          # 实际成交股数
    fill_price : float        # 实际成交价(已含滑点,比理论价差一点)
    commission : float        # 本次交易的佣金金额(元)
    slippage   : float        # 滑点额(实际成交价 - 开盘价)

    @property
    def total_cost(self):
        # 买入:现金减少(负号)
        # 卖出:现金增加(正号)
        sign = -1 if direction in (LONG, SHORT) else +1
        return sign * (fill_price * quantity + commission)

FillEvent 是最终事实 (Ground Truth)——一旦产生就意味着交易确实发生了Portfolio 必须据此更新账户状态。


4. 数据处理器 (DataHandler)

4.1 核心职责

DataHandler 负责逐根 K 线地向系统"喂"数据,模拟"不知道未来"的真实状态。

class DataHandler:
    def stream_next(self) -> bool:
        """
        每次调用,只释放"下一根 K 线"的数据。
        就像真实交易中,每天只能看到当天的数据,
        明天的数据还没发生。
        """
        ...
        self.event_queue.put(bar)  # 把新 K 线放入队列

4.2 历史记录与指标计算

DataHandler 还维护了一个"已经看到的 K 线"的历史记录,供策略查询:

def get_close_series(self) -> pd.Series:
    """
    返回到目前为止所有已见过的收盘价序列。
    策略可以用这个序列计算均线、RSI 等指标。
    
    关键:这个序列只包含"过去"的数据,不包含任何"未来"数据。
    这从结构上保证了不会有前视偏差。
    """

为什么不直接给策略整个数据集?

这正是事件驱动的防错设计——即使你不小心让策略代码 data.get_close_series() 在某天调用,它也只能拿到"截至今天"的数据,不可能拿到明天的收盘价,因为 DataHandler 只把已经流出去的数据存入历史记录。


5. 策略引擎 (Strategy)

5.1 抽象基类设计

代码用 Python 的抽象基类 (Abstract Base Class, ABC) 定义了策略的"接口合同"

class Strategy(ABC):
    @abstractmethod
    def on_market(self, event: MarketEvent) -> None:
        """所有策略都必须实现这个方法"""
        ...

好处

  • 任何具体策略MA策略、RSI策略、机器学习策略只需继承 Strategy 并实现 on_market
  • 回测引擎不关心策略具体怎么计算,只知道"来了 MarketEvent 就调用 on_market"
  • 这叫开放封闭原则 (Open-Closed Principle):对扩展开放,对修改封闭

5.2 MA 策略的事件驱动实现

向量化版本(上一篇)一次性计算了所有日期的均线。
事件驱动版本在每一根新 K 线到来时,只用截至今天的历史数据重新计算一次:

class MACrossoverStrategy(Strategy):
    def on_market(self, event: MarketEvent) -> None:
        # 从 DataHandler 获取迄今为止的所有收盘价(不含未来)
        closes = self.data.get_close_series()
        
        # 只用"窗口内最近的数据"计算当前均线值
        sma_short = closes.iloc[-self.short_window:].mean()
        sma_long  = closes.iloc[-self.long_window:].mean()
        
        # 检测交叉
        sma_short_prev = closes.iloc[-(self.short_window+1):-1].mean()
        sma_long_prev  = closes.iloc[-(self.long_window+1):-1].mean()
        
        was_above = sma_short_prev > sma_long_prev
        is_above  = sma_short      > sma_long
        
        # 金叉:昨天还在下方,今天穿越到上方
        if is_above and not was_above and not self._in_position:
            self.send_signal(..., Direction.LONG)
            self._in_position = True
        
        # 死叉:昨天还在上方,今天穿越到下方
        elif not is_above and was_above and self._in_position:
            self.send_signal(..., Direction.EXIT)
            self._in_position = False

策略内部状态 _in_position
这个布尔变量追踪"当前是否已经持有多仓",防止在已经持仓时重复买入,或者在没有持仓时发出平仓信号。这在向量化回测中很难优雅处理,在事件驱动中则非常自然。

5.3 RSI 策略的完整多空逻辑

class RSIMeanReversionStrategy(Strategy):
    def on_market(self, event: MarketEvent) -> None:
        rsi = self._compute_rsi(closes)   # 只用迄今为止的数据
        
        # 空仓时的入场规则Entry Rules
        if rsi < 30 and self._position == 0:
            self.send_signal(..., Direction.LONG,  strength=0.8)  # 超卖 → 做多
            self._position = 1
        
        elif rsi > 70 and self._position == 0:
            self.send_signal(..., Direction.SHORT, strength=0.8)  # 超买 → 做空
            self._position = -1
        
        # 持仓时的出场规则Exit Rules
        elif self._position == 1 and rsi > 50:    # 多仓RSI 回到中性 → 平多
            self.send_signal(..., Direction.EXIT)
            self._position = 0
        
        elif self._position == -1 and rsi < 50:   # 空仓RSI 回到中性 → 平空
            self.send_signal(..., Direction.EXIT)
            self._position = 0

strength=0.8 的含义
信号强度 0.8 表示"我不是百分之百确定,只动用 80% 的可用资金"。Portfolio 收到这个信号后,会用 capital × position_pct × strength 来计算下单金额。


6. 组合管理器 (Portfolio)

Portfolio 是管理"钱"的大脑,负责:

  1. 决定买多少(仓位管理)
  2. 更新现金余额和持仓记录
  3. 计算盈亏
  4. 记录净值曲线

6.1 仓位管理:固定比例法 (Fixed Fraction)

def on_signal(self, event: SignalEvent):
    current_price = self.data.get_close_series().iloc[-1]
    
    # 计算下单资金 = 当前总资产 × 仓位比例 × 信号强度
    capital_to_deploy = self.cash * self.position_pct * event.strength
    
    # 下单股数 = 下单资金 / 当前价格(向下取整,必须整数股)
    qty = int(capital_to_deploy / current_price)

仓位管理 (Position Sizing) 是风险管理的核心。代码使用了最简单的固定比例法 (Fixed Fraction):每次动用总资产的固定比例(如 95%)。

更高级的仓位管理方法:

方法 中文 逻辑
Fixed Dollar 固定金额 每次固定买 X 元
Fixed Fraction 固定比例 每次买总资产的 X%
Kelly Criterion 凯利公式 根据"胜率和盈亏比"计算最优比例,理论上最大化长期增长率
Volatility Scaling 波动率缩放 仓位与波动率反比,高波动时减仓,低波动时加仓

6.2 持仓均价与盈亏计算

def on_fill(self, event: FillEvent):
    if direction == LONG:
        # 更新持仓均价(加权平均成本)
        # 例:已有 1000 股均价 10 元,再买 500 股 @ 12 元
        # 新均价 = (1000×10 + 500×12) / 1500 = 10.67 元
        new_qty  = old_qty + qty
        self.avg_cost[symbol] = (old_cost * old_qty + price * qty) / new_qty
        
        # 现金减少(买入)
        self.cash -= price * qty + commission
    
    elif direction == EXIT:
        # 计算已实现盈亏Realized P&L
        # 盈亏 = (出场价 - 入场均价) × 数量 - 佣金
        pnl = (exit_price - entry_price) * qty - commission
        
        # 现金增加(卖出回款)
        self.cash += exit_price * qty - commission

两种盈亏概念

概念 中文 含义
Unrealized P&L 浮动盈亏 / 未实现盈亏 持仓中但尚未卖出的账面盈亏
Realized P&L 已实现盈亏 已经卖出、真正落袋的盈亏

"浮盈不是赚,浮亏不是亏"——只有平仓后才算真正的盈亏。


7. 模拟券商 (SimulatedBroker)

7.1 两大成本来源

① 佣金 (Commission)

\text{佣金} = \max(\text{交易金额} \times \text{佣金率},\ \text{最低佣金})

代码中:commission_rate = 0.0003(万分之三),min_commission = 5.0(最低 5 元)

最低佣金很重要:小额交易(如 500 元的单子)理论佣金 = 500 × 0.03% = 0.15 元,但实际按 5 元收取,成本率高达 1%

② 滑点 (Slippage)

def _compute_fill_price(self, order, bar):
    if direction == LONG:
        # 买入时,实际成交价比开盘价"稍贵"(滑点向上)
        return bar.open * (1 + self.slippage_rate)
    else:
        # 卖出时,实际成交价比开盘价"稍便宜"(滑点向下)
        return bar.open * (1 - self.slippage_rate)

滑点向不利方向运动的原因

  • 你想买的时候,有人知道你要买,会把价格稍稍抬高(市场冲击)
  • 你想卖的时候,买家会压低报价

7.2 订单的执行时机

今天T 日)收盘后:策略产生信号 → Portfolio 挂出 OrderEvent
今天T 日收盘时Broker 收到订单,加入 pending_orders 列表
                    ⚠️ 注意:今天不成交!

明天T+1 日开盘时Broker 以今天的开盘价成交昨天挂的所有 pending 订单
                      → 产生 FillEvent更新 Portfolio

代码逻辑

# 每天开始时,先把昨天的待处理订单用今天的开盘价成交
def fill_pending(self, bar: MarketEvent):
    for order in self._pending_orders:
        fill_price = bar.open * (1 ± slippage)   # 今天的开盘价 ± 滑点
        commission = max(fill_price * qty * rate, min_comm)
        self.queue.put(FillEvent(...))
    self._pending_orders.clear()

8. 回测引擎主循环 (Backtest Engine)

8.1 主循环算法全貌

def run(self):
    # 外层循环:每天执行一次
    while self.data.has_more_data():
        
        # ① 推进一天DataHandler 推送今天的 MarketEvent
        self.data.stream_next()
        current_bar = self.data._history[-1]   # 今天的 K 线
        
        # ② 成交待处理订单(以今天开盘价)
        #    昨天的信号在今天才能执行,这正确模拟了成交延迟
        self.broker.fill_pending(current_bar)
        
        # ③ 处理队列中所有事件(内层循环)
        while not self._queue.empty():
            event = self._queue.get()
            
            if event.type == MARKET:
                self.strategy.on_market(event)   # 策略观察市场
            elif event.type == SIGNAL:
                self.portfolio.on_signal(event)  # 组合决定下单量
            elif event.type == ORDER:
                self.broker.on_order(event)      # 券商接收订单
            elif event.type == FILL:
                self.portfolio.on_fill(event)    # 组合更新持仓现金
        
        # ④ 记录今天收盘时的净值快照
        self.portfolio.record_equity(current_bar.date, current_bar.close)

8.2 为什么内层循环很重要?

注意步骤 ③ 是一个内层 while 循环,处理所有队列中的事件。

这是因为事件会产生新的事件:

  • MarketEvent → 策略处理后产生 SignalEvent放入队列
  • SignalEvent → Portfolio 处理后产生 OrderEvent放入队列
  • 下一次循环开始才处理 OrderEvent

但如果有 FillEvent今天的成交回报它也需要在今天内处理完更新好 Portfolio 状态,才能保证 record_equity 记录的是更新后的净值。

8.3 工厂函数 (Factory Function)

代码提供了一个 build_engine 函数,每次调用都创建全新、独立的组件实例:

def build_engine(strategy_class, strategy_kwargs, name, ...):
    q         = queue.Queue()    # ← 全新事件队列,与其他回测完全隔离
    data      = DataHandler(ohlcv, SYMBOL, q)
    strategy  = strategy_class(data, q, **strategy_kwargs)
    portfolio = Portfolio(data, q, initial_capital=1_000_000.0)
    broker    = SimulatedBroker(q, ...)
    return BacktestEngine(data, strategy, portfolio, broker, name=name)

为什么需要全新实例?
如果两次回测共享同一个 Portfolio第一次回测的持仓和现金状态会污染第二次回测。每次回测都必须从"干净的初始状态"开始。


9. 绩效分析 (Performance Analytics)

9.1 两个维度的绩效

事件驱动回测比向量化回测多了一个维度:交易级别 (Trade-Level) 分析。

分析维度 数据来源 代表指标
日度水平 (Daily Level) equity_curve 每日净值 夏普、最大回撤、年化收益
交易水平 (Trade Level) trade_log 每笔交易 交易胜率、单笔平均盈亏、盈亏比

9.2 交易日志 (Trade Log)

每笔完成的"买-卖"一个完整轮次叫一笔交易 (Round-Trip Trade)。代码记录了每笔交易:

self.trade_log.append({
    "date"        : 出场日期,
    "symbol"      : 股票代码,
    "direction"   : "LONG→EXIT"  "SHORT→EXIT",
    "entry_price" : 入场均价最初买入的均价,
    "exit_price"  : 出场价格本次卖出价,
    "quantity"    : 成交股数,
    "pnl"         : 盈亏金额 = (出场价 - 入场价) × 股数 - 佣金,
    "commission"  : 本笔交易总佣金,
})

9.3 逐笔绩效的价值

通过 trade_log 可以深挖很多问题:

  • 盈利的交易有多少笔?亏损的有多少笔?(交易胜率)
  • 平均每笔赚多少?每笔亏多少?(期望值分析)
  • 最大单笔亏损是多少?(单笔风险控制)
  • 是否有明显的持仓时长规律?(持有时间分析,虽然本 Demo 未直接展示)
  • 特定月份/季节表现如何?(季节性)

10. 事件驱动 vs 向量化:结果差异解析

本 Demo 的最后部分对比了两种方法对完全相同策略、完全相同数据的回测结果差异:

                       事件驱动        向量化
总收益率 Total Return    165.00%        173.92%
夏普比率 Sharpe Ratio      0.958          0.950
最大回撤 Max Drawdown    -18.95%        -20.34%

数字有差距,原因如下:

差异 1成交时机不同

向量化 事件驱动
成交价格 当天收盘价(信号产生的同一根 K 线) 下一天开盘价(有 1 天延迟)
现实性 不现实(无法做到) 保守但现实

在上涨趋势中明天开盘价通常比今天收盘价稍高Gap Up所以事件驱动的买入成本更高收益率更低。

差异 2整数股 vs 分数股

向量化 事件驱动
交易单位 分数股(如 123.456 股) 整数股(如 123 股,向下取整)
现实性 不现实 现实

每次取整都会有少量资金无法部署(零头现金),随着股价升高,这个影响会越来越明显。

差异 3动态资金 vs 静态假设

向量化 事件驱动
仓位计算 隐性假设固定一个单位 每次用当前实际现金余额计算
效果 每笔交易资金相同 盈利后仓位增大,亏损后仓位缩小(自动复利)

事件驱动更真实地模拟了复利效应 (Compounding Effect)

差异 4最低佣金

事件驱动设置了 min_commission = 5 元。当单笔交易金额较小时,按比例算出的佣金不足 5 元,实际收 5 元,成本率会高于向量化的固定比例假设。


11. 术语速查表

中文 English 简要说明
事件驱动 Event-Driven 通过事件队列驱动组件间通信的软件架构
事件队列 Event Queue 存放待处理事件的先进先出数据结构
解耦 Decoupling 各组件通过事件通信,相互不直接依赖
市场事件 MarketEvent 新 K 线数据到达的事件
信号事件 SignalEvent 策略产生买卖建议的事件
订单事件 OrderEvent 组合管理器向券商下单的事件
成交事件 FillEvent 券商确认订单已成交的事件
数据处理器 DataHandler 逐根 K 线流出历史数据的组件
策略 Strategy 观察市场并产生信号的逻辑层
抽象基类 ABC (Abstract Base Class) Python 强制要求子类实现特定方法的基类
组合管理器 Portfolio 管理现金、持仓、盈亏的组件
仓位管理 Position Sizing 决定每次交易动用多少资金的方法
固定比例法 Fixed Fraction 每次用总资产固定比例的仓位管理方法
凯利公式 Kelly Criterion 理论最优仓位比例的数学公式
持仓均价 Average Cost (Entry Price) 所有买入成本的加权平均价
已实现盈亏 Realized P&L 已平仓确认的盈亏金额
浮动盈亏 Unrealized P&L 持仓中尚未平仓的账面盈亏
模拟券商 Simulated Broker 回测中模拟真实券商行为的组件
市价单 Market Order 立即以最优价成交的订单类型
限价单 Limit Order 只在指定价格或更优时成交的订单
止损单 Stop Order 价格触发后转为市价单的保护性订单
滑点 Slippage 理论价格与实际成交价的差值
佣金 Commission 券商收取的交易手续费
成交延迟 Fill Delay 信号产生到实际成交的时间间隔
回测引擎 Backtest Engine 协调所有组件运行的主循环
主循环 Main Event Loop 驱动整个回测顺序推进的核心循环
K 线 Candlestick / Bar 一段时间内的开高低收量数据
OHLCV OHLCV Open / High / Low / Close / Volume
开盘价 Opening Price 当日第一笔成交价
最高价 High Price 当日最高成交价
最低价 Low Price 当日最低成交价
收盘价 Closing Price 当日最后一笔成交价
成交量 Volume 当日总成交股数
向量化 Vectorized 用数组运算一次性处理所有时间步
前视偏差 Lookahead Bias 回测中使用了"当时未知"的未来数据
整数股 Integer Shares 现实中股票必须以整数股交易
复利效应 Compounding Effect 每期收益都在扩大的本金上继续赚取收益
成交时机 Fill Timing 订单被实际成交的时间点
信号强度 Signal Strength 策略对本次信号的置信度(用于仓位缩放)
圆跳交易 Round-Trip Trade 从开仓到平仓的一个完整交易周期
交易胜率 Trade Win Rate 盈利交易次数 / 总交易次数
净值曲线 Equity Curve 账户总价值随时间变化的曲线
工厂函数 Factory Function 用于创建初始化好的完整对象的辅助函数

上一篇:策略开发与向量化回测
系列完结:恭喜你完成了量化交易基础三部曲!


附录:系列文档导航

文件 文档 核心内容
第 1 篇 quant_data_pipeline_demo.py doc_01_data_pipeline.md 复权、收益率、缺失值、异常值、涨跌停
第 2 篇 quant_strategy_backtest_demo.py doc_02_strategy_backtest.md 技术指标、策略逻辑、向量化回测、绩效指标
第 3 篇 quant_event_driven_backtest_demo.py doc_03_event_driven_backtest.md 事件驱动架构、6大组件、成本模型、与向量化对比