Python 行情数据留痕:symbol、timestamp、字段和 raw_snapshot 怎么记录
摘要
研究报告里写“截至某日某时,某股票价格为 X”,一周后复查时发现数据源已刷新,当时的快照没保存,数字无法核对。这不是数据源的问题,是取数流程里缺了一环——没有在取值的同时留下原始快照和字段溯源记录。本文给出一套最小留痕模型:用 Python 保存请求参数、原始响应、字段路径和校验记录,让报告里的每一个行情数字都能追溯到“什么时候、从哪个接口、用哪个字段取的、原始返回值是什么”。
研究员用 Python 拉行情数据写报告,最常见的一个坑不是数据源挂了,而是报告交上去之后发现数据对不上,回头查的时候数据源已经刷新了——那条行情的快照早就没了。
问题不在数据源,在于取值的时候没有同步留痕。你只记了数字本身,没记这个数字是从哪个字段路径拿到的、原始返回体长什么样、取值时间是多少。一周后想复核,只剩报告里一个孤零零的数字,既查不到来源,也复现不了当时的状态。
本文给出一套最小留痕模型,帮你把每一次行情取值都变成一条可追溯的记录。它不是数据库设计教程,也不是生产级数据管道,而是一套能让你的研究数据“经得起复核”的工程方法。
1. 最小留痕模型:一条记录至少包含什么
每次取数,至少保留以下六类信息。这六类合在一起,构成了一个行情数字的“完整身世”。
| 留痕维度 | 核心字段 | 回答什么问题 |
|---|---|---|
| 时空坐标 | symbol、market、query_time | 什么品种、什么市场、什么时间取的 |
| 数据来源 | data_source、query_method | 从哪个数据源、用哪种接入方式拿的 |
| 时间语义 | payload_timestamp | 行情发生在什么时候(不是取数时间) |
| 取值路径 | field_name、field_path | 从返回体的哪条路径取到这个数字 |
| 原始凭证 | raw_snapshot、raw_snapshot_hash | 原始返回体是什么,有没有被改动过 |
| 复核状态 | processing_note、review_status | 这个值后来有没有被人工修改、有没有复核过 |
为什么raw_snapshot是必选项:保存原始响应是留痕和日志最大的区别。日志只记录“发生了什么”,原始快照记录的是“当时数据源交回了什么”。一周后如果价格对不上,你可以把原始快照重新灌进校验脚本,复现当时的取值过程。没有快照,只剩猜测。
2. 字段表:研究报告引用的行情数据溯源记录
以下字段表适合作为研究报告的数据引用附录,每引用一个行情数字,填一行。这张表的核心是让读者(或审查者)能从报告里的数字反查到你取数时的原始状态。
| 字段名 | 类型 | 说明 | 示例值 |
|---|---|---|---|
report_id | str | 报告唯一标识 | RPT-2026-001 |
report_section | str | 报告中的位置 | §3.2 估值分析 |
symbol | str | 品种代码 | 600519.SH |
market | str | 市场 | A |
data_source | str | 数据源标识 | tickdb |
query_method | str | 接入方式 | REST |
request_params_hash | str | 请求参数哈希 | sha256:abc123... |
requested_at | str | 请求发起时间(ISO 8601) | 2026-07-01T14:30:00+08:00 |
payload_timestamp | int | 行情时间戳(原始值) | 1719823400000 |
field_name | str | 取值字段名 | last_price |
field_path | str | 字段在返回体中的路径 | data[0].last_price |
field_semantics | str | 字段含义说明 | 最新成交价(快照) |
value_raw | str | 原始返回值 | "1675.00" |
value_normalized | str | 规范化后的值 | 1675.00 |
raw_snapshot_id | str | 原始快照唯一标识 | SNAP-20260701-001 |
raw_snapshot_hash | str | 原始快照哈希 | sha256:def456... |
processing_note | str | 处理备注 | 字段核对通过 |
review_status | str | 复核状态 | 待复核 / 已复核 / 存疑 |
注意:上表中的
payload_timestamp和value_raw为字段占位说明,实际值以接口返回为准。request_params_hash和raw_snapshot_hash用于防篡改校验,具体哈希算法可选用 SHA-256。
3. Python 伪代码:取值即留痕
以下代码为教学伪代码,展示取值留痕的核心逻辑流程,非生产级可运行代码。实际使用时需根据数据源的接口文档调整字段路径和鉴权方式。
importjsonimporthashlibfromdatetimeimportdatetime,timezonefromdecimalimportDecimaldeffetch_market_data(symbol:str,market:str)->dict:"""向行情数据源请求快照数据。 返回原始响应体。实际实现需替换为真实请求逻辑。 """# 教学伪代码:实际应使用 requests.get() 并处理超时、异常# response = requests.get(..., params={...}, headers={...}, timeout=10)# return response.json()passdefsave_raw_snapshot(data:dict)->tuple:"""保存原始响应快照,返回 (snapshot_id, snapshot_hash)。"""raw_json=json.dumps(data,ensure_ascii=False,sort_keys=True)snapshot_id=f"SNAP-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"snapshot_hash=hashlib.sha256(raw_json.encode()).hexdigest()# 实际实现:将 raw_json 写入文件或数据库returnsnapshot_id,snapshot_hashdefextract_field_value(data:dict,field_path:str)->str:"""从返回体中按路径提取字段值。 例如 field_path='data[0].last_price'。 """# 教学伪代码:简化路径解析,实际需处理嵌套和数组索引parts=field_path.replace("[",".").replace("]","").split(".")current=dataforpartinparts:ifisinstance(current,dict):current=current.get(part)elifisinstance(current,list)andpart.isdigit():current=current[int(part)]else:returnNonereturnstr(current)ifcurrentisnotNoneelseNonedefwrite_lineage_record(report_id:str,section:str,symbol:str,market:str,data_source:str,query_method:str,requested_at:str,payload_ts:int,field_name:str,field_path:str,field_semantics:str,value_raw:str,snapshot_id:str,snapshot_hash:str,processing_note:str)->dict:"""写入一条数据溯源记录。"""# 教学伪代码:实际实现应写入数据库record={"report_id":report_id,"report_section":section,"symbol":symbol,"market":market,"data_source":data_source,"query_method":query_method,"requested_at":requested_at,"payload_timestamp":payload_ts,"field_name":field_name,"field_path":field_path,"field_semantics":field_semantics,"value_raw":value_raw,"value_normalized":str(Decimal(value_raw))ifvalue_rawelseNone,"raw_snapshot_id":snapshot_id,"raw_snapshot_hash":snapshot_hash,"processing_note":processing_note,"review_status":"待复核"}# db.insert(record)returnrecorddefbuild_report_citation_note(record:dict)->str:"""根据溯源记录生成报告中可引用的数据来源注释。"""return(f"数据来源:{record['data_source']}({record['query_method']}),"f"品种{record['symbol']},字段{record['field_path']},"f"请求时间{record['requested_at']},"f"快照 ID{record['raw_snapshot_id']},"f"复核状态:{record['review_status']}")4. 失败分支速查
取数留痕流程中,以下八类异常各有对应的处理方式。任何一类失败,这条数据的review_status都应标记为“存疑”或直接阻断。
| 失败场景 | 处理方式 |
|---|---|
请求成功但data为空 | 记录value_raw=None,processing_note写明“数据为空”,不填默认值 |
| JSON 解析失败 | 保留原始响应文本,processing_note记录“JSON解析失败” |
symbol缺失或与请求不一致 | 阻断,不写入溯源表。静默修正可能指向错误品种 |
timestamp缺失或语义不明 | 写入溯源表但review_status=存疑,processing_note标注“时间戳待确认” |
| 字段路径不存在 | value_raw留空,processing_note写明“字段路径不存在”,检查接口版本 |
raw_snapshot未保存 | 整条记录标记为“不可追溯”,review_status=存疑 |
raw_snapshot_hash与原始不一致 | 快照可能被篡改或损坏,标记“哈希不一致”并阻断下游引用 |
人工修改了值但未写processing_note | 复核时发现值与快照不一致且无备注,标记“数据存疑” |
5. TickDB 在取数留痕流程中的合理位置
TickDB 可作为候选结构化行情入口,帮助统一保存请求参数、字段语义、原始快照和检查记录。它在取数留痕流程里的作用不是“保证数据一定正确”,而是让你的取值动作有一个结构化的起点——symbol 和 market 有统一格式,接口返回有明确的字段路径,原始响应体可以完整保存。这些恰好是留痕模型前四类信息的基础。
验证方式:用自己的 symbol 发一次请求,按本文字段表逐列填写。保存原始快照和哈希,计算value_normalized,写一条完整溯源记录。如果当前接口返回的字段路径和本文示例不同,以官方文档或实测为准。
不适合什么:不替代研究报告的数据审核流程;不承诺数据永远正确;不保证所有字段在所有市场可用。具体端点、Header、字段枚举和错误码,以 TickDB 官方文档和审核为准。
6. 发布前检查清单
| 序号 | 检查项 | 通过标准 |
|---|---|---|
| 1 | 请求参数已保存 | 每次取数记录 symbol、market、请求时间、参数哈希 |
| 2 | 原始快照已保存 | raw_snapshot完整写入文件或数据库 |
| 3 | 快照哈希已计算 | raw_snapshot_hash可用于防篡改校验 |
| 4 | 字段路径明确 | 报告引用的每个数字都能追溯到具体field_path |
| 5 | 时间语义区分 | requested_at和payload_timestamp分开记录 |
| 6 | 异常有备注 | data为空、解析失败等情况在processing_note中写明 |
| 7 | 人工修改有留痕 | 任何手工调整都在processing_note中说明原因 |
| 8 | 复核状态可查 | 每条记录有review_status,存疑数据不进入最终报告 |
| 9 | 报告中可引用 | 每个行情数字附build_report_citation_note()生成的来源注释 |
📡 本文取数留痕示例以 TickDB.ai 作为候选行情入口,具体字段以官方文档和审核为准
⚠️ 本文为技术教程,不构成任何投资建议