美股财报季盘前盘后跳空怎么盯?FMP 盘前盘后实时行情、逐笔成交与交易所休市日历接入方案(Aftermarket & Market Hours API)

在美股财报季搭建事件驱动监控、财报跳空(Earnings Gap)捕捉或隔夜风险预警系统时,量化与投研团队经常遇到一个棘手的工程盲区:美股大型科技股的季报与年报普遍在美东时间(America/New_York)16:00 常规交易收盘后(After Market Close,简称 AMC)或次日 09:30 开盘前(Before Market Open,简称 BMO)披露,第一波剧烈的价格重估往往在常规交易时段之外完成,并直接体现为次日开盘的隔夜跳空缺口。如果策略管线只接入了常规日线行情或标准分钟级 K 线,甚至直接拿日线返回体里的日内涨跌幅字段当作财报次日收益,不仅无法在盘后第一时间捕获跳空幅度,还会在次日复盘时产生方向性误判。

更进一步,当工程团队把监控服务切入盘前盘后快照流与交易日历调度任务时,还会撞上四类典型的字段级暗坑:第一,在美股盘前尚未开市的非活跃时段调用盘前盘后接口,服务端不会报错或返回空数组,而是原样返回上一交易日盘后收尾驻留的陈旧快照(周末与节假日休市期间同理);第二,盘前盘后逐笔成交与盘口报价接口采用 13 位毫秒级时间戳,与常规报价接口的 10 位秒级时间戳相差 1000 倍;第三,不同标的之间的买卖盘口价差(Bid-Ask Spread)横截面分化超 15 倍,且部分成交记录的股数字段返回空值 null;第四,交易所休市日历接口在不传日期参数时默认仅返回过去约一年的历史记录(不含未来日期),而显式传参时 from 参数为左开区间(date > from),直接传年初首日会漏掉元旦休市。

本文将基于 FMP(Financial Modeling Prep)的 Quote(实时行情接口)与 MarketHours(交易时间接口)两大模块,调取英伟达(NVDA)、苹果(AAPL)、微软(MSFT)、Meta(META)、亚马逊(AMZN)、谷歌(GOOGL)、特斯拉(TSLA)与超威半导体(AMD)共 8 只核心科技股的实盘快照、6 组最新财报窗口以及 7 大交易所休市日历,逐层拆解财报隔夜跳空口径、盘前盘后成交与盘口字段对齐,以及跨市场交易时段与半日市日历的自动化判定方案。


一、 为什么日线 changePercent 与标准分钟线抓不到财报第一跳?6 大科技股财报跳空实盘对账

在复盘美股财报日表现时,许多初级量化脚本会直接拉取 Stock Price and Volume Data(日线完整行情接口,/stable/historical-price-eod/full),读取财报次日(T+1 日)的 change 与 changePercent 字段作为“财报反应收益”。正如我们在 FMP 财务指标与股票行情计算公式大全 与 美股分红复权行情与总回报率计算实战 中所验证的,FMP 日线接口中的 changePercent 严格定义为当日常规交易时段内的实体涨跌幅 (close[T+1] / open[T+1] - 1) * 100,完全不包含从财报披露日收盘价 close[T] 到次日开盘价 open[T+1] 的隔夜跳空缺口 (open[T+1] / close[T] - 1) * 100。

为了直观呈现这一口径差异对财报事件研究的影响,我们调取 Earnings Report(公司财报数据接口,/stable/earnings)与日线完整行情接口,对 6 大科技巨头最近一次已披露财报的 T0 收盘价、T+1 开盘跳空缺口、FMP 日线原生 changePercent(日内实体涨跌幅)以及真实跨日收盘涨跌幅 (close[T+1] / close[T] - 1) * 100 进行逐笔对账。需要说明的是,/stable/earnings 接口仅返回财报日期 date、实际每股收益 epsActual 与一致预期 epsEstimated 等财务结果,不含 BMO/AMC 披露时点字段;据各公司投资者关系(IR)公告惯例,下表所列 6 家科技巨头均在 T0 当日美东 16:00 收盘后(AMC)发布季报,因此我们以 T0 收盘价至 T+1 开盘价的隔夜跳空缺口来反推财报发布后至次日开盘前的综合价格反应:

股票代码财报日与 EPS(实际 / 预期)T0 收盘价T+1 开盘 / 收盘价隔夜跳空缺口(开盘反推)日内涨跌幅(原生字段)真实跨日涨跌与方向比对
NVDA2026-08-26(2.22 / 2.09,+6.22%)209.66222.86 / 227.98+13.20(+6.30%)+5.12(+2.30%)+18.32(+8.74%),跳空占总涨幅 72.1%
META2026-10-21(7.40 / 6.54,+13.15%)697.52676.35 / 648.85-21.17(-3.04%)-27.50(-4.07%)-48.67(-6.98%),跳空与日内同向续跌
GOOGL2026-10-28(2.12 / 3.02,-29.80%)324.89286.88 / 283.82-38.01(-11.70%)-3.06(-1.07%)-41.07(-12.64%),缺口占净跌幅 92.5%
MSFT2026-10-28(4.89 / 4.72,+3.60%)531.01540.22 / 530.14+9.21(+1.73%)-10.08(-1.87%)-0.87(-0.16%),跳空高开、日内回吐转跌
AMZN2026-10-29(1.89 / 1.97,-4.06%)243.79232.54 / 240.35-11.25(-4.61%)+7.81(+3.36%)-3.44(-1.41%),日内正收益、跨日净下跌
AAPL2026-11-02(2.05 / 1.99,+3.02%)338.32325.58 / 331.90-12.74(-3.77%)+6.32(+1.94%)-6.42(-1.90%),日内正收益、跨日净下跌

从上表 6 组实盘财报窗口的拆解中,可以提炼出两条直接决定回测准确度的量化排雷结论:

  1. 6 组样本中有 3 组(MSFT、AMZN、AAPL)出现“隔夜跳空”与“日内实体涨跌”方向相反,其中 2 组(AMZN、AAPL)的日线原生 changePercent 与真实跨日收盘收益符号完全反转:以亚马逊(AMZN,2026-10-29 披露,epsActual 为 1.89 美元 vs epsEstimated 1.97 美元)与苹果(AAPL,2026-11-02 披露)为例,以 T+1 开盘缺口反推,两只股票在财报发布后分别大幅跳空低开 -4.61%(从 243.79 美元跌至 232.54 美元)与 -3.77%(从 338.32 美元跌至 325.58 美元)。然而次日常规交易时段内买盘涌入,推动日内收盘价较开盘价回升 +3.36% 与 +1.94%。如果回测脚本直接读取日线 changePercent(+3.36% 与 +1.94%),就会把这两次真实的跨日下跌(实际较 T0 收盘分别下跌 -1.41% 与 -1.90%)错误标记为“财报后上涨”。而微软(MSFT,2026-10-28 披露)则是隔夜跳空高开 +1.73%(540.22 美元),但日内回吐 -1.87%(收于 530.14 美元),导致日内与跨日收盘收益(-0.16%)同为负值,与隔夜跳空方向相反。
  2. 以谷歌(GOOGL)单样本为例,其开盘跳空缺口金额占全日跨日净跌幅的 92.5%:GOOGL 在 2026-10-28 盘后披露财报后,T+1 全天真实跨日净跌幅为 -41.07 美元(-12.64%),其中次日开盘跳空缺口达 -38.01 美元(-11.70%,占全日净跌幅金额的 92.5%),而 09:30 开盘后的日内实体跌幅仅 -3.06 美元(-1.07%)。

那么,能否直接调用 1 Min Interval Stock Chart(个股分时1分钟线接口,/stable/historical-chart/1min)或 5 Min Interval Stock Chart(个股分时5分钟线接口,/stable/historical-chart/5min)去复盘 16:00 之后的盘后价格轨迹?我们对本次实测的三个财报窗口样本——NVDA(from=2026-11-05&to=2026-11-06,实测共返回 780 根 1 分钟 K 线,其中 2026-11-05 与 2026-11-06 各严格返回 390 根)以及 META(from=2026-10-21&to=2026-10-22,共返回 156 根 5 分钟 K 线,两日各 78 根)、AMZN(from=2026-10-29&to=2026-10-30,共返回 156 根 5 分钟 K 线,两日各 78 根)做全量时间戳扫描后确认:针对本次验证的 /stable/historical-chart/1min 与 /stable/historical-chart/5min 三个样本窗口,1 分钟线每日时间范围严格落在 09:30:00 至 15:59:00,5 分钟线严格落在 09:30:00 至 15:55:00,均只返回常规交易时段分时柱,不含延长交易时段分时柱。因此,要在常规交易时段之外实时捕获最新成交与盘口报盘,必须接入专门的盘前盘后成交与盘口报价接口。


二、 盘前盘后逐笔成交(Aftermarket Trade)与盘口报价(Aftermarket Quote)实盘对拍与四大排雷点

在 FMP 的 /stable/ 实时行情体系中,尽管端点名称以 aftermarket 命名,但它们同时承担了美股盘前(04:00–09:30 ET)与盘后(16:00–20:00 ET)延长交易时段的实时快照分发,并按“最新逐笔成交”与“买卖一档盘口报价”拆分为两组互补端点:

为了完整验证“盘前未开市时的驻留状态”与“04:00 盘前开市后的实时刷新状态”,我们在 2026-11-09 进行了两次对照采样:第一次于 07:55:00 UTC(美东时间周一凌晨 02:55:00,早于 04:00 盘前开市) 调取 Stock Batch Quote(股票批量报价接口,/stable/batch-quote)、/stable/batch-aftermarket-trade 与 /stable/batch-aftermarket-quote,此时接口原样返回上一交易日(2026-11-06 周五)盘后收尾的驻留快照;第二次于 09:03:36 UTC(美东时间周一 04:03:36,盘前开市后 3 分钟) 再次调用,8 只股票均已刷新为周一盘前实时成交与报盘(例如 NVDA 在 04:03:42 盘前成交价更新为 178.78 美元、tradeSize 为 150 股、timestamp 为 1794215022000,盘口更新为 178.59×3 / 178.97×30、盘口 volume 返回带小数的浮点数 2089200.91815,AAPL 盘口 volume 为 35786.35569)。以下为第一次采样(美东 02:55,盘前未开市)拿到的 NVDA 与 AAPL 原始 JSON 响应切片:

// 1. 常规行情批量报价:GET /stable/batch-quote?symbols=NVDA,AAPL (节选核心字段)
[
  {"symbol": "NVDA", "price": 186.64, "previousClose": 181.6, "change": 5.04, "changePercentage": 2.77533, "volume": 180212285, "timestamp": 1793998800},
  {"symbol": "AAPL", "price": 342.37, "previousClose": 344.17, "change": -1.8, "changePercentage": -0.523, "volume": 44703806, "timestamp": 1793998801}
]

// 2. 盘后逐笔成交(02:55 EST 采样,返回周五盘后驻留快照):GET /stable/batch-aftermarket-trade?symbols=NVDA,AAPL
[
  {"symbol": "NVDA", "price": 188.7001, "tradeSize": null, "timestamp": 1794013199000},
  {"symbol": "AAPL", "price": 342.7937, "tradeSize": 10, "timestamp": 1794013194000}
]

// 3. 盘后买卖盘口报价(02:55 EST 采样):GET /stable/batch-aftermarket-quote?symbols=NVDA,AAPL
[
  {"symbol": "NVDA", "bidSize": 4, "bidPrice": 188.67, "askSize": 21, "askPrice": 188.72, "volume": 180212285, "timestamp": 1794013202000},
  {"symbol": "AAPL", "bidSize": 4, "bidPrice": 342.6, "askSize": 4, "askPrice": 342.8, "volume": 44703806, "timestamp": 1794013162000}
]

将 07:55 UTC(美东 02:55)采集的 8 大核心科技股(NVDA、AAPL、MSFT、META、AMZN、GOOGL、TSLA、AMD)三组接口数据进行全量字段横向对拍,结果汇总如下表。表中常规收盘价与成交量取自 /stable/batch-quote 的 price、volume 与 10 位秒级 timestamp;盘后成交价与股数取自 /stable/batch-aftermarket-trade 的 price、tradeSize 与 13 位毫秒级 timestamp;盘后买一/卖一报盘及价差取自 /stable/batch-aftermarket-quote 的 bidPrice、bidSize、askPrice、askSize(盘口中间价 mid = (bidPrice + askPrice) / 2,价差基点按 (askPrice - bidPrice) / mid * 10000 计算):

股票代码常规收盘价(美东事件时间)盘后最新价与股数(事件时间)盘后较收盘涨跌盘后买一 / 卖一报盘(价×量)盘口中间价与价差报价接口成交量核对
NVDA186.64(11-06 16:00:00)188.7001 / 空值(19:59:59)+2.0601(+1.10%)188.67×4 / 188.72×21188.6950(2.65 bps)180,212,285(两接口相同)
AAPL342.37(11-06 16:00:01)342.7937 / 10 股(19:59:54)+0.4237(+0.12%)342.60×4 / 342.80×4342.7000(5.84 bps)44,703,806(两接口相同)
MSFT530.61(11-06 16:00:01)531.4200 / 30 股(19:59:56)+0.8100(+0.15%)531.13×18 / 531.67×3531.4000(10.16 bps)26,956,423(两接口相同)
META610.15(11-06 16:00:01)611.6620 / 10 股(19:59:54)+1.5120(+0.25%)611.18×24 / 612.00×1611.5900(13.41 bps)15,705,472(两接口相同)
AMZN259.30(11-06 16:00:01)259.8500 / 1 股(19:59:59)+0.5500(+0.21%)259.30×1 / 260.03×1259.6650(28.11 bps)42,571,534(两接口相同)
GOOGL284.78(11-06 16:00:01)285.4800 / 5 股(19:59:56)+0.7000(+0.25%)285.36×2 / 285.69×1285.5250(11.56 bps)36,345,938(两接口相同)
TSLA358.22(11-06 16:00:01)359.3000 / 1 股(19:59:57)+1.0800(+0.30%)358.80×1 / 359.95×2359.3750(32.00 bps)36,730,427(两接口相同)
AMD449.04(11-06 16:00:01)448.1000 / 4 股(19:59:53)-0.9400(-0.21%)447.00×211 / 448.80×10447.9000(40.19 bps)45,759,759(两接口相同)

结合上述美东 02:55(盘前未开市)与 04:03(盘前已开市)两组实盘对照,工程团队在解析盘前盘后数据时必须重点防范以下四项字段级暗坑:

  1. 在盘前开市前调用接口不会报错,而是返回上一交易日驻留的陈旧快照(Stale Snapshot):在美东周一凌晨 02:55(盘前 04:00 开市前)实测调用时,/stable/batch-aftermarket-trade 与 /stable/batch-aftermarket-quote 均返回 HTTP 200,其中 8 只股票的盘后成交时间戳落在 2026-11-06 19:59:53 至 19:59:59,盘口报价时间戳落在 19:59:22(AAPL)至 20:00:03(AMD),总体停留在周五盘后收尾的 19:59:22 至 20:00:03 区间(据此驻留机制推测,周末或节假日休市期间调用同样会返回上一交易日快照)。直到美东 04:00 盘前交易启动后(如 04:03:36 采样),时间戳才刷新为周一当日。此外,由于成交与盘口分属两个独立端点,不排除盘前刚开市时“某标的已有新成交、但盘口仍停留在上一交易日”(或反之)的跨日错配情形,因此监控脚本必须同时校验成交事件日期 ext_dt[:10] 与盘口事件日期 quo_dt[:10] 是否均等于当前参考日期 ref_et,并仅在 quo_dt[:10] == ext_dt[:10] 时计算盘口价差。
  2. 时间戳单位相差 1000 倍,且未除 1000 在 Linux 与 Windows 下抛出的异常类型不同:Stock Quote(股票报价接口,/stable/quote)与 /stable/batch-quote 返回 10 位秒级 timestamp(如 NVDA 1793998800),而 4 个 aftermarket 端点全部返回 13 位毫秒级 timestamp(如 1794013199000)。若未执行 ts / 1000 直接传入 Python datetime.fromtimestamp(),在阿里云 Ubuntu Linux(Python 3.10)环境下实测会抛出 ValueError: year 58820 is out of range,而在本机 Windows 环境下则会抛出 OSError: [Errno 22] Invalid argument。
  3. tradeSize 存在 null 空值与 1 股零股成交(Odd-Lot),轮询落库必须保留 None 语义并去重:在周五盘后收尾快照中,NVDA 最新成交价为 188.7001 美元,但 tradeSize 返回 null;而在周一 04:03 盘前快照中,AAPL、MSFT、AMD 的最新成交 tradeSize 均为 1 股。工程解析时应保留 tradeSize: None 的“股数未知”语义(切勿转写为 0 股);同时因为接口返回的是最新驻留快照,轮询时必须按 (symbol, timestamp, price, tradeSize) 复合键去重,严禁把每次轮询响应直接累加。
  4. 盘后快照的 volume 与常规报价完全相等、盘前快照则出现带小数的浮点数,且不同标的盘后价差横截面分化超 15 倍:对比两次采样可以发现,在周五盘后收尾快照中,8 只股票在 /stable/batch-aftermarket-quote 中的 volume 与 /stable/batch-quote 的常规日总成交量 8/8 完全相同(均为整数,如 NVDA 为 180212285);而在周一 04:03:36 盘前采样中,该字段变为远小于常规日总量的数值且带有小数位(如 NVDA 为 2089200.91815、AAPL 为 35786.35569,对应零股成交汇总)。因此工程代码中既不能将 volume 按整型 int 强转解析,也不能将其直接当作独立统计的盘后累计成交量。同时观察盘后买卖价差横截面分布:NVDA 盘后价差仅 0.05 美元(2.65 bps),而 AMZN 为 0.73 美元(28.11 bps)、TSLA 为 1.15 美元(32.00 bps),AMD 则达到 1.80 美元(40.19 bps,买一 447.00 美元 vs 卖一 448.80 美元,为 NVDA 的 15.17 倍)。生产风控中建议同时核对盘口中间价 mid 与价差基点(例如将 spread_bps > 35 设为高价差告警示例阈值),并对零盘口或买卖倒挂做容错保护。

三、 轮询任务怎么避开休市空转与半日市漏判?交易时段(Market Hours)与休市日历(Holidays API)排雷

为了防止定时采集任务在美股冬夏令时切换、法定节假日全天休市以及感恩节次日、平安夜半日市中空转或误判,FMP 的 /stable/ 提供了三个交易时间与日历端点:

  1. 单交易所开闭市状态:Global Exchange Market Hours(全球交易所交易时段接口,/stable/exchange-market-hours)。
  2. 全球 81 家交易所时段总表:All Exchange Market Hours(交易所交易时段接口,/stable/all-exchange-market-hours),单次返回全球 81 家交易所的 openingHour、closingHour、标准时区 timezone 及当前常规时段开闭市布尔值 isMarketOpen。
  3. 交易所法定节假日与半日市日历:Holidays By Exchange(交易所休市日查询接口,/stable/holidays-by-exchange)。

以下是我们在 2026-11-09 补充探针(08:43:56 UTC 与 09:03:36 UTC)中,对 /stable/exchange-market-hours(NASDAQ 与港交所 HKSE)以及 /stable/holidays-by-exchange(显式传入 from=2025-12-31&to=2027-12-31 参数)实测拿到的 JSON 响应切片:

// 1. 交易所交易时段:GET /stable/exchange-market-hours?exchange=NASDAQ 与 HKSE
[
  {"exchange": "NASDAQ", "name": "NASDAQ", "openingHour": "09:30 AM -05:00", "closingHour": "04:00 PM -05:00", "timezone": "America/New_York", "isMarketOpen": false},
  {"exchange": "HKSE", "name": "Hong Kong Stock Exchange", "openingHour": "09:30 AM +08:00", "closingHour": "12:00 PM +08:00", "openingAdditional": "01:00 PM +08:00", "closingAdditional": "04:00 PM +08:00", "timezone": "Asia/Hong_Kong", "isMarketOpen": false}
]

// 2. 纳斯达克休市与半日市日历(显式传参):GET /stable/holidays-by-exchange?exchange=NASDAQ&from=2025-12-31&to=2027-12-31 (节选)
[
  {"exchange": "NASDAQ", "date": "2027-11-26", "name": "Day after Thanksgiving (early close)", "isClosed": null, "adjOpenTime": null, "adjCloseTime": "13:00", "isFullyClosed": false},
  {"exchange": "NASDAQ", "date": "2026-12-25", "name": "Christmas", "isClosed": true, "adjOpenTime": null, "adjCloseTime": null},
  {"exchange": "NASDAQ", "date": "2026-12-24", "name": "Christmas", "isClosed": null, "adjOpenTime": null, "adjCloseTime": "13:00", "isFullyClosed": false},
  {"exchange": "NASDAQ", "date": "2026-11-27", "name": "Thanksgiving Day", "isClosed": null, "adjOpenTime": null, "adjCloseTime": "13:00", "isFullyClosed": false},
  {"exchange": "NASDAQ", "date": "2026-11-26", "name": "Thanksgiving Day", "isClosed": true, "adjOpenTime": null, "adjCloseTime": null},
  {"exchange": "NASDAQ", "date": "2026-01-01", "name": "New Year's Day", "isClosed": true, "adjOpenTime": null, "adjCloseTime": null}
]

我们对 /stable/all-exchange-market-hours 的 81 家交易所时段表,以及 NASDAQ、NYSE、HKSE、LSE、SHH、SHZ、JPX 共 7 家交易所的 /stable/holidays-by-exchange(对比默认无参调用、from=2026-01-01&to=2027-12-31 及左边界前移一天的 from=2025-12-31&to=2027-12-31 三组实测)做全量扫描后,整理出跨市场调度器核心特征对比表:

交易所代码与名称常规 / 早盘时段午休后附加时段标准时区2026–2027 休市条数(from=2025-12-31 vs 默认无参)实测半日市(提前收盘)记录与 name 差异
NASDAQ(纳斯达克)09:30 AM - 04:00 PM无午休(字段缺失)America/New_York23 条(2026-01-01 至 2027-12-24;默认 12 条仅到 2026-09-07)3 条(2026-11-27、12-24 name 与正日子同名;2027-11-26 带 early close)
NYSE(纽交所)09:30 AM - 04:00 PM无午休(字段缺失)America/New_York23 条(2026-01-01 至 2027-12-24;默认 12 条仅到 2026-09-07)3 条(日期与 adjCloseTime: "13:00" 同上,但 name 均带 early close 字样)
HKSE(港交所)09:30 AM - 12:00 PM01:00 PM - 04:00 PMAsia/Hong_Kong17 条(2026-01-01 至 2027-01-01;默认 16 条仅到 2026-10-19)2 条(2026-12-24、2026-12-31,isClosed: null 且 adjCloseTime: "12:00")
LSE(伦敦交易所)08:00 AM - 04:30 PM无午休(字段缺失)Europe/London10 条(2026-01-01 至 2026-12-31;默认 10 条仅到 2026-08-31)2 条(2026-12-24 name 为 Christmas、12-31 为 Half day,adjCloseTime: "12:30")
SHH / SHZ(沪深)09:30 AM - 11:30 AM01:00 PM - 03:00 PMAsia/Shanghai各 19 条(2026-01-01 至 2026-10-07;默认各 19 条)实测 0 条半日市(19 条全部为 isClosed: true)
JPX(东京交易所)09:00 AM - 11:30 AM12:30 PM - 03:30 PMAsia/Tokyo22 条(2026-01-01 至 2027-01-01;默认 21 条仅到 2026-11-03)实测 0 条半日市(22 条全部为 isClosed: true)

围绕上述 7 家交易所的实盘日历数据,编写生产级交易日历守卫时必须处理好四个工程细节:

  1. 默认无参调用仅返回“过去约一年”的历史记录(不含任何未来节假日),且显式传参时 from 为左开区间(date > from):在 2026-11-09 实测中,若不传 from/to 参数,7 家交易所返回的全部是早于查询日的历史休市记录(例如 NASDAQ/NYSE 最晚到 2026-09-07,HKSE 到 2026-10-19,JPX 到 2026-11-03,LSE 到 2026-08-31),不包含任何未来的节假日。更隐蔽的是,当传入 from=2026-01-01&to=2027-12-31 时,由于服务端将 from 按严格大于(左开区间)过滤,全美股、欧洲与亚太市场 2026-01-01 元旦当天的休市记录会被直接过滤掉(NASDAQ/NYSE 变为 22 条、起点跳至 2026-01-19,LSE 变为 9 条、起点跳至 2026-04-03,SHH/SHZ 变为 18 条、起点跳至 2026-01-02)。因此请求目标年度日历时,必须将 from 前移一天设为上一年度末(如 from=2025-12-31&to=2027-12-31),才能拿齐包含 2026-01-01 元旦在内的完整 23 条美股休市记录。
  2. 切勿用 max(holidays) 推导日历覆盖上限,应显式维护经官方日历核验的覆盖区间 cal_cov:节假日接口只返回非交易日与半日市,最后一个节假日之后往往还有正常交易日(例如 2027 年最后一个美股节假日是 2027-12-24,其后的 2027-12-27 至 12-31 均为正常交易日;沪深市场当前列表最后一条为 2026-10-07 国庆假期,其后的 11 月至 12 月工作日同样不在列表中)。如果用 ref_et > max(holidays) 判断,会把 2027-12-27 误判为“日历未覆盖”。正确做法是对照交易所官方日历核验后,显式声明已核验覆盖起止区间——例如本文针对美股 NASDAQ/NYSE 对照纽交所与纳斯达克官方 2026–2027 休市表核验后设为 cal_cov = ("2026-01-01", "2027-12-31")(若用于其他交易所需按表 3 各自核验上限传入):仅当 ref_et 落在 cal_cov 之外时返回 CALENDAR_UNCOVERED;在已核验区间内依次判断周末、全天休市、半日市,其余日期判定为 REGULAR_DAY。
  3. 同一半日市在不同交易所的 name 命名不一,必须以 adjCloseTime 非空作为唯一判据:对比 2026-11-27(感恩节次日)与 2026-12-24(平安夜)两天,NYSE 的 name 字段明确带有 "Day after Thanksgiving (early close)" 与 "Christmas Eve (early close)",而 NASDAQ 对完全相同的两天却返回 "Thanksgiving Day" 与 "Christmas"(与全天休市同名,不含 "early close"),LSE 对 2026-12-24 半日市同样返回 "Christmas"。因此代码中严禁靠匹配 name 字符串识别半日市,必须严格判断 adjCloseTime 非空(且此时 isClosed 为 null、isFullyClosed 为 false)。按 Nasdaq 官方提前收盘技术规范,半日市常规交易在美东 13:00 收盘后,盘后延长交易时段同步前移为 13:00 至 17:00。
  4. 切勿硬解析 openingHour 中的 -05:00 固定偏移,且 isMarketOpen 仅反映常规交易时段(盘前盘后返回 false):/stable/exchange-market-hours 返回的 "openingHour": "09:30 AM -05:00" 中的 -05:00 仅代表查询当下的美东冬令时偏移(夏令时为 -04:00),代码必须只读取 "timezone": "America/New_York" 交由 ZoneInfo 动态换算。同时,我们在美东周一 04:03:36(盘前活跃交易时段)实测调用 /stable/exchange-market-hours?exchange=NASDAQ,确认其返回的 "isMarketOpen" 为 false(与站点接口文档“只覆盖常规时段、不含盘前盘后”定义一致)。此外,全球 81 家交易所中有 7 家亚太交易所(HKSE、SHH、SHZ、JPX、SET、JKT、HOSE)带有午休分段字段 openingAdditional 与 closingAdditional,跨市场调度时需读取 closingAdditional 作为全天收盘时刻。

四、 Python 实战:50 行代码实现休市/半日市五态识别、陈旧快照检测与盘后价差对账

下面我们将上述全部排雷规则封装为一段 50 行 的 Python 脚本。脚本顺序发起 5 次 HTTP 请求,实现五项核心能力:① ref_et 默认动态取美东当前日期,并将 from 自动设为上一年末 {y-1}-12-31 以防左开区间漏掉元旦;② 基于显式核验区间 cal_cov 精准判定 CALENDAR_UNCOVERED、WEEKEND、FULL_HOLIDAY、EARLY_CLOSE(13:00) 与 REGULAR_DAY 五种状态;③ 筛选大于等于参考日期的下一次半日市 next_early_close;④ 同时校验成交时间戳 ext_dt 与盘口时间戳 quo_dt 的美东日期,拦截跨日陈旧快照与“新成交配旧盘口”的跨日错配;⑤ 保留 tradeSize: None 语义并对单股缺失、零盘口或倒挂盘口做容错保护。

运行环境说明:Python 3.9+ 标准库 zoneinfo 在 Linux 与 macOS 下直接读取操作系统内置的 IANA 时区数据(零第三方依赖);在 Windows 环境下,因系统未内置 IANA 时区库,直接调用 ZoneInfo("America/New_York") 会抛出 ZoneInfoNotFoundError,运行前需执行一次 pip install tzdata。

# Linux / macOS 终端设置环境变量
export FMP_KEY="your_actual_key_here"

# Windows PowerShell 安装时区数据包并设置环境变量
pip install tzdata
$env:FMP_KEY="your_actual_key_here"

完整可运行的 50 行 Python 脚本如下:

import os, urllib.request, json
from datetime import datetime
from zoneinfo import ZoneInfo

BASE, KEY = "https://quote-fmp.com/stable", os.environ["FMP_KEY"]
NY_TZ = ZoneInfo("America/New_York")

def get_json(path: str):
    req = urllib.request.Request(f"{BASE}/{path}&apikey={KEY}" if "?" in path else f"{BASE}/{path}?apikey={KEY}", headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=15) as r:
        return json.loads(r.read().decode("utf-8"))

def audit_session_and_aftermarket(symbols: list[str], exchange: str = "NASDAQ", ref_et: str = "", cal_cov: tuple[str, str] = ("2026-01-01", "2027-12-31")):
    ref_et = ref_et or datetime.now(NY_TZ).strftime("%Y-%m-%d")
    y, mkt = int(ref_et[:4]), get_json(f"exchange-market-hours?exchange={exchange}")[0]
    holidays = {h["date"]: h for h in get_json(f"holidays-by-exchange?exchange={exchange}&from={y-1}-12-31&to={y+1}-12-31")}
    h_day, wd = holidays.get(ref_et, {}), datetime.strptime(ref_et, "%Y-%m-%d").weekday()
    today_status = "CALENDAR_UNCOVERED" if not (cal_cov[0] <= ref_et <= cal_cov[1]) else (
        "WEEKEND" if wd >= 5 else ("FULL_HOLIDAY" if h_day.get("isClosed") else (f"EARLY_CLOSE({h_day['adjCloseTime']})" if h_day.get("adjCloseTime") else "REGULAR_DAY"))
    )
    next_early = next((f"{d}({v['adjCloseTime']})" for d, v in sorted(holidays.items()) if d >= ref_et and v.get("adjCloseTime")), None)
    syms = ",".join(symbols)
    reg_map = {x["symbol"]: x for x in get_json(f"batch-quote?symbols={syms}") if isinstance(x, dict) and "symbol" in x}
    trd_map = {x["symbol"]: x for x in get_json(f"batch-aftermarket-trade?symbols={syms}") if isinstance(x, dict) and "symbol" in x}
    quo_map = {x["symbol"]: x for x in get_json(f"batch-aftermarket-quote?symbols={syms}") if isinstance(x, dict) and "symbol" in x}
    rows = []
    for s in symbols:
        reg, trd, quo = reg_map.get(s), trd_map.get(s), quo_map.get(s)
        if not (reg and trd and quo) or not reg.get("price") or not trd.get("price") or not reg.get("timestamp") or not trd.get("timestamp"):
            rows.append({"symbol": s, "today_status": today_status, "status": "MISSING_DATA"})
            continue
        reg_dt = datetime.fromtimestamp(reg["timestamp"], tz=NY_TZ).strftime("%Y-%m-%d %H:%M:%S")
        ext_dt = datetime.fromtimestamp(trd["timestamp"] / 1000, tz=NY_TZ).strftime("%Y-%m-%d %H:%M:%S")
        quo_dt = datetime.fromtimestamp(quo["timestamp"] / 1000, tz=NY_TZ).strftime("%Y-%m-%d %H:%M:%S") if quo.get("timestamp") else ""
        bid, ask = quo.get("bidPrice") or 0, quo.get("askPrice") or 0
        mid = round((bid + ask) / 2, 4) if (bid > 0 and ask >= bid and quo_dt[:10] == ext_dt[:10]) else None
        rows.append({
            "symbol": s, "is_open": mkt["isMarketOpen"], "today_status": today_status,
            "reg_close": reg["price"], "reg_time_et": reg_dt,
            "ext_price": trd["price"], "ext_size": trd.get("tradeSize"), "ext_time_et": ext_dt,
            "is_stale": (ext_dt[:10] < ref_et) or (quo_dt[:10] < ref_et), "ext_chg_pct": round((trd["price"] / reg["price"] - 1) * 100, 2),
            "bid_ask": f"{bid}x{quo.get('bidSize')} / {ask}x{quo.get('askSize')}",
            "spread_bps": round((ask - bid) / mid * 10000, 2) if mid else None,
            "next_early_close": next_early,
        })
    return rows

if __name__ == "__main__":
    for item in audit_session_and_aftermarket(["NVDA", "AAPL", "MSFT", "META", "AMZN", "GOOGL", "TSLA", "AMD"]):
        print(item)

基于第一次采样冻结快照(2026-11-09 美东凌晨 02:55 盘前未开市,ref_et="2026-11-09")离线重放上述脚本,输出的 8 只股票结构化对账结果如下(today_status 识别为 REGULAR_DAY,因盘前尚未开市、接口返回周五盘后快照故 is_stale 全部标记为 True,下一次半日市 next_early_close 指向 2026-11-27(13:00);若以第二次采样即美东 04:03:36 盘前快照重放,NVDA 则输出 is_stale: False、ext_price: 178.78、ext_time_et: '2026-11-09 04:03:42'、spread_bps: 21.26):

{'symbol': 'NVDA', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 186.64, 'reg_time_et': '2026-11-06 16:00:00', 'ext_price': 188.7001, 'ext_size': None, 'ext_time_et': '2026-11-06 19:59:59', 'is_stale': True, 'ext_chg_pct': 1.1, 'bid_ask': '188.67x4 / 188.72x21', 'spread_bps': 2.65, 'next_early_close': '2026-11-27(13:00)'}
{'symbol': 'AAPL', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 342.37, 'reg_time_et': '2026-11-06 16:00:01', 'ext_price': 342.7937, 'ext_size': 10, 'ext_time_et': '2026-11-06 19:59:54', 'is_stale': True, 'ext_chg_pct': 0.12, 'bid_ask': '342.6x4 / 342.8x4', 'spread_bps': 5.84, 'next_early_close': '2026-11-27(13:00)'}
{'symbol': 'MSFT', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 530.61, 'reg_time_et': '2026-11-06 16:00:01', 'ext_price': 531.42, 'ext_size': 30, 'ext_time_et': '2026-11-06 19:59:56', 'is_stale': True, 'ext_chg_pct': 0.15, 'bid_ask': '531.13x18 / 531.67x3', 'spread_bps': 10.16, 'next_early_close': '2026-11-27(13:00)'}
{'symbol': 'META', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 610.15, 'reg_time_et': '2026-11-06 16:00:01', 'ext_price': 611.662, 'ext_size': 10, 'ext_time_et': '2026-11-06 19:59:54', 'is_stale': True, 'ext_chg_pct': 0.25, 'bid_ask': '611.18x24 / 612x1', 'spread_bps': 13.41, 'next_early_close': '2026-11-27(13:00)'}
{'symbol': 'AMZN', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 259.3, 'reg_time_et': '2026-11-06 16:00:01', 'ext_price': 259.85, 'ext_size': 1, 'ext_time_et': '2026-11-06 19:59:59', 'is_stale': True, 'ext_chg_pct': 0.21, 'bid_ask': '259.3x1 / 260.03x1', 'spread_bps': 28.11, 'next_early_close': '2026-11-27(13:00)'}
{'symbol': 'GOOGL', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 284.78, 'reg_time_et': '2026-11-06 16:00:01', 'ext_price': 285.48, 'ext_size': 5, 'ext_time_et': '2026-11-06 19:59:56', 'is_stale': True, 'ext_chg_pct': 0.25, 'bid_ask': '285.36x2 / 285.69x1', 'spread_bps': 11.56, 'next_early_close': '2026-11-27(13:00)'}
{'symbol': 'TSLA', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 358.22, 'reg_time_et': '2026-11-06 16:00:01', 'ext_price': 359.3, 'ext_size': 1, 'ext_time_et': '2026-11-06 19:59:57', 'is_stale': True, 'ext_chg_pct': 0.3, 'bid_ask': '358.8x1 / 359.95x2', 'spread_bps': 32.0, 'next_early_close': '2026-11-27(13:00)'}
{'symbol': 'AMD', 'is_open': False, 'today_status': 'REGULAR_DAY', 'reg_close': 449.04, 'reg_time_et': '2026-11-06 16:00:01', 'ext_price': 448.1, 'ext_size': 4, 'ext_time_et': '2026-11-06 19:59:53', 'is_stale': True, 'ext_chg_pct': -0.21, 'bid_ask': '447x211 / 448.8x10', 'spread_bps': 40.19, 'next_early_close': '2026-11-27(13:00)'}

五、 常见问题(FAQ)

1. /stable/aftermarket-trade 是否支持传入 date 或 from/to 参数回溯历史某一天的盘后逐笔记录?

不支持。我们在实盘探针中分别测试了 GET /stable/aftermarket-trade?symbol=NVDA&date=2026-10-28 与 GET /stable/aftermarket-trade?symbol=NVDA&from=2026-10-28&to=2026-10-29,接口均返回 HTTP 200 及完全相同的最新一条驻留快照(price: 188.7001, timestamp: 1794013199000),说明服务端会静默忽略历史日期参数。若策略需要积累财报夜 16:00 之后的盘后价格与盘口演变序列,标准做法是每日盘前通过 Earnings Calendar(财报日历接口,/stable/earnings-calendar)筛选出当日发财报的标的池,并在盘后窗口内使用 /stable/batch-aftermarket-trade 与 /stable/batch-aftermarket-quote 定时采样并按时间戳去重落库(结合财报电话会文本提取管理层指引的流程,可参考 从零搭建 AI 美股投研 Agent:FMP 财报电话会纪要 API 实战)。

2. 在采集盘后快照时,如何处理 20:00 整点之后的尾部时间戳与半日市提前收盘?

美股正式盘后交易时段在常规交易日截至美东时间 20:00,在感恩节次日(2026-11-27)或平安夜(2026-12-24)等 adjCloseTime: "13:00" 的半日市则按 Nasdaq 官方规范前移至 13:00 至 17:00。鉴于实测 /stable/batch-aftermarket-quote 存在跨整点几秒的尾部报价记录(如 NVDA 与 GOOGL 时间戳为 20:00:02、AMD 为 20:00:03),工程采集任务可在正式盘后截止时刻之后配置数分钟的程序采集宽限期(例如常规日等待至 20:05:00、半日市等待至 17:05:00),并对正式截止边界之后收到的尾部快照单独打标归档,从而在不混淆交易所正式时段定义的前提下完整保留尾盘最后一条有效盘口。若需获取测试凭证并验证上述批量盘前盘后行情与休市日历接口,可参阅 快速接入指南 与 定价与会员套餐。

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