股票 API 怎么用?报价展示与历史研究,为什么要分开处理

TickDB Research ·

摘要:面向正在交付股票行情页和历史导出功能的金融产品经理,本文拆解报价快照、K 线与研究文件的区别,解释相同价格为何可能表达不同信息,以及行情时间、取数日期和研究截止日各自解决什么问题。结合 TickDB 保存的真实响应和可运行 Python 示例,生成带时间的静态报价卡片与按日期筛选的研究 CSV,帮助产品和技术同事用明确规则检查交付。

假设你正在做一个股票详情页:上方显示报价,下方支持导出历史数据。接口已经接通,页面有价格,CSV 也能下载。但用户还会问:这个价格截至什么时候?导出的最后一天包含哪些信息?为什么页面价格与研究表最后一行不同?这些问题没有讲清,两个功能即使都能运行,用户仍可能用错数据。

处理这类需求,可以先分清两类输入:报价快照用于展示查询取得的行情状态,历史 K 线用于按周期组织观察和研究记录。本例使用 TickDB 的报价与历史 K 线查询结果,再由应用分别生成卡片和研究表。两条路径围绕同一只股票展开,各自保留用途所需的时间和口径。

一、先分清:报价、K 线和 CSV 分别是什么

报价快照:查询取得的行情状态

行情页上的“最新价”,通常需要与证券和行情时间一起理解。比如同样显示 880,旁边写“10 月 30 日 15:00:02”,与没有时间的 880,读者能作出的判断就不同。

报价快照也可能包含开盘价、最高价、成交量等统计字段,不能简单理解为“只有一个价格”。但一次快照不会自动提供一段完整的逐周期历史序列。使用 TickDB 时,可以从报价响应中的 symbol、last_price 和 timestamp 识别证券、最新成交价和返回的数据时间;其他字段的含义与返回条件见行情快照文档。

K 线:指定周期内的统计记录

一根交易价格 K 线,把某个周期内的成交价格整理为开盘、最高、最低、收盘等信息。把多根 K 线按时间排列,才得到对应周期的历史序列。

因此,使用历史数据之前,至少要知道:是哪只股票、什么周期、采用什么复权口径,以及各条记录对应哪个时间区间。日线与分钟线表达的区间不同,不能只按字段都叫 close 就互相替代。

CSV:应用整理后的文件

CSV 是保存表格的一种格式。把最新价写入 CSV,保存下来的仍然是一条报价;把经过筛选的日线写进去,得到的是按条件整理的历史记录。

文件格式决定怎样保存,数据含义决定怎样使用。HTML、JSON、CSV 可以承载结果,但不能替代对价格、周期和时间的解释。

image.png

图 1:AI 解释插图。报价展示与历史研究按用途分别处理。TickDB 提供本例的行情输入,卡片排版与 CSV 导出由应用实现。

二、为什么分开处理?两个容易混淆的底层问题

价格相同,表达的信息仍可能不同

用一个教学构造来说明。假设 10:00:00 至 10:01:00 这个区间内只有三笔成交:

成交时间成交价
10:00:05100
10:00:20102
10:00:50101

这一分钟结束后,开盘价是 100,最高价是 102,最低价是 100,收盘价是 101。最后一笔成交价也恰好是 101。

这两个 101 数字相同,一个描述最后一笔成交,一个描述这个指定区间的收盘。只保留 101,无法知道这一分钟曾到过 102,也无法还原区间内的成交过程。即便保留开高低收,也没有保留全部逐笔路径。

所以,行情展示可以围绕最新成交价及其时间展开;需要比较多个周期的研究,则应加载相应历史序列,并保持周期和价格口径一致。具体任务决定保留哪些信息。

记录标注的时间,与信息可用的时间要分开理解

继续看上面的三笔成交。如果程序在 10:00:30 做判断,10:00:50 那笔 101 还没有发生。当时已发生的最后一笔成交是 102,这一分钟最终以 101 收盘,尚不能被当作已知事实。

image.png

图 2:AI 解释插图,数值为教学构造,非 TickDB 实测。假设程序在判断前已收到前两笔成交;真实系统还需考虑数据发布与接收的延迟。

历史文件里,一根 K 线可能用区间起点标注,也可能采用其他时间标签。读取时需要确认时间定义,不能看到标签就把整根 K 线的最终值放进该时刻的判断。QuantConnect 的时间建模文档也区分了周期起止与数据交付时间,以避免提前使用尚未形成的信息。时间建模说明。

对股票日线,还要留意复权。复权用于处理公司行动对历史价格比较的影响;以后重新查询时,历史复权数值可能变化。TickDB 的 K 线说明指出,末根 K 线可能仍在形成,复权历史可能随公司行动或数据更新而调整。K 线使用说明。

这里有三个不同的问题:记录属于哪一天?周期是否已经结束?现在下载到的历史版本,当时是否可取得?仅靠筛选日期,只处理了第一个问题。

三、怎样落实?先给两个功能各写一句使用规则

对于这个股票详情页,可以把需求写成:

报价展示:显示指定股票的一条保存报价,同时展示行情时间、取数日期与更新状态。

研究导出:导出指定股票、指定周期与复权口径的记录,按约定时区保留截止日及之前的数据,并记录实际输出范围。

行情时间回答“价格对应哪个时点”,取数日期回答“什么时候取得这份输入”,研究截止日回答“这次输出保留到哪里”。三个条件分别参与判断,不应相互代替。

例如,11 月 1 日取得一份行情响应,其中的报价时间仍可能是 10 月 30 日。页面如果把请求当天直接写成行情日期,用户就可能把保存的收盘后快照理解为刚发生的新成交。

对于研究导出,产品经理也需要明确“截止到某一天”是按什么日期规则处理。本例只处理 A 股日线,使用北京时间的日历日期筛选;跨市场或分钟线任务应重新定义周期和交易日期。

四、用 TickDB 保存的真实输入生成两份结果

本例的原始响应取得于 2026 年 11 月 1 日,证券是寒武纪 688256.SH。其中:

  • 报价 last_price 原值为 880,数据时间换算为北京时间是 2026-10-30 15:00:02。
  • 历史输入包含 10 月 26 日至 30 日的五条日线,周期为 1d,复权方式为 forward,即前复权。

示例没有重新取当前行情。我们用这组保存输入,分别制作静态报价卡片和日期筛选导出。

报价卡片保留 880,并同时保留数据时间和 11 月 1 日的取数日期;卡片标明它是保存快照,不持续更新。

研究表另设演示截止日 2026 年 10 月 29 日。程序将五条输入中的 10 月 30 日记录排除,留下四行:

日期前复权收盘价原值
2026-10-26968
2026-10-27912.98
2026-10-28958
2026-10-29913

因此,报价卡片中是 880,研究表最后一行是 913。本例可以沿日期条件追查差异:两份输出采用了不同的记录范围。实际项目遇到价格差异,还应核对周期、复权和更新状态,不能仅凭数值判断对错。

这里使用 TickDB 的两类查询,是因为同一个功能页同时需要报价状态和历史序列。报价响应保留证券与行情时间,K 线响应保留证券、周期、复权和逐条记录,便于应用按对应条件检查与整理。接入入口见股票 API 专题页。

五、用 Python 复现,再核对结果

文末脚本使用 Python 3.9+ 标准库。默认内置本例保存响应的必要字段,不需要密钥和网络即可复现本地处理。将代码保存为 prepare_stock_outputs.py,运行:

python3 prepare_stock_outputs.py --out stock_outputs

它会生成四个文件:

文件用途
quote_card.html浏览器打开静态报价卡片
quote_card.json保留卡片使用的结构化记录
research.csv保存筛选后的四条日线记录,含证券、时间戳、周期、复权和收盘价
processing.json保存截止日、输入条数、保留范围和未检查事项

2026 年 11 月 3 日保存的运行结果为:

QUOTE 688256.SH price=880 data_time=2026-10-30T15:00:02+08:00
RESEARCH input=5 kept=4 excluded=1 cutoff=2026-10-29
LAST date=2026-10-29 close=913 adjust=forward
FILES quote_card.html quote_card.json research.csv processing.json
BOUNDARY saved-data processing; no network requests; calendar/finalization/PIT not checked

可以先检查卡片上的行情时间,再核对 CSV 的全部四行及处理说明:输入五行、保留四行、排除一行,所有输出日期均不晚于截止日。最后一行是 10 月 29 日,只是其中一项检查。

脚本按原始字符串写入价格,CSV 使用带 BOM 的 UTF-8 编码。Excel、WPS 可能自动改变显示格式,复查时保留源文件。重复运行应改用新输出目录,例如 --out stock_outputs_run2,程序会拒绝写入已有目录。

使用自己的保存响应

取得自己的 TickDB 报价和日线业务响应后,将完整 JSON 分别保存为 ticker.body.json 与 kline.body.json,保留返回结构。对应本文样本的命令如下:

python3 prepare_stock_outputs.py --ticker ticker.body.json --kline kline.body.json --retrieved-on 2026-11-01 --cutoff 2026-10-29 --symbol 688256.SH --adjust forward --out stock_outputs_from_files

换用新数据时,按实际输入调整证券、取数日期、截止日和复权参数。这个处理器要求匹配的报价与 A 股日线结构,并固定使用北京时间日期;并非改几个参数就能直接覆盖其他市场和所有周期。

写文件前,程序核对业务状态、证券、日线周期、复权设置、重复日期和价格。已有运行记录显示,证券不匹配、复权不匹配、截止日前无记录,以及输出目录已存在时,程序均停止。

六、进入真实产品或研究之前,还要补哪一步

这份示例完成了静态展示和日期筛选。接入真实功能时,下一步由用途决定:

  • 行情页需要持续更新,就进一步实现更新机制、记录接收时间,并设计断线与过期提示;静态 HTML 本身不会订阅行情。
  • 历史数据准备进入回测,就继续核对交易日覆盖、周期是否完成,以及复权历史版本的可用性。把后来下载的数据筛到过去某天,没有恢复当时的数据版本。

可以先用自己准备支持的一只股票,写清“展示哪个时点的报价、导出哪个口径和范围”,再交回卡片、研究表、处理条件与原始输入。用户看到的价格,以及技术同事保存的记录,就有了可以共同核对的依据。

数据来源:TickDB,原响应取得于 2026 年 11 月 1 日;处理程序于 2026 年 11 月 3 日本地运行。文中三笔成交为教学构造,证券与历史价格用于数据整理演示,不构成投资建议。

附录:完整 Python 代码

import argparse
import csv
import html
import json
from datetime import date, datetime, timedelta, timezone
from decimal import Decimal
from pathlib import Path

SHANGHAI = timezone(timedelta(hours=8), name="Asia/Shanghai")
# Fields extracted from TickDB responses retrieved on 2026-11-01.
SAVED_QUOTE = {"symbol": "688256.SH", "last_price": "880", "timestamp": 1793343602000}
SAVED_BARS = [
    {"time": 1792944000000, "close": "968"},
    {"time": 1793030400000, "close": "912.98"},
    {"time": 1793116800000, "close": "958"},
    {"time": 1793203200000, "close": "913"},
    {"time": 1793289600000, "close": "880"},
]


def local_time(value):
    if type(value) is not int or value < 0:
        raise ValueError("Expected a nonnegative integer Unix timestamp in milliseconds")
    return datetime.fromtimestamp(value / 1000, SHANGHAI)


def price(value):
    if not isinstance(value, str):
        raise ValueError("Expected an original price string")
    number = Decimal(value)
    if not number.is_finite() or number <= 0:
        raise ValueError("Expected a finite positive price")
    return value  # Preserve the original string.


def main():
    p = argparse.ArgumentParser(description="Prepare saved China A-share quote/daily outputs")
    p.add_argument("--ticker", type=Path)
    p.add_argument("--kline", type=Path)
    p.add_argument("--retrieved-on", help="Actual retrieval date, YYYY-MM-DD")
    p.add_argument("--cutoff", help="Inclusive Asia/Shanghai calendar date")
    p.add_argument("--symbol", default="688256.SH")
    p.add_argument("--adjust", choices=["none", "forward", "backward"], default="forward")
    p.add_argument("--out", type=Path, default=Path("stock_outputs"))
    a = p.parse_args()
    custom = a.ticker is not None or a.kline is not None
    if custom:
        if not all([a.ticker, a.kline, a.retrieved_on, a.cutoff]):
            raise ValueError("Custom mode requires --ticker, --kline, --retrieved-on and --cutoff")
        ticker = json.loads(a.ticker.read_text(encoding="utf-8-sig"))
        kline = json.loads(a.kline.read_text(encoding="utf-8-sig"))
        if ticker.get("code") != 0 or kline.get("code") != 0:
            raise ValueError("API response was not successful; inspect code/message")
        matches = [q for q in ticker["data"] if q.get("symbol") == a.symbol]
        if len(matches) != 1:
            raise ValueError("Expected exactly one quote for the requested symbol")
        quote, data = matches[0], kline["data"]
        retrieved_on, source_mode = a.retrieved_on, "saved_response_files"
    else:
        if a.retrieved_on:
            raise ValueError("Embedded sample retrieval date is fixed at 2026-11-01")
        quote = SAVED_QUOTE
        data = {"symbol": "688256.SH", "interval": "1d", "adjust": "forward", "klines": SAVED_BARS}
        retrieved_on, source_mode = "2026-11-01", "embedded_saved_field_extract"
    date.fromisoformat(retrieved_on)
    cutoff = date.fromisoformat(a.cutoff or "2026-10-29")
    if (quote["symbol"], data["symbol"], data["interval"], data["adjust"]) != (a.symbol, a.symbol, "1d", a.adjust):
        raise ValueError("Symbol, daily interval or adjustment does not match settings")
    quote_price = price(quote["last_price"])
    quote_time = local_time(quote["timestamp"]).isoformat()
    rows = sorted(data["klines"], key=lambda row: local_time(row["time"]))
    if not rows:
        raise ValueError("No daily bars")
    stamps = [row["time"] for row in rows]
    dates = [local_time(ts).date() for ts in stamps]
    if len(set(stamps)) != len(stamps) or len(set(dates)) != len(dates):
        raise ValueError("Duplicate daily timestamps or dates")
    for row in rows:
        price(row["close"])
    kept = [row for row in rows if local_time(row["time"]).date() <= cutoff]
    if not kept:
        raise ValueError("No daily rows at or before cutoff")
    card = dict(symbol=a.symbol, price_original=quote_price,
                data_time=quote_time, retrieved_on=retrieved_on,
                view="saved snapshot; no continuous updates")
    meta = dict(symbol=a.symbol, interval="1d", adjust=a.adjust,
                date_basis="Asia/Shanghai calendar date", cutoff=cutoff.isoformat(),
                retrieved_on=retrieved_on, source_mode=source_mode,
                input_rows=len(rows), kept_rows=len(kept), excluded_rows=len(rows)-len(kept),
                first_date=local_time(kept[0]["time"]).date().isoformat(),
                last_date=local_time(kept[-1]["time"]).date().isoformat(),
                last_close_original=kept[-1]["close"],
                not_checked=["trading-calendar completeness", "bar finalization", "point-in-time adjusted history"])
    a.out.mkdir(parents=True, exist_ok=False)
    (a.out / "quote_card.json").write_text(json.dumps(card, ensure_ascii=False, indent=2), encoding="utf-8")
    fields = "symbol,date_asia_shanghai,time_ms,interval,adjust,close_original".split(",")
    with (a.out / "research.csv").open("w", newline="", encoding="utf-8-sig") as f:
        w = csv.writer(f)
        w.writerow(fields)
        for row in kept:
            w.writerow([a.symbol, local_time(row["time"]).date().isoformat(), row["time"], "1d", a.adjust, row["close"]])
    (a.out / "processing.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
    content = "".join(f"<dt>{html.escape(str(k))}</dt><dd>{html.escape(str(v))}</dd>" for k, v in card.items())
    (a.out / "quote_card.html").write_text(
        '<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">'
        '<title>Saved stock quote</title><style>body{font:18px system-ui;max-width:600px;margin:40px auto;padding:20px;background:#f7f8fa}'
        'main{background:white;padding:28px;border:1px solid #dce2ea;border-radius:12px}dt{color:#536070;margin-top:18px}dd{margin:6px 0;overflow-wrap:anywhere}</style>'
        f'<main><h1>Saved stock quote</h1><dl>{content}</dl><p>Source: TickDB. Static saved-data example.</p></main></html>', encoding="utf-8")
    print(f"QUOTE {a.symbol} price={quote_price} data_time={quote_time}")
    print(f"RESEARCH input={len(rows)} kept={len(kept)} excluded={len(rows)-len(kept)} cutoff={cutoff}")
    print(f"LAST date={meta['last_date']} close={meta['last_close_original']} adjust={a.adjust}")
    print("FILES quote_card.html quote_card.json research.csv processing.json")
    print("BOUNDARY saved-data processing; no network requests; calendar/finalization/PIT not checked")


if __name__ == "__main__":
    try:
        main()
    except (ValueError, KeyError, TypeError, OSError, ArithmeticError, OverflowError) as exc:
        raise SystemExit(f"ERROR: {exc}")

相关文章

美股行情API遇上冬令时:盘前盘后时间怎么对齐?

负责美股行情页和开盘提醒的产品经理,冬令时切换后需要一起核对交易时段、北京时间和交易日期。本文结合TickDB交易时段、交易日历的真实返回,解释盘前盘后怎样换算,为什么凌晨收盘仍属于美国前一个交易日,并提供时间对照表、可运行换算示例与页面检查清单,处理半日交易等容易遗漏的例外。

TickDB Research ·

股票API接口费用怎么算?同样5000只股票,调用量为何差100倍

准备做A股行情看板、盘中筛选或投研观察池,股票API接口费用该怎么估?本文面向行情应用和投研工具负责人,从使用用途、数据范围、取数方式和计量规则拆解预算。结合TickDB实际取回的全市场行情,用5000只股票说明请求次数为何能相差100倍,并提供可运行的估算代码、场景选择表和询价清单,帮助团队比较满足同一需求的方案。

TickDB Research ·

外汇行情还在更新吗?用 Python 订阅 EURUSD 并记录双时间

适合正在开发外汇报价监控、需要确认脚本持续收到行情的 Python 开发者。使用 TickDB 的 WebSocket 行情订阅,记录 EURUSD 报价、数据时间和本机接收时间,生成原始消息与 CSV。通过目标报价数量、接收间隔和数据时间推进情况,区分连接仍在、消息到达与数据更新。本次30秒订阅观察收到38条目标报价;代码支持有限运行和自动保存结果,方便在自己的环境复查。

TickDB Research ·

Codex股票分析实测:用TickDB MCP查A股行情、财报数据和估值

用Codex分析A股,怎样把行情、财报数据和估值放在一起看?本文以寒武纪为例,实测通过TickDB MCP获取历史行情、单季财务与PE估值,结合公司公告核对期间和口径,再用Python生成事实卡、整理个股研究笔记。附可复制的任务指令、真实输入和可运行代码,适合个人研究者与AI投研产品团队,减少重复查数和整理。

TickDB Research ·

A股历史数据下载:回测结果变了,先别急着改策略

同一只股票,换一份历史数据,研究结果为什么对不上?这篇写给定期做A股研究、准备回测输入的小团队,以及负责研究功能的产品经理。我们用寒武纪的真实日线对照,拆开价格口径、样本范围和数据更新带来的差异,再说明怎样借助TickDB的历史行情、股票信息与复权查询,让研究表、脚本和AI工具有共同的数据依据。

TickDB Research ·