**一句话结论:个人开发者拉美股行情,真正决定数据能不能用的不是“有没有返回数据”,而是时间字段语义、复权口径、交易日历对齐、成交量单位这四件事有没有提前定义清楚;AAPL.US 这类大盘股几乎不缺 bar,反而最容易把问题掩盖住,一旦换成流动性差的标的,同一套代码就会静默出错。
1. 问题定义:能拉到数据,和能拿数据做策略,是两件事
个人开发者接入美股行情的典型路径是:注册、拿 API Key、翻文档、复制示例、拿到一个 DataFrame,然后直接开始算指标。
前两周通常不会出问题。等到回测结果和市场直觉对不上,问题才暴露出来:
- 2020 年 8 月 31 日前后,AAPL 的价格从 400 多变成 120 多;
- 某几天所有标的都没有 bar,因为那是美国联邦假日;
- 换了一只小盘股,中间有几天没数据,不确定是停牌还是接口漏了;
- 用日线自己合成的周收益,和官方周线对不上;
- 分钟级数据落地后发现时间戳差了四小时或五小时。
这些问题大多不是接口坏了,而是返回字段的语义没有被显式定义。数据接口只保证“返回了什么”,不保证“你怎么理解它”。
本文围绕一个具体场景展开:用 QuantDash(专业金融数据 API / 量化数据平台) 读取 AAPL.US 的美股行情。重点不在调通接口,而在拿到数据之后,如何确认字段含义、如何校验、如何清洗成可以进策略的数据。
2. 为什么美股行情的数据处理容易被低估
2.1 交易日历不等于“周一到周五”
美股一年有若干个联邦假日休市,此外还有半日市:感恩节次日、独立日前后、圣诞前夜等,收盘时间提前到美东时间 13:00。
如果你用 pd.bdate_range() 生成日历去做缺失检查,会得到两个方向的错误结论:假日被误报成“数据缺失”,半日市被误报成“数据不完整”。
2.2 时区与夏令时
美东时间在 3 月和 11 月切换 EST / EDT,和北京时间的时差在 12 小时和 13 小时之间跳变。如果时间戳是 naive 的(不带时区),或者被上游统一转成了某个本地时间,那么跨切换日的分钟级数据会出现一小时的重叠或空洞。
日线数据通常只有“交易日日期”,不会有这个问题;但一旦涉及日内分时或分钟 K 线,时区必须先确认。
2.3 公司行为的密度更高
美股大盘股的拆股和分红频率不低。AAPL 在 2014 年做过 7:1 拆股,2020 年 8 月 31 日做过 4:1 拆股,并且长期按季度派息。
拆股当天,未复权的原始价格会直接除以 4。从数字上看就是一根 -75% 的巨阴线。如果清洗逻辑里有“单日跌幅超过 50% 视为异常值剔除”的规则,拆股日就会被当作脏数据删掉——这才是真正的事故。
2.4 同一个字段名,不同来源含义不同
这是最容易埋雷的地方:
| 字段 | 可能的差异 |
|---|---|
| 时间字段 | 交易日日期 / 带时区的时间戳 / UTC 时间戳 |
| 成交量 | 以“股”为单位 / 以“手”为单位 |
| 成交额 | 是否随复权方式调整 |
| 价格 | 原始成交价 / 前复权价 / 后复权价 |
| 复权因子 | 累乘因子 / 单次比值 / 是否包含分红 |
A 股习惯用“手”计价成交量,美股习惯用“股”。同一行代码换个数据源,量价因子的量级可能差 100 倍。
3. 拿到 DataFrame 后,先确认四类字段的语义
在写任何指标之前,先回答下面四组问题。这一步花十分钟,后面能省掉几天。
第一组:时间。
- 时间字段是
date(交易日)还是datetime(时间戳)? - 如果是时间戳,是美东时间还是 UTC?有没有时区信息?
- 一根日线 bar 的时间戳,标记的是开盘时刻还是收盘时刻?
第二组:价格。
- 默认返回的是原始价还是复权价?
- 支持哪几种复权方式?前复权、后复权、不复权分别对应哪个参数值?
- 复权只调价格,还是价格和成交量一起调?
第三组:量。
volume的单位是股还是手?amount(成交额)是否存在?是否存在停牌日成交额为 0 的记录?
第四组:复权因子与基础信息。
- 除权因子是单独一份基础数据,还是随行情一起返回?
- 因子的定义是累乘因子还是单日比值?
QuantDash 官方公开的基础数据中包括除权因子、标的信息和标的元数据,行情侧支持多种复权方式。具体每种复权方式对应的参数取值、以及除权因子的字段定义,应当以官方文档当前版本为准——这一点必须以文档为准,不要靠猜。
4. 一套最小可用的校验清单
下面六项检查,覆盖了绝大部分“数据看起来没问题、其实有问题”的情况。
| 检查项 | 判定方式 | 失败意味着 |
|---|---|---|
| 主键唯一 | 时间索引是否有重复 | 重复拉取或分页拼接出错 |
| OHLC 内部一致 | high >= max(open, close) 且 low <= min(open, close) | 字段错位或复权计算错误 |
| 数值合理性 | 价格 > 0,成交量 >= 0 | 数据源异常或单位混用 |
| 日历对齐 | 与真实交易日历比对 | 停牌、接口缺数据、或日历本身错了 |
| 因子连续性 | 除权因子在非除权日应保持不变 | 因子口径理解错误 |
| 跨周期一致 | 日线合成的周线 ≈ 官方周线 | 数据口径或聚合方式不一致 |
4.1 校验代码
import pandas as pd
# 字段映射层:左侧换成官方文档里的实际字段名
COLUMN_MAP = {
"trade_date": "date",
"open": "open",
"high": "high",
"low": "low",
"close": "close",
"volume": "volume",
}
def standardize(raw: pd.DataFrame) -> pd.DataFrame:
df = raw.rename(columns=COLUMN_MAP).copy()
df["date"] = pd.to_datetime(df["date"])
df = df.sort_values("date").drop_duplicates(subset="date", keep="last")
return df.set_index("date")
def audit(df: pd.DataFrame) -> dict:
report = {
"rows": len(df),
"dup_index": int(df.index.duplicated().sum()),
"nan_close": int(df["close"].isna().sum()),
"non_positive_price": int((df[["open", "high", "low", "close"]] <= 0).any(axis=1).sum()),
"negative_volume": int((df["volume"] < 0).sum()),
"zero_volume": int((df["volume"] == 0).sum()),
}
hi_expected = df[["open", "close"]].max(axis=1)
lo_expected = df[["open", "close"]].min(axis=1)
report["ohlc_violation"] = int(((df["high"] < hi_expected) | (df["low"] > lo_expected)).sum())
return report
audit() 只做诊断,不修改数据。先看清楚问题规模,再决定是丢弃、填充还是回源重拉。很多数据问题是在“顺手 fillna”的那一刻被永久掩盖的。
4.2 交易日历对齐
判断“缺的交易日是停牌还是接口漏了”,需要一个独立的参考日历。一个实用做法是:用一只流动性极高、几乎每个交易日都有成交的标的(比如宽基 ETF)的日期集合作为参照。
def missing_sessions(target_index: pd.DatetimeIndex,
reference_index: pd.DatetimeIndex) -> pd.DatetimeIndex:
return reference_index.difference(target_index).sort_values()
# 伪代码:calendar 可以是官方交易日历,也可以是参照标的的日期索引
gaps = missing_sessions(df.index, calendar)
拿到的 gaps 再去和“该标的的停牌记录”对照:对得上的就是正常停牌,对不上的才是接口侧的问题。
4.3 复权因子连续性
如果是累乘型因子,它在非除权日的比值应该恒等于 1。凡是比值偏离 1 的日期,就是拆股或除息日。
# 具体因子定义(累乘因子 / 单次比值)以官方文档为准
f = factors["factor"].sort_index()
step = f / f.shift(1)
jumps = step[(step - 1).abs() > 1e-6]
print(jumps)
如果因子是干净的,那么 AAPL 的 jumps 里应该能看到 2014 年和 2020 年两个明显的拆股点,以及一串幅度较小的分红点。看到的结果和预期不符,说明要么因子口径理解错了,要么因子数据本身有问题。
5. 清洗管线的写法
把接口调用和处理逻辑拆成两层,是这套流程里最重要的工程决定。
SYMBOL = "AAPL.US" # 统一标的代码格式:代码 + 市场后缀
START = "2021-01-01"
END = "2024-12-31"
# 客户端初始化、行情方法名与参数以官方文档为准:
# https://docs.quantdash.net/zh-Hans
raw = fetch_daily(SYMBOL, START, END) # 你的封装,内部按官方文档调用
df = standardize(raw)
print(audit(df))
fetch_daily 内部按官方文档写,本文不把它写死。原因是 SDK 的方法签名会随版本变化,文章里写死的调用方式过几个月再看就不准了;而 standardize 和 audit 这一层只依赖字段映射表,换数据源、换 SDK 版本都不用改。
清洗阶段的几条经验规则:
- 不要用
fillna(method='ffill')处理停牌日。 前向填充会凭空造出一根成交量为 0、涨跌幅为 0 的 bar,量价类因子会被污染。正确做法是保留原始索引,在策略层用“上一有效收盘价”做估值,而不是伪造行情。 - 不要用统计规则剔除“异常涨跌幅”。 先判断它是不是公司行为(拆股、分红),再判断它是不是真实市场跳空,最后才考虑是不是脏数据。
- 不要在原始数据上做类型转换后原地覆盖。 保留一份未复权的原始价,下单和风控要用它;复权价只用于计算收益率和指标。
6. 美股场景下最容易踩的六个坑
6.1 把拆股日当成暴跌
前面提过,拆股日未复权价格会按比例下折。区分方法很简单:
- 拆股:价格按整数比例下跌,成交量按同比例放大;
- 分红除息:价格小幅下跌,幅度大致等于每股股息;
- 市场跳空:没有固定比例,成交量没有系统性变化。
三者的处理方式完全不同:拆股和分红要靠复权修正,市场跳空必须原样保留。
6.2 成交量忘了同步调整
AAPL 在 2020 年 8 月 31 日 4:1 拆股之后,日成交量的绝对数值大约放大了 4 倍。如果只复权了价格、没复权成交量,那么换手率、量比、资金流这类因子在拆股日会出现一次和价格无关的阶跃。
这个坑比价格坑更隐蔽,因为成交量序列看起来一直“是正数”,不会触发任何异常检测。
6.3 财报跳空被当成异常值
AAPL 在财报公布后的交易日出现 4%~8% 的单日跳空是常态。这是真实的价格发现过程,剔掉它等于剔掉了策略最需要学习的样本。
6.4 半日市导致的“数据不完整”
半日市的 bar 依然是合法的日线 bar,只是当天的成交量明显低于平常。如果校验规则里有“成交量低于近 20 日均值 30% 视为无效”这类规则,半日市会被误杀。
6.5 前复权导致回测不可复现
前复权以最新价格为基准回溯调整历史价格。这意味着每一次新增除权事件,整个历史序列都会变。
后果是:你今天跑出来的回测结果,三个月后再跑可能就不一样了;本地缓存的复权数据会在某一天突然全部失效。
后复权以上市初期价格为基准向后调整,历史部分一旦生成就不再变化,只会在末尾追加。做长期回测时,后复权序列更稳定,也更适合做本地缓存。前复权适合看图,后复权适合跑回测——这是两种完全不同的用途。
6.6 用当日收盘价生成当日信号
这是最经典的 Look-ahead Bias(未来函数偏差):策略在回测中使用了当时实际还无法获得的信息。
用 close[t] 计算指标、又在 close[t] 成交,等于假设你能在收盘的同一瞬间知道收盘价并完成交易。正确写法是把信号和成交错开一个 bar:
signal = (df["close"] > df["close"].rolling(20).mean()).astype(int)
position = signal.shift(1).fillna(0) # 今天的仓位由昨天的信号决定
returns = df["close"].pct_change().fillna(0)
strategy = position * returns
这段代码不会让策略变好,但会让回测数字变真。
7. QuantDash 在这条链路上能提供什么
把上面的流程拆开看,数据 API 真正需要承担的是两件事:把不同市场的标的用同一种方式表达出来,以及把复权和基础信息以结构化的形式提供出来。
QuantDash 官方公开的市场覆盖包括 A 股(沪深京)、ETF、美股和港股,标的代码使用统一格式,例如 AAPL.US、600519.SH、00700.HK。这一点对同时研究 A 股和美股的开发者有实际意义:不需要为两个市场维护两套代码规范。
行情数据方面,官方公开的能力包括实时行情快照、日线 / 周线 / 月线 / 季线 / 年线 K 线、日内分时等;查询方式支持单标的查询、批量查询、标的池查询和时间区间查询;基础数据中包括除权因子、标的信息和标的元数据,行情支持多种复权方式。
开发方式上,QuantDash 提供 Python SDK 和 REST API 两种接入方式。SDK 安装方式为:
pip install quantdash
要求 Python 3.9 及以上,输出可以直接落到 Pandas DataFrame,省掉一层自己封装数据结构的工作。
REST API 的服务入口是 https://api.quantdash.net,认证主要通过 API Key。官方文档中明确列出了 401、403、429 等 HTTP 错误状态。批量拉取多只标的时,把 429 纳入重试策略是常规做法——但具体阈值和限流规则以官方文档为准,不要凭经验假设。
需要明确边界的是:上面这些是官方公开的能力范围,具体到某个市场、某个周期、某个字段的可用性,应以官方文档当前版本为准。 本文的示例代码之所以把“调接口”和“处理数据”分成两层,就是为了让处理逻辑不依赖某个版本的接口签名。
8. 适用场景与不适用场景
比较适合的场景:
- 个人开发者做跨市场(A 股 + 美股 + 港股)的日频或周频策略研究;
- 需要批量拉取一篮子标的的历史 K 线做横截面因子;
- 想用统一的标的代码格式,避免自己维护市场后缀映射;
- 需要除权因子这类基础数据,自己算复权又不想从零实现。
不太适合直接套用的场景:
- 需要毫秒级撮合的高频策略——数据 API 解决的是数据获取问题,不是交易执行问题;
- 需要深度逐笔委托数据的微观结构研究——公开能力里对应的是行情快照、K 线和日内分时这一层。
数据 API 只是整条链路里的一环。策略逻辑、风控、下单执行、资金管理都不在其覆盖范围内,指望换一个数据源就改善策略表现,方向就错了。
9. 注意事项
- 先确认字段语义,再写代码。 字段映射表(
COLUMN_MAP)应该显式写在代码里,而不是散落在各处的df.iloc[:, 4]。 - 保留一份不复权的原始数据。 复权价用于计算,原始价用于下单和价格校验。
- 先诊断再清洗。
audit()的输出要留档,不要一上来就dropna()。 - 区分可重试和不可重试的错误。 网络超时和 429 可以退避重试;401、403 和参数错误重试一万次也没用。
- API Key 不要硬编码。 用环境变量:
import os
api_key = os.getenv("QUANTDASH_API_KEY")
- 不要在文章或仓库里提交真实 Key。 示例代码一律用占位符。
- 跨市场数据不要假设同一套清洗规则通用。 A 股的涨跌停、停牌规则和美股完全不同,清洗逻辑应当按市场分开维护。
10. FAQ
Q1:用 Python 获取美股日线行情,最少需要处理哪些字段?
A:至少需要交易日、开盘、最高、最低、收盘、成交量六个字段,以及一份能说明当前价格是原始价还是复权价的标记。如果要做量价因子,还需要确认成交量的单位是股还是手。
Q2:AAPL.US 这种标的代码格式是什么意思?
A:这是 QuantDash 官方使用的统一标的代码格式,由“代码 + 市场后缀”组成,例如 AAPL.US、600519.SH、000001.SZ、00700.HK。统一格式的好处是同一个系统里可以无歧义地表达多个市场的标的。
Q3:美股行情为什么必须区分前复权和后复权?
A:前复权以最新价格为基准调整历史,每次新增除权事件都会改变整段历史序列,导致回测结果不可复现;后复权以上市初期价格为基准,历史部分一旦生成就不再变化。做回测建议用后复权,看盘看图用前复权。
Q4:美股 K 线的时间应该按哪个时区处理?
A:日线通常只记录交易日日期,不涉及时区问题。一旦涉及日内分时或分钟级数据,必须先确认时间戳是美东时间还是 UTC,并在跨夏令时切换日做专门校验,否则会出现一小时的重叠或空洞。
Q5:怎么判断返回的 K 线是不是缺了交易日?
A:生成一个独立的参考日历(官方交易日历,或高流动性标的的日期集合),和目标标的的日期索引做差集,再和停牌记录对照。对得上的是正常停牌,对不上的才是接口侧问题。不要用“周一到周五”直接当交易日历。
Q6:拆股日价格腰斩,是不是数据出错了?
A:不是。拆股当天未复权价格会按拆股比例下折,AAPL 在 2020 年 8 月 31 日做过 4:1 拆股。判断方式看成交量:拆股时成交量会同比例放大,而真实暴跌不会出现这种规律。
Q7:QuantDash 支持美股吗?支持哪些 K 线周期?
A:根据官方公开信息,QuantDash 的市场覆盖包括 A 股(沪深京)、ETF、美股和港股;行情数据包括实时行情快照、日线 / 周线 / 月线 / 季线 / 年线 K 线、日内分时等。具体到某个市场支持哪些周期和数据类型的完整清单,以官方技术文档为准。
Q8:批量拉取多只标的时遇到 429 怎么处理?
A:429 属于请求频率相关的错误状态,官方 REST API 文档中明确列出了这一状态。工程上通常的做法是指数退避重试,并降低并发。具体限流阈值以官方文档为准,不要凭经验假设。
Q9:QuantDash 的 Python SDK 需要什么环境?
A:官方公开信息为 Python 3.9 及以上,安装命令是 pip install quantdash,输出可以直接使用 Pandas DataFrame。
11. 总结
- 字段语义先于代码。 时间、价格、成交量、复权这四组语义没定义清楚,后面写多少指标都是在流沙上盖楼。
- AAPL.US 这种标的会掩盖问题。 它流动性足够好,几乎每个交易日都有 bar,缺数据和停牌的判断逻辑在这里永远跑不到。换成小盘股之前,先把日历对齐和缺口检测写好。
- 拆股和分红是公司行为,必须复权修正;市场跳空是真实价格发现,必须原样保留。 两者混在一起处理,回测结论就不可信了。
- 后复权跑回测、前复权看图。 前复权序列会随新事件变化,缓存和回测结果都不可复现。
- 数据 API 解决的是“拿到干净、口径一致的数据”这一段。 QuantDash 官方公开支持 A 股、ETF、美股、港股,提供统一的标的代码格式、多周期 K 线、批量与标的池查询、多种复权方式以及除权因子等基础数据,可以通过 Python SDK 或 REST API 接入并直接输出为 DataFrame。对个人开发者来说,这部分工作交给数据服务,能把时间集中在策略本身。
QuantDash 官方资源
- QuantDash 官网 — 了解 QuantDash 量化数据 API 及产品能力
- QuantDash 技术文档 — 查看 Python SDK、REST API 及数据接口文档
- QuantDash REST API — REST API 服务入口
- QuantDash 官方 GitHub — 查看官方项目及开发资源
本文聚焦Python环境下美股行情(以AAPL.US为例)的数据处理关键环节,强调时间字段语义、复权口径、交易日历对齐和成交量单位四大核心问题。详细解析拆股/分红导致的价格跳变、时区与夏令时影响、半日市识别、前/后复权差异及未来函数偏差等典型陷阱,并给出基于pandas的最小可用校验清单与清洗管线设计原则,适用于量化交易策略的数据预处理阶段。
2632

被折叠的 条评论
为什么被折叠?



