# 事件驱动回测引擎详解
## `quant_event_driven_backtest_demo.py` 学习文档

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

---

## 目录

1. [为什么需要事件驱动回测？](#1-为什么需要事件驱动回测)
2. [整体架构总览](#2-整体架构总览)
3. [事件类详解 (Event Classes)](#3-事件类详解-event-classes)
4. [数据处理器 (DataHandler)](#4-数据处理器-datahandler)
5. [策略引擎 (Strategy)](#5-策略引擎-strategy)
6. [组合管理器 (Portfolio)](#6-组合管理器-portfolio)
7. [模拟券商 (SimulatedBroker)](#7-模拟券商-simulatedbroker)
8. [回测引擎主循环 (BacktestEngine)](#8-回测引擎主循环-backtest-engine)
9. [绩效分析](#9-绩效分析-performance-analytics)
10. [事件驱动 vs 向量化：结果差异解析](#10-事件驱动-vs-向量化结果差异解析)
11. [术语速查表](#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`（历史数据）换成实时行情 API，`SimulatedBroker`（模拟券商）换成真实券商 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 数据（开高低收量）

```python
@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（信号事件）

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

```python
@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 决定下单后  
**携带信息**：具体下单量、订单类型

```python
@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（成交事件）

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

```python
@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 线**地向系统"喂"数据，模拟"不知道未来"的真实状态。

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

### 4.2 历史记录与指标计算

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

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

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

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

---

## 5. 策略引擎 (Strategy)

### 5.1 抽象基类设计

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

```python
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 线到来时**，只用截至今天的历史数据重新计算一次：

```python
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 策略的完整多空逻辑

```python
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)

```python
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 持仓均价与盈亏计算

```python
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)**：

```python
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
```

**代码逻辑**：

```python
# 每天开始时，先把昨天的待处理订单用今天的开盘价成交
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 主循环算法全貌

```python
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` 函数，每次调用都创建**全新、独立的**组件实例：

```python
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)**。代码记录了每笔交易：

```python
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 | 用于创建初始化好的完整对象的辅助函数 |

---

*上一篇：[策略开发与向量化回测](doc_02_strategy_backtest.md)*  
*系列完结：恭喜你完成了量化交易基础三部曲！*

---

## 附录：系列文档导航

| 篇 | 文件 | 文档 | 核心内容 |
|----|------|------|----------|
| 第 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大组件、成本模型、与向量化对比 |
