Skip to content

Latest commit

 

History

History
313 lines (238 loc) · 14.9 KB

File metadata and controls

313 lines (238 loc) · 14.9 KB

stocklens/analyzer/ — LLM 分析器

职责

构建 Prompt、调用 LLM 生成决策仪表盘 JSON、解析响应。通过 LiteLLM Router 支持多模型负载均衡和 fallback。

文件清单

文件 职责
llm_analyzer.py GeminiAnalyzer 主类,LLM 调用与响应处理(via LiteLLM Router)
prompt_builder.py PromptBuilder Prompt 组装,system + user prompt
response_parser.py JSON 提取与修复(json_repair
result_types.py 分析结果数据类,字段校验与填充
trend_analyzer.py 薄壳 — 仅 re-export quantcore.indicators.trend.{StockTrendAnalyzer, TrendSnapshot};纯算法已下沉到 quantcore
__init__.py 导出

核心类

GeminiAnalyzer(llm_analyzer.py)

LLM 分析器主类(名称历史遗留,实际支持所有 LLM)。

初始化

analyzer = GeminiAnalyzer(config: Config)
# 内部初始化 LiteLLM Router(从 channels/YAML/legacy key)

关键方法

  • analyze_stock(code: str, name: str, metadata: dict) -> dict — 单股 AI 分析

    • 流程:
      1. PromptBuilder.build() 组装 prompt
      2. LiteLLM Router.completion() 调用 LLM
      3. ResponseParser.parse() 提取 JSON
      4. ResultTypes.validate_and_fill() 校验填充
      5. 保存到 analysis_results
    • 返回: 决策仪表盘 dict(包含 sentiment_score, trend_prediction, dashboard 等)
    • 超时: 默认 6000s
    • 重试: 默认 2 次
  • _init_router() -> litellm.Router — 初始化 LiteLLM Router

    • 优先级: LiteLLM YAML > 多通道 > Legacy 单 Key
    • 日志: 打印已初始化的 deployment 数量和模型列表
  • _call_llm(system_prompt: str, user_prompt: str) -> str — 底层 LLM 调用

    • 参数: temperature, max_tokens=8192
    • 返回: LLM 原始响应文本

PromptBuilder(prompt_builder.py)

Prompt 组装器。

关键方法

  • build(code: str, name: str, metadata: dict) -> Tuple[str, str] — 构建 system + user prompt

    • 返回: (system_prompt, user_prompt)
    • system_prompt: 分析师角色定义 + 评分规则 + 输出 JSON 格式
    • user_prompt: 股票数据 + 量化评分 + 新闻情报
  • _build_system_prompt() -> str — 系统提示词

    • 角色: 专业投资分析师
    • 核心规则: 评分不可修改、仓位映射表
    • 分析框架: 5 步(确认评分→解读模型→判断环境→技术择时→操作建议)
    • 布林带多周期框架
    • 输出 JSON 格式定义
  • _build_user_prompt(code, name, metadata) -> str — 用户提示词

    • 包含的数据段:
      • 股票基础信息(代码/名称/日期)
      • 今日行情(OHLCV)
      • 均线系统(MA5/10/20 + 形态)
      • 实时行情增强(量比/换手率/PE/PB/市值)
      • 市值趋势(30 天变化)
      • PE 历史百分位
      • 布林带多周期(7 个时间框架)
      • 财报与分红
      • 成长指标
      • Finviz 前瞻数据
      • 同行对比
      • 趋势分析数据(MA50/MA200/MACD/RSI/BB Squeeze)
      • 斐波那契回撤
      • 量价变化
      • 大盘环境
      • 宏观环境
      • 新闻情报
      • 量化模型评分总览表
      • 重点关注问题(5 个必答题)
      • 决策仪表盘要求

ResponseParser(response_parser.py)

LLM 响应解析器。

关键方法

  • parse(response_text: str) -> dict — 从 LLM 响应中提取 JSON

    • 尝试顺序:
      1. 直接 json.loads()
      2. 提取 ```json ... ``` 代码块
      3. 使用 json_repair 库修复
    • 返回: 解析后的 dict
  • extract_json_block(text: str) -> Optional[str] — 提取 JSON 代码块

ResultTypes(result_types.py)

分析结果数据类 + LLM 输出后置校验/纠偏函数。

关键方法(按调用顺序)

pipeline/stages/llm_stage.pypipeline/analysis_flow.py(Agent 路径)调用顺序: fill_chip_structure_if_needed → fill_price_position_if_needed → enforce_trend_status_consistency → enforce_score_band_consistency → enforce_stop_loss_consistency

函数 作用
check_content_integrity(result) 检查必填字段(sentiment_score / operation_advice / dashboard.core_conclusion / risk_alerts / stop_loss)是否齐全
apply_placeholder_fill(result, missing) 用占位字符串补齐缺失字段(弱模式兜底)
enforce_stop_loss_consistency(result) battle_plan.sniper_points.stop_loss 为权威源,把 has_position 文本里不一致的止损价改写成同一个数字
enforce_trend_status_consistency(result, trend_result) trend_result.golden_cross 为权威,纠正 LLM 在 Death Cross 数据下错误返回的 is_bullish=true 与 "多头排列" 描述。返回被纠正的字段名列表
enforce_score_band_consistency(result) sentiment_score 区间硬性校准 operation_advice / trend_prediction:40-59 区间禁止"加仓/买入",0-19 区间强制"卖出/强烈看空",反向规则同理
fill_chip_structure_if_needed(result, chip_data) 用 ChipDistribution 数据补齐 LLM 未返回的筹码字段
fill_price_position_if_needed(result, trend_result, realtime) 用趋势/实时数据补齐 price_position 中的 MA5/10/20/50/200/current_price

StockTrendAnalyzer(trend_analyzer.py — 薄壳)

拆包公告(2026-05):本文件已退化为 quantcore.indicators.trend 的纯 re-export shim。 实际类名是 StockTrendAnalyzer(旧文档里写的"TrendAnalyzer"是简称)。新代码请:

from quantcore.indicators import StockTrendAnalyzer, TrendSnapshot

2026-05 删除stocklens.analyzer.trend_analyzer re-export shim 已下线,请直接 from quantcore.indicators.trend import ...

趋势分析器,纯 Python 计算(不依赖 LLM)。

关键方法

  • analyze(df: pd.DataFrame, realtime_quote: dict = None) -> dict — 完整趋势分析

    • 返回:
      {
          "ma50": float,           # MA50 值
          "ma200": float,          # MA200 值
          "price_vs_ma50_pct": float,  # 价格偏离 MA50 百分比
          "price_vs_ma200_pct": float, # 价格偏离 MA200 百分比
          "ma_cross": str,         # "Golden Cross" / "Death Cross" / "无交叉"
          "ma50_slope": float,     # MA50 斜率(%/日)
          "rsi_14": float,         # RSI(14)
          "macd": dict,            # {dif, dea, macd_bar, cross}
          "bb_squeeze": bool,      # 布林带收窄
          "bb_trend": float,       # MA20 斜率
          "volume_analysis": dict, # 量能分析
          "relative_strength": dict, # 相对强度 vs SPY
      }
  • _calculate_rsi(series: pd.Series, period: int = 14) -> float — RSI 计算

  • _calculate_macd(series: pd.Series) -> dict — MACD 计算

  • _analyze_volume(df: pd.DataFrame) -> dict — 量能分析(量比/放量天数/连续放量)

  • _calculate_relative_strength(df: pd.DataFrame) -> dict — 相对强度 vs SPY

依赖关系

  • 依赖:litellmjson_repairpandasconfigstorage(AnalysisRepo, LLMUsageRepo)
  • 被依赖:pipeline(orchestrator.py)

配置项

变量 默认值 说明
LITELLM_MODEL 自动推断 主模型
LLM_TEMPERATURE 0.7 采样温度
LITELLM_FALLBACK_MODELS 备用模型

注意事项

  • GeminiAnalyzer 名称是历史遗留,实际通过 LiteLLM Router 支持所有 LLM
  • LLM 返回的 sentiment_score 必须与量化评分引擎计算的一致,Prompt 中有强制规则
  • json_repair 库用于修复 LLM 输出的不规范 JSON(缺少引号、多余逗号等)
  • LLM 用量会自动记录到 llm_usage 表(model/tokens/cost/latency)

Anthropic prompt caching(2026-05 新增)

_call_litellm 检测到 anthropic/ 系列模型时(包括 bedrock/anthropic.*openrouter/anthropic/...),把 system 消息打包成 list-of-blocks 并附加 cache_control: {"type": "ephemeral"}。LiteLLM 透传该字段;非 Anthropic provider (Gemini / OpenAI / DeepSeek 等)静默忽略,向后兼容。

# 触发条件由 _supports_prompt_caching(model) 判定
system_content = [
    {
        "type": "text",
        "text": SYSTEM_PROMPT,  # ~5000 chars / ~1500 tokens
        "cache_control": {"type": "ephemeral"},
    }
]

usage 字典额外暴露 cache_creation_input_tokens / cache_read_input_tokens 用于 审计。日志在命中缓存时会输出 [LiteLLM] anthropic/claude-X prompt cache hit: NNN tokens

预期效果:批量分析 30 只股票时,第 1 次调用为缓存创建(成本略高),第 2 次起 system 提示 部分按缓存读取计费(Anthropic 缓存读取 ≈ 输入 token 价格的 10%),整体输入 token 成本 下降约 70-90%。Gemini 通过 implicit context caching 自动获得类似收益,无需 client 标记。

LLM 输出一致性纪律(Prompt + Python 双保险)

由于 LLM 偶发自相矛盾(典型表现:Death Cross 数据下输出 is_bullish=true / 评分 57 输出"加仓"),系统采用 Prompt 显式约束 + Python 后置纠偏 双保险:

  1. Prompt 端prompt_builder.py):

    • 趋势分析表中明确写出 is_bullish 应取的布尔值,并在表后用块引用强调"权威结论"。
    • 评分区间块根据当前 sentiment_score 仅展开命中的区间,并写出"必须 / 严禁"措辞(如 40-59 严禁"加仓",仓位上限 ≤ 2 成)。
    • RSI 缺失(None / 0)时显示 N/A(数据不足,请勿引用具体数值),避免 LLM 把 dataclass 默认 0.0 当成"极度超卖"。
    • 估值结论必须引用同行业排名,对高增长股优先引用 Forward PE / PEG,禁止"PEG 低估 + 估值偏高"自相矛盾的描述。
  2. Python 端result_types.py):

    • enforce_trend_status_consistency 在 LLM 返回后对照 trend_analyzer 算出的 golden_cross 校准 dashboard.data_perspective.trend_status.{is_bullish, ma_alignment}
    • enforce_score_band_consistencysentiment_scoreoperation_advice / trend_prediction 做硬一致性,偏离区间直接降级为安全默认(如"观望/持有")。
    • 两个函数都返回被纠正的字段列表,触发 WARNING 日志便于观测 LLM 出错频率。
    • 单测见 tests/unit/test_result_consistency.pytests/unit/test_prompt_builder.py

文件拆分与方法迁移(2026-05)

G13 — SYSTEM_PROMPT 抽到独立文件(2026-05)

180 行的 system prompt 之前作为 GeminiAnalyzer.SYSTEM_PROMPT 类常量内嵌在 llm_analyzer.py 顶部。现在迁移到 stocklens/analyzer/system_prompt.py

  • prompt 工程师调整提示词不需要碰 Python 文件
  • 阅读 llm_analyzer.py 直接看到 LLM 调用 / 重试 / 解析逻辑,不再被 prompt 内容遮蔽
  • prompt 可以被测试 / dashboard / 文档 import,无需实例化 GeminiAnalyzer

向后兼容:GeminiAnalyzer.SYSTEM_PROMPT = _SYSTEM_PROMPT 保留为 1 行 re-export, 任何 GeminiAnalyzer.SYSTEM_PROMPT 引用照常解析。

llm_analyzer.py 998 → 812 行(-19%)。

prompt_builder 分段提取

prompt_builder.py_format_prompt(337 行单方法)保留主编排逻辑,把 6 个独立段落 抽离到新模块 stocklens/analyzer/prompt_sections.py

函数 段落
build_realtime_section 实时行情富化(量比 / 换手率 / PE/PB / 市值 / 60d 涨幅)
build_mv_trend_section 美股 30 天市值变化
build_chip_section A 股筹码分布表
build_yesterday_section 量价昨日对比
build_us_market_env_section / build_us_macro_section US 市场环境 / 宏观快照
build_systemic_risk_guidance 系统性 vs 个股风险区分指引
build_data_missing_section 数据缺失警告

每个 builder 是无状态纯函数(接受 context: dict + 必要的格式 helper),便于单测。

三个 prompt 方法从 llm_analyzer 迁回 prompt_builder

_supports_prompt_caching / _build_system_message_content / _build_integrity_complement_prompt / _build_integrity_retry_prompt 之前散在 llm_analyzer.py(842 行)中,本质都是 prompt 构建辅助,已迁移到 prompt_builder._PromptMixinGeminiAnalyzer 通过 mixin 继承访问,调用方零改动。 迁移后 llm_analyzer.py 只剩 LLM 路由 / 重试 / 解析职责,prompt 相关全在 prompt_builder.py

M1+M2+M6+M12 — sync/async 去重 + prompt sections 续抽(2026-05)

Background

analyze / aanalyze 此前几乎逐字重复整个分析主循环(~350 行); _call_litellm / _acall_litellm 同理重复 ~80 行。_format_prompt 仍有 ~700 行内联段落构建。

改动

llm_analyzer.py 抽出 7 个共享 helper

Helper 用途
_resolve_stock_name(context, code) 三段式名称兜底(context → realtime → STOCK_NAME_MAP)
_unavailable_result(code, name) 无 API key 时返回 dashboard-合规默认 result
_error_result(code, name, exc) 兜底异常时返回默认 result
_decorate_result(result, ...) 写 raw_response / market_snapshot / model_used + 调 apply_quant_score_override
_next_integrity_action(...) 决定「停 / 重试 / placeholder fill」三态
_build_call_plan(prompt, gen_cfg) 解析 config + 生成 fallback model 列表
_build_call_kwargs(model, ...) / _should_use_router(...) / _apply_direct_call_params(...) / _extract_usage(response, model, async_tag) LLM 调用的 4 个共享步骤

analyzeaanalyze 现在只差 (i) time.sleep vs await asyncio.sleep (ii) self._call_litellm(...) vs await self._acall_litellm(...)_call_litellm_acall_litellm 同理。

result_types.py 新增 apply_quant_score_override(result, quant_score) — 把原本在 analyze/aanalyze 中各自重复 25 行的 score → trend/advice 映射收口为单一函数,与其他 enforce_* 函数并列。

prompt_sections.py 新增 4 个 builder(M6)

Builder 段落
build_financial_report_section 财报快照 + TTM 派息(value-investing)
build_growth_quarterly_section 成长指标 + 最近 4 季报告
build_finviz_forward_section Finviz 前瞻数据(profitable / unprofitable 双路径)
build_finviz_changes_section 前瞻数据变更信号(vs 上次分析)
build_peer_comparison_section 同行对比表

prompt_builder.py 从 865 行降到 651 行(-25%),_format_prompt 主体几乎只剩 对 prompt_sections.build_* 的顺序调用。

影响

  • 任何 LLM 行为调整(新 telemetry 字段 / 新重试规则 / 新一致性 enforcer)只需要改一处。
  • apply_quant_score_override 现在是 score band → 决策映射的唯一入口;测试只用断言这一个函数,不再需要分别 mock sync/async 两条路径。
  • 新加 prompt 段落只需要写一个 build_xxx 函数 + 在 _format_prompt 加一行;不再编辑 700 行的巨大 _format_prompt
  • 全部测试(unit + 包级)518/518 通过。