DSAWiki
04系统 · 核心

一次分析,究竟发生了什么

跟随一只股票,从行情请求走到推送到你手机上的那份决策仪表盘。

约 10 分钟 进阶 第 04 章 · 系统

这一章跟着一只股票走完全程:从「今天该不该跑」开始,到数据、指标、新闻、Prompt、模型、守卫、落库、推送。每一站都告诉你它为什么存在,以及失败时会发生什么。

先记住一个判断

DSA 的可靠性不来自「模型很聪明」,而来自把不确定性切成很多小段,每段都允许降级。行情失败用收盘价,基本面超时用缺失标记,模型 JSON 坏了换模型,结论太激进用规则压回来。整条链路的设计哲学是:不追求完美输入,只追求可解释的次优输出。

全景:八站流水线

01预检preflight交易日 · 断点续传
02取数fetch行情 · 筹码 · 基本面
03指标technicalMA · MACD · RSI
04情报news新闻 · 公告 · 舆情
05上下文context阶段 · 大盘 · 结构
06LLM 分析analysisPrompt → JSON 契约
07守卫guard支撑压力 · 资金 · 阶段
08交付deliver报告 · 信号 · 推送

这条线由 StockAnalysisPipeline.analyze_stock() 串起来,外层是 run() 的线程池与批次调度。下面按站点拆解。

预检:今天该跑吗?这只股票能跑吗?

流水线真正开始前,先做三件「不产生分析内容、但决定是否继续」的事:

1
交易日判断

按股票所属市场查交易所日历。A 股休市就跳过 A 股,港股、美股各自判断;--force-run 可强制。指数并入 cn 市场语义。

2
断点续传

如果 SQLite 里已经有该股票当日行情,抓数步骤可以直接跳过;整轮运行会冻结一个「目标交易日」,避免跨零点时前后数据日期不一致。

3
股票身份归一

代码、名称、市场、资产类型(股票/指数/ETF)被解析成结构化目标。裸 6 位默认股票,显式指数才会进入指数路径。

失败会怎样

交易日不匹配 → 跳过该股票并记录原因;股票列表为空 → 整轮失败并写 empty_stock_list;单个代码解析失败 → 只影响该目标,不阻断同批其他股票。

取数:把「现在」和「过去」都拿到

一只股票需要四类输入。它们来自不同供应商,失败策略也不同:

数据用途失败策略
实时行情盘中最新价、涨跌幅、量比,替换最后一根日线失败退回最近收盘价,并标注数据来源
近 90 天日线均线、MACD、RSI、支撑压力、历史对比多源降级;仍失败则该股无法分析
筹码与资金获利盘、集中度、主力资金流不支持/超时 → 字段标记缺失,继续
基本面财务快照、估值、板块归属、公司事件独立预算(默认约 8 秒),超时降级

日线通常已落库,所以这里同时承担「增量更新」职责:先查当天是否已有数据,没有才走网络。数据源的选择与降级细节见数据源与降级。

失败会怎样

实时价失败不是致命错误;日线失败会触发多源切换;基本面超时会留下「数据缺失」标记,Prompt 会明确告诉模型「这块没有」,而不是让模型自由发挥。

指标:先算出一份确定性观点

StockTrendAnalyzer 用 pandas/numpy 计算均线、MACD、RSI、乖离率与量能,输出趋势状态、买入信号、0–100 的技术分和风险因素。这个分数不依赖 LLM,是模型之外的第二意见。

维度分值看什么
趋势30均线排列、MA20 方向、趋势强度
乖离率20离 MA5/MA10/MA20 的距离,MA5 阈值默认 5%
量能15量比、放量/缩量、与均量比较
支撑10价格与均线/关键位的距离
MACD15金叉/死叉、零轴位置、柱体变化
RSI10超买超卖、多周期一致性

总分映射为买入信号(强买/买入/观望/卖出等)。盘中可选「实时价叠加」:把最后一根收盘价替换为最新价,让均线更贴近当下;代价是数据未收盘,稳定性略差。

为什么先算指标,再让 LLM 分析?

因为 LLM 对数字的敏感度不稳定。先把均线、MACD、RSI、乖离率算成明确的结构化字段,再喂给模型,模型的任务从「计算」变成「解释与综合」。这是降低幻觉的关键工程手段。

情报:给模型一双看新闻的眼睛

新闻不是「越多越好」,而是「越相关越好」。系统会围绕股票名称、代码、行业与事件,最多发起约 5 组检索,把新闻、公告、舆情汇总成情报上下文;可选接入社交舆情(如 Reddit/X/Polymarket,部分市场)。

  • 命中的新闻会落库,后续可以复用,避免每轮重复抓取。
  • 没有配置搜索源时不会报错,只是情报为空——报告的风险与催化部分会更保守。
  • 新闻内容会作为「外部证据」进入 Prompt,并在某些路径里参与证据约束,减少模型凭空编造催化。

失败会怎样

搜索服务不可用 → 跳过情报,报告仍能生成;单条新闻抓取失败 → 丢弃该条;社交舆情未配置 → 不进入上下文。整个阶段是 fail-open 的。

上下文:把「一只股票」放进「今天的市场」

只看个股很容易误判:大盘暴跌时,再漂亮的多头排列也可能被砸穿。上下文装配会把至少五层信息拼在一起:

市场阶段

盘前、盘中、午休、收盘竞价、盘后。盘前不能假装知道今天怎么收盘。

当日大盘上下文

指数涨跌、市场宽度、板块强弱,给个股一个「背景板」。

市场结构

龙头、题材、资金风格;没有成分股证据时,不允许模型宣称「龙头」。

上下文包

把技术面、基本面、情报、数据质量打包成结构化摘要,控制长度与优先级。

这些片段不是简单拼接,而是各自带 schema 版本与质量状态。旧版本或 not_supported 的块会被跳过,避免脏数据污染 Prompt。

Prompt 的本质是「有损压缩」

90 天日线不可能全塞进去,必须压缩成均线、MACD、关键位、量能与结论。压缩得好,模型抓重点;压缩得差,模型只能猜。上下文包的工程价值就在这里。

LLM:按 JSON 契约生成决策仪表盘

分析器把系统提示词、行情表、技术指标、筹码、新闻、市场阶段拼成一条完整请求,要求模型返回严格 JSON。请求不是一次性的:

1
模型队列

主模型 + 回退模型依次尝试;主模型超时、限流、JSON 不合法都会切下一个。

2
流式与非流式

优先流式,能更早发现空响应;流式失败可退回非流式。

3
解析与修复

先提取 JSON 对象,用 json-repair 修常见格式问题;再做最小契约校验(关键字段是否齐全)。

4
兜底

完全无法解析时走文本解析,给一个保守分数并标记 success=False;这类结果不会作为正常分析落库。

模型输出包含六大块:核心结论、数据透视、情报、作战计划、阶段决策、信号归因。每个字段都有枚举或格式约束,方便前端、通知与 DecisionSignal 复用。

失败会怎样

所有模型都失败 → 该股分析失败,但同批其他股票继续;JSON 缺字段 → 有完整性重试,仍缺则用占位文本而不是崩溃;模型只返回散文 → 文本解析保底,但可信度下降。

守卫:让结论「落地」而不是「飘着」

这是 DSA 与其他「把股票数据丢给 GPT」的项目最不一样的地方。模型给出结论后,至少还有四道确定性检查:

守卫检查什么典型动作
结构守卫现价相对支撑/压力的位置:破位 <0.985、近支撑 ≤1.03、突破 >1.01、近压力 ≥0.97在压力位附近买入但资金没流入 → 降为持有/观望;在支撑位附近卖出但无重大风险 → 降为持有
阶段守卫盘前/盘中/收盘竞价/盘后,能说什么盘前不允许编造当日走势;数据 stale 时不允许高置信度
大盘守卫当日市场上下文与个股结论是否冲突大盘系统性风险下,压低激进买入
评分校准持有/观望的分数区间把 hold/watch 分数夹到 45–59,避免「观望 85 分」这种自相矛盾

买入还要看资金流:没有资金流入的「突破」会被降级,资金流出且未突破的「强势」也会被降级。这些规则不保证赚钱,但能显著减少「模型被一根阳线冲昏头」的概率。

一句话理解

LLM 负责写「分析稿」,守卫负责当「风控编辑」。稿件可以精彩,但不能越过红线。

交付:落库、报告、信号与推送

分析成功后,系统会做四件事:

  1. 写历史:整份结果序列化进 analysis_history,Web、回测、历史对比都从这里读。
  2. 抽信号:把结论转成 DecisionSignal,带动作、评分、周期、入场区间、止损、目标价与过期时间。
  3. 生成报告:默认在通知层拼装 Markdown;开启 REPORT_RENDERER_ENABLED 时使用 Jinja2 模板按平台渲染。
  4. 推送:按渠道能力分片、降噪、转图片,再逐个发送;单个渠道失败不影响其他渠道。

批量模式下,单股报告会先汇总成「决策仪表盘」,再与大盘复盘合并推送;同时本地 reports/ 会保留一份 Markdown。

失败会怎样

落库失败 → 标记 report_save_failed;所有通知渠道失败 → 报告仍在本地;某个渠道超时 → 状态记为 partial_failed,其他渠道照常。

报告长什么样:六个问题,一份答案

一份完整报告本质上在回答六个问题:发生了什么?证据是什么?结论是什么?在哪里买/卖?风险在哪?下一步做什么? 对应到结构大致是:

报告区块回答的问题谁消费
摘要与评分这只股票今天值不值得看?推送首屏、仪表盘
情报速览有什么新闻、业绩、催化、风险?人、后续追问
核心结论买/卖/观望,理由是什么?决策信号、回测
数据透视行情、均线、量能、筹码怎么组合?人、调试
作战计划狙击点、止损、目标、检查清单实际执行
信号归因 / 历史对比为什么是现在?和上次有什么不同?复盘、策略改进

评分不是「上涨概率」

统一的五档刻度是:80–100 强烈买入、60–79 买入、40–59 观望、20–39 减仓、0–19 卖出。它衡量的是「当前证据下建议动作的强度」,不是「明天涨的概率」。把它当概率用,是初学者最常见的误读。

DecisionSignal:把「观点」变成「可追踪的承诺」

报告是给人看的,信号是给系统追踪的。每份分析会抽取一条结构化信号,核心字段包括:动作、评分、持有周期、入场区间、止损价、目标价、过期时间、来源报告。它有三个好处:

  • 可回测:过期后可以对照真实价格,评估这次建议是否有效。
  • 可告警:价格触发入场/止损时,告警中心可以通知你。
  • 可复盘:同一只股票的历史信号形成一条决策轨迹,而不是散落在聊天记录里。

注意「会过期」是特性不是缺陷。三天前的买入区间,今天可能已经完全失效;不设过期时间的信号,等于鼓励你刻舟求剑。

批量与并发:三只股票同时跑,但不互相拖累

默认 MAX_WORKERS=3,这是一个刻意的保守值:数据源大多免费或有限流,并发太高容易触发封禁。批量执行时:

  • 每只股票独立处理,单只异常被捕获,不影响同批其他结果。
  • 股票数量较多时会尝试批量预取行情,减少重复请求。
  • 进度通过回调上报,Web 端用 SSE 实时显示,不必轮询。
  • 成功结果才会进入汇总与推送;失败结果会出现在日志与诊断里。

并发调高不是「更快更好」。如果你的数据源是 Tushare 这类有明确频率限制的服务,贸然开 10 个线程只会让整批一起限流。

失败学:一份「不会全军覆没」的清单

如果……系统会……你该……
某个数据源挂了按优先级切下一个,连续失败 3 次熔断 5 分钟看数据质量页,必要时配更强数据源
LLM 超时切回退模型;仍失败则该股失败降低并发、换更稳的模型或缩短 Prompt
JSON 不合法json-repair → 契约校验 → 完整性重试 → 文本兜底看日志里的模型名与错误类型
盘中进程重启已落库的当日数据可跳过抓取;目标交易日冻结直接重跑,通常能续上
定时任务重复触发文件锁 / 原子认领尽量保证至多一次确保只有一个调度入口与共享数据库
自测一下

模型给出「买入」,但现价已经非常接近压力位,且资金流没有明显流入。结构守卫最可能做什么?

原因:结构守卫会用现价与支撑/压力的比例关系判断风险。接近压力位且没有资金流入支撑的「买入」,会被降为 hold/watch;分数还会被夹到 45–59,避免出现「观望但 85 分」的矛盾。它不会删除报告,而是在模型结论上加一层风控。

源码与文档锚点