/~learn/s/b69e3030
> 来源:量化交易中的幻觉和陷阱 — 微信公众号文章 > > 本文结合文章揭示的三大回测陷阱,逐一对照 adata 量化系统的现状,给出改进方案。
---
回测的三大陷阱(按发现顺序):
陷阱1: 净值的幻觉
├── 复利放大效应 → 在牛市中净值被夸大
├── 单利 vs 复利 → 两种计算方式差异巨大
└── 路径依赖 → 盈利时利滚利越堆越大,亏损时同理
陷阱2: 被忽视的滑点
├── 滑点无处不在 → 市价单成交价总比期望值差
├── 最低2 tick → 最保守的起步估计
└── 流动性不足 → 滑点更高
陷阱3: 容量的瓶颈
├── 策略容量有限 → 10万赚钱,50万未必
├── 冲击成本 → 大资金推动价格不利变动
└── 竞争衰减 → 对手学习你的 pattern → 生命周期缩短
---
当前 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)
结论:现有系统是纯复利模式。每次交易盈利/亏损后,现金池会变大/变小,下一轮建仓的股数随之变化。在牛市中,这个复利效应确实会放大净值。
增加单利基准线,与复利净值并列展示:
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行),但能让你看清复利放大效应有多大。
---
当前系统完全没有滑点处理。 只有手续费:
# Strategy.py L355
fee_rate = 0.00005 # 万分之0.5,仅买入时扣除
没有 tick 级别的滑点建模。买入价 = open/close * (1 + fee_rate),卖出价 = open/close(无折价)。
这会导致回测收益系统性偏高。
在买入和卖出时各加 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。
优先级:高。这是回测中最基础也最容易被忽略的成本。不加滑点的回测约等于"理想情况",不能直接用于实盘。
---
- 冲击成本:大资金入场推动价格不利变动 - 订单薄深度不足:挂单少,大单成交成本高 - 竞争衰减:你的 pattern 被对手学习,策略生命周期缩短
当前系统完全没有容量分析。 基于日线级别回测,假设成交价不受资金量影响——这在小资金时成立,但无法回答"放多少钱进去还能赚钱"。
分两层做容量估算:
用日均成交额做粗略估算:
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行),能快速发现容量瓶颈。第二层远期再做。
---
> "过分追求完美本身就是一种弯路。"
这条对 adata 系统特别有意义。我们已经有 30+ 策略,各种优化方案(Walk-Forward、Regime Detection、仓位区间可见性),但不要陷入"打磨完美策略"的陷阱。正确的做法是:
> "横向增加品种,充分的低相关分散让组合获得了接近 2 的夏普比率。"
这验证了现有 DualGroupFourMAStrategy 等多品种组合策略的方向是正确的。
| 文章揭示的陷阱 | 现有系统状态 | 改进方向 |
| :--- | :--- | :--- |
| 复利放大净值 | 纯复利,无单利对比 | 增加单利基准线 |
| 滑点被忽略 | 完全没有滑点 | 加入 2 tick 滑点建模 |
| 容量瓶颈 | 完全没有容量分析 | 增加简单容量估算 |
| 复利路径依赖 | 未做 Monte Carlo | 可考虑随机打乱交易顺序做鲁棒性测试 |
---
本项目的回测体系优化方案已经覆盖了 Walk-Forward、Regime Detection、Benchmark 对比等方向。本文的三个陷阱是更基础层面的问题——它们影响的是单次回测的准确性,而不是策略选择的框架。
整合优先级:
第一层(基础准确性 — 本文覆盖):
□ 滑点建模(2 tick 起步)
□ 单利/复利对比
□ 简单容量估算
第二层(策略选择框架 — 已有方案覆盖):
□ Buy & Hold Benchmark
□ Walk-Forward 滚动窗口
□ 仓位区间可见性(已实现)
第三层(高级优化):
□ Regime Detection 元策略
□ 冲击成本模型
□ Monte Carlo 鲁棒性测试
---
Strategy.calculate_returns() 中加入滑点参数(slippage_ticks=2)calculate_simple_interest_return() 单利计算方法estimate_capacity() 简单容量估算