围绕 querybus.SearchProvider 的股票领域薄壳:股票代码识别(A股 / 港股 / 美股 / ETF / 指数)、市场感知的查询模板、5 维度情报并发搜索、新闻时效策略 → news_window_days、价格兜底搜索。
通用搜索能力(cache / fall-over / 时间窗过滤 / 通用情报报告渲染)已经下沉到 querybus,此处不重复。
| 文件 | 职责 | 行数 |
|---|---|---|
stock_search_service.py |
StockSearchService 主类 + STOCK_INTEL_DIMENSIONS + 进程级 singleton (get_stock_search_service / configure_stock_search_service / reset_stock_search_service) |
~580 |
__init__.py |
重新导出 StockSearchService + SearchResponse / SearchResult(来自 querybus) |
~25 |
旧的 1135 行
search_service.py已在 querybus 拆分(Commit 3)中整体删除,不留 shim;所有调用点已直接改造为新 API。
from querybus import SearchProvider
from stocklens.search import StockSearchService
provider = SearchProvider(tavily_keys=[...], ...)
service = StockSearchService(
provider=provider,
news_max_age_days=3,
news_strategy_profile="short", # ultra_short / short / medium / long
)search_stock_news(stock_code, stock_name, max_results=5, focus_keywords=None) -> SearchResponse- A 股 / 港美股自动切换中英文 query 模板
- 走
provider.search()→ cache → manager → filter
search_stock_events(stock_code, stock_name, event_types=None) -> SearchResponse- 默认事件:港美股 =
earnings report / insider selling / quarterly results,A 股 =年报预告 / 减持公告 / 业绩快报
- 默认事件:港美股 =
search_comprehensive_intel(stock_code, stock_name, max_searches=3) -> Dict[str, SearchResponse]- 5 维度并发:
latest_news / market_analysis / risk_check / earnings / industry - ETF / 指数 vs 个股自动切 query
- 失败时自动放宽窗口到 7 天再试一次
- 结果跨维度去重(标题前 50 字符为 key)
- 用
STOCKLENS_SEARCH_WORKERS控制并发(默认 5)
- 5 维度并发:
format_intel_report(intel_results, stock_name) -> str- 委托给
querybus.SearchProvider.format_intel_report+STOCK_INTEL_DIMENSIONS
- 委托给
search_stock_price_fallback(stock_code, stock_name, max_attempts=3, max_results=5) -> SearchResponse- 数据源全挂时的 web 兜底搜索
search_stock_with_enhanced_fallback(...)— 组合 news + price 的便利方法format_price_search_context(response) -> str— 兜底结果 → AI prompt 上下文
_is_foreign_stock(code) -> bool— 港股 / 美股识别(/^[A-Za-z]{1,5}(\.[A-Za-z])?$/+hk前缀 + 5 位纯数字)is_index_or_etf(code, name) -> bool— A 股 ETF 前缀 + 美股指数 (is_us_index_code) + 港美股 ETF 名称关键词
from stocklens.search import (
configure_stock_search_service, # 启动时安装实例
get_stock_search_service, # 业务代码读取(带懒加载兜底)
reset_stock_search_service, # 配置 reload 后清空
)API 层在 api/app.py 的 lifespan 中调用 configure_stock_search_service(...);业务代码(agent 工具、pipeline)通过 get_stock_search_service() 读取。CLI / Bot 入口若没经过 lifespan,第一次调用 get_stock_search_service() 会自动从 get_config() 构造一个备选实例。
STOCK_INTEL_DIMENSIONS = [
DimensionSpec(key="latest_news", label="📰 Latest news"),
DimensionSpec(key="market_analysis", label="📈 Analyst view"),
DimensionSpec(key="risk_check", label="⚠️ Risk check"),
DimensionSpec(key="earnings", label="📊 Earnings outlook"),
DimensionSpec(key="industry", label="🏭 Industry view"),
]如果未来要换 emoji / 顺序,只改这一处。
- 依赖:
querybus、tickbridge.codes.us_index.is_us_index_code、stocklens.config.settings、stocklens.utils.concurrency.get_pool_workers - 被依赖:
stocklens/pipeline/orchestrator.py— 流水线编排时构造一次stocklens/pipeline/data_collector.py/analysis_flow.py—is_index_or_etf静态分类stocklens/agent/tools/search_tools.py—build_search_tools(service)DI 工厂stocklens/services/system_config_service.py— config reload 时调reset_stock_search_service()api/app.pylifespan — 启动时安装为进程级 singleton
| 变量 | 默认值 | 说明 |
|---|---|---|
TAVILY_API_KEYS |
空 | Tavily 搜索 Key(逗号分隔,多 key 负载均衡) |
SERPAPI_API_KEYS |
空 | SerpAPI Key |
BOCHA_API_KEYS |
空 | 博查搜索 Key |
MINIMAX_API_KEYS |
空 | MiniMax Key |
BRAVE_API_KEYS |
空 | Brave Search Key |
SEARXNG_BASE_URLS |
空 | SearXNG 实例 URL 列表 |
NEWS_MAX_AGE_DAYS |
3 | 新闻最大时效 |
NEWS_STRATEGY_PROFILE |
short | 时效策略 (ultra_short=1d / short=3d / medium=7d / long=30d) |
STOCKLENS_SEARCH_WORKERS |
5 | 多维度并发线程数 |
- 不配置任何搜索 Key 也能运行:querybus 默认开启 Akshare 兜底(东方财富免费新闻)。
- 多 Key 配置(逗号分隔)由 querybus 的
BaseSearchProvider处理负载均衡 + 错误计数,业务层无需感知。 - 新闻过滤会丢弃日期未知的结果(宁缺毋滥),但
filter_by_time_window会在数量不够时把 undated 结果标记为"recent (date unknown)"补足。 - 不要直接
from querybus.providers.* import—— CLAUDE.md 已禁止;所有股票业务一律走StockSearchService。
StockSearchService.batch_search(stocks, ..., max_concurrency=2) 替代之前的纯串行 + 1s sleep 实现:
- 默认
max_concurrency=2:用ThreadPoolExecutorfan-out,每线程在调用前sleep(delay_between/max_concurrency)维持上游限流礼貌 max_concurrency=1:完全等价于旧的 strict-sequential 行为(向后兼容)- 10 只股票的
batch_search总耗时从 ~10s 降至 ~3s(max_concurrency=2 时)