Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

前瞻偏差 回测 陷阱

前瞻偏差让回测结果虚高,实盘即失效。本文详解同柱决策、幸存者偏差、数据修订三种机制,并用SPY数据量化其影响,教你如何识别与检验。

前瞻偏差是指在回测中使用了模拟交易发生时任何人都无法掌握的信息。权益曲线攀升,统计结果看似优异,但一旦投入实盘交易,这一切便不复存在。本页将定义该偏差,阐述三种产生机制,并使用真实市场数据对每种机制进行量化。

什么是前瞻偏差

回测是对一个决策的主张:在此时间戳,依据此信息,该规则本应如此执行。只有当规则读取的每个输入,在其最终形式下,都早于该时间戳存在时,这个主张才成立。前瞻偏差是对这一条件的任何违反,并且它是无声的。当一行数据返回一个晚于其所支撑决策的时间戳的值时,电子表格或脚本中没有任何东西会发出警告。

实践中出现的大部分问题可归结为三种违反情况。一个决策读取了它也在其中进行交易的价格柱。一个由至今仍在上市的名称组成的股票池。一个在其所标注日期之后被修订过的数据字段。

机制一:在交易的价格柱上做决策

从一个听起来无害的规则开始。在强势时买入,即在基金收盘价高于开盘价的交易日。如果编码粗心,测试会在该交易日的开盘价入场,收盘价离场,而它筛选的条件正是同一价格柱的收盘价。在开盘钟声敲响时,无人知晓收盘价。

下面的面板在追踪标普500指数的SPY上,跨越十个日历年度,运行了这两个版本。第一列是不可能实现的版本。第二列将相同的条件滞后一个交易日,这是有资金账户能够据此行动的最早时间。

查询同柱决策 vs 滞后一期:SPY,平均每期收益,2016-2025
每个数字背后的完整 SQL
WITH bars AS (
    SELECT date,
           toFloat64(open) AS o,
           toFloat64(close) AS c,
           toFloat64(close) > toFloat64(open) AS up_day
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
lagged AS (
    SELECT date, o, c, up_day,
           any(up_day) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prior_up
    FROM bars
)
SELECT toYear(date) AS year,
       countIf(up_day) AS signal_days,
       round(100 * avgIf(c / o - 1, up_day), 3) AS same_bar_avg_pct,
       round(100 * avgIf(c / o - 1, prior_up), 3) AS lagged_avg_pct,
       round(100 * (avgIf(c / o - 1, up_day) - avgIf(c / o - 1, prior_up)), 3) AS gap_pp
FROM lagged
WHERE date >= toDate('2016-01-01')
GROUP BY year
ORDER BY year
Run this yourself

差异并不微妙。在2016年,同柱版本在符合条件的交易日平均获得0.447%的收益,而滞后版本平均获得-0.008%,每日相差0.455个百分点。在2025年,两者分别为0.636%和-0.03%。假设一年约有140个符合条件的交易日,每个交易日半个百分点的差异,累积成的权益曲线是任何实盘账户都未曾实现过的。

整个优势是定义性的。选择那些收盘价高于开盘价的交易日,然后在这些交易日上测量开盘到收盘的收益,测量的是一个保证为正的量。该规则发现了它自己的过滤器。

这种变体在日线数据中随处可见:一个包含今日收盘价并用于今日交易的移动平均线,一个基于已按尚未发生的拆股调整过的价格计算的信号,一个设置在尚未出现的日内高点的止损单。其共同形态是决策时间戳早于其自身某个输入的时间戳。

机制二:由今日幸存者构成的股票池

第二种机制从不触及规则本身。它存在于规则被允许看到的名称列表中。

从当前数据提供商拉取一个股票代码列表,将其回测十年,样本会悄然排除所有在此期间破产、被收购或退市的公司。该规则从未被给予那些输家。下面的面板衡量了这种遗漏的规模:在给定日历年度内,所有交易至少200个交易日且股价高于1美元的美国上市名称,该群体中在2026年7月仍在报价的比例,以及整个群体的中位数日历年度回报与仅幸存者的中位数回报的对比。

查询幸存者偏差:每年交易标的,2026年7月仍上市比例及中位数回报
每个数字背后的完整 SQL
WITH recent AS (
    SELECT DISTINCT ticker
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2026-06-15')
      AND date <= toDate('2026-07-28')
),
per_name AS (
    SELECT toYear(date) AS year,
           ticker,
           argMin(toFloat64(close), date) AS first_close,
           argMax(toFloat64(close), date) AS last_close,
           count() AS sessions
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2015-01-01')
      AND date <= toDate('2024-12-31')
      AND close > 1
    GROUP BY year, ticker
    HAVING sessions >= 200
)
SELECT year,
       uniqExact(ticker) AS names_trading,
       round(100 * uniqExactIf(ticker, ticker IN (SELECT ticker FROM recent)) / uniqExact(ticker), 1) AS still_listed_pct,
       round(100 * quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker)), 2) AS median_return_all_pct,
       round(100 * quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent)), 2) AS median_return_survivors_pct,
       round(100 * (quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent))
                    - quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker))), 2) AS survivor_gap_pp
FROM per_name
GROUP BY year
ORDER BY year
Run this yourself

2015年达到标准的7063个名称中,有52.5%个在2026年7月仍在交易。对于2024年的群体,该数字为87.5%,这主要是时间流逝的结果:一个2024年的名称有两年的时间消失,而一个2015年的名称则有十一年。

回报列承载了偏差本身。在2017年,中位数名称回报为7.51%,而中位数幸存者回报为11.32%,两者相差3.81个百分点,这是免费获得的差距。在图表所示的10年中,每年的差距都是正的。一个在幸存者身上测试的策略,在做出任何决策之前就继承了这一优势,而那些筛选弱势股的规则,其继承的优势会超过中位数,因为消失的名称不成比例地落在这个区间。

修正方法是使用时间点股票池:在每个调仓日期存在的名称列表,包括退市股票,退市头寸以其最终报价平仓,而不是从记录中删除。

机制三:在其日期戳之后被修订的数据

价格是一次性标注的。基本面数据则不然。一个季度数据带有季度末的日期,但会被后来的申报文件修订,而只存储当前版本的数据库会愉快地将修订后的数字与原始日期一起提供。一个基于该数字进行筛选的规则,读取的是一个需要数月才能到达的修正。

同样的模式也出现在:为匹配当前成分股而重建的指数成员列表,被最终共识覆盖的分析师盈利预测历史,以及反向应用于整个价格序列的公司行为调整。在每种情况下,存储的记录都是一个带有过去式日期的现在时快照。

两种做法可以控制它。使用保留版本的数据源,即存储每个日期当时已知的值,而不是现在已知的值。如果无法获得版本数据,则应用足够覆盖实际报告滞后的报告时滞,并将该时滞的长度视为一个值得测试的参数,而不是一个值得猜测的细节。

当未来信息渗入时,曲线看起来什么样

前瞻偏差有一个特征:表现远高于基础市场所能提供的水平。一个有用的参考点是后见之明上限,即一个完美的一日预测在单一工具上所能产生的增长。

查询后见之明天花板:SPY买入持有、剔除最大涨幅日及完美单日预知
每个数字背后的完整 SQL
WITH bars AS (
    SELECT date,
           toFloat64(close) AS c,
           any(toFloat64(close)) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_c
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
rets AS (
    SELECT toYear(date) AS year,
           c / prev_c - 1 AS r
    FROM bars
    WHERE date >= toDate('2016-01-01')
      AND prev_c > 0
)
SELECT year,
       count() AS sessions,
       round(100 * (exp(sum(log(1 + r))) - 1), 1) AS buy_and_hold_pct,
       round(100 * (exp(sumIf(log(1 + r), r < 0.02)) - 1), 1) AS without_big_up_days_pct,
       round(exp(sum(log(1 + abs(r)))), 1) AS hindsight_ceiling_multiple
FROM rets
GROUP BY year
ORDER BY year
Run this yourself

这个上限是巨大的。在2020年,对SPY的完美一日预测,在253个交易日内使资本增长了27.7倍,而单纯持有该基金则增长16.2%倍。在十年中最平静的一年,2017年,同样的完美预测使资本增长了2.2倍。

中间列显示了这种上限的集中程度。剔除涨幅超过2%的交易日,2020年的回报从16.2%降至-44.9%。每年仅泄露少数几个这样的交易日,就能将回测结果推得很远。一条具有浅回撤、高胜率,且最佳交易日集中在市场最剧烈交易日的曲线,值得进行审计。

校准比任何单一阈值都更重要。一个声称十年间年化收益稳定在40%的规则,是在声称占据了后见之明上限的很大一部分,而诚实的问题是,哪个输入告诉了它即将发生什么。

如何测试回测是否存在前瞻偏差

  • 将所有信号滞后一个柱。 将每个信号向后移动一个完整周期并重新运行。真正的优势会略微衰减。泄露则会崩溃,通常归零。
  • 扰动未来数据。 取每个决策时间戳之后的数据,用噪声或重新排列的数据替换,然后重新运行决策逻辑。每个决策都应保持不变。任何发生变化的决策都读取了它本不可能知道的信息。
  • 将时间戳视为数据。 对于每个输入,存储一个值变得可用的时刻以及它所描述的时刻。一个从不比较这两列的连接操作无法强制执行规则。
  • 按日期重建股票池。 包括退市和被收购的名称,以其最后报价平仓,并在两个股票池上运行相同的规则,以量化幸存者偏差的贡献。
  • 与上限进行比较。 将策略的回报与同一时间窗口内的买入并持有回报以及完美预测回报进行比较。与上限的距离是夏普比率单独无法提供的背景信息。

仓位管理是这一切的下游环节。被夸大的胜率会导致被夸大的仓位规模,这正是凯利公式仓位管理将会计错误转化为回撤的地方。在动用真实资本之前,同样的纪律也适用:实盘交易前的模拟交易测试订单执行机制,而自动化交易系统则以机器速度承载着相同的风险,相关内容在多智能体AI交易系统中有所介绍。那些能够经受住严格时间点测试的效应,例如低波动率异象,正是因为这个原因才显得有趣。

常见问题解答

什么是回测中的前瞻偏差?

它是指在模拟交易中使用了交易发生时尚未获得的数据。常见形式包括:在交易执行的价格柱上做决策,筛选至今仍在上市的公司股票池,以及读取一个在其标注日期之后被修订过的财务数据。

如何检测前瞻偏差?

将每个信号滞后一个完整周期并重新运行:泄露通常会崩溃,而真正的优势会温和衰减。一个更强的测试是用噪声替换每个决策时间戳之后的所有数据,并确认每个决策保持不变。

幸存者偏差和前瞻偏差是一回事吗?

它们不同但常相伴出现。幸存者偏差是一个由幸存下来的名称构成的样本,而知道哪些名称幸存下来本身就是来自未来的信息,因此一个仅包含幸存者的股票池是一个位于数据中而非规则中的前瞻性泄露。

为什么带有前瞻偏差的回测看起来那么好?

泄露是选择结果而非预测结果。在上面的面板中,根据自身收盘价筛选交易日,在2016年产生的平均交易日收益比滞后版本高出约0.455个百分点,这是一个定义性的而非赚取的优势。

什么是时间点数据?

时间点数据存储每个历史日期当时已知的每个值,包括原始数字和所有后续修订,以及该日期的证券股票池。这是回测需要用来证明其输入在决策之前就已存在的参考依据。


以上每个数字都是基于真实日线数据的存储、版本化查询结果。在Strasmore终端上展开任何面板的SQL,或对您自己的规则运行滞后测试。

#backtesting#look-ahead bias#survivorship bias#quant#research process