大模型不是水晶球。它不会「算出」明天涨跌,也不会替你承担风险。它真正擅长的是:把一堆结构化数据、新闻与规则,归纳成一份有结构、能执行、可解释的分析稿。理解这一点,你才能理解 DSA 为什么要在 Prompt 和守卫上花这么多功夫。
类比:一位很会写报告的分析师
你雇了一位分析师,他读过很多书、文笔极好,但没有实时行情终端。你要做的是:把行情、指标、新闻、市场背景整理好交给他,告诉他「按这个模板写,不许编数据,拿不准就写观望」。他写得漂亮不代表结论正确,但至少不会因为缺少上下文而胡说。Prompt 就是这份工作说明书。
LLM 在 DSA 里到底负责什么
它负责
- 把技术面、基本面、新闻、市场阶段归纳成结论。
- 按固定 JSON 契约表达,让前端、推送、回测都能复用。
- 生成可执行的作战计划:入场区间、止损、目标、检查清单。
- 解释「为什么」,包括风险与反方证据。
它不负责
- 计算指标:均线、MACD、RSI 都由代码先算好。
- 获取行情:数据来自适配器,模型看不到「实时接口」。
- 保证收益:任何结论都可能错,且不构成投资建议。
- 最终拍板:规则守卫与风险否决保留最后一道闸。
这种分工是刻意设计的:确定性的事交给代码,模糊的事交给模型,危险的事交给规则。三者叠加,才有可能得到「既好读又不太离谱」的输出。
接入层:像插座一样换模型
src/llm/ 定义了一个统一后端协议 GenerationBackend,核心只有一个 generate() 方法。上层只认这个协议,不关心底层是 LiteLLM、Codex CLI 还是 Claude Code CLI。
| 后端 ID | 用途 | 工具调用 | 适用场景 |
|---|---|---|---|
litellm | 统一调用 100+ 供应商 | 支持 | 默认,分析 + Agent 都走它 |
codex_cli | 本地 CLI 文本生成 | 不支持 | 实验性,本地环境 |
claude_code_cli | 本地 CLI 文本生成 | 不支持 | 实验性,本地环境 |
opencode_cli | 本地 CLI 文本生成 | 不支持 | 实验性,本地环境 |
Agent 的工具调用必须走 LiteLLM,因为只有它声明了 tools 能力。这个约束不是随意写的:本地 CLI 后端只负责「产出一段文本」,无法可靠地按函数名与参数返回工具调用。
每个错误都带分类(超时、空输出、非法 JSON、schema 校验失败、工具不支持、需要登录等),并标记是否可重试、是否可回退。上层据此决定「等一下再试」还是「换模型」,而不是所有错误都无脑重试。
模型与配置:三层优先级
DSA 不直接枚举所有供应商,而是交给 LiteLLM。配置按优先级从高到低是:
| 渠道 | 协议 / 接入方式 | 典型配置 |
|---|---|---|
| Modelflare / AIHubMix | OpenAI 兼容聚合 | MODELFLARE_KEY / AIHUBMIX_KEY |
| Gemini | Google 原生 | GEMINI_API_KEY + GEMINI_MODEL |
| DeepSeek / 通义等 | OpenAI 兼容 | OPENAI_API_KEY + OPENAI_BASE_URL |
| Anthropic | 原生 | ANTHROPIC_API_KEY |
| Ollama | 本地 OpenAI 兼容 | 本地地址 + 模型名 |
分析模型由 LITELLM_MODEL 指定,回退列表由 LITELLM_FALLBACK_MODELS 指定;Agent 可以用 AGENT_LITELLM_MODEL 单独指定,留空则继承全局。温度由 LLM_TEMPERATURE 给出基线,再按模型能力归一。
Prompt 解剖:五段式「任务书」
主分析 Prompt 很长,但结构清晰。可以把它拆成五段:
先定义「你是谁、对谁说话」。系统会注入市场角色(A 股 / 港股 / 美股等)、报告语言与输出风格。角色不是装饰,它决定了模型引用哪些本地概念:A 股可以谈涨跌停、北向资金;美股谈这些就会显得荒谬。
把风险纪律写进系统提示词。例如:MA5 乖离率超过 5% 不追高、优先多头排列、资金流不明时倾向观望。这些规则同时存在于代码守卫里——Prompt 让模型「知道」,守卫让系统「做到」。
喂结构化事实,而不是一堆散乱文本。行情表、均线与量能、筹码、新闻情报、市场阶段、市场结构、上下文包摘要,都会以带标题的文本块注入。缺失的块会明确标注,而不是留白让模型脑补。
定义严格的 JSON 输出。核心六大块:core_conclusion、data_perspective、intelligence、battle_plan、phase_decision、signal_attribution,约 25 个文本字段,每个字段都有枚举或格式示例。这样前端、通知、信号抽取才能稳定消费。
最后写「不许做什么」。不得因单日涨跌在买卖之间翻转;盘前不得编造当日走势;数据 stale 时置信度不得为高;不得在缺少证据时宣称龙头。这些是行为红线,不是写作建议。
下面是改写后的结构示意,不是仓库原文。读真实 Prompt 时,重点看它的分段与约束,而不是逐字背诵。
[角色]
你是一名 {market} 投资分析师,产出「决策仪表盘」。
只依据下方数据判断;数据缺失必须明确说明。
[交易纪律]
- MA5 乖离率 > 5%:严禁追高
- 优先 MA5 > MA10 > MA20 的多头排列
- 资金流不明时,倾向持有 / 观望
[输入数据]
行情表(OHLCV、量比、换手)
均线 / MACD / RSI / 乖离率
筹码分布 / 基本面 / 新闻情报
市场阶段 / 当日大盘上下文 / 市场结构
[输出契约]
{
"core_conclusion": { "one_sentence": "...", "signal": "buy|hold|sell", ... },
"data_perspective": { ... },
"intelligence": { "risk_alerts": [], "catalysts": [] },
"battle_plan": { "sniper_points": {...}, "action_checklist": [] },
"phase_decision": { ... },
"signal_attribution": { ... }
}
[稳定性约束]
- 不得因单日涨跌在买卖间翻转
- 盘前不得描述当天尚未发生的走势
- 数据 stale 时,置信度不得为 high
上下文包:把一只股票装进一个信封
模型的上下文窗口有限,90 天日线不可能全塞。DSA 用「上下文包」做有损压缩,至少包含:
技术面
趋势状态、均线排列、MACD/RSI、乖离率、量能、关键支撑压力。
基本面
财务快照、估值、行业归属、资金流;按市场能力标注缺失。
情报
新闻、公告、舆情、催化与风险;来自搜索服务并去重。
市场背景
市场阶段、指数涨跌、市场宽度、板块强弱。
历史对比
与上一次分析的结论、评分、关键位变化。
质量元数据
每个数据块的状态与来源,决定模型该多自信。
每个子块都有 schema 版本与质量状态。版本不匹配或标记为 not_supported 的块会被跳过,避免旧数据或错误字段污染判断。这听起来很工程化,但它直接决定了模型会不会「拿错事实说对话」。
结构化输出:从「散文」到「可消费的数据」
如果模型返回一段自由文本,下游只能展示,不能回测、告警、做信号抽取。DSA 要求 JSON,并准备了一条完整的降级链:
| 阶段 | 做什么 | 失败后 |
|---|---|---|
| 提取 | 从响应中找出 JSON 对象,去掉前后解释 | 尝试修复 |
| 修复 | json-repair 处理尾逗号、缺引号、注释 | 判为无效 |
| 契约校验 | 检查关键字段与类型是否存在 | 触发完整性重试 |
| 完整性重试 | 默认 1 次,要求补全缺失字段 | 占位填充 |
| 文本兜底 | 按正负关键词给保守分,标记 success=False | 该结果不按正常分析落库 |
这里的取舍很现实:宁可要一份带缺失字段的可用报告,也不要因为一个字段缺失就整轮失败。但兜底结果必须被标记,不能被当成正常结论继续传播。
后置守卫:LLM 出稿,规则定稿
模型最容易犯的错不是「不会写」,而是「写得太顺」。它会把一根阳线描述成趋势反转,把接近压力位的追高说成突破确认。守卫的职责就是把这些漂亮话拉回价格事实。
| 规则 | 阈值 / 条件 | 动作 |
|---|---|---|
| 破位 | 现价 / 支撑 < 0.985 | 提高风险等级,压制买入 |
| 贴近支撑 | 现价 / 支撑 ≤ 1.03 | 卖出建议需有重大风险证据,否则降为持有 |
| 突破 | 现价 / 压力 > 1.01 | 才算有效突破,结合量能与资金流 |
| 贴近压力 | 现价 / 压力 ≥ 0.97 | 买入若缺资金流入,降为持有/观望 |
| 评分校准 | hold / watch | 分数夹到 45–59,避免「观望 85 分」 |
| 阶段约束 | 盘前 / 数据 stale | 不得描述未发生的走势,置信度不得为高 |
这些阈值不是「市场真理」,而是项目选择的风险偏好。它们可以被讨论、被调整,但必须被显式写出来,而不是藏在模型的即兴判断里。
一句话总结
LLM 负责「写得像分析师」,守卫负责「别像疯了的分析师」。两者缺一不可。
多模型回退:把「模型挂了」变成可恢复事件
分析侧会按「主模型 + 回退列表」依次尝试。失败原因不同,处理也不同:
- 超时 / 限流:同一 provider 内可以退避重试;跨 provider 直接切换,因为限流桶互不相干。
- JSON 不合法:判定该模型本次失败,切下一个;不会拿坏 JSON 硬解析。
- 流式失败:可退回非流式再试一次。
- 参数不支持:部分模型只接受默认温度,系统能识别报错并自动修复参数,只重试一次。
多 key 场景下,LiteLLM 的 Router 可以做简单负载均衡,并对失败请求重试;但最终能否成功,仍取决于你配置的模型质量与额度。
参数:温度、窗口与「别乱发挥」
| 参数 | 建议 | 原因 |
|---|---|---|
| 温度 | 低(如 0.1–0.3) | 分析任务要稳定、可复现;高温适合创意写作,不适合风控 |
| 上下文窗口 | 按模型能力控制输入长度 | 太长会增加成本与「中间遗忘」,太短会丢关键证据 |
| 超时 | 分析 300 秒级别可配 | 长 Prompt 的首 token 延迟较高,过短会误杀 |
| 最大输出 | 按 JSON 契约估算 | 过小会截断 JSON,直接触发解析失败 |
有些模型固定温度、不支持某些参数。系统会从报错文本识别并缓存修复结果,避免每次调用都踩同一个坑。
Token 与成本:钱花在哪
一次单股分析的输入通常包含行情表、指标、新闻、市场背景与很长的系统提示词;输出是一份结构化 JSON。总体量级通常在几千到数万 token,取决于股票数量、新闻长度、上下文包与模型。Agent 多轮问股会更高,因为每一轮都要带历史与工具结果。
降低成本的三件事
- Prompt 缓存:系统提示词与稳定前缀可被供应商缓存,按更低价计费。
- 控制上下文:上下文包做有损压缩,避免把整篇新闻塞进去。
- 选对模型:分析任务不一定需要最贵的旗舰模型;回退模型可以用更便宜的。
用量与隐私
- 每次调用记录 prompt/completion/total 与缓存命中,按 provider 归一。
- 消息内容只保留 HMAC 指纹,不把完整 Prompt 明文写进遥测。
- Web「Token 用量」页可按模型/时间查看消耗。
风险清单:LLM 分析最容易翻车的五件事
① 幻觉
编造不存在的新闻、财务数字或「机构观点」。缓解:只喂结构化证据、要求引用、后置校验。
② 数据过期
拿旧价格当现价。缓解:质量状态、stale 标记、禁止高置信度。
③ 提示注入
新闻正文里可能藏着「忽略上述指令」之类的文本。缓解:把新闻当数据、限定证据范围、规则守卫;但没有系统能 100% 免疫。
④ 过度自信
语言流畅让读者误以为结论可靠。缓解:统一评分刻度、风险提示、置信度约束。
⑤ 单日翻转
昨天买、今天卖、明天又买。缓解:稳定性约束与守卫。
⑥ 成本失控
批量分析 + 多轮回退把额度烧光。缓解:用量统计、回退模型、预算护栏。
模型返回了一段看起来合理、但 JSON 少了关键字段的响应。系统最可能怎么处理?