当前位置: 首页 > news >正文

多源价格冲突时先写一个 Quote Evidence Adapter

一、问题:两个价格,该信谁

你给开源项目接了两个行情数据源。同一个 symbol,同一时间附近,一个返回 100.12,另一个返回 100.18。

接口都返回了 200。价格都看起来正常。你的第一反应可能是:其中有一个错了。第二反应可能是:我熟的那个应该更准。

这两个反应都合理。但它们都在把你带向同一个坑——在没有证据的情况下选边站。这次你选对了,下次呢?一旦你选了其中一个作为“信任源”,另一个数据源的所有异常都会被你解释为“它本来就不准”。这是一个自我强化的偏见循环,不是数据验收流程。

多源冲突时,真正要查的不是谁更准,而是谁的证据链更完整。价格本身不会告诉你它从哪来、什么时间生成的、市场当时是什么状态。但这些信息,才是判断一条数据能不能用的真正依据。


二、为什么“谁更准”是一个坏问题

两个价格冲突时,直接问“谁更准”,你会被三个东西困住。

第一,你不知道这两个价格的生成条件是否一致。一个可能是盘中实时成交价,另一个可能是几秒前的快照。一个可能是交易所原始价格,另一个可能是经过复权处理的价格。拿这两个数字直接比较,相当于拿不同时间、不同口径的测量结果比大小——结论没有意义。

第二,“谁更准”隐含一个假设——其中一个数据源整体优于另一个。但数据源的表现不是均匀的。一个数据源可能在美股上延迟更低,另一个可能在港股上状态标记更完整。用整体判断覆盖所有场景,会把真正的数据质量问题掩盖掉。

第三,也是最重要的——“谁更准”不需要你去看原始返回。你只需要比两个数字。但真正的冲突原因,从来不在价格数字本身,而在价格旁边的那些字段里:symbol 是不是被悄悄改了后缀?时间戳是哪个时刻?市场状态是不是一个在盘中、一个在盘后?这些信息不查,你永远不知道为什么冲突。

正确的问题不是“谁更准”,而是:这两条数据,哪一条能让我追溯到它是什么时候、在什么市场状态下、以什么口径生成的?


三、设计一个 Quote Evidence Adapter

解决这个问题的工程手段,不是写一个“选数据源”的规则引擎,而是在数据源和下游消费者之间加一层Quote Evidence Adapter。它的职责只有一条:把每条价格数据连同它的元信息封装成一条可追溯的证据记录。

3.1 模块目录结构

quote_adapter/ normalizer.py # symbol 规范化、时间戳对齐 evidence.py # QuoteEvidence 数据结构定义 conflict_checker.py # 多源冲突检测与仲裁 tests/ test_normalizer.py test_evidence.py test_conflict_checker.py

3.2 QuoteEvidence 数据结构

这是整个模块的核心。每一条从数据源获取的价格,都包装成这个结构再往下游传递。不丢失任何可以用于仲裁的信息。

from dataclasses import dataclass, field from typing import Optional, Dict, Any @dataclass class QuoteEvidence: """一条行情报价的证据记录。 不判断价格是否正确,只保留所有可用于仲裁的元信息。 """ source: str # 数据源标识,如 "tickdb"、"source_b" requested_symbol: str # 请求时使用的 symbol returned_symbol: str # 数据源返回的原始 symbol normalized_symbol: str # 规范化后的 symbol,用于跨源比对 price: Optional[str] # 原始价格字符串 timestamp: Optional[int] # 数据源返回的时间戳 timezone: Optional[str] # 时间戳时区,如 "UTC" market_status: Optional[str] # 市场状态,如 "open"、"closed"、"pre_market" raw_snapshot: Dict[str, Any] # 数据源完整原始返回

字段说明:

  • source:区分这条记录来自哪个数据源,所有后续仲裁都依赖这个标识。
  • requested_symbol/returned_symbol/normalized_symbol:请求的 symbol、实际返回的 symbol 和规范化后的可比 symbol。这三者不同的情况,就是冲突的第一手证据。
  • price:原始字符串。不做类型转换,保留数据源原始形态。
  • timestamp/timezone:时间戳及其时区。没有时区标注的时间戳,在跨市场对比中不可用。
  • market_status:如果数据源提供市场状态字段,这是仲裁的第一优先级——盘中和盘后价格不可直接比较。
  • raw_snapshot:完整原始返回。冲突无法解释时,这是最后一条可追溯的证据。

3.3 normalizer.py:symbol 规范化

不同数据源对同一个品种的 symbol 表示可能不同——有的带后缀,有的不带,有的加了前导零。symbol 规范化把这些差异统一为一个可比较的标识。

def normalize_symbol(raw_symbol: str, source: str) -> str: """根据数据源的 symbol 规则,规范化为统一格式。 各数据源的规范化规则需要按实际返回结构单独实现。 当前为示例骨架,需根据接入的数据源补充具体映射逻辑。 """ # 示例:去除前后空格、统一大写 normalized = raw_symbol.strip().upper() return normalized

3.4 evidence.py:构造证据记录

from typing import Dict, Any, Optional from .normalizer import normalize_symbol def build_evidence( source: str, requested_symbol: str, raw_response: Dict[str, Any], extract_symbol_fn, extract_price_fn, extract_timestamp_fn, extract_market_status_fn=None, timezone: str = "UTC", ) -> QuoteEvidence: """从数据源原始返回构造一条 QuoteEvidence。 各数据源的字段提取函数需按实际返回结构实现。 """ returned_symbol = extract_symbol_fn(raw_response) normalized = normalize_symbol(returned_symbol, source) price = extract_price_fn(raw_response) timestamp = extract_timestamp_fn(raw_response) market_status = None if extract_market_status_fn: market_status = extract_market_status_fn(raw_response) return QuoteEvidence( source=source, requested_symbol=requested_symbol, returned_symbol=returned_symbol, normalized_symbol=normalized, price=price, timestamp=timestamp, timezone=timezone, market_status=market_status, raw_snapshot=raw_response, )

3.5 conflict_checker.py:冲突检测与仲裁

冲突检测器的职责不是选出“正确的价格”,而是判断两条证据记录是否具备可比性。不可比较时,给出具体原因。

from typing import List, Optional from .evidence import QuoteEvidence class ConflictResult: """两条证据记录的对比结果。""" def __init__(self): self.comparable: bool = False self.conflicts: List[str] = [] self.reason: Optional[str] = None def check_conflict(a: QuoteEvidence, b: QuoteEvidence) -> ConflictResult: """检查两条 QuoteEvidence 是否可比较,以及是否存在冲突。 不判断哪条数据更准确,只提供冲突检查结果。 """ result = ConflictResult() # 检查 normalized_symbol 是否一致 if a.normalized_symbol != b.normalized_symbol: result.reason = ( f"symbol 不一致: {a.normalized_symbol} vs {b.normalized_symbol}" ) return result # 检查市场状态是否一致 if a.market_status and b.market_status: if a.market_status != b.market_status: result.reason = ( f"市场状态不一致: {a.market_status} vs {b.market_status}," f"无法直接比较价格" ) return result # 检查时间戳是否在同一可接受窗口内 if a.timestamp and b.timestamp and a.timezone and b.timezone: if a.timezone != b.timezone: result.reason = ( f"时区不一致: {a.timezone} vs {b.timezone}," f"时间戳不可直接比较" ) return result # 标记为可比较 result.comparable = True # 如果价格不同,记录冲突 if a.price and b.price and a.price != b.price: result.conflicts.append( f"价格冲突: {a.source}={a.price}, {b.source}={b.price}" ) return result

3.6 Smoke Test 思路

不追求完整测试覆盖率。只验证模块的核心链路能否跑通。

def test_smoke_two_sources(): """Smoke test:两个数据源输入,检查是否可比较。 不可比较时输出 reason,不做价格判断。 """ # 构造两条来自不同数据源的证据记录 evidence_a = QuoteEvidence( source="source_a", requested_symbol="AAPL.US", returned_symbol="AAPL.US", normalized_symbol="AAPL.US", price="100.12", timestamp=1719500000000, timezone="UTC", market_status="open", raw_snapshot={"symbol": "AAPL.US", "last_price": "100.12"}, ) evidence_b = QuoteEvidence( source="tickdb", requested_symbol="AAPL.US", returned_symbol="AAPL.US", normalized_symbol="AAPL.US", price="100.18", timestamp=1719500001000, timezone="UTC", market_status="open", raw_snapshot={"symbol": "AAPL.US", "last_price": "100.18"}, ) result = check_conflict(evidence_a, evidence_b) if result.comparable: print(f"可比较,冲突: {result.conflicts}") else: print(f"不可比较,原因: {result.reason}")

四、TickDB 在这类场景里的合理位置

上面这套 Quote Evidence Adapter,是一套多源冲突时的排查框架。不管你用哪个行情数据源,这套框架都能用。问题在于:如果数据源本身的字段定义不清晰、异常情况没有约定、原始返回不可追溯,你光是把这套框架跑通,就要花大量精力在猜数据和补日志上。

TickDB 在这里能做的,是让你在排查多源冲突时,至少有一端的数据是有清晰证据链的。它适合被放进数据质量验收流程里,作为一个字段契约清晰、便于逐项核对的候选行情入口。多源冲突时,你拿 TickDB 的返回和其他数据源对比,至少 TickDB 这一端的 symbol、时间戳、状态标记和异常返回是明确可查的——你可以把精力花在分析冲突原因上,而不是花在猜“这一端的数据到底是什么意思”上。

验证方法:用你自己的 symbol 跑一次请求。保存请求参数、原始返回、检查时间。对照 symbol、时间戳、市场状态、字段口径、异常返回逐项核对。如果要做多源对比,不要只比价格——比证据链。

不适合什么:不替代投资判断。不替代生产监控和异常回放。不用来证明某个数据源永远更准。数据验收的责任,最终还是在你自己手上。


五、接入前检查清单

  • source字段是否在每条证据记录中明确标注
  • normalized_symbol是否可用于跨源比对
  • 时间戳是否带时区信息
  • 市场状态字段是否已解析
  • raw_snapshot是否保存了完整原始返回
  • 冲突检测器是否先判断可比性,再判断冲突
http://www.cnnetsun.cn/news/3200623.html

相关文章:

  • Ubuntu 22.04 安装 MATLAB R2025a 全流程故障预判与 GUI 稳定性加固
  • AI的工程基础2-反向传播
  • 炉石传说插件HsMod:终极游戏体验增强完全指南
  • 什么是 GEO?未来3年,为什么它是比SEO更重要的流量风口
  • 保姆级教程:手把手搭建MCP服务器让AI替你干活
  • 京东JoyAI-VL-Interaction:实时视觉语言交互系统从原理到实践
  • 无限制OCR技术解析:从Tesseract到分布式处理的长文档识别方案
  • 3分钟救回损坏视频!Untrunc视频修复工具的神奇魔力
  • TensorRT安装避坑指南:CUDA/cuDNN/驱动版本兼容性详解
  • Minecraft基岩版启动器BedrockLauncher:告别版本冲突,打造个性化游戏空间
  • 面向非数字化商户的对话式AI系统设计:零登录、多语言、意图路由
  • OpCore-Simplify:15分钟搞定黑苹果配置的终极简化工具
  • FR-4 PCB Tg值选型指南:3类板材(130°C/150°C/170°C+)适用场景与成本分析
  • 重连成功,图表却骗了你
  • Turbo Boost配置分享
  • CIC研判:数字化越转越重,病根还是在低代码工作流
  • RIP动态实验
  • 在Windows平台构建企业级RTMP流媒体服务器的完整指南
  • YOLOv10模型改进-特定领域应用-第93篇:YOLOv10改进策略【特定领域应用】| YOLOv10在医疗影像中的应用
  • KES是什么?国产数据库技术架构、核心能力与选型实战解析
  • PyTorch MSE Loss 维度不匹配:从 2048 vs 2088 报错到 3 步定位法
  • 【专业服务】游戏试玩广告全流程定制开发:从创意到数据,助力高效获客
  • 2026年企业上Agent的真实落地率只有17%——剩下的83%,卡在哪了?
  • 具身智能十年演进:从物理仿真到世界模型的认知跃迁
  • 6种技术文档类型,Baklib轻松搞定
  • SWEET32漏洞实战:TLS安全加固与3DES加密套件禁用指南
  • Python爬虫经典案例第83篇:在线博客平台爬取:掘金数据采集实战
  • PyTorch 2.x 张量调试:5 个 print 语句定位 90% 维度不匹配问题
  • 公司注销指南:登报清算公告怎么写?登报流程与要求详解
  • AI程序员生存指南25-技术更新太快跟不上?技术趋势跟踪的实战方法