← 文档库
📄 文档learn/量化回测陷阱与改进/01-回测三大陷阱与系统改进方案.md
🌱 创建于2026-06-27 01:04
✏️ 修改于2026-06-27 01:04
👁️ 浏览量—
🔗 短链接/~learn/s/b69e3030
🔐 可见性

回测三大陷阱与系统改进方案

> 来源:量化交易中的幻觉和陷阱 — 微信公众号文章 > > 本文结合文章揭示的三大回测陷阱,逐一对照 adata 量化系统的现状,给出改进方案。

---

文章核心观点速览

回测的三大陷阱(按发现顺序):

  陷阱1: 净值的幻觉
    ├── 复利放大效应 → 在牛市中净值被夸大
    ├── 单利 vs 复利 → 两种计算方式差异巨大
    └── 路径依赖 → 盈利时利滚利越堆越大,亏损时同理

  陷阱2: 被忽视的滑点
    ├── 滑点无处不在 → 市价单成交价总比期望值差
    ├── 最低2 tick → 最保守的起步估计
    └── 流动性不足 → 滑点更高

  陷阱3: 容量的瓶颈
    ├── 策略容量有限 → 10万赚钱,50万未必
    ├── 冲击成本 → 大资金推动价格不利变动
    └── 竞争衰减 → 对手学习你的 pattern → 生命周期缩短

---

一、陷阱1:净值的幻觉 — 单利 vs 复利

1.1 文章要点

1.2 现有系统分析

当前 Strategy.calculate_returns() 使用复利计算:

# Strategy.py L415-436
# 建仓时:用 total_assets * position_ratio 计算买入股数
self.stock_amount = (self.df['total_assets'].iloc[i-1] * current_position) / entry_price
# 总资产 = 股票市值 + 剩余现金(现金会随盈利/亏损变化)
self.df.at[i, 'total_assets'] = stock_value + self.cash
# 累计收益率 = 当前总资产 / 初始资产 - 1
self.df.at[i, 'cumulative_strategy_return'] = (self.df.at[i, 'total_assets'] / self.initial_assets - 1)

结论:现有系统是纯复利模式。每次交易盈利/亏损后,现金池会变大/变小,下一轮建仓的股数随之变化。在牛市中,这个复利效应确实会放大净值。

1.3 改进方案

增加单利基准线,与复利净值并列展示:

def calculate_simple_interest_return(self):
    """
    单利计算:每次交易使用固定本金,不滚动复利。
    每次交易的盈亏 = 固定本金 × 交易盈亏比例
    总收益率 = 所有交易盈亏之和 / 固定本金
    """
    fixed_principal = self.initial_assets
    total_pnl = 0
    for trade_return in self.trades:
        total_pnl += fixed_principal * trade_return
    return total_pnl / fixed_principal * 100

输出对比:

─────────────────────────────────────────────
  📊 净值计算方式对比
─────────────────────────────────────────────
  复利年化收益: 15.2%    (利滚利模式)
  单利年化收益: 11.8%    (每次固定本金)
  差异: 3.4%              ← 复利放大效应
─────────────────────────────────────────────

优先级:中。改动量小(约20行),但能让你看清复利放大效应有多大。

---

二、陷阱2:被忽视的滑点

2.1 文章要点

2.2 现有系统分析

当前系统完全没有滑点处理。 只有手续费:

# Strategy.py L355
fee_rate = 0.00005  # 万分之0.5,仅买入时扣除

没有 tick 级别的滑点建模。买入价 = open/close * (1 + fee_rate),卖出价 = open/close(无折价)。

这会导致回测收益系统性偏高。

2.3 改进方案

在买入和卖出时各加 1 tick 滑点(合计 2 tick):

# 不同品种的 tick 大小
TICK_SIZE = {
    'ETF': 0.001,     # 0.001元
    'stock': 0.01,    # 0.01元(A股)
    'index': 0.01,    # 0.01点(指数)
    'hk_stock': 0.01, # 0.01港元
    'us_stock': 0.01, # 0.01美元
}

def apply_slippage(price, side, tick_size=0.001, ticks=1):
    """
    side='buy'  → 价格变差,成交价更高
    side='sell' → 价格变差,成交价更低
    """
    if side == 'buy':
        return price + tick_size * ticks
    else:
        return price - tick_size * ticks

在 calculate_returns() 中集成:

# 建仓时(买入):滑点让价格变高
entry_price = self.df[self.buy_timing].iloc[i] * (1 + fee_rate)
entry_price = apply_slippage(entry_price, 'buy', tick_size, ticks=1)

# 清仓时(卖出):滑点让价格变低
exit_price = self.df[self.buy_timing].iloc[i]
exit_price = apply_slippage(exit_price, 'sell', tick_size, ticks=1)

可配置化:滑点 ticks 数作为策略参数,默认 2 tick(买入 1 + 卖出 1),支持不同品种配置不同 tick_size。

优先级:高。这是回测中最基础也最容易被忽略的成本。不加滑点的回测约等于"理想情况",不能直接用于实盘。

---

三、陷阱3:容量的瓶颈

3.1 文章要点

- 冲击成本:大资金入场推动价格不利变动 - 订单薄深度不足:挂单少,大单成交成本高 - 竞争衰减:你的 pattern 被对手学习,策略生命周期缩短

3.2 现有系统分析

当前系统完全没有容量分析。 基于日线级别回测,假设成交价不受资金量影响——这在小资金时成立,但无法回答"放多少钱进去还能赚钱"。

3.3 改进方案

分两层做容量估算:

第一层:简单估算(低难度,快速实现)

用日均成交额做粗略估算:

def estimate_capacity(self, max_participation_rate=0.01):
    """
    简单容量估算:策略资金不超过日均成交额的 1%
    
    max_participation_rate: 最大参与率(默认1%,避免冲击成本过高)
    """
    # 筛选持仓期间的平均日成交额
    holding_days = self.df[self.df['position'] > 0]
    avg_daily_amount = holding_days['amount'].mean() if 'amount' in holding_days.columns else None
    
    if avg_daily_amount is None or avg_daily_amount == 0:
        return None
    
    # 策略容量 = 日均成交额 × 参与率 / 仓位比例
    avg_position = holding_days['position'].mean()
    capacity = avg_daily_amount * max_participation_rate / avg_position if avg_position > 0 else float('inf')
    return capacity

在回测报告中输出:

─────────────────────────────────────────────
  📦 策略容量估算
─────────────────────────────────────────────
  日均成交额: 2.3亿
  平均仓位: 60%
  建议容量: ~380万   (1% 参与率)
  激进容量: ~1900万  (5% 参与率,可能有冲击成本)
─────────────────────────────────────────────

第二层:冲击成本模型(中难度,远期)

def estimate_market_impact(self, order_amount, avg_daily_volume):
    """
    基于 Almgren-Chriss 简化模型的冲击成本估算
    
    impact = σ * (Q / V)^γ
    σ: 波动率
    Q: 订单量
    V: 日均成交量
    γ: 冲击系数(通常 0.5~0.8)
    """
    pass

容量-收益曲线

为每个策略画出"容量-年化收益"曲线:

年化收益
  ↑
15%│●
   │  ●
10%│     ●
   │        ●
 5%│           ●
   │              ●
 0%│_________________●__→ 投入资金
   10万  50万 100万 500万 1000万

优先级:中。第一层简单估算改动量小(约30行),能快速发现容量瓶颈。第二层远期再做。

---

四、文章的金句与启示

4.1 对量化心态的启示

> "过分追求完美本身就是一种弯路。"

这条对 adata 系统特别有意义。我们已经有 30+ 策略,各种优化方案(Walk-Forward、Regime Detection、仓位区间可见性),但不要陷入"打磨完美策略"的陷阱。正确的做法是:

> "横向增加品种,充分的低相关分散让组合获得了接近 2 的夏普比率。"

这验证了现有 DualGroupFourMAStrategy 等多品种组合策略的方向是正确的。

4.2 对回测系统的启示

文章揭示的陷阱 现有系统状态 改进方向
:---:---:---
复利放大净值 纯复利,无单利对比 增加单利基准线
滑点被忽略 完全没有滑点 加入 2 tick 滑点建模
容量瓶颈 完全没有容量分析 增加简单容量估算
复利路径依赖 未做 Monte Carlo 可考虑随机打乱交易顺序做鲁棒性测试

---

五、与现有优化方案的整合

本项目的回测体系优化方案已经覆盖了 Walk-Forward、Regime Detection、Benchmark 对比等方向。本文的三个陷阱是更基础层面的问题——它们影响的是单次回测的准确性,而不是策略选择的框架。

整合优先级:

第一层(基础准确性 — 本文覆盖):
  □ 滑点建模(2 tick 起步)
  □ 单利/复利对比
  □ 简单容量估算

第二层(策略选择框架 — 已有方案覆盖):
  □ Buy & Hold Benchmark
  □ Walk-Forward 滚动窗口
  □ 仓位区间可见性(已实现)

第三层(高级优化):
  □ Regime Detection 元策略
  □ 冲击成本模型
  □ Monte Carlo 鲁棒性测试

---

六、TODO

▶ 📜 变更历史(1 次提交)

4616ca62026-06-27 01:04docs: 同步 learn/ 文档 & 工作区改动
🍃quant 知识库
by 伊甸园和枫

🔊 播放设置

勾选音源后调节配比,总和自动归一化
已选 0 个 · 合计:0%
合成中...
00:00/00:00
1.0x

分享图片

生成中...