多数据源 + cache → DB → source fall-over 抽象层;目标是可独立开源。 顶层目录:
tickbridge/(不是 stocklens 的子包)。
stocklens 主仓的最终形态是「拼凑各个独立好仓 → 整合成一个 Web UI(看热力图等)」:
┌──────────────────────────────────────────────┐
│ web UI (FastAPI + 模板) — 行情热力图 / 分析 │
└────────────┬─────────────────────────────────┘
│ HTTP / API
▼
┌──────────────────────────────────────────────┐
│ stocklens (业务编排:pipeline / scoring / │
│ agent / pipeline / portfolio) │
└─┬──────────┬──────────┬──────────┬───────────┘
│ │ │ │
▼ ▼ ▼ ▼
tickbridge notify- search- llm-bridge ...
bridge bridge
(本文) (规划) (规划) (规划)
每个 bridge 都做成「单类对外 + 插件继承 + cache→DB→源 三级读路径」的统一形态,满足三件事:
- 在 stocklens 主仓里 in-tree 共存(开发期);
- 自带
pyproject.toml/LICENSE/README.md,未来可独立成单独 GitHub 仓 / PyPI 包; - web UI 可以直接驱动它们 —— REST endpoint 简单转发到
bridge.X(),不再嵌套业务层。
tickbridge 是这条路线上的第一个,也是验证抽象套路是否可行的样板。下一个轮到 notify-bridge。
- 唯一对外门面:
tickbridge.DataProvider。 - 多源行情插件:
tickbridge.contracts.BaseFetcher的子类(雪球/yfinance/...),按 priority 顺序 fall-over。 - 单源领域插件:
tickbridge.contracts.DomainFetcher的子类(finviz/reddit/...),单源直通,不 fall-over。 - 可注入存储/缓存:
CacheProtocol/RepositoryProtocol—— 默认走 tickbridge 内置的 SQLite + 内存缓存,但 stocklens 这类宿主应用可注入自有实现。
stocklens 把 tickbridge 当作外部依赖使用:所有数据获取一律走 from tickbridge import DataProvider,禁止再 from data_provider.fetchers.X import Y 或自己拼 HTTP。
tickbridge/
├── __init__.py 顶层暴露 DataProvider + __version__
├── pyproject.toml 独立包定义(pip install -e tickbridge/)
├── LICENSE MIT
├── README.md 独立项目说明(不引用 stocklens)
│
├── core/ 门面 / 注册表 / 读路径 / 工具
│ ├── provider.py DataProvider(唯一对外类,Commit 2 实装读路径)
│ ├── manager.py DataFetcherManager(多源 fall-over)+ get_default_manager()
│ │ / reset_default_manager()(进程级单例工厂,F1)
│ ├── realtime_mixin.py 实时报价能力 mixin
│ ├── chip_mixin.py 筹码分布 mixin
│ ├── fundamental_mixin.py 基本面聚合 mixin
│ ├── market_mixin.py 指数 / 板块 / 市场统计 mixin
│ ├── stock_info_mixin.py 股票名 / sector mixin
│ ├── circuit_breaker.py CircuitBreaker — pybreaker 薄包装(per-source CLOSED/OPEN/HALF_OPEN)
│ ├── fail_open.py fail_open(logger, op, exc) — 统一 fail-open 日志;
│ │ tickbridge 不可 import stocklens,所以这是
│ │ stocklens.utils.fail_open 的本地副本
│ ├── exceptions.py DataFetchError / RateLimitError + 工具
│ └── utils.py safe_float / safe_int
│
├── contracts/ ABC / Protocol(接口契约)
│ ├── base_fetcher.py BaseFetcher (ABC) — 多源行情接口
│ ├── domain_fetcher.py DomainFetcher (ABC) — 单源领域接口
│ ├── cache_protocol.py CacheProtocol — 缓存接口
│ └── repository_protocol.py RepositoryProtocol — 存储接口
│
├── models/ 公开数据契约(dataclass)
│ ├── quote.py UnifiedRealtimeQuote / RealtimeSource (`trade_date` 字段 — 2026-05 新增,让下游做新鲜度验证)
│ ├── chip.py ChipDistribution
│ ├── ohlcv.py / fundamental.py / market.py (Commit 2 填充)
│
├── sources/ 10 个 BaseFetcher 子类
│ └── xueqiu / yfinance / akshare / tushare / efinance / openbb /
│ pytdx / baostock / twelvedata / tickflow
│
├── domain_sources/ 单源 DomainFetcher 子类(搬自 stocklens/services/)
│ ├── finviz.py FinvizFetcher + 5 个 fetch_* 函数
│ └── social_sentiment.py SocialSentimentService(DomainFetcher 子类)
│
├── codes/ 股票代码工具
│ ├── normalize.py normalize_stock_code / 市场识别(搬自 stock_code.py)
│ ├── us_index.py US 指数符号映射(搬自 us_index_mapping.py)
│ └── stock_mapping.py 代码→中文名(搬自 common/stock_mapping.py)
│
├── adapters/ 上游 → 公开模型的字段适配
│ └── fundamental.py A/港/美基本面字段统一(搬自 fundamental_adapter.py)
│
├── internal/ 私有补丁(不开放给调用方)
│ └── eastmoney_patch.py 东方财富 NID 授权
│
├── config/ DI 钩子
│ └── settings.py register_config_provider / get_dp_config / register_symbol_supplier / get_symbol_supplier(host 注入)
│
├── cache/ 默认 In-Memory TTL + SQLite 缓存(Commit 2 实现)
├── storage/ 默认 SQLite Repository(Commit 2 实现)
└── tests/
└── test_smoke.py 32 个烟测:公开面、ABC、模块导入、模型 round-trip、CircuitBreaker
| Commit | 状态 | 范围 |
|---|---|---|
| 1a | ✅ | 骨架、空 ABC、pyproject.toml + LICENSE + README、4 个 smoke test |
| 1b | ✅ | git mv data_provider/* → tickbridge/* + 拆分 realtime_types.py(4 份)+ 合并 base_fetcher.py 接口契约 + 全工程 import 替换(30+ 文件,60+ 处)+ 32 个 smoke test |
| 2 | ✅ | DataProvider 实装 cache → DB → manager 读路径;tickbridge/cache/ 出 InMemory + SQLite + Tiered 三层;tickbridge/storage/ 出默认 SqliteRepository;core/read_through.py 高阶函数;67 个测试(cache / storage / read_through / provider integration / smoke) |
| 3 | ✅ | git mv finviz / social_sentiment 到 tickbridge/domain_sources/;FinvizFetcher + SocialSentimentService 实现 DomainFetcher 协议;DataProvider 增 finviz_* / social_* 方法 + source(name) 逃生口;stocklens/services/ 留薄壳 re-export;4 处直接 import + 2 处雪球野路子(sectors.py / etf_quote_service.py)全部改走 DataProvider;70+ tests |
| 4 | ✅ | CLAUDE.md 强制约束("代码层禁止 from tickbridge.sources.*","requests.get 拉行情"等);tickbridge/README.md 加 ASCII 架构图 + DI 烹饪手册;主仓 README.md 加产品愿景段落;薄壳 re-export 仍保留(10 处 stocklens 调用方过渡期使用,不强制立即清理) |
| 5 | ❌ | (已撤销 2026-05)原 5a-5d 引入了 market_hours.py / BackgroundPoller / dashboard 1s 轮询 / 7 个免费实时 fetcher。项目重新定位为盘后复盘工具,全部撤回,TTL 统一为 30 min,报价仅来自 stock_quote_snapshot DB 表。 |
DataProvider.get_realtime_quote("AAPL")
│
▼
tickbridge.core.read_through.read_through(
cache_key="quote:AAPL",
cache=TieredCache(L1, L2),
db_loader=lambda: repo.load_quote("AAPL"),
db_saver=lambda v: repo.save_quote("AAPL", v),
source_call=lambda: manager.get_realtime_quote("AAPL"),
is_hit=_quote_is_hit,
)
│
├─ L1 (InMemoryTTLCache) ─ HIT ⇒ return
│ MISS ↓
├─ L2 (SqliteTTLCache) ─ HIT ⇒ populate L1, return
│ MISS ↓
├─ Repository.load_quote ─ HIT ⇒ populate L1+L2, return
│ MISS ↓
└─ DataFetcherManager ─ HIT ⇒ persist + cache, return
MISS ⇒ return None
每个 DataProvider.get_* 方法都走同一个 read_through() 高阶函数,避免 9 套相同形状的代码漂移。
TTL 政策(tickbridge/core/provider.py 顶部常量):
| 方法 | TTL |
|---|---|
get_realtime_quote / get_index_quote / get_etf_quote(美股) |
盘中 30s,盘外 12h |
get_realtime_quote(A股 / HK,待扩展) |
60s(legacy default) |
get_daily_data |
6h |
get_fundamental |
12h |
get_chip_distribution |
1h |
get_main_indices / get_market_stats / get_sector_rankings |
60s |
Quote TTL 统一 30 min(盘后复盘工具,无需盘中切换)。
读路径 cache / DB hit 是否"算数"由 _quote_is_hit(value) 决定(provider.py):
value is None→ missvalue.has_basic_data()必须为真(即price > 0)
from tickbridge import DataProvider
# Default:内置 InMemoryTTLCache + SqliteTTLCache + SqliteRepository
provider = DataProvider()
# 自带 Redis 的宿主:注入自己的 Cache 实现
provider = DataProvider(cache=MyRedisCache())
# 完全无 cache 无 DB(测试 / 一次性脚本)
provider = DataProvider(cache=False, repository=False)
# 自带 SQLAlchemy 的宿主:注入 RepositoryProtocol 实现
provider = DataProvider(repository=MyHostRepoAdapter(host_db))
# 注入 Domain 插件(Commit 3)
from tickbridge.domain_sources import SocialSentimentService
provider = DataProvider(
social_sentiment=SocialSentimentService(api_key="sk_live_..."),
)
# 显式禁用 finviz / social:
provider = DataProvider(finviz=False, social_sentiment=False)少数情形下(比如 XueqiuFetcher.fetch_bars 或 YfinanceFetcher.get_earnings_calendar)需要直接调用某个 fetcher 的子类专属方法,
这些不在 BaseFetcher 标准契约里。provider.source(name) 提供逃生口:
xq = provider.source("xueqiu") # 返回 XueqiuFetcher 实例
df = xq.fetch_bars(code, interval="1d", count=30)注意:用 source() 拿到 fetcher 后绕过了 cache → DB → fall-over 读路径,
请优先用 provider.get_* 系列方法;只有在标准契约真的不够用时才回落到 source()。
from tickbridge import DataProvider
provider = DataProvider() # 默认配置 + 内置 SQLite
# Multi-source(cache → DB → fetcher)
provider.get_realtime_quote("AAPL")
provider.get_index_quote(".DJI")
provider.get_etf_quote("XLK")
provider.get_daily_data("AAPL", days=120)
provider.get_chip_distribution("000001")
provider.get_fundamental("AAPL")
provider.get_main_indices(region="us")
provider.get_market_stats()
provider.get_sector_rankings(n=5)
# Domain helpers(单源直通;Commit 3 实装)
provider.finviz_forward("AAPL") # forward valuation snapshot
provider.finviz_news("AAPL", limit=10)
provider.finviz_peers("AAPL")
provider.finviz_insider("AAPL")
provider.finviz_sector_etf("Tech")
provider.social_reddit_report("AAPL")
provider.social_x_trending()
provider.social_get_context("AAPL") # combined Reddit+X+Polymarket text block
# Escape hatch — backend-specific methods (rare)
xq = provider.source("xueqiu")
df = xq.fetch_bars("AAPL", interval="60m", count=200)Commit 3 完成后所有方法都是真实实装;不再有 stub。
过渡期 stocklens/services/finviz_service.py / social_sentiment_service.py 保留薄壳 re-export shim(指向 tickbridge),现存调用点零改动;Commit 4 删除 shim 时再统一迁移。
- ✅
stocklens/**可以from tickbridge import DataProvider、from tickbridge.core.manager import get_default_manager(生产代码)或DataFetcherManager(测试隔离) - 🔒 F1(2026-05):所有生产入口(pipeline / API / Bot / agent tools / services)必须用
get_default_manager()而不是DataFetcherManager()。原因:每个 fetcher 持有 pybreaker 熔断器,重建 manager 会让 OPEN/HALF_OPEN 状态归零,等于熔断失效。reset_default_manager()仅在 SystemConfigService 改完 .env 时由内部调用,外部代码不要碰。 - ❌
tickbridge/**不可以from stocklens.*或from api.*或from bot.*(保证 tickbridge 可独立开源) - ❌
stocklens/**不可以from data_provider.*——data_provider/已删除(Commit 1b) - ❌
stocklens/services/**不可以自己requests.get(...)拉行情(应走provider.X(...))—— Commit 3 / 4 强制
完整规则见 CLAUDE.md 的"代码 → 文档同步映射"。
pytest tickbridge/tests/(32 项):
| 类别 | 用例数 | 覆盖 |
|---|---|---|
| 公开面 | 3 | tickbridge.DataProvider + __version__ 暴露;stub 方法必须 raise NotImplementedError |
| ABC / Protocol | 1 | BaseFetcher 拒绝实例化、DomainFetcher 可子类化、Cache/RepositoryProtocol 通过 isinstance |
| 模块导入烟测 | 22 | 每个迁移过来的模块都能 import(含 10 个 fetcher 子类) |
| 数据模型 | 2 | UnifiedRealtimeQuote / ChipDistribution to_dict round-trip |
| CircuitBreaker | 2 | 失败计数到阈值后 OPEN;reset 恢复 |
| Manager 接口 | 1 | DataFetcherManager 可导入 |
tests/unit/tickbridge/(搬自 tests/unit/data_provider/,stocklens 的 pytest 套件下)—— failover 行为、雪球字段适配、共 12 个用例 |
主仓 pytest tests/(341 项)—— 全部通过。
- 顶层目录命名
tickbridge/,PyPI 包名将来 publish 时再决定(可能因占用而改名,但本仓不受影响) - LICENSE 已选定 MIT;与 stocklens 主仓 LICENSE 独立
pyproject.toml已声明独立 build:pip install -e tickbridge/可作为单独的 Python 包安装- 所有 ABC 默认实现返回
None而非raise NotImplementedError,让"不支持某接口"成为合法状态(避免每个子类大量def get_X(...): return None的占位代码) get_dp_config的dp_前缀临时保留以减少 fetcher 子类的 churn,等 Commit 4 一并 rename 为get_config
引入模块级 requests.Session / httpx.Client 单例,避免每次调用都做 TCP+TLS 握手
(每次冷连接 50-200ms):
| 文件 | 复用对象 | 用途 |
|---|---|---|
tickbridge/sources/akshare.py |
_get_session() |
Sina + Tencent 实时行情 |
tickbridge/sources/twelvedata.py |
_get_session() |
TwelveData API |
tickbridge/sources/tushare.py |
_get_session() |
api.tushare.pro |
tickbridge/domain_sources/social_sentiment.py |
_get_session() |
api.adanos.org |
均采用 requests.adapters.HTTPAdapter(pool_connections=N, pool_maxsize=2N) 调优。
注意:xueqiu.py / eastmoney_patch.py 因深度耦合 requests 内部 API(cookie jar、Session
monkey-patch)保持不变;新增 HTTP 调用一律走 httpx。
tickbridge/domain_sources/finviz.py 中 5 个手写 Dict[str, tuple] + time.time() 缓存
全部迁移到 kvcache namespace(详见 docs/modules/kvcache.md):
| 旧 dict | 新 namespace | TTL |
|---|---|---|
_page_cache |
finviz_page |
30 min |
_cache (forward) |
finviz_forward |
1 h |
_news_cache |
finviz_news |
30 min |
_peer_cache |
finviz_peer |
1 h |
_sector_etf_cache |
finviz_sector_etf |
1 h |
收益:统一 TTL 调优入口、共享 janitor 清理、纳入命中率统计。
tickbridge/core/fundamental_mixin.py 的 _fundamental_cache(self-state,紧耦合
_prune_fundamental_cache / _fundamental_cache_lock)暂未迁移;后续如要迁移需改为
基于 kvcache namespace + 删除 mixin 内部 prune 逻辑。
XueqiuFetcher.get_finance_data 之前 6 个独立 HTTP 请求串行执行,单只股票最差 60 秒
(10s timeout × 6)。重构后拆为 6 个 _fetch_*_block 静态方法,
通过 ThreadPoolExecutor(max_workers=6, thread_name_prefix='xq-finance')
并行 fan-out。共享 requests.Session(_ensure_session,pool_maxsize=30)天然
线程安全。预期单股财务取数从 ~30s 降至 ~5-10s。每个 block 独立 try/except,
失败追加 errors[],不影响其他 block。
SocialSentimentService.get_social_context(ticker) 之前只返回格式化文本(供 LLM
prompt 使用)。新增 get_social_raw_data(ticker) 返回 {"reddit": {...}, "x": {...}, "polymarket": {...}} 字典,供 stocklens/pipeline/stages/persistence_stage.py
写入 social_sentiment_snapshot 表。get_social_context 内部委托 get_social_raw_data
后再格式化,保证两条路径数据一致。
_fetch_stock_data_sina / _fetch_stock_data_tx 之前各自包了一层
try/except Exception as e: raise e 的无意义 try/except,已删除。
异常现在自然向上传播到 manager 的 failover 层。
tickbridge/codes/us_index.py:is_us_stock_code加@lru_cache(maxsize=2048)—— 每只股票每次分析会被调用十几次,且 code 集合小,memoize 完美命中。
背景:tickbridge-server 升级为独立服务后,
refresh/tasks/{quote_snapshot,daily_bars,index_quote}.py仍然硬编码AkshareFetcher,没有利用BaseFetcher抽象的多源 fall-over 能力。一旦 akshare 的上游(东方财富 IPv6)抖动或被限流,整个 cohort cycle 阻塞,DB 一直 empty,前端全空。
新增模块,把 _internal/sources/* 下的 fetcher 包装成有序链:
- 构造:
FetcherChain.default_us_chain()读TICKBRIDGE_SOURCE_PRIORITY环境变量(默认xueqiu,akshare,yfinance,openbb,twelvedata),按 fetcher 名实例化。 - 懒加载:每个
_FetcherSlot只在首次调用时实例化,openbb没装也不阻塞守护进程启动。 - per-call 超时:
TICKBRIDGE_FETCH_TIMEOUT(默认 15s),单源单次调用超时即跳到下一个源。 - 冷却:单源连续失败 5 次自动 cooldown 60s,避免被限流的源在并发场景下持续浪费时间预算。
- trade_date 戳印:xueqiu 等不带交易日的 quote 上,链层会戳上 cohort 期望的
trade_date,让 cohort validator 的漂移检查仍能工作。 - 三个公开方法:
try_quote(code, expected_trade_date)/try_index_quote(code, expected_trade_date)/try_daily_bars(code, days, end_date)。 - 观测:
chain.stats()返回{source: {successes, failures, cooled_down}}。
DailyBarsTask / QuoteSnapshotTask / IndexQuoteTask 现在共享 同一个 FetcherChain 实例(default_tasks() 里构造一次),让 cooldown / 统计跨任务累计;任意一个任务把 xueqiu 打到冷却,下一个任务自动跳过 xueqiu 走 akshare/yfinance。
老的硬编码 AkshareFetcher() / _get_us_realtime_quote_from_daily() / _get_us_index_realtime_quote_from_daily() / ak.stock_us_daily 全部替换成 chain.try_*()。data_source 列从 "akshare" 改成 "chain"(具体走的源在日志里看 [chain] loaded fetcher: xxx + DEBUG 级 [chain] xxx.try_*(code) failed: ...)。
tickbridge/refresh/freshness.py 扩展三个新函数:
is_us_market_open(now=None):今天是 NYSE 交易日 + 当前 NY 时间在 09:30-16:00(含 30min 收盘缓冲)。has_committed_cohort_for(trade_date):DB 是否已经有该日期的 committed cohort(直接 SQLSELECT 1 FROM tb_cohort WHERE status='committed' AND trade_date=?)。should_skip_refresh(expected_date=None)二元决策:- 市场开盘 → 永远刷新(盘中行情会变)
- 市场闭盘 + DB 已有目标日 cohort → 跳过整个 cycle(不开新 tb_cohort 行),event bus 推
refresh_skipped - 市场闭盘 + DB 缺数据 → 正常刷新
实战效果:周末/节假日重启 tickbridge-server 时,看到的就是
[refresh] skipping cycle: market closed and DB already has committed cohort for trade_date=2026-05-22
而不是再触发一轮 6700-symbol 重抓。
orchestrator._verify_cross_symbol_dates(outcomes, expected) 在 CohortValidator.validate 之后再扫一遍:每个 task 内按 code 取最大 row date(daily_bars 一只股 5 行历史 K 线,stock_quote_snapshot 一只股 1 行 snapshot),latest != expected 直接整轮 reject。
per-symbol "最大日期" 是关键 — 如果用「每行都校验」会把 daily_bars 的历史回填行(5/18/19/20/21/22 五行)误判为漂移。task 内部已经按"最后一行 date != cohort.trade_date 不入库"做了一道过滤;这里是 belt-and-braces 第二道,防止有 task 后续改逻辑时漏掉漂移分支。
| 环境变量 | 默认值 | 含义 |
|---|---|---|
TICKBRIDGE_SOURCE_PRIORITY |
xueqiu,akshare,yfinance,openbb,twelvedata |
链顺序(逗号分隔) |
TICKBRIDGE_FETCH_TIMEOUT |
15 |
单源单次调用超时(秒) |
TICKBRIDGE_REFRESH_QUOTE_WORKERS |
32 |
quote_snapshot 并发线程数 |
TICKBRIDGE_REFRESH_DAILY_BARS_WORKERS |
32 |
daily_bars 并发线程数 |
TICKBRIDGE_MAX_SYMBOLS |
10000 |
单 cycle 最多刷新的 symbol 数 |
TICKBRIDGE_SYMBOLS |
empty | 显式 symbol 列表(开发/烟测用,覆盖 universe) |
TICKBRIDGE_BOOT_REFRESH |
true |
启动时立刻跑一轮(叠加新鲜度门) |
TICKBRIDGE_REFRESH_PERIOD_SECONDS |
3600 |
周期刷新间隔 |
_default_symbol_supplier 不再直接 import stocklens.*。改为通过 tickbridge.config.settings.register_symbol_supplier(fn) 注入 universe 构建回调。解析顺序:
TICKBRIDGE_SYMBOLSenv var(显式覆盖,开发/烟测用)- 已注册的 symbol supplier 回调(host 应用启动时注入)
- 空列表(daemon 仍运行 index-only 任务)
TICKBRIDGE_MAX_SYMBOLS 对回调返回值做截断。Host 应用(如 StockLens)在启动时调用 register_symbol_supplier(build_universe) 注入 watchlist + ETF + catalog 逻辑。
所有 10 个 stub task 已替换为真实实现。每个 task 遵循两阶段模式:
fetch_to_memory(cohort, symbols)→persist(session, cohort, outcome)。
| Task 类 | kind | 目标表 | 数据源 | 并发 |
|---|---|---|---|---|
DailyBarsTask |
stock_daily |
tb_stock_daily |
FetcherChain (multi-source) | 32 workers |
QuoteSnapshotTask |
quote_snapshot |
tb_stock_quote_snapshot |
FetcherChain | 32 workers |
IndexQuoteTask |
index_quote |
tb_stock_quote_snapshot |
FetcherChain | 4 indices |
MacroTask |
macro |
tb_macro_snapshot |
akshare + yfinance | sequential |
ChipTask |
chip |
tb_chip_distribution_history |
akshare (stock_cyq_em) |
8 workers |
FundamentalTask |
fundamental |
tb_fundamental_snapshot |
yfinance (Ticker.info) |
16 workers |
IntradayBarsTask |
intraday_bars |
tb_stock_bars (interval='60m') |
yfinance (history(interval='1h')) |
16 workers |
FinvizTask |
finviz |
tb_finviz_forward_snapshot |
finvizfinance | 4 workers |
SentimentTask |
sentiment |
tb_social_sentiment_snapshot |
sentiment API / yfinance news proxy | 8 workers |
NewsTask |
news |
tb_market_news_archive |
akshare (Jin10) + yfinance (SPY news) | sequential |
IVTask |
iv |
tb_iv_snapshot |
yfinance options chain | 8 workers |
AnalystTargetTask |
analyst_target |
tb_analyst_target_snapshot |
yfinance (Ticker.info) |
16 workers |
SharesOutstandingTask |
shares_outstanding |
tb_shares_outstanding_snapshot |
yfinance (Ticker.info) |
16 workers |
InstitutionalTask |
institutional |
tb_institutional_snapshot |
yfinance (holders + insider + analyst) | 8 workers |
tickbridge/refresh/tasks/
├── __init__.py 汇总导出 16 个 task 类
├── base.py RefreshTask + TaskOutcome 基类
├── _stubs.py (已清空,保留向后兼容)
├── daily_bars.py DailyBarsTask
├── quote_snapshot.py QuoteSnapshotTask
├── index_quote.py IndexQuoteTask
├── macro.py MacroTask
├── chip.py ChipTask (A-share only)
├── fundamental.py FundamentalTask
├── intraday_bars.py IntradayBarsTask
├── finviz.py FinvizTask
├── sentiment.py SentimentTask
├── news.py NewsTask
├── iv.py IVTask
├── analyst_target.py AnalystTargetTask
├── shares_outstanding.py SharesOutstandingTask
└── institutional.py InstitutionalTask
所有新 task 的 kind 均注册在 tickbridge/refresh/cohort.py:KIND_TIER 中:
- CORE (≥90% 通过率):
stock_daily,stock_weekly,quote_snapshot - AUX (≥70% 通过率):所有其他 task(macro, chip, fundamental, intraday_bars, finviz, sentiment, news, iv, analyst_target, shares_outstanding, institutional, index_quote, market_env, pe_history)
AUX task 允许稀疏覆盖(冷门股票无 Finviz 页面、无期权链、无社交提及等属正常情况)。
- ChipTask 仅适用于 A 股(6 位代码 0/3/6 开头),通过
applicable_symbols()过滤。 - FundamentalTask 存储完整 JSON payload(不拆列),下游按需提取字段,无需 schema migration。
- InstitutionalTask 每只股票产出 1-3 行(13f / insider / analyst 三个 snapshot_type),
succeeded按 symbol 计数而非按行。 - IVTask 包含 max pain 计算(遍历所有行权价,找总损失最小的 strike)。
- NewsTask / MacroTask 不依赖 symbol universe(
applicable_symbols()返回空列表)。 - FinvizTask 并发数故意压低(4 workers)—— Finviz 限流激进。
- SentimentTask 支持外部 API(
TICKBRIDGE_SENTIMENT_API_URLenv var),fallback 到 yfinance news count 作为粗略信号。
- 2026-05 Refresh Tasks 全量实现:
tickbridge/refresh/tasks/{chip,fundamental,intraday_bars,finviz,sentiment,news,iv,analyst_target,shares_outstanding,institutional}.py— 10 个 stub 全部替换为真实上游调用。_stubs.py清空,__init__.py改为直接导入各模块。cohort.py:KIND_TIER新增intraday_bars条目。 - 2026-05 FetcherChain + 新鲜度门:
tickbridge/refresh/{fetcher_chain.py,freshness.py,orchestrator.py,tasks/{quote_snapshot,daily_bars,index_quote}.py}见上节。 - 2026-05 清理死变量:
tickbridge/sources/xueqiu.py:_fetch_raw_data— 原session = self._ensure_session()后从未使用,内部_hit()闭包另起一次self._ensure_session()调用;删除外层死变量。tickbridge/core/chip_mixin.py— 删除未使用的config = get_dp_config()及对应 import。tickbridge/core/fundamental_mixin.py:get_fundamental_context— 删除未使用的start_ts = time.time()(函数内部所有 duration 统计都通过 per-stage_consume_budget(ms)完成,外层start_ts是历史遗留)。