我在阿里云函数计算里定时拉美股行情时,最早只取了 last_price。后来发现延长交易时段、夜盘的价格都没进库,财报日历还是手工维护的。问题不是接口调不通,是一开始没看清美股数据分层。
本文以 TickDB 作为统一数据服务参考,把它接入阿里云数据链路。TickDB 提供 REST + WebSocket 双协议接入,覆盖 A股/美股/港股/期货/外汇等多市场数据。重点不是介绍某个接口,而是把美股数据的五层结构摊开,并给出在阿里云上落地的参考架构。
美股数据不是接口问题,是分层问题。
一、美股数据 API 为什么是分层问题?
先看整体结构。从行情到 AI 接入,是一条五层链路:
Layer 5:AI-native 接入(怎么用)
REST / WebSocket / MCP / CLI / Skill
↓
Layer 4:基本面(公司值多少)
财务三表 / 估值 / 行业 / 股东 / 公司档案
↓
Layer 3:公司行动(除权除息、拆股)
分红 / 回购 / 公司行动 / 除权日
↓
Layer 2:事件驱动(什么时候发生什么)
财报日历 / 预估实际 EPS / 营收预估实际
↓
Layer 1:行情(市场发生了什么)
快照 / K线 / 延长交易时段 / 盘口 / 逐笔 / 交易时段
Layer 1 是地基,Layer 2–4 是纵深,Layer 5 是出口。五层缺一层,数据管道就会在某个节点断掉。
五层能力速查
| 层级 | 解决什么问题 | 什么时候需要 | 核心接口/字段 | 典型使用方 |
|---|---|---|---|---|
| Layer 1 | 实时价格 + 延长交易时段 | 盘中信号、延长交易时段跳空 | get_ticker、pre_market_quote、post_market_quote |
盘中策略、看板、Agent |
| Layer 2 | 事件驱动、财报日历 | 财报季前后事件响应 | calendar?market=US&category=report、value_type |
事件策略、风控 |
| Layer 3 | 公司行动、股息除权 | 回测处理除权跳空 | dividends、corp-actions、ex_date |
红利策略、回测 |
| Layer 4 | 基本面季报 | 基本面选股、估值过滤 | financials/latest、OperatingRevenue、NetProfit、EPS |
多因子、估值 |
| Layer 5 | AI-native 接入 | LLM 工作流取数 | REST、WebSocket、MCP、CLI、Skill | AI 应用、Agent |
这张表的正确读法:不是让你五层全接,而是先确认项目在哪一层停。停在 Layer 1 可以,但要知道 Layer 2 的缺口会在财报季暴露。
二、阿里云上的五层数据管道参考架构
如果你已经在阿里云上,我当时的做法不是重新造一套数据层,而是把每一层映射到已有云产品。
| 数据层 | 阿里云组件 | 作用 | 与数据服务的衔接 |
|---|---|---|---|
| Layer 1 行情 | 函数计算 FC、API 网关、消息队列 Kafka、Tablestore、ClickHouse | 定时拉取、实时订阅、快照存储、K线存储 | FC 定时调 REST;Kafka 承接 WebSocket tick;API 网关暴露统一查询 |
| Layer 2 事件 | DataWorks、MaxCompute、函数计算 | 日历拉取、事件过滤、离线回测 | DataWorks 调度日历任务;MaxCompute 做事件回测 |
| Layer 3 公司行动 | OSS、MaxCompute | 原始事件存档、复权计算 | 原始 JSON 存 OSS;MaxCompute 算除权跳空 |
| Layer 4 基本面 | RDS、Tablestore、PAI | 财务字段存储、因子计算 | 财务行存 RDS/Tablestore;PAI 做因子 |
| Layer 5 AI 接入 | 百炼、DashScope、DashVector、MCP | Tool Call、向量化财报、Agent 取数 | 百炼配置工具调用;DashVector 存财报向量 |
| 运维安全 | SLS、云监控、KMS | 日志、告警、密钥管理 | API Key 放 KMS 或环境变量,不硬编码 |
根据阿里云文档,函数计算支持定时触发器和环境变量,适合做行情拉取任务;API 网关适合暴露统一查询入口;OSS 适合存原始 JSON;Tablestore、ClickHouse 适合时序数据;DataWorks、MaxCompute 适合离线回测;百炼、通义千问适合做 Tool Call;DashVector 适合向量化财报。
以下集成代码为部署示意,未在阿里云实际运行,需按你的环境调整。API Key 必须使用环境变量或 KMS,不写进代码。
三、Layer 1:行情——美股从 4AM ET 就开始交易,A 股 9:30 才有第一笔
这是第一层。我一开始只接了 last_price,后来发现延长交易时段的 5.5 小时可交易窗口完全没覆盖。
A 股在集合竞价后 9:30 开盘,没有连续延长交易时段。美股不一样:东部时间 4:00 开始延长交易时段,9:30 正式开盘,16:00 收盘,16:00–20:00 继续交易。一天 16 个小时有价格。
美股实时行情 API 怎么接入?延长交易时段和 A 股有什么区别? 这里展开。
实时快照
在 TickDB 的 ticker 返回里,延长交易时段、盘中、夜盘是三个独立嵌套对象:
# 测试环境:Python 3.11 / Ubuntu 22.04 / 2026-09-17
# API Key 使用环境变量,不硬编码
import os
import requests
API_KEY = os.getenv("TICKDB_API_KEY")
BASE_URL = "https://api.tickdb.ai/v1"
HEADERS = {
"X-API-Key": API_KEY}
try:
resp = requests.get(
f"{BASE_URL}/market/ticker",
params={
"symbols": "US_SAMPLE.US"}, # 替换为你的目标美股代码
headers=HEADERS,
timeout=10,
)
resp.raise_for_status()
data = resp.json()["data"][0]
pre = data.get("pre_market_quote", {
})
post = data.get("post_market_quote", {
})
overnight = data.get("overnight_quote", {
})
# last_done:最新成交价;timestamp:Unix 毫秒
print("延长交易时段 last_done:", pre.get("last_done"))
print("盘后时段 last_done:", post.get("last_done"))
print("夜盘 last_done:", overnight.get("last_done"))
print("时间戳:", data.get("timestamp"))
except requests.RequestException as e:
print(f"请求失败: {e}")
运行后我这边看到的是:pre_market_quote、post_market_quote、overnight_quote 三个对象,每个包含 last_done、timestamp、volume、quote_volume、high、low、prev_close。时间戳是整数 Unix 毫秒。
注意用 .get() 处理缺失。延长交易时段对象在非交易时段可能为空,不要假设每个标的、每个时刻都返回相同对象。
交易时段
调用 GET /v1/market/trading-sessions?market=US,返回 data[0].trading_sessions[],三段数值区间:
| begin_time–end_time | 额外字段 |
|---|---|
400–930 |
trade_session: 1 |
930–1600 |
无 trade_session 字段 |
1600–2000 |
trade_session: 2 |
响应没有返回 pre_market、regular、post_market 的文本枚举。需要在代码里自己做数值映射:
try:
sessions = requests.get(
f"{BASE_URL}/market/trading-sessions",
params={
"market": "US"},
headers=HEADERS,
timeout=10,
).json()["data"][0]["trading_sessions"]
SESSION_MAP = {
1: "pre_market", 2: "post_market", None: "regular"}
for s in sessions:
name = SESSION_MAP.get(s.get("trade_session"))
print(f"{name}: {s['begin_time']} - {s['end_time']}")
except requests.RequestException as e:
print(f"交易时段请求失败: {e}")
K 线与历史行情
get_kline 可以取日线,返回 data.klines。复权支持 none、forward、backward。前复权适合实时信号,后复权适合历史比较分析,混用会产生系统性误差。
try:
klines = requests.get(
f"{BASE_URL}/market/kline",
params={
"symbols": "US_SAMPLE.US",
"interval": "1d",
"adjust": "forward",
"limit": 20,
},
headers=HEADERS,
timeout=10,
).json()["data"]["klines"]
for k in klines[-3:]:
# open/high/low/close/volume/timestamp
print(k["timestamp"], k["open"], k["close"], k["volume"])
except requests.RequestException as e:
print(f"K线请求失败: {e}")
盘口与逐笔
get_order_book 返回多档买卖盘,get_trades 返回逐笔成交。盘口对时序和连接稳定性敏感,用之前必须验证目标市场的实际可用性,不能从接口存在推断全市场覆盖。本次实测为 L1 盘口,非 Level 2 深度。
Layer 1 关键认知:如果只接 last_price,延长交易时段的 5.5 小时可交易窗口完全没覆盖。
本层实测边界:以上字段基于 2026-09-17 对 US_SAMPLE.US 的单次调用。延长交易时段报价是否从 4:00 ET 起持续可取、每只美股是否返回相同对象,待补测。
四、Layer 2:事件驱动——财报日历,全市场事件流才是正确打开方式
这是第二层。第一层加第二层,事件驱动策略的基础设施就有了。
我一开始以为财报日历是传入某个标的就返回该标的的财报日期。实测发现不是。calendar 端点返回的是全市场 report 事件流。
调用:
GET /v1/fundamentals/calendar?market=US&category=report&from=2026-09-17&to=2026-12-31
返回结构是 data.events[]。每个事件包含 category、event_datetime、symbol、event_type、date_type、content、market、counter_name、currency、star,以及 data[]。后者通过 value_type 区分 estimate_eps、estimate_revenue、actual_eps、actual_revenue,数值在 value_raw / value_text。
这反而更强。按标的查只能做单标的事件响应;全市场事件流可以一次拉全市场,为自己的股票池做批量事件过滤。
try:
resp = requests.get(
f"{BASE_URL}/fundamentals/calendar",
params={
"market": "US",
"category": "report",
"from": "2026-09-17",
"to": "2026-12-31",
},
headers=HEADERS,
timeout=10,
)
events = resp.json()["data"]["events"]
# 按自己的股票池过滤,注意替换为你的目标代码
watchlist = {
"US_SAMPLE.US", "US_SAMPLE_2.US"}
my_events = [e for e in events if e["symbol"] in watchlist]
for e in my_events[:5]:
metrics = {
d["value_type"]: d["value_raw"] for d in e.get("data", [])}
print(e["symbol"], e["event_datetime"], metrics.get("estimate_eps"))
except requests.RequestException as e:
print(f"日历请求失败: {e}")
注意:本次拉取短窗口达到单次上限 500 条,未在返回集合中找到目标样本。如果需要某只标的的预计财报发布日,也可以从公司行动端点观察 FinancialReport 与 ReportDate 事件。
Layer 2 关键认知:财报日历不是按标的查的,是全市场事件流。这才是事件驱动策略的正确打开方式。
本层实测边界:以上结构基于 2026-09-17 对美股全市场 report 事件的单次调用。目标标的直属日历样本待补,按标的筛选契约待产品提供。
五、Layer 3:公司行动——不处理除权跳空,回测结果不可信
公司行动是价格非市场跳空的来源。除权除息日,股价会向下跳空,这不是市场下跌,是分红除权。如果回测框架不处理,系统会把除权跳空误判为价格下跌信号。
实测两个端点:
| 端点 | 关键字段 |
|---|---|
/v1/fundamentals/dividends?symbol=US_SAMPLE.US&limit=5 |
data.events[] 的 amount、ex_date、declaration_date、record_date、payment_date、currency、type |
/v1/fundamentals/corp-actions?symbol=US_SAMPLE.US |
data.events[] 的 event_date、action_code、act_type、act_desc、date_type、date_zone |
try:
divs = requests.get(
f"{BASE_URL}/fundamentals/dividends",
params={
"symbol": "US_SAMPLE.US", "limit": 5},
headers=HEADERS,
timeout=10,
).json()["data"]["events"]
for d in divs:
# ex_date:除权日;amount:分红金额;currency:币种;type:分红类型
print(d["ex_date"], d["amount"], d["currency"], d["type"])
except requests.RequestException as e:
print(f"分红请求失败: {e}")
注意:本次事件中未观察到结构化 split_ratio 字段。如果需要拆分比例,需要自己从 act_desc 解析或另找数据源。这是边界。
Layer 3 关键认知:不处理除权跳空,回测在历史上存在公司行动的时间段结果不可信。
本层实测边界:以上字段基于 2026-09-17 对 US_SAMPLE.US 的单次调用。结构化拆分比例字段未验证,不承诺提供。
六、Layer 4:基本面——字段名不是你以为的那个
这是第四层。字段名不对,估值比较就是错的。
我第一次调 financials/latest 时用了 period_type=quarter,返回 HTTP 400、业务码 40001,消息是 period_type contains an unsupported value。后来才发现要用 period_type=q1,q2,q3,q4。
字段名也不是 revenue 和 net_income。实际是 OperatingRevenue、NetProfit、EPS。响应是 data.rows[] 的字段行形式,每行有 field_name、value、fiscal_year、fiscal_period、period_type、period_end、currency、yoy。
try:
resp = requests.get(
f"{BASE_URL}/fundamentals/financials/latest",
params={
"symbol": "US_SAMPLE.US",
"kind": "IS",
"n": 4,
"period_type": "q1,q2,q3,q4",
},
headers=HEADERS,
timeout=10,
)
rows = resp.json()["data"]["rows"]
# 字段行形式,需要按 field_name 过滤
for r in rows:
if r["field_name"] in ("OperatingRevenue", "NetProfit", "EPS"):
print(r["fiscal_year"], r["fiscal_period"], r["field_name"], r["value"])
except requests.RequestException as e:
print(f"财务请求失败: {e}")
P/E 不在利润表里。我另行调用 /v1/fundamentals/valuation/latest?symbol=US_SAMPLE.US,实际路径是 data.metrics.PE.value。
try:
val = requests.get(
f"{BASE_URL}/fundamentals/valuation/latest",
params={
"symbol": "US_SAMPLE.US"},
headers=HEADERS,
timeout=10,
).json()["data"]["metrics"]
print("PE:", val["PE"]["value"])
except requests.RequestException as e:
print(f"估值请求失败: {e}")
所以我后来养成了一个习惯:先调 financials/fields 查字段字典,再写代码。 我后来用 TickDB 的 financials/fields 查字段字典,这一步省掉了很多试错。
Layer 4 关键认知:字段名不对,估值比较就是错的。先查字段字典,再写代码。
本层实测边界:以上字段基于 2026-09-17 对 US_SAMPLE.US 的单次调用。字段字典以官方接口文档为准。
七、美股财务字段的 5 个常见错误
这张表是我踩过的坑。按文档写代码会出错,这是对的写法。
| 你以为的字段 | 实际字段 | 后果 |
|---|---|---|
revenue |
OperatingRevenue |
返回空 |
net_income |
NetProfit |
返回空 |
period_type=quarter |
q1,q2,q3,q4 |
返回 400 |
| P/E 在利润表 | P/E 在 valuation/latest |
找不到 |
trade_session="pre_market" |
返回数值 1/2,需映射 |
判断错误 |
这张表就是收藏的理由。下次写代码前先看一眼。
八、Layer 5:AI-native 接入——只有 REST 的金融数据,LLM 工作流会断连
Agent 工作流中的行情数据,必须在模型推理之前到位。让模型用记忆猜价格,是把分析过程变成幻觉生成过程。
在阿里云上,可以把数据服务包装成百炼或通义千问可调用的工具。MCP 协议层实测成功:JSON-RPC tools/call 的工具名是 get_ticker,参数是 symbols/type,结果外层是 content[].text。
# MCP 工具调用(协议层示意)
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_ticker",
"arguments": {
"symbols": "US_SAMPLE.US", "type": "stock"}
}
}
注意:这是协议层实测,不是 AI 聊天界面中的工具调用证据。我不声称 Claude、Cursor 等 AI 客户端已调用。阿里云百炼接入时,需要配置 X-TickDB-Key,并在工具描述里写清楚参数和返回字段。
阿里云函数计算拉取行情示意
# 测试环境:Python 3.11 / 阿里云函数计算 Python 3.11 运行时 / 2026-09-17
# 以下为部署示意,未在函数计算实际运行,需按你的环境调整
import os
import json
import requests
def handler(event, context):
api_key = os.getenv("TICKDB_API_KEY")
if not api_key:
raise ValueError("缺少 TICKDB_API_KEY")
try:
resp = requests.get(
"https://api.tickdb.ai/v1/market/kline",
params={
"symbols": "US_SAMPLE.US",
"interval": "1d",
"adjust": "forward",
"limit": 100,
},
headers={
"X-API-Key": api_key},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
# 这里可以写入 OSS / Tablestore / ClickHouse
print(json.dumps(data, ensure_ascii=False)[:500])
return {
"ok": True, "rows": len(data.get("data", {
}).get("klines", []))}
except requests.RequestException as e:
print(f"拉取失败: {e}")
return {
"ok": False, "error": str(e)}
Layer 5 关键认知:AI 工作流中的行情数据,必须在模型推理之前到位。
本层实测边界:WebSocket 仅验证握手和订阅确认,推送字段待补测;MCP 仅验证协议层 tools/call,不声称 AI 客户端已调用;CLI 本次未验证。
九、项目阶段路径表
| 你的阶段 | 该看 | 带走 | 最容易踩的坑 |
|---|---|---|---|
| 刚开始建美股数据层 | Layer 1 + 2 + 验收脚本 | 一套能跑的基础数据层代码 | 只接 last_price,漏延长交易时段 |
| 已经在跑策略,想加事件驱动 | Layer 2 + 3 + 字段纠错表 | 财报日历过滤逻辑 + 复权处理 | 以为日历能按标的查 |
| 想做基本面多因子 | Layer 4 + 字段字典 | 正确的字段名和参数 | 用 revenue 取 OperatingRevenue |
| 想让 AI Agent 接入 | Layer 5 + MCP 示例 | 一套 Agent 取数工作流 | 让模型用记忆猜价格 |
十、五层验收清单
| 能力层 | 品类 | 是否需要 | 是否已接入 |
|---|---|---|---|
| Layer 1 | 实时快照 + 延长交易时段 | ||
| Layer 1 | K线(含复权) | ||
| Layer 1 | 盘口深度 | ||
| Layer 1 | 逐笔成交 | ||
| Layer 1 | 交易时段 | ||
| Layer 2 | 财报日历 | ||
| Layer 3 | 分红历史 | ||
| Layer 3 | 公司行动 | ||
| Layer 4 | 财务三表 | ||
| Layer 4 | 估值指标 | ||
| Layer 5 | REST | ||
| Layer 5 | WebSocket | ||
| Layer 5 | MCP | ||
| Layer 5 | CLI / Skill |
把这张表填完,你的美股数据架构图就出来了。
十一、常见问题 FAQ:美股数据 API 在阿里云上怎么接?
Q1:美股数据 API 在阿里云上怎么接?
A:常见做法是函数计算 FC 定时拉取 REST 数据,API 网关暴露统一查询入口,OSS 存原始 JSON,Tablestore 或 ClickHouse 存时序数据,DataWorks + MaxCompute 做离线回测,百炼 + DashVector 做 AI 工作流。API Key 放 KMS 或环境变量。
Q2:财报日历为什么不能按标的查?
A:calendar 端点返回全市场事件流,通过 symbol 字段过滤。这反而更适合做股票池的批量事件过滤。
Q3:字段名为什么和文档不一样?
A:我实测时发现 revenue 返回空,实际字段是 OperatingRevenue;net_income 实际是 NetProfit。建议先调 financials/fields 查字段字典,再写代码。
Q4:WebSocket 推送字段为什么没写?
A:我实测了连接和订阅确认,但在等待窗口未收到 ticker 推送。推送字段、频率、延长交易时段推送行为,等美股交易时段补测后再写。
Q5:MCP 示例能在百炼里直接用吗?
A:MCP 协议层 tools/call 实测成功,但我不声称具体 AI 客户端已调用。实际接入需要配置 X-TickDB-Key,并在百炼工具描述里写清楚参数和返回字段。
Q6:阿里云函数计算拉取行情要注意什么?
A:API Key 不要硬编码,用环境变量或 KMS;设置超时和 try/except;原始 JSON 先落 OSS,再写 Tablestore 或 ClickHouse;用 SLS 记录失败日志,用云监控做告警。
十二、附:五层验收脚本
"""
US-FULL-01 五层验收脚本
运行前配置环境变量 TICKDB_API_KEY
测试环境:Python 3.11 / Ubuntu 22.04 / 2026-09-17
"""
import os
import requests
API_KEY = os.getenv("TICKDB_API_KEY")
BASE_URL = "https://api.tickdb.ai/v1"
HEADERS = {
"X-API-Key": API_KEY}
def check_layer_1():
"""Layer 1: 实时行情 + 延长交易时段"""
try:
data = requests.get(
f"{BASE_URL}/market/ticker",
params={
"symbols": "US_SAMPLE.US"},
headers=HEADERS,
timeout=10,
).json()["data"][0]
has_pre = "pre_market_quote" in data
has_post = "post_market_quote" in data
print(f"Layer 1: {'PASS' if has_pre and has_post else 'FAIL'}")
return has_pre and has_post
except Exception as e:
print(f"Layer 1: FAIL - {e}")
return False
def check_layer_2():
"""Layer 2: 财报日历"""
try:
events = requests.get(
f"{BASE_URL}/fundamentals/calendar",
params={
"market": "US",
"category": "report",
"from": "2026-09-17",
"to": "2026-12-31",
},
headers=HEADERS,
timeout=10,
).json()["data"]["events"]
print(f"Layer 2: {'PASS' if len(events) > 0 else 'FAIL'} ({len(events)} events)")
return len(events) > 0
except Exception as e:
print(f"Layer 2: FAIL - {e}")
return False
def check_layer_3():
"""Layer 3: 公司行动与股息"""
try:
events = requests.get(
f"{BASE_URL}/fundamentals/dividends",
params={
"symbol": "US_SAMPLE.US", "limit": 5},
headers=HEADERS,
timeout=10,
).json()["data"]["events"]
print(f"Layer 3: {'PASS' if len(events) > 0 else 'FAIL'} ({len(events)} events)")
return len(events) > 0
except Exception as e:
print(f"Layer 3: FAIL - {e}")
return False
def check_layer_4():
"""Layer 4: 基本面季报"""
try:
rows = requests.get(
f"{BASE_URL}/fundamentals/financials/latest",
params={
"symbol": "US_SAMPLE.US",
"kind": "IS",
"n": 4,
"period_type": "q1,q2,q3,q4",
},
headers=HEADERS,
timeout=10,
).json()["data"]["rows"]
fields = {
r["field_name"] for r in rows}
ok = "OperatingRevenue" in fields and "NetProfit" in fields
print(f"Layer 4: {'PASS' if ok else 'FAIL'}")
if not ok:
print("提示:检查字段名是否为 OperatingRevenue/NetProfit,参数是否为 q1,q2,q3,q4")
return ok
except Exception as e:
print(f"Layer 4: FAIL - {e}")
print("提示:period_type 不能用 quarter,要用 q1,q2,q3,q4")
return False
def check_layer_5():
"""Layer 5: AI-native 接入(MCP 协议层)"""
print("Layer 5: 需单独配置 X-TickDB-Key 进行 MCP 测试")
print("参考:JSON-RPC tools/call,工具名 get_ticker,参数 symbols/type")
return None
if __name__ == "__main__":
print("=" * 50)
print("美股数据五层验收")
print("=" * 50)
results = {
"Layer 1": check_layer_1(),
"Layer 2": check_layer_2(),
"Layer 3": check_layer_3(),
"Layer 4": check_layer_4(),
"Layer 5": check_layer_5(),
}
print("=" * 50)
passed = sum(1 for v in results.values() if v is True)
print(f"通过: {passed}/4 (Layer 5 需单独测试)")
print("对照五层结构,确认你的项目需要哪几层。")
结尾
美股数据不是接口问题,是分层问题。
接入前先确认项目需要哪几层,漏接事件驱动层是最常见的低成本规避错误。如果你的项目在阿里云上,可以先把 TickDB 作为统一数据源之一做 POC,再用函数计算、API 网关、OSS、Tablestore、DataWorks、百炼逐层补齐。
你现在就能做的一件事:打开自己的策略代码,对照五层验收清单,看看每一层数据是否已接入或明确不需要。然后复制文末验收脚本跑一遍,你会知道自己缺了哪一层。