如何消除美股回测中的“幸存者偏差”?基于 FMP 退市股票(Delisted)与历史标普 500 成分股接口实战
如果一套美股低估值或超跌反弹策略在回测中跑出了 25% 以上的年化收益,先别急着上实盘——去检查一下你的股票池是不是直接拉取了当前最新的标普 500 成分股列表。
拿着今天的存活名单去回测过去 3~5 年的行情,是量化回测中最隐蔽也最致命的“幸存者偏差(Survivorship Bias)”。我们通过 FMP数据源中文站 实盘调取 FMP 官方标普 500 现存与历史成分股、退市公司及历史日线接口,直接跑出了三组足以颠覆回测结论的实测证据:
- 不到 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 年初根本不在指数内。 - 2023 年五组“被剔除股 vs 顶替股”全年收益剪刀差高达 133.68 个百分点:实测 2023 年 5 只典型被剔除标的(
SIVBQ、SBNY、LUMN、FRCB、DISH)全年等权收益为-84.71%;而同年顶替它们入池的 5 只新贵(PODD、BG、FICO、AXON、PANW)全年等权收益高达+48.97%。用静态名单回测等于同时“抹掉暴雷股 + 开了天眼提前买入未来赢家”。 - 退市并非只有“破产归零”一种结局:只拉主板代码
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:
replacement(一进一出替换,共 1,228 条,占80.21%;2023 年以来为 60 / 78 条):如2026-10-06TWLO替换被收购的WBD、2024-09-23PLTR替换AAL。回滚时需从集合中删掉addedTicker(或symbol),同时加回removedTicker。addition(纯新增,共 262 条,占17.11%,常由母公司分拆 Spin-off 触发):如2026-10-01科迪华(CTVA)分拆VYLR入池(removedTicker: "")。回滚时只需删掉addedTicker。removal(纯剔除,共 41 条,占2.68%,分拆后市值不达标或双层股合并):如2026-10-06CTVA因分拆后市值变动被移除(addedTicker: "")。回滚时只需加回removedTicker。
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-14 | FDIC 接管硅谷银行 | 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-14 | FDIC 接管签名银行 | 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-03 | FDIC 接管第一共和银行 | 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
}
]
这段原生报文暴露了三个极易踩中的回测撮合与字段口径漏洞:
- 只查主板代码
SIVB会少算一半亏损:symbol=SIVB的日线序列在2023-03-09(收盘$106.04)戛然而止。如果回测框架按SIVB最后一根 K 线清算平仓,每股还能拿回$106.04(相比年初$225.22仅记账亏损-52.92%)。 - 调仓日遇上停牌期(
volume: 0)不能按收盘价撮合:标普道琼斯指数公司在2023-03-14正式将硅谷银行移出标普 500。但看上面2023-03-14的报文:从2023-03-10到2023-03-27连续 12 个交易日,该股处于 FDIC 接管停牌状态,FMP 日线填充的是close: 106.04, volume: 0!如果指数跟踪或量化策略在2023-03-14调仓日不校验volume > 0,就会在停牌期按$106.04完成“幽灵逃顶”。而在真实市场里,所有持仓者都被关门锁死,直到2023-03-28转入粉单市场(SIVBQ)恢复交易(volume: 84,639,946),当天直接以$0.53开盘、$0.40收盘(较停牌前$106.04跳空暴跌-99.62%),最终在2024-11-07跌至$0.006摘牌!第一共和银行(FRC → FRCB)同样在2023-05-01与05-02停牌两天(close: 3.51, volume: 0),2023-05-03粉单复牌即跌至收盘$0.3336,2026 年 10 月最新残值仅$0.0003。 - 🔴 隐藏字段口径坑:FMP 日线的
change与changePercent是“日内收盘减开盘(close - open)”,不含隔夜跳空缺口! 仔细核对上面2023-03-09与2023-03-28两根暴雷 K 线:2023-03-08收盘价为$267.83,2023-03-09跳空低开于$176.55并收于$106.04,全天真实较前收涨跌幅应为106.04 / 267.83 - 1 = -60.41%;但接口返回的change却是-70.51(即106.04 - 176.55),changePercent却是-39.94%(即-70.51 / 176.55)!同理,2023-03-28粉单复牌从$106.04崩至开盘$0.53、收盘$0.40,接口changePercent只显示日内跌幅-24.53%((0.40 - 0.53) / 0.53),把-99.50%的隔夜跳空缺口完全漏掉了。因此,回测计算日收益率时严禁直接读取changePercent字段,必须用相邻两个可交易日的收盘价close[t] / close[t-1] - 1自行计算。
暗坑二:把“现金并购溢价退市”一刀切当成“破产清零”
另一个极端错误,是给所有出现在 /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 日线,底层真相彻底水落石出:
- 四个代码共享同一个
CIK: 0001130713与CUSIP: 690370101:BYON的公司简介明确记载"Known previously as Overstock.com, Inc., the company officially adopted the name Beyond, Inc. in November 2023"。Overstock 在买下 Bed Bath & Beyond 品牌 IP 后,先后改名为BYON、在纽交所复用了BBBY代码,并最终于2026-08-17更名为NXH(Neighborhood Intelligence, Inc.)。 - 老
BBBY的历史日线已被 Overstock 覆盖,而元数据却成了“缝合怪”:实测/stable/historical-price-eod/full在2023-01-03这一天,symbol=BBBY、symbol=BYON与symbol=OSTK返回的 K 线完全一致(均为open: 19.73, high: 20, low: 18.26, close: 18.88)!更反常的是,/stable/profile?symbol=BBBY里虽然cik变成了 Overstock 的0001130713、CEO 变成了Marcus Anthony Lemonis,但ipoDate却还挂着老 Bed Bath & Beyond 的"1992-06-01",公司简介也还写着"established in 1971... located in Union, New Jersey"! - 回测后果:如果你根据
/stable/historical-sp500-constituent还原 1999~2017 年的标普 500 成分股并直接按symbol=BBBY拉取日线,你拿到的根本不是当年那家破产的标普 500 成分股,而是后来借用该代码的 Overstock.com 的历史股价!因此,在跨年回测中必须校验cik一致性(老 BBBY 的 SEC CIK 为0000886158,而非 Overstock 的0001130713)。
洗干净数据之后怎么用?基于退市与历史成分股接口的三大高阶回测与风控策略
在把底层接口边界摸透后,我们可以将这四组接口组合成三套直接提升实盘存活率的高阶工程方案:
高阶应用一:Point-in-Time 动态成分股状态机 + 停牌日 volume > 0 强制顺延撮合规则
在构建指数增强(Index Enhancement)或行业中性多因子回测时,严禁使用静态股票列表,而应按以下三步构建动态状态机:
- 第一步(生成每日可投资掩码矩阵
Universe_Mask[t, symbol]):以今日/stable/sp500-constituent为基准,用/stable/historical-sp500-constituent逆向回滚生成每个交易日t的真实在池集合,并按cik合并GOOGL/GOOG、FOXA/FOX、NWSA/NWS双重股权。 - 第二步(调仓信号日与停牌流动性校验):当某成分股在日期
t_remove被移出指数或触发止损信号时,检查当日日线volume。若volume == 0(如2023-03-14的SIVB、2023-05-02的FRC或2023-10-18的ATVI),严禁按当日静态close撮合平仓,持仓必须等待真实流动性恢复或进入退市结算分支。 - 第三步(穿透 OTC 粉单序列或执行无流动性显式告警):对于被 FDIC 接管或申请破产保护的主板股票,优先拼接 FMP 中保留了完整退市行情的粉单代码(如
SIVB → SIVBQ、FRC → FRCB、YELL → YELLQ),顺延至首个volume > 0的复牌日(如SIVBQ的2023-03-28)按当日close($0.40)或vwap($0.3637)执行真实斩仓;若如BBBYQ等粉单序列在数据源中已为空集,则必须显式标记为HALTED_NO_LIQUIDITY并按残值兜底清算,严禁静默忽略。
高阶应用二:退市终值三级自动分类器(并购交割 vs 破产残值 vs 代码更名)
当回测持仓中的股票在日期 t_end 后不再更新常规日线时,通过以下三级流水线自动裁定终值收益率(Terminal Return):
- 第一级(排查正常代码更名,校验
cik后无缝继承):先查/stable/symbol-change是否存在oldSymbol == sym的记录,并核对新旧代码的cik是否为同一实体。若属正常品牌更名(如FB → META),将持仓平滑映射至newSymbol;若发现cik发生跳变(如代码复用),立即切断历史序列。 - 第二级(识别良性并购退市,按最后可成交日收盘价现金结算):检索
/stable/historical-sp500-constituent的reason字段或/stable/mergers-acquisitions-search。若命中acquired/merged关键词,按停牌前最后一根volume > 0的可成交日收盘价(如ATVI在2023-10-12的$94.42、WBA在2025-08-27的$11.98)全额现金平仓。 - 第三级(裁定恶性破产/强制摘牌,计入粉单残值或清零惩罚):若
reason含FDIC/Receivership/Bankruptcy,或转入exchange == "OTC"后摘牌,拉取对应粉单代码的复牌日成交价结算;若查无粉单承接,直接按破产残值清零记账,宁可严苛绝不美化。
高阶应用三:“左侧抄底策略(低 PB / 高股息 / 超跌反转)”的防退市熔断器
幸存者偏差对动量策略(Momentum)影响较小(因为动量买的是不断创新高的赢家),但对价值回归、低市净率(Low P/B)、高股息率(High Dividend Yield)和 RSI 超卖抄底策略具有毁灭性打击——因为在真实市场里,跌得最深、静态 PB 最低、表观股息率飙到 15% 以上的股票,往往不是被错杀的白马,而是即将被踢出指数甚至破产退市的 SIVB、FRC、LUMN 与 DISH!
在实盘运行左侧反转策略时,必须联动 财务报表分类 下的 财务评分接口(/stable/financial-scores)与 关键财务指标接口(/stable/key-metrics-ttm)设置两道硬熔断:
- 非金融股硬门槛(严防掉入破产区与灰色价值陷阱):Altman Z-Score 的经典划分为
> 2.99财务安全区、1.81~2.99灰色警戒区、< 1.81破产高危区。左侧抄底策略必须硬性剔除Altman Z-Score < 1.81的破产高危标的(稳健型组合建议直接要求Z-Score > 2.99),同时要求 Piotroski F-Score ≥ 5,过滤掉依靠举债维持的“价值陷阱”; - 金融/银行股硬门槛(用真实前收重算跌幅):因银行股不适用常规 Altman Z-Score,一旦按相邻可交易日收盘价重算的单日真实跌幅
close[t] / close[t-1] - 1超过-15%(切勿使用仅算日内close - open的changePercent字段)且伴随成交量放大至 20 日均量的5 倍以上(如2023-03-09SIVB放量 3,875 万股真实暴跌-60.41%),风控模块无条件禁止“越跌越买”网格加仓,并在次日开盘执行一票否决清仓。
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}
用真实序列验证上述两个分支:
- 只传主板
SIVB日线(未传otc_bars):调用settle_exit_return(sivb_bars, "2023-01-03", "2023-03-14")会明确返回{"status": "HALTED_NO_LIQUIDITY", "last_tradable_date": "2023-03-09", "last_px": 106.04},提醒回测引擎该股在调仓日已停牌断更,严禁静默放行; - 传入粉单
SIVBQ序列拼接:调用settle_exit_return(sivb_bars, "2023-01-03", "2023-03-14", otc_bars=sivbq_bars)会自动跳过2023-03-10至2023-03-27连续 12 个volume == 0的停牌日,精准定位到首个真实可成交的复牌日actual_exit_date: "2023-03-28"(exit_px: 0.4,真实斩仓收益率-99.82%)。
常见问题(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 的精确历史记录。