如何消除美股回测中的“幸存者偏差”?基于 FMP 退市股票(Delisted)与历史标普 500 成分股接口实战

如果一套美股低估值或超跌反弹策略在回测中跑出了 25% 以上的年化收益,先别急着上实盘——去检查一下你的股票池是不是直接拉取了当前最新的标普 500 成分股列表。

拿着今天的存活名单去回测过去 3~5 年的行情,是量化回测中最隐蔽也最致命的“幸存者偏差(Survivorship Bias)”。我们通过 FMP数据源中文站 实盘调取 FMP 官方标普 500 现存与历史成分股、退市公司及历史日线接口,直接跑出了三组足以颠覆回测结论的实测证据:

  1. 不到 4 年标普 500 换血超 12.6%:自 2023-01-01 至 2026-10-23,标普 500 累计发生 78 次成分股调整。还原 2023-01-01 历史时点股票池(504 个代码)可见,当时在池的 64 只股票已被剔除(含硅谷银行 SIVB、第一共和银行 FRC、签名银行 SBNY 等破产爆雷股),而当前在池的 PLTR、SMCI、CRWD、DELL、AXON、FICO 等 63 只大牛股在 2023 年初根本不在指数内。
  2. 2023 年五组“被剔除股 vs 顶替股”全年收益剪刀差高达 133.68 个百分点:实测 2023 年 5 只典型被剔除标的(SIVBQ、SBNY、LUMN、FRCB、DISH)全年等权收益为 -84.71%;而同年顶替它们入池的 5 只新贵(PODD、BG、FICO、AXON、PANW)全年等权收益高达 +48.97%。用静态名单回测等于同时“抹掉暴雷股 + 开了天眼提前买入未来赢家”。
  3. 退市并非只有“破产归零”一种结局:只拉主板代码 SIVB 会停在停牌前一日的 $106.04,漏掉转入粉单市场 SIVBQ 后跌至 $0.006(-99.997%)的真实清算;反过来,若把所有退市股一刀切按归零处理,又会把微软现金收购动视暴雪(ATVI,停于 $94.42)与 Paramount Skydance 收购华纳兄弟探索(WBD,停于 $30.95)的正收益算成血亏。

本文将基于 2026 年 10 月最新实测报文,详解如何联动 FMP 四大类接口拼出无幸存者偏差的 Point-in-Time(历史时点)股票池,并给出处理停牌期 volume = 0 幽灵成交、日线 changePercent 日内口径陷阱、退市终值分类结算与股票代码复用覆盖的核心 Python 逻辑。

延伸阅读:初次接触 FMP 接口体系可先查阅《FMP 是什么?数据范围、接口体系、价格限制与中文接入指南》;财报三表勾稽与 acceptedDate 防未来函数实测见《FMP 数据 API 深度测评:财报颗粒度、更新时效、竞品横评与真实短板(2026)》;若需高频逐笔(Tick)或期权链对比,参见《FMP vs Massive(原 Polygon.io):做美股研究,应该怎么选?》;机构 13F 与内部人增减持因子见《如何用 FMP 追踪华尔街聪明钱:13F 机构持仓、内部人交易与国会山议员买卖接口实战》。


美股回测中的“幸存者偏差”从哪来?FMP 哪四组接口能拼出完整的历史时点(Point-in-Time)股票池?

一套无偏差的量化回测系统,必须在任意历史回测截面日 T 回答两个问题:第一,当天哪些股票真正在指数或可交易池内?第二,后来退市或改名的股票,在 T 之后的真实可成交价格是多少?

FMP 官方将这两块拼图拆在 指数行情与成分股(15 个接口)、公司概况与信息(17 个接口)、证券与代码目录(11 个接口) 与 历史K线与图表(10 个接口) 四个分类下,工程协同关系如下:

解决的回测痛点FMP 接口中文页与分类入口核心 /stable/* 端点路径实测数据规模与关键字段回测工程职责
还原历史时点成分股(Point-in-Time Universe)标普500成分股<br>标普500历史成分股/stable/sp500-constituent<br>/stable/historical-sp500-constituent现存 503 条(500 家唯一 cik)<br>历史变更 1,531 条从当前成分股出发,按 date、changeType、addedTicker、removedTicker 逆向回滚至历史日 T
捕获全市场摘牌与退市标的退市公司列表<br>并购搜索/stable/delisted-companies<br>/stable/mergers-acquisitions-search按页返回 symbol、exchange、ipoDate、delistedDate识别股票退市时间与原因(区分破产摘牌 vs 现金/换股并购退市)
对齐股票改名与借壳复用股票代码变更记录<br>公司概况/stable/symbol-change<br>/stable/profile返回 oldSymbol、newSymbol、date 及 isActivelyTrading、cik防止把正常改名(如 FB → META)当退市,拦截老代码被其他公司复用后覆盖历史行情
提取已退市股票的历史行情与财报全量历史日线<br>利润表/stable/historical-price-eod/full<br>/stable/income-statement保留已退市/被收购股票的历史日 K 线与 10-K/10-Q 报表联动粉单市场(OTC)代码拉取破产归零全过程日线,校验 volume > 0 真实可成交价格

如何用 /stable/historical-sp500-constituent 逆向还原任意历史日期的标普 500 真实成分股?

1. 现存成分股的“503 vs 500”双重股权去重细节

先调取 标普500成分股接口(/stable/sp500-constituent)。实测返回 503 个股票代码,按 cik 去重后恰好对应 500 家上市公司——这是因为 Alphabet(CIK: 0001652044,含 GOOGL 与 GOOG)、Fox Corp(CIK: 0001754301,含 FOXA 与 FOX)以及 News Corp(CIK: 0001564708,含 NWSA 与 NWS)三家公司的 A/B 或 A/C 类双层股票同时留在标普 500 内。

工程细节:做截面基本面因子选股或等权组合(Equal-Weight)回测时,必须先按 cik 聚合或只保留流动性更高的主代码(如 GOOGL、FOXA、NWSA),否则这三家公司会被无意赋予 2 倍权重。

2. 解析 1,531 条历史调仓记录的三种变更形态(changeType)

再调取 标普500历史成分股接口(/stable/historical-sp500-constituent),实测一次性返回自 1957-03-04(首条记录为纳入 General Signal Corp GSX)以来的 1,531 条成分股进出流水。每条记录不仅给出精确调仓日期 date 与新旧代码,还通过 changeType 和 reason 交代了调仓原委:

// GET /stable/historical-sp500-constituent (节选 3 条代表性调仓记录,取数时间 2026-10-23)
[
  {
    "dateAdded": "October 06, 2026",
    "addedSecurity": "Twilio Inc.",
    "removedTicker": "WBD",
    "removedSecurity": "Warner Bros. Discovery",
    "date": "2026-10-06",
    "symbol": "TWLO",
    "reason": "Paramount Skydance acquired Warner Bros. Discovery",
    "changeType": "replacement",
    "addedTicker": "TWLO"
  },
  {
    "dateAdded": "May 3, 2023",
    "addedSecurity": "Axon Enterprise Inc",
    "removedTicker": "FRC",
    "removedSecurity": "First Republic Bank",
    "date": "2023-05-03",
    "symbol": "AXON",
    "reason": "The Federal Deposit Insurance Corporation (FDIC) placed First Republic Bank into FDIC Receivership.",
    "changeType": "replacement",
    "addedTicker": "AXON"
  },
  {
    "dateAdded": "November 29, 2001",
    "addedSecurity": "Nvidia Corp.",
    "removedTicker": "ENRNQ",
    "removedSecurity": "Enron Corp.",
    "date": "2001-11-29",
    "symbol": "NVDA",
    "reason": "Annual Re-ranking",
    "changeType": "replacement",
    "addedTicker": "NVDA"
  }
]

看上面第 3 条历史记录:英伟达(NVDA)首次进入标普 500 的日期是 2001-11-29,而被它顶替踢出指数的,恰恰是财务造假破产的安然公司(ENRNQ)。

在编写逆向回滚算法(从今日成分股集合 S_now 倒推历史日期 T 的成分股集合 S_T)时,必须将所有 date > T 的变更记录按时间从新到旧倒序遍历,严格区分全量 1,531 条记录中的三种 changeType:

3. 实盘量化算账:2023 年标普 500 五组“被剔除股 vs 顶替股”的 133.68% 收益鸿沟

为什么不还原历史成分股会让回测彻底失真?我们把 2023 年标普 500 发生的 5 次典型调仓抽出来,直接调取 全量历史日线接口(/stable/historical-price-eod/full),对拍它们在 2023 全年(首个交易日 2023-01-03 至年末 2023-12-29)的真实涨跌幅:

标普 500 调仓日 (date)调仓原因 (reason)2023 年初真实在池、后被剔除的标的 (removedTicker)被剔除股 2023 全年走势 (01-03 → 12-29)后视镜静态名单里顶替入池的标的 (addedTicker)顶替股 2023 全年走势 (01-03 → 12-29)单组首尾收益差
2023-03-14FDIC 接管硅谷银行SIVB → SIVBQ<br>(SVB Financial Group)$225.22 → $0.0331<br>(-99.99%,主板停于 $106.04)PODD<br>(Insulet Corp)$286.38 → $216.98<br>(-24.23%)+75.76%
2023-03-14FDIC 接管签名银行SBNY<br>(Signature Bank)$113.17 → $1.6009<br>(-98.59%)BG<br>(Bunge Ltd)$95.57 → $100.95<br>(+5.63%)+104.22%
2023-03-17市值萎缩被移出LUMN<br>(Lumen Technologies)$5.37 → $1.83<br>(-65.92%)FICO<br>(Fair Isaac & Co)$591.19 → $1,164.01<br>(+96.89%)+162.81%
2023-05-03FDIC 接管第一共和银行FRC → FRCB<br>(First Republic Bank)$121.54 → $0.041<br>(-99.97%,主板停于 $3.51)AXON<br>(Axon Enterprise)$168.24 → $258.33<br>(+53.55%)+153.52%
2023-06-20市值萎缩被移出DISH<br>(DISH Network)$14.10 → $5.77<br>(-59.08%)PANW<br>(Palo Alto Networks)$69.22 → $147.44<br>(+113.00%)+172.08%
五组等权均值—真实在池暴雷/衰退组合平均收益 -84.71%后视镜顶替赢家组合平均收益 +48.97%剪刀差 133.68%

账面数字一目了然:5 只在 2023 年初真正在标普 500 池子里的成分股,年内平均亏掉 84.71%(其中三家银行几乎清零);而顶替它们进来的 5 只股票,同年平均大涨 +48.97%(FICO 与 PANW 直接翻倍)。如果回测时不回滚成分股,任何“逢低买入低市净率/超跌成分股”的策略都会自动避开全部 5 颗大雷,并把后来才入选的 FICO、PANW、AXON 当成年初就持有的底仓。


股票退市后 FMP 还能拉出日线与财报吗?深扒三个只有实盘跑过才懂的退市暗坑

许多开发者以为只要把 SIVB、FRC、ATVI、BBBY 加回历史股票池就算消除了幸存者偏差。但当我们真正调用 退市公司列表(/stable/delisted-companies)、股票代码变更记录(/stable/symbol-change)和历史日线接口去跑回测撮合时,发现了三个足以让回测净值再次失真的底层暗坑:

暗坑一:主板退市截断、停牌期 volume = 0 幽灵逃顶,以及 changePercent 日内口径陷阱

FMP 完整保留了已退市或被接管公司的历史财报(如 /stable/income-statement?symbol=SIVB 仍可拉出截至 2022-12-31 的年报)与历史日线,但银行破产或财务退市往往伴随着“主板停牌 → 转入 OTC 粉单市场改代码复牌”的两段式跳水。

对比 /stable/historical-price-eod/full 下 SIVB(纳斯达克主板代码)与 SIVBQ(粉单市场代码)在 2023 年 3 月的真实日线原生返回:

// GET /stable/historical-price-eod/full?symbol=SIVBQ&from=2023-03-08&to=2023-03-29 (节选关键转折日,取数时间 2026-10-23)
[
  {
    "symbol": "SIVBQ",
    "date": "2023-03-28",
    "open": 0.53,
    "high": 0.74,
    "low": 0.01,
    "close": 0.4,
    "volume": 84639946,
    "change": -0.13,
    "changePercent": -24.53,
    "vwap": 0.3637
  },
  {
    "symbol": "SIVBQ",
    "date": "2023-03-14",
    "open": 106.04,
    "high": 106.04,
    "low": 106.04,
    "close": 106.04,
    "volume": 0,
    "change": 0,
    "changePercent": 0,
    "vwap": 106.04
  },
  {
    "symbol": "SIVBQ",
    "date": "2023-03-09",
    "open": 176.55,
    "high": 177.75,
    "low": 100,
    "close": 106.04,
    "volume": 38746481,
    "change": -70.51,
    "changePercent": -39.94,
    "vwap": 127.93
  },
  {
    "symbol": "SIVBQ",
    "date": "2023-03-08",
    "open": 266.86,
    "high": 271.01,
    "low": 264,
    "close": 267.83,
    "volume": 835185,
    "change": 0.97,
    "changePercent": 0.36349,
    "vwap": 267.61
  }
]

这段原生报文暴露了三个极易踩中的回测撮合与字段口径漏洞:

暗坑二:把“现金并购溢价退市”一刀切当成“破产清零”

另一个极端错误,是给所有出现在 /stable/delisted-companies 或日线断更的股票统一强加 -100% 的破产惩罚。实际上,美股每年有大量大型成分股是因为高溢价现金并购或私有化(M&A Buyout)而光荣退市(且部分并购股在停牌交割日后也会挂几天 volume = 0 的横盘填充线):

退市/剔除股票退市类型定性标普 500 剔除或摘牌日期/stable/historical-sp500-constituent 披露原因 (reason)2023 年初收盘价 (2023-01-03)退市前最后可成交日收盘价 (volume > 0)真实持有收益(非破产!)
ATVI(动视暴雪)巨头全现金收购2023-10-17 移出<br>(10-13..20 为 volume: 0)Microsoft acquired Activision Blizzard.$76.88$94.42(2023-10-12 成交 732 万股,贴近微软 $95 交割价)+22.81%
PXD(先锋自然资源)巨头换股并购2024-05-02 停止交易埃克森美孚(XOM)完成并购交割$220.25$269.62(2024-05-02 成交 443 万股)+22.42%
MRO(马拉松石油)巨头全股票并购2024-11-21 停止交易康菲石油(COP)完成并购交割$25.64$28.55(2024-11-21 成交 3,603 万股)+11.35%
WBA(沃博联)私募财团私有化2025-08-28 移出<br>(delistedDate: 2025-08-29)Sycamore acquired Walgreen Boots Alliance.$37.19$11.98(2025-08-27 成交 4.52 亿股,08-28 为 volume: 0)-67.79%(按交割价结算,非归零)
WBD(华纳兄弟探索)同业并购退市2026-10-06 移出<br>(delistedDate: 2026-10-07)Paramount Skydance acquired Warner Bros. Discovery$9.54$30.95(2026-10-05 成交 4.36 亿股)+224.42%

因此,当某只持仓股日线终止时,必须结合 reason 字段(是否包含 acquired / merged / taken over)、并购搜索接口(/stable/mergers-acquisitions-search)以及最后一个 volume > 0 可成交日的收盘价做分类结算。

暗坑三:美股“借尸还魂 / 股票代码复用覆盖陷阱(Ticker Recycling)”——以 OSTK → BYON → BBBY → NXH 实测为例

在 A 股市场,退市股票的 6 位数字代码永久封印不再复用;但在美股,破产退市公司的知名 Ticker 可以被其他上市公司买走重新启用,甚至直接在数据源里覆盖掉老公司的历史日线!

我们实测调用 退市公司列表、股票代码变更记录 与 公司概况接口(/stable/profile),对拍了 BBBY、BYON、OSTK、NXH 四个代码,抓到了一个极具警示意义的“张冠李戴”现场:

// 1) GET /stable/delisted-companies?page=0&limit=100 (节选 BBBY 记录,取数时间 2026-10-23)
{
  "symbol": "BBBY",
  "companyName": "Bed Bath & Beyond Inc.",
  "exchange": "NYSE",
  "ipoDate": "1992-06-01",
  "delistedDate": "2026-08-17"
}

// 2) GET /stable/symbol-change (节选 BBBY 更名记录,取数时间 2026-10-23)
{
  "date": "2026-08-17",
  "companyName": "Neighborhood Intelligence, Inc. Common Stock",
  "oldSymbol": "BBBY",
  "newSymbol": "NXH"
}

如果只看表面,你可能会疑惑:在 /stable/historical-sp500-constituent 里于 1999-09-30 纳入标普 500、2017-07-25 被剔除的老牌家居零售商 Bed Bath & Beyond,明明在 2023 年已经破产清盘(且实测 FMP 中粉单代码 symbol=BBBYQ 返回空数组 []),为什么 symbol=BBBY 直到 2026-08-17 还有 $4.28 的纽交所日线?

进一步调取 OSTK(Overstock.com, Inc.)、BYON(Beyond, Inc.)、BBBY 与 NXH 四个代码的 /stable/profile 和 2023-01-03 日线,底层真相彻底水落石出:


洗干净数据之后怎么用?基于退市与历史成分股接口的三大高阶回测与风控策略

在把底层接口边界摸透后,我们可以将这四组接口组合成三套直接提升实盘存活率的高阶工程方案:

高阶应用一:Point-in-Time 动态成分股状态机 + 停牌日 volume > 0 强制顺延撮合规则

在构建指数增强(Index Enhancement)或行业中性多因子回测时,严禁使用静态股票列表,而应按以下三步构建动态状态机:

高阶应用二:退市终值三级自动分类器(并购交割 vs 破产残值 vs 代码更名)

当回测持仓中的股票在日期 t_end 后不再更新常规日线时,通过以下三级流水线自动裁定终值收益率(Terminal Return):

高阶应用三:“左侧抄底策略(低 PB / 高股息 / 超跌反转)”的防退市熔断器

幸存者偏差对动量策略(Momentum)影响较小(因为动量买的是不断创新高的赢家),但对价值回归、低市净率(Low P/B)、高股息率(High Dividend Yield)和 RSI 超卖抄底策略具有毁灭性打击——因为在真实市场里,跌得最深、静态 PB 最低、表观股息率飙到 15% 以上的股票,往往不是被错杀的白马,而是即将被踢出指数甚至破产退市的 SIVB、FRC、LUMN 与 DISH!

在实盘运行左侧反转策略时,必须联动 财务报表分类 下的 财务评分接口(/stable/financial-scores)与 关键财务指标接口(/stable/key-metrics-ttm)设置两道硬熔断:


Point-in-Time 标普 500 股票池还原与退市真实收益结算有哪些核心 Python 逻辑?

下面这段经过实盘数据验证的核心代码(43 行),展示了如何用 /stable/sp500-constituent 与 /stable/historical-sp500-constituent 逆向还原任意历史截面的标普 500 成分股,并在处理退市/剔除股票时拼接粉单(OTC)序列、穿透停牌期(volume == 0),对无流动性锁死标的返回显式状态:

import os, urllib.request, json

FMP_KEY = os.environ["FMP_KEY"]
BASE_URL = "https://quote-fmp.com/stable"

def fmp_get(path: str) -> list:
    sep = "&" if "?" in path else "?"
    with urllib.request.urlopen(f"{BASE_URL}{path}{sep}apikey={FMP_KEY}", timeout=15) as r:
        return json.loads(r.read().decode("utf-8"))

# 1. 逆向回滚还原 target_date (如 "2023-01-01") 的历史时点标普 500 成分股池
def reconstruct_sp500_pit(curr_rows: list, hist_rows: list, target_date: str) -> set:
    universe = {r["symbol"] for r in curr_rows if r.get("symbol")}
    future_events = sorted([r for r in hist_rows if r.get("date", "") > target_date],
                           key=lambda x: x["date"], reverse=True)
    for ev in future_events:
        added = (ev.get("addedTicker") or ev.get("symbol") or "").strip()
        removed = (ev.get("removedTicker") or "").strip()
        if ev.get("changeType") in ("addition", "replacement") and added:
            universe.discard(added)
        if removed:
            universe.add(removed)
    return universe

# 2. 拼接主板与 OTC 粉单序列,穿透停牌期 (volume == 0) 结算真实斩仓收益率
def settle_exit_return(main_bars: list, entry_date: str, exit_signal_date: str, otc_bars: list = None) -> dict:
    by_date = {b["date"]: b for b in (otc_bars or [])}
    for b in main_bars:
        if float(b.get("volume") or 0) > 0 or b["date"] not in by_date:
            by_date[b["date"]] = b
    bars = [by_date[d] for d in sorted(by_date)]
    entry_bar = next((b for b in bars if b["date"] >= entry_date and float(b.get("volume") or 0) > 0), None)
    if not entry_bar:
        return {"status": "NO_ENTRY"}
    # 调仓/退市信号发出后,必须等到首个 volume > 0 的真实可交易日才能按收盘价平仓(防停牌日幽灵逃顶)
    exit_bar = next((b for b in bars if b["date"] >= exit_signal_date and float(b.get("volume") or 0) > 0), None)
    if not exit_bar:
        last_valid = next((b for b in reversed(bars) if float(b.get("volume") or 0) > 0), entry_bar)
        return {"status": "HALTED_NO_LIQUIDITY", "entry_date": entry_bar["date"],
                "entry_px": entry_bar["close"], "last_tradable_date": last_valid["date"], "last_px": last_valid["close"]}
    ret = round((float(exit_bar["close"]) / float(entry_bar["close"]) - 1.0) * 100, 2)
    return {"status": "SETTLED", "entry_date": entry_bar["date"], "entry_px": entry_bar["close"],
            "actual_exit_date": exit_bar["date"], "exit_px": exit_bar["close"], "ret_pct": ret}

用真实序列验证上述两个分支:


常见问题(FAQ):使用退市与历史指数成分股接口时最容易遇到哪些问题?

Q1:为什么 /stable/sp500-constituent 返回 503 条记录而不是 500 条?

因为标普 500 指的是 500 家入选上市公司(对应 500 个唯一 cik),其中 Alphabet(GOOGL/GOOG)、Fox Corp(FOXA/FOX)与 News Corp(NWSA/NWS)各有两类投票权不同的股票同时作为成分股。在计算基本面因子截面分位数或构建等权组合时,建议按 cik 字段去重,避免对这 3 家公司重复赋权。

Q2:已经从主板退市或被收购的股票,在 FMP 里还能查到历史财务三表(10-K/10-Q)吗?

可以。只要该股票在退市前向 SEC 提交过定期报告,直接将其代码传入 利润表接口(/stable/income-statement?symbol=SIVB)、资产负债表接口 或按 10 位 cik 检索,均可正常拉取其退市前的历史各期财报。回测对齐财报发布时间时,同样必须使用 acceptedDate 或 filingDate 防止未来函数。

Q3:除了标普 500,纳斯达克 100 和道琼斯 30 指数也能还原历史成分股吗?

可以。在 指数行情与成分股分类 下,FMP 同步提供了 纳斯达克历史成分股(/stable/historical-nasdaq-constituent)与 道琼斯历史成分股(/stable/historical-dowjones-constituent),返回字段结构与标普 500 完全一致。例如调用 /stable/historical-dowjones-constituent 即可直接查到 2024-11-08 英伟达(NVDA)正式替换英特尔(INTC)、宣伟(SHW)替换陶氏化学(DOW)纳入道指 30 的精确历史记录。

登录后可领取FMP Ultimate档1个月试用 Key,覆盖全量接口。免费试用 1 个月 →