Skip to content

Latest commit

 

History

History
265 lines (195 loc) · 15.6 KB

File metadata and controls

265 lines (195 loc) · 15.6 KB

stocklens/services/ — 业务服务层

职责

业务服务集合,涵盖量化评分、任务队列、系统配置、历史管理、技术指标、行业分析等。

文件清单

文件 职责
scoring/ (子包) 评分引擎薄壳 + ScoringService 主入口:纯算法已下沉到 quantcore.scoring;本子包仅保留向后兼容的 re-export + 注入 CBOE fetcher 的 ScoringService 子类(2026-05 旧的 scoring_service.py 顶层 shim 已删除,统一从 stocklens.services.scoring 导入)
scoring/_cboe_fetcher.py fetch_cboe_options() 网络调用(CBOE 期权链 delayed quotes API),唯一保留在 stocklens 侧的网络函数
task_queue.py AnalysisTaskQueue 单例任务队列(线程池 TASK_QUEUE
task_service.py TaskService 单例任务服务(线程池 TASK_SERVICE
system_config_service.py SystemConfigService 运行时系统配置 + .env 读写(核心服务,545 行)
system_config_validators.py LLM 通道与运行时模型校验(从 service 抽出,约 559 行,纯函数)
history_service.py HistoryService 分析历史查询与管理
history_comparison_service.py 分析历史对比
bollinger_service.py fetch_bollinger_multi_timeframe() — akshare/雪球抓取 + 两级缓存;纯计算(compute_bollinger + 各种 resample)已下沉到 quantcore.indicators.bollinger
finviz_service.py FinvizService 前瞻数据/同行/英文新闻(仅美股)
tech_indicators_service.py compute_correlation() — 通过 akshare 抓取 SPY/QQQ 日线 + 4h 共享缓存;其他纯算法(Fibonacci/ATR/OBV/VWAP/K 线形态/Sharpe/MaxDD)已下沉到 quantcore.indicators.techquantcore.stats
industry_service.py 行业分类与对比
social_sentiment_service.py 社交情绪(仅美股)
stock_service.py 股票综合查询
stock_code_utils.py 股票代码工具
name_to_code_resolver.py 名称 → 代码
image_stock_extractor.py OCR 图片识别股票
import_parser.py 导入数据解析
report_renderer.py 报告 Markdown 渲染
agent_model_service.py Agent 模型管理
analysis_service.py 分析服务薄壳(API 入口)

评分引擎(scoring/)

拆包公告(2026-05):纯量化算法已全部下沉到独立的 quantcore/ 包(见 quantcore.md)。 本子包现在只是一个向后兼容的薄壳:所有 from stocklens.services.scoring.* import * 的旧调用仍然有效, 实际实现位于 quantcore.scoring.*

新代码请直接 from quantcore.scoring import ScoringEngine, ...

stocklens/services/scoring/ 仅保留以下两部分有"实际逻辑"的代码:

文件 内容
__init__.py ScoringService(quantcore.scoring.ScoringEngine) 子类,构造时注入 _cboe_fetcher.fetch_cboe_options,让 ScoringService().evaluate(context) 在拿不到 cboe_data 时自动从 CBOE 抓
_cboe_fetcher.py fetch_cboe_options(symbol) — 唯一保留的网络函数(调 CBOE delayed quotes 公开接口)

其余六个文件(types.py / fundamental.py / valuation.py / sentiment.py / technical.py / engine.py)已经退化为对 quantcore.scoring.* 的纯 re-export shim — 内部不再有业务逻辑。

quantcore 文件 内容(即原 stocklens.services.scoring.* 的实际实现)
quantcore/scoring/engine.py ScoringEngine.evaluate() 主入口 + calc_composite_score() 静态方法
quantcore/scoring/types.py 18 个 dataclass 结果类型 + _sf / _unwrap_block / classify_industry helpers
quantcore/scoring/fundamental.py calc_piotroskicalc_distress(Altman Z + Ohlson O + 综合困境)
quantcore/scoring/valuation.py calc_valuation(含 Rule of 40 + PEG)、calc_beneishcalc_dupontcalc_earnings_qualitycalc_magic_formulacalc_dividend_safety
quantcore/scoring/sentiment.py calc_insider_activitycalc_options_sentiment(纯计算)、calc_momentumcalc_earnings_surprisecalc_short_interest
quantcore/scoring/technical.py calc_sctrcalc_riskcalc_macrocalc_market_regimecalc_peer_rank

主入口

from stocklens.services.scoring.engine import ScoringService
svc = ScoringService()
overview = svc.evaluate(enhanced_context)        # 跑 18 模型
score = ScoringService.calc_composite_score(overview)  # 0-100

综合评分公式

score = macro 25            # 宏观 + 市场状态
      + fundamental 20      # Piotroski / 盈余质量 / DuPont
      + valuation 20        # Rule of 40 / PEG / Magic Formula
      + technical+momentum 15 # SCTR / 12-1m 动量
      + distress 10         # Altman / Ohlson 等
      + sentiment 10        # 期权 / 内部人 / 做空 / 派息

满分 100,被 tests/unit/test_composite_score.py 14 用例锁定行为(边界、单调性、上下限)。

18 个量化模型

模型 函数 说明
Piotroski F-Score calc_piotroski 9 项财务健康信号
Altman Z-Score calc_distress 破产风险
Ohlson O-Score calc_distress 违约概率
Rule of 40 calc_valuation 营收增速 + 净利率
PEG Fair Value calc_valuation 基于增速的合理估值
Beneish M-Score calc_beneish 财务造假检测
DuPont 分析 calc_dupont ROE = 净利率 × 周转 × 杠杆
盈余质量 calc_earnings_quality (NI - OCF) / TA
Magic Formula calc_magic_formula 盈利收益率 + ROIC
派息安全性 calc_dividend_safety 派息率 + FCF 覆盖
机构持股 calc_insider_activity 持股 + 内部人 + 做空
期权情绪 calc_options_sentiment P/C ratio + IV skew
动量 calc_momentum 12-1m 动量
盈利惊喜 calc_earnings_surprise SUE
做空 calc_short_interest days to cover
SCTR calc_sctr 技术综合评分
风险指标 calc_risk Sharpe / Sortino / MaxDD
宏观 + 市场状态 calc_macro + calc_market_regime 利率 / VIX / 趋势

关键服务详情

AnalysisTaskQueue(task_queue.py)

进程内单例,异步任务队列。

  • submit(...) -> task_id
  • get_task(task_id) / list_tasks()
  • cancel(task_id)
  • 状态:pending → running → completed / failed / cancelled
  • 线程池 TASK_QUEUE(默认 cpu_count,可调)

TaskService(task_service.py)

API 友好的任务服务封装,单例。线程池 TASK_SERVICE

SystemConfigService(system_config_service.py)

运行时系统配置:

  • get_all() — 按分类分组
  • set(key, value) — 写入 .env 同时更新内存
  • set_batch(updates)
  • get_field_meta(key) — 字段元数据(来自 config/registry.py
  • validate(key, value)
  • get_llm_status()

bollinger_service.py

fetch_bollinger_multi_timeframe(stock_code) 编排多达 7 个时间框架的布林带(1H / 2H / 4H / Daily / Weekly / Monthly / Yearly)。

  • 抓取(仅本文件做):akshare stock_us_daily 取日线;60m kline 通过 stocklens.data_service.LegacyManagerShim.fetch_bars() 获取(Xueqiu HTTP 调用封装在 shim 层)。
  • 缓存(仅本文件做):kvcache bollinger namespace,4 小时 TTL。
  • 计算(委托 quantcore):
    • quantcore.indicators.bollinger.compute_bollinger(df, timeframe) — 单周期 BB
    • quantcore.indicators.bollinger.resample_to_{weekly,monthly,yearly}(df) — 周期 resample
    • quantcore.indicators.bollinger.resample_hourly(df_60m, hours) — 60m → 2H/4H
    • quantcore.indicators.bollinger.update_position_signal(result, daily_price, label) — 用日收盘统一 current_price

返回 quantcore.indicators.MultiBollingerSnapshot(dataclass,含 to_prompt_text() / has_data())。

FinvizService(finviz_service.py)

仅美股:

  • get_forward_data(code) — 前瞻 PE / PEG / EPS 预期 / 目标价 / 评级
  • get_peer_comparison(code) — 同行对比表(动态发现)
  • get_news(code, limit) — 英文新闻

tech_indicators_service.py

compute_correlation(stock_code, df) — 与 SPY/QQQ 的相关性 / Beta / Alpha:

  • 抓取(仅本文件做):akshare stock_us_daily 取 SPY/QQQ 日线,4h 模块级缓存(_bm_cache)。
  • 计算(委托 quantcore.stats.compute_correlation_pair):成对返回序列 → CorrelationResult。

其余纯计算(compute_tech_indicators / detect_swing_points / select_fibonacci_swing / 各 dataclass)已下沉到 quantcore.indicators.tech。本文件仅 re-export 兼容旧导入路径。

HistoryService / HistoryComparisonService

  • 历史查询、按代码/日期范围筛选、删除、统计
  • 历史信号对比(同股 N 次分析的差异)

SocialSentimentService

仅美股;接入第三方社交情绪源(受配置 SOCIAL_SENTIMENT_* 控制)。

依赖关系

  • 依赖:configstoragerepositoriestickbridgesearchmarketutils.concurrency
  • 被依赖:pipelineapiagent

注意事项

  • ScoringService 的评分是确定性的(纯 Python 计算),LLM 不可修改
  • Finviz 数据通过 finvizfinance 库抓取,仅支持美股;可 lazy import
  • AnalysisTaskQueueTaskService 都是单例,import 即获取
  • SystemConfigService 修改配置会同时写入 .env 文件和内存
  • 布林带分析的小时级数据通过日线数据模拟(非真实分时数据)
  • 评分引擎被 246 单测中的 50 用例覆盖(types + composite score)
  • fail-open 日志(2026-05):服务层(bollinger_service / image_stock_extractor / import_parser 等)所有"退化到次路径"的 try/except 统一调 stocklens.utils.fail_open.fail_open(logger, op, exc),输出格式 [fail-open] <op>: <ExcType>: <msg>。新增 fail-over 路径请直接 import 这个 helper,不要手写 logger.debug("silent except ...")

性能优化(2026-05)

  • industry_service.fetch_industry_snapshot 并行化:sections 4-9(insider trades / analyst ratings / description / peer tickers / etf holders / news)从 6 次顺序 finvizfinance 调用 改为 ThreadPoolExecutor(max_workers=4) 并行执行,预计单股节省 4-8 秒墙钟时间。Finviz 站点 的 throttle(tickbridge/domain_sources/finviz._REQUEST_INTERVAL=0.5s)控制了实际并发度。
  • scoring/_cboe_fetcher.py 改用 httpx.Client 单例_get_client() 模块级 client 通过 httpx.Limits(max_keepalive_connections=8, max_connections=16) 复用连接, 消除每只美股期权情绪评分时的 TCP+TLS 握手开销。

history_service 拆分(2026-05)

stocklens/services/history_service.py(881 行)的 Markdown 报告渲染逻辑 (_generate_single_stock_markdown 234 行 + 4 个静态 helper)抽离到新文件 stocklens/services/history_report_renderer.py

函数 职责
render_single_stock_markdown(result, record) 顶层入口,编排 5 个 section
_render_intel_block / _render_core_conclusion / _render_data_perspective / _render_battle_plan / _render_legacy_flat_sections 各自独立的 section builder
escape_md / clean_sniper_value / safe_format_number / get_signal_level / append_market_snapshot_to_report 模块级 helper(含纯函数版的 signal-level 5-band 映射)

HistoryService._generate_single_stock_markdown 现在只是一个委托薄壳; 旧的 in-class 实现保留为 _generate_single_stock_markdown_legacy(紧急回滚用, soak 期后删除)。HistoryService 本身保持数据访问职责(query / pagination / news intel lookup),与渲染解耦。

与 stocklens/integrations 的关系(2026-05)

stocklens/utils/feishu_doc.pyFeishuDocManager(飞书云文档发布)原本错误地 放在 utils/ 下,已迁移到新建的 stocklens/integrations/feishu_doc.pyintegrations/ 是新增子包,专门承载第三方 SaaS SDK 的 wrapper:飞书 / Notion 等)。utils/feishu_doc.py 保留为 backward-compat shim(from stocklens.integrations.feishu_doc import FeishuDocManager), 现存调用点零改动。

M11 — name_to_code_resolver 缓存迁移到 kvcache(2026-05)

name_to_code_resolver.py 此前用模块级 _akshare_cache: Optional[tuple[float, Dict[str, str]]] = None + _AKSHARE_CACHE_TTL = 3600 这种 Dict + time.time() 模式,违反了 CLAUDE.md 中「禁止手写 TTL 缓存」的项目规则。

修复:

  1. stocklens/utils/cache_bootstrap.py 注册新 namespace name_to_code(TTL=3600s,policy=stale_while_revalidate,max_entries=4)。
  2. _get_akshare_name_to_code() 改为通过 kvcache.get_manager().namespace("name_to_code") 读写;_fetch_akshare_name_to_code() 拆出无缓存的真实 fetch 实现给 caller 包。
  3. kvcache 不可用(manager 没 configure 的测试场景)时 fallback 直接调 _fetch_akshare_name_to_code(),保持单元测试可运行。

公开 API(resolve_name_to_code(name))零改动。

G2 — services/scoring_labels.py(新增,2026-05)

把过去散在 4 个文件的「评分→标签/Emoji/tag」映射收口为单模块:

函数 用途 历史调用点
get_sentiment_label(score) 英文 5-band 标签(API 响应用) analysis_service._get_sentiment_label / history_service._get_sentiment_label
get_signal_level_en(score) → (label, emoji, tag) 英文 markdown 渲染 history_report_renderer.get_signal_level
get_signal_level_zh(advice, score) → (text, emoji, tag) 中文推送渲染(advice 优先,score 兜底) report_renderer._get_signal_level

旧函数全部成为新模块的 1 行 delegate(保留以满足向后兼容)。新增加 band 或调整阈值只需要改这一个文件。31 个回归测试 (tests/unit/test_scoring_labels.py) 锁住所有 band 边界 + advice/score 兜底逻辑。

G3 — image_stock_extractor._normalize_code 委托给 stock_code_utils(2026-05)

原本 image_stock_extractor.py 内联 18 行的 _normalize_code(A 股 6 位 / HK 5 位 / US 1-5 字母 / .SH/.SZ 后缀),与 stock_code_utils.normalize_code 几乎一致但缺少 SH600519 / HK00700 这种 exchange-prefix 形式。改为 1 行 delegate _shared_normalize_code(raw),行为等价但现在多识别 prefix 形式。

G4 — industry_service.fetch_industry_snapshot 拆解(2026-05)

原 330 行的 god function 拆成:

角色 函数
编排器 fetch_industry_snapshot (~30 行,4 步顺序调用)
Section 1 (fundamentals) _fetch_ticker_fundament + _populate_ticker_fundament + _TICKER_FUNDAMENT_FIELDS
Section 2 (sector) _fetch_sector_overview + _populate_sector_row + _row_to_sector_payload
Section 3 (peers) _fetch_peer_companies + _format_market_cap
Sections 4-9 (并行) _fetch_industry_subsections(保留 ThreadPoolExecutor,6 个内部 closure)

_TICKER_FUNDAMENT_FIELDS 是 60 元组的 (snapshot_attr, finviz_key, coerce_blank) 表,替换原 80 行 snapshot.x = info.get(...) 样板。新增字段直接往 tuple 里加一行。

G5 — task_queue / task_service 文档化分工(2026-05)

两个 task 服务的 ergonomics 不同(API+SSE+轮询 vs Bot 会话回复 fire-and-forget),不强行合并。在 task_service.py 模块头部加了「Why this exists alongside task_queue」段落,明确告诉新调用者优先用 task_queue.get_task_queue(),仅当需要 bot 会话回复路径时才走 TaskService