跳到主要内容

数据源总览

多源降级架构​

本项目采用按数据类型分级的多源降级策略(权威声明见 backend/app/services/data_sources/fallback_registry.py,35 个数据类型 × 主源/备胎/兜底), 参考 a-stock-data V3.4.0 十层架构 + AkShare 分页模式设计:

注意:降级方向因数据类型而异,上图仅为整体优先级示意。例如行业排名主源是 腾讯 getRank、资金流向主源是 TDX;东财只在独有数据(龙虎榜/解禁/两融/大宗/ 研报/新闻/财务指标/涨停池)上作主源,K线/实时行情/市值/PE/PB 禁止东财作主源。

数据源目录(可信度页的单一真源)​

上面「数据源对比」表只列了参与主备竞争的源,但本仓实际接入的外部源远不止这些。 用户视角的「有哪些源」由 backend/app/services/data_sources/source_catalog.py 声明式目录统一定义,23 个源分8 组:

分组源定位
行情腾讯财经 / 通达信 TDX / 新浪 / 东方财富 / 东财涨停池 / 雪球主链路
交易所上交所 / 深交所官方口径(市场概况/龙虎榜/股票列表)
披露巨潮资讯 / 同花顺 F10法定披露与 F10 档案
固收指数申万宏源研究 / 中债估值 / 中证指数官网行业分类 / 国债曲线 / 指数估值
资讯同花顺快讯 / 财联社电报 / 金融界 JRJ / 央视财经舆情与资讯
宏观金十数据 / FRED 圣路易斯联储国内外宏观
海外yfinance港美股兜底(腾讯优先 / Yahoo 兜底)
备用集思录 / 乐咕乐股 / 雪球私募主链路拿不到或需交叉校验

三条派生用途:

SOURCE_CATALOG ─┬─ probe_specs() → /health/inspect(主动探测)
├─ catalog_as_dicts() → /health/sources(目录,前端标签真源)
└─ audit_catalog_vs_fallback() → 交叉审计

交叉审计断言「降级链注册表里的每个源都在目录中出现」。若不成立,意味着 存在「业务在用它、但用户在可信度页看不到」的真盲区—— 2026-10-06 首次运行该 审计即抓出 ths 漏登记(降级链键是 ths,目录键是 ths_f10,靠 aliases 对上)。

目录与降级链注册表是互补不是包含关系:fallback_registry.SOURCE_CAPABILITIES 只登记参与主备竞争的 9 个源(降级关系真源),目录覆盖全部 23 个(展示真源)。 两者都需要,缺一不可。

探针纪律三条(违反即产生假信号):

  1. 必须走业务真实代码路径(调本仓客户端函数),不走裸 URL —— 否则会出现 「探针绿灯、业务因缺 Cookie/指纹而失败」的假健康信号
  2. 不产生副作用:只读接口,不落库、不发通知
  3. probe=None 表示不支持主动探测(如需登录态的雪球私募),前端显示「未探测」 而非「异常」—— 把"没测"说成"坏了"同样是误导

页面入口 /data-trust(侧栏「发现」→ 数据可信度)。

数据源对比​

数据源协议封IP风险数据覆盖延迟适用场景
腾讯财经HTTP无K线/指数/估值/行业排名/全市场行情~100msK线、指数、估值、行业排名主源
TDX 自实现TCP 7709无盘口/资金流/财务30字段/板块/市场统计/除权除息~50ms实时盘口/资金流主源,K线第一备胎
东方财富HTTP高独有数据最全(龙虎榜/两融/大宗/涨停池…)~200ms独有数据主源,需限流
新浪HTTP低财报三表/复权因子/北交所K线/资金流兜底~150ms东财被封时备用
巨潮HTTP低公告/定期报告/F10~200ms公告主源
申万宏源研究HTTP低行业分类/成分股/行业指数日线~300ms申万口径行业唯一源

上表只列参与主备竞争的源;全量 23 个源见上方「数据源目录」。 历史遗留的 baostock_client.py / baostock_provider.py 是自建兼容壳 (原 baostock 依赖已完全移除,K线转发东财 push2his),保留仅为兼容旧引用。

限速与节流参数​

各 HTTP 数据源客户端内置进程级最小间隔节流(配置均可环境变量覆盖,见 core/config.py):

参数默认值说明
EM_MIN_INTERVAL0.3s东财两次请求最小间隔(附 0.1~0.5s 随机抖动;批量同步实测安全值,可用环境变量调回更保守值)
TENCENT_MIN_INTERVAL0.2s腾讯最小间隔(不封 IP,宽松)
XQ_MIN_INTERVAL1.0s雪球最小间隔(风控较严,保守)
HOST_FAILURE_THRESHOLD2东财镜像主机连续失败 N 次才进入 30s 冷却
STOCK_CHANGES_CONCURRENCY3push2ex 异动监控并发上限(Semaphore)

TDX 为 TCP 协议自带连接管理(心跳 15s + 健康评分 + 共享冷却熔断),不做 HTTP 式节流。 数据源级成功/失败计数可通过 GET /datamgr/probe-all 响应的 counters/breakers 字段查看。

数据源抽象层​

data_source.py 定义统一接口,通过工厂模式切换实现:

class DataSource(ABC):
def search(self, keyword: str) -> list[dict]: ...
def get_stock(self, code: str) -> dict | None: ...
def get_kline(self, code: str, days: int = 120) -> list[dict]: ...
def get_finance(self, code: str) -> list[dict]: ...
def get_industry_compare(self, code: str) -> list[dict]: ...

def get_data_source(source: str = "mock") -> DataSource:
# "real" → RealDataSource (腾讯+TDX自实现+东财+新浪,腾讯为K线/估值主源)
# "akshare" → AkshareDataSource (直连东财/新浪API)
# "tushare" → TushareDataSource (占位,需 TUSHARE_TOKEN)
# "mock" → MockDataSource (开发测试)

K线数据三级降级​

RealDataSource.get_kline() 的降级链:

北交所(920xxx/8xxxxx/4xxxxx)为例外链路:腾讯实测仅返回最新 1 根、TDX 不覆盖, 故走 新浪 → 东财 push2his。分钟线为独立链路(minute_providers,1/5/15/30/60m)。

财务数据二级降级​

涨跌分布/资金概况三级降级​

首页涨跌分布和资金概况使用 TDX TCP 实时统计,不依赖已移除的 stock_base_info 静态快照:

⚠️ 东财 push2 filter 参数已被服务端停用(2026-09-22 实测):带括号 / 无括号 / filters / URL 编码等任何形式都被忽略(同一 fs 传两种取值 total 不同,可证只认 fs),所有分段请求都返回全量总数 → up == down == total、13 段全等。 em_market_breadth() 因此改为先发 2 路探针(全量 + 上涨)验证 filter 是否生效, 失效即整体判不可用:既不白打 17 路请求(东财有风控,无效请求同样计入主机冷却, 会牵连龙虎榜/两融等东财独有数据),也不把"静默错值"交给下游。filter 恢复后无需改码, 探针通过即自动回到链上。

🔴 分布载荷自洽门禁:builders.distribution_counts_consistent() 要求 up + down + flat ≈ total ≈ Σbins.count(±10%,容忍停牌无涨跌幅行)且 13 段不全等, 作为所有返回路径(实时 / 快照 / 缓存)与东财源的统一闸门——不自洽的载荷 无法落快照、也无法被展示,存量脏快照行会被 is_valid_data 跳过并重新获取。 交易时段禁用宽表/DB 旧分布兜底(盘中展示昨日分布会误导短线决策),此时宁可 不给直方图、由前端退化为 5 分类展示,家数仍保持实时源。

db_market_breadth() 等 DB 聚合函数自 2026-08-22 起恒返回 None(stock_base_info 表已移除),资金概况的「上一日成交额」改走 fox_market_daily.total_amount。

数据标准化​

所有数据源的返回值经过统一标准化处理:

  • 数值转换:safe_float() 处理 "-"、""、None 等异常值
  • 代码校验:_validate_stock_code() 正则验证6位数字格式
  • 字段映射:东财 f2/f3/f4... → 语义化 price/change_percent/...
from .data_sources.em_paginated import safe_float

# 东财返回的 "-" 不会导致 ValueError
price = safe_float(item.get("f2")) # "-" → 0.0

相关文档​