trading/doc_03_event_driven_backtes...

689 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 事件驱动回测引擎详解
## `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大组件、成本模型、与向量化对比 |