四大陷阱:未来函数、幸存者偏差、交易成本、滑点

青 青柠量化
2026-10-18

一句话结论:A股历史数据库里共有5497只非北交所股票,当前在交易的5225只,272只已退市——幸存率95.1%。只用今天还活着的股票做10年回测,等于把最差的那批从样本里删掉了。加上未来函数、交易成本和滑点,四重失真叠加,回测漂亮是必然的,不是你的策略真的好。

第59课讲了样本外检验,这一课讲让回测失真的四个根本原因,以及青柠如何应对。


一、未来函数(Look-Ahead Bias)

什么是未来函数

最常见的回测bug:用今天收盘价算出的信号,决定今天买入——但收盘价要到当天结束才能知道。

# 错误写法:当日收盘价 > 20日均线就买
df['signal'] = (df['close'] > df['close'].rolling(20).mean())
# 问题:计算信号时已经"看到"了今天的收盘价

# 正确写法:用昨日收盘价的信号,今日开盘执行
df['signal'] = (df['close'].shift(1) > df['close'].rolling(20).mean().shift(1))

换句话说:你在下午3点前不可能知道当天3点整的收盘价,但大量策略代码在回测里假装知道。

A股的常见变体

变体 描述 现实情况
用收盘价生成当日信号 最典型 收盘价知道时,当天已无法交易
财报数据提前用 一季报3月31日结束,但5月才披露 不能在4月1日就用
技术指标当天计算当天买 SAR、布林带等 当天值要等收盘后才有

青柠怎么应对

青柠回测引擎的执行规则:信号日收盘确认,次日开盘买入。这一条就消除了最常见的未来函数错误。所有fwd(5, 20)计算的也是次日开盘价到第5/20日收盘的真实持仓收益。


二、幸存者偏差(Survivorship Bias)

实测:A股消失了多少股票

我们查询了青柠引擎数据库(市场ID=1,排除北交所),统计了历史上出现过的全部A股:

指标 数量 说明
历史上曾出现的A股 5,497只 数据库中有过交易记录的全部A股
当前仍在交易的股票 5,225只 最近一个交易日有成交
已退市或消失的股票 272只 差值,即幸存者偏差的来源
幸存率 95.1% 那4.9%退市的通常是最差的

从上市年份来看,2011年上市的2309只中已有217只退市(9.4%),而2020年后上市的股票退市率极低,因为退市往往在上市后若干年才发生。

为什么这很重要

退市的股票往往是经营恶化、股价长期低迷的,正是被策略"筛掉"后表现最差的那批。只用今天活着的股票回测,等于告诉策略"你已经知道这些股票会活到今天了"——这是实盘中不可能有的信息。

学术估计:幸存者偏差使策略年化收益虚高100-300个基点(1%-3%)。

青柠怎么应对

青柠数据库保留了所有历史上有过记录的股票,包括已退市的。with_bars() 函数从daily_bars里拉数据,不会只取当前股票——退市前有过数据的股票,在其历史区间内照样参与回测。


三、交易成本(Transaction Costs)

A股的真实成本

一笔A股交易来回的成本构成:

成本类型 买入 卖出 备注
佣金 ~0.025% ~0.025% 最低佣金约5元
印花税 0 0.1% 只在卖出时收
买卖价差 ~0.05% ~0.05% 取决于流动性
合计估算 ~0.075% ~0.175% 来回约0.15%

换手率越高,成本越高

年换手次数 年化成本 若毛收益10%则净收益 成本占毛收益比
1次 0.15% 9.85% 1.5%
5次 0.75% 9.25% 7.5%
10次 1.50% 8.50% 15.0%
20次 3.00% 7.00% 30.0%
50次 7.50% 2.50% 75.0%

换手20次/年(每月换1-2次)成本就达3%,消耗掉年化10%毛收益的30%。高频策略的净收益比想象中差得多。

青柠的处理方式

课程中所有数据实验默认对每笔交易扣0.15%来回成本,输出r20_net和e20_net(净收益)。第56课关于过拟合的分析也指出:某些回测漂亮的策略,扣完成本就翻负。


四、滑点(Slippage)

什么是滑点

滑点是回测假设的成交价 vs 实盘真实成交价之间的差距。

场景 回测假设 实盘现实
普通股票买入 次日开盘价成交 实际可能更高(开盘竞价)
大资金买入 开盘价100%成交 冲击市价,实际均价更高
A股涨停板 次日涨停开盘买入 完全买不到(一字板)
止损平仓 设定的止损价成交 跳空低开,实际更低

A股独有:一字涨停板

A股的T+1+涨跌停制度,在滑点问题上有一个极端场景:一字涨停板。信号日之后的次日,某股票直接以涨停价开盘且全天不打开,全天无法买入。青柠引擎对此的处理是:检测到次日开盘价=最高价=最低价且高于前日收盘4%以上,记为NULL(无法买入,不计入统计)。

这不是小概率事件——每课实验的meta文件里都有limit_up字段,统计一字板比例,通常在0.4%-1%之间。

如何减少滑点影响

  • – 选流动性好的股票(日成交额>1000万)
  • – 避免在集合竞价时立刻买入
  • – 回测时用"次日开盘价"而不是"当日收盘价"作为成交价假设(青柠默认)
  • – 成本压力测试:把假设成本翻2-3倍,看策略是否还盈利

五、常见误区

误区1:「我已经加了手续费,所以没问题」
只加佣金不加印花税和滑点,还是少算了。印花税0.1%单向,大多数情况下是最大的单项成本。

误区2:「回测期间策略很好,实盘就会一样好」
实盘面临的是未来(样本外)数据,参数不会再最优,还要加上真实滑点和成本。

误区3:「退市的都是垃圾股,我的策略不会选到」
你不能提前知道哪只股票会退市。幸存者偏差不是说你的策略选了退市股,而是回测时的股票池本身就是事后筛过的。


六、在青柠中实操

青柠从设计上已消除了上述大部分陷阱:

  1. 1. 未来函数:信号确认→次日开盘执行,不允许当日买当日卖
  2. 2. 幸存者偏差:历史数据库保留所有股票,包括已退市
  3. 3. 交易成本:每笔默认扣0.15%来回成本,可在设置中调整
  4. 4. 一字板滑点:自动检测并记为NULL,不虚计收益

你还能做的是:用"参数实验室"(第57课)做成本压力测试——把成本调到2倍,看策略净收益是否还为正。


七、常见问题

Q:数据库里的272只消失股票,是全部退市了吗?
不完全是。部分可能是更名后symbol变化,或因转移到北交所导致记录断裂。但绝大多数是真实退市。

Q:策略信号数量里的NULL是什么意思?
NULL代表由于一字涨停板无法买入,这些交易在统计胜率和收益时被排除,不计入也不计出。

Q:成本0.15%是怎么算的?
买入佣金0.025% + 买卖价差0.05% + 卖出佣金0.025% + 印花税0.1% ≈ 0.20%,保守一点取0.15%(价差较低时)。


口径说明

本课数据来自青柠量化引擎,股票池为A股(非ST、排除北交所4/8/9开头),价格均为后复权。幸存者偏差统计基于截至2026-10-18的数据库快照。本文内容仅供量化学习参考,不构成投资建议。

读到这里,不如自己验证一次

文章里的逻辑,你可以在青柠量化里用真实数据重新跑一遍。免费,零代码。

免费下载客户端

写下你的看法

邮箱仅用于回复通知,不会公开。