我投资美股多年,回报率一直不太行。信息渠道太多,分不清哪些该看、哪些是噪音。干脆自己动手做了个工具:Ward。基于市场数据和 AI 大模型,把实时行情、技术分析、AI 解读、即时问答整合在一起,降低决策成本。
本文分两个部分:功能设计讲这个工具解决什么问题、怎么用;技术实现讲怎么做 AI 和 Agent 设计、上下文管理。

功能设计
我看美股的三个核心需求
作为一个普通投资者(非专业交易员),我盯美股主要就三件事:
1. 指数行情:三大指数(Nasdaq、道琼斯、标普 500)的实时涨跌幅和 K 线 2. 个股研究:关注个股的 60 日 K 线、近期走势、AI 分析报告 3. 即时问答:对页面上的行情数据、AI 分析报告随时提问,让 AI 帮忙解读和对比
Ward 就是围绕这三个需求设计的。主页三个指数卡片,每个卡片可以展开 60 日 K 线(Canvas 绘制)和 AI 分析;底部是智能问答区,直接聊。
AI 分析报告:看数据,也要读懂数据
指数卡片和个股页面都有一键「🧠 AI 分析」入口。点击后,后端拉取 60 日 K 线数据(开盘价、最高价、最低价、收盘价、成交量),组装进 prompt,LLM 生成一篇结构化分析报告。
报告内容包括:
整体趋势判断 — 上涨、下跌还是震荡整理,给出明确的定性结论
关键价位识别 — 支撑位(价格可能在此企稳)、压力位(继续上涨可能遇到阻力),基于近期高低点和成交量变化推断
走势特征描述 — 是突破型走势还是回调走势?均线多头排列还是空头排列?近期波动率偏大还是偏小?
成交量配合分析 — 涨跌时成交量是否放大(量价配合)还是缩量(量价背离),判断趋势是否可靠
与历史对比 — 当前位置处于历史区间的什么分位数,和自身相比算强势还是弱势
用户不需要懂技术分析,直接看报告就能对一个指数或个股的基本面和技术面有个全局判断。
每日市场报告:开盘前的一站式复盘
主页顶部有一个独立的「生成市场报告」按钮,点击后生成一份当日的综合市场报告,涵盖四大模块:
今日行情概述 — 用 2-3 句话定性当日整体市场环境
主要指数表现 — Nasdaq 综合、Nasdaq 100、标普 500、道琼斯,以表格形式呈现涨跌幅和当前点位
技术面分析 — 均线位置(MA5/MA20/MA60 的关系)、短期趋势判断、关键支撑压力位、量能配合情况
市场情绪评分 — 基于当日新闻标题,LLM 给出 1-9 分的情绪评分(1=极度恐慌,9=极度乐观),并列出市场最关注的 3 个核心议题
这份报告的设计初衷是:每天开盘前花 1-2 分钟看完,就能对当日市场环境有个基本判断,不需要在多个数据源之间来回切换。
智能问答:不是 API 对接,是跨卡片信息聚合
这是 Ward 和普通 AI Chatbot 最大的区别。
普通的 AI 问答就是用户发一句"英伟达最近走势如何",后端把这条消息发给 LLM,LLM 基于自己的训练数据回答。LLM 不知道英伟达今天涨了多少、成交量是多少、处于什么技术形态。
Ward 的智能问答做了信息聚合:用户发一条消息,后端会同时拉取六类数据:
1. 三大指数实时行情(价格、涨跌幅、成交量)2. 用户查看过的个股实时行情3. 三大指数的 60 日 K 线数据4. 用户查看过的个股的 60 日 K 线数据5. 用户生成的指数 AI 分析报告6. 用户生成的个股 AI 分析报告这六类数据按需组装进 system prompt,后端会优先把用户当前页面已加载的数据注入上下文。LLM 看到的上下文大致是这样的:
当前市场数据:- Nasdaq 综合:{price} ({change_pct}%)- 道琼斯:{price} ({change_pct}%)- 标普 500:{price} ({change_pct}%)个股行情:- NVDA:${price},涨幅 {change_pct}%,成交量 {volume}- TSLA:${price},涨幅 {change_pct}%,成交量 {volume}...指数AI分析报告(用户最近生成过):---# Nasdaq 综合 AI 分析近期走势呈上升趋势,突破了 60 日均线...# 标普 500 AI 分析目前处于震荡整理阶段,支撑位 5000...---用户问题:{user_message}也就是说,LLM 看到的信息和用户在页面上看到的是同步的。用户如果刚才在指数卡片上点过「AI 分析」,切换到问答页问"纳斯达克现在处于什么趋势",AI 已经有完整的分析报告可以参考。
这不是简单的 API 对接,是以用户当前页面状态为上下文的实时信息注入。
核心功能一览
指数卡片
• 实时价格、涨跌幅 • Canvas 绘制的 60 日 K 线 • 一键 AI 分析(调用 LLM 生成技术分析报告)
个股搜索
• 输入股票代码查询 • 显示 60 日 K 线 • 提供 AI 分析入口
智能问答
• 整合页面各卡片信息,自动注入上下文 • 基于指数行情、个股数据、AI 分析报告回答问题 • 支持多轮上下文记忆
对话历史
• 所有对话自动存入 SQLite • 支持切换历史对话 • 持久化,不刷新丢失
技术实现
AI 的两种用法:工具调用 vs 信息解读
在 Ward 里,AI 有两种不同的使用模式,理解这两种模式的区别对后续的 prompt 设计很关键。
模式一:工具调用(指数/个股 AI 分析)
点击「🧠 AI 分析」时,流程是这样的:
1. 后端拉取 60 日 K 线数据(OHLCV) 2. 组装进 prompt,让 AI 输出结构化 Markdown 报告
这里 AI 扮演的是分析工具的角色,输入是结构化的金融数据,输出是格式确定的 Markdown。Prompt 的核心任务是告诉 AI:数据的含义、关注哪些指标、输出格式是什么。
这部分 prompt 大致长这样(简化版):
你是一位专业美股技术分析师。请基于以下 K 线数据,对 {index_name} 进行技术分析。数据格式:[日期, 开盘, 最高, 最低, 收盘, 成交量]---{data}---分析需包含:1. 整体趋势判断(上升/下降/震荡)2. 关键支撑位和压力位3. 近期走势特征4. 成交量配合情况输出格式为 Markdown,支持加粗、表格、列表。这个 prompt 的设计原则是给结构而非给结论——不告诉 AI 应该看出什么,而是告诉它用什么框架来分析。分析质量很大程度取决于 K 线数据是否干净、prompt 是否覆盖了足够的分析维度。
Prompt Schema 设计:约束输出稳定性
AI 分析报告的输出如果格式飘忽,页面渲染就会乱掉。Ward 在 prompt 层做了几件事来约束输出稳定性:
1. 明确的输出结构
prompt 里直接列出必须包含的章节:
分析报告必须包含以下四个部分,每个部分用 ## 二级标题开头:## 整体趋势## 关键价位## 走势特征## 成交量分析这样即使 AI 偶尔走神,输出也大概率有这四个部分。
2. 数据格式声明
prompt 开头声明输入数据的格式:
K 线数据格式:[日期, 开盘, 最高, 最低, 收盘, 成交量]以下是近 60 个交易日的 K 线数据:---{data}---这解决了数据来源不明确的问题,AI 知道这些数字是从哪来的、代表什么含义。
3. 第一行锚点
要求输出的第一行必须是 # 标题。前端渲染时会检查第一行是否符合预期,不符合就降级处理(比如只显示纯文本),不会整个页面挂掉。
4. 输出格式白名单
prompt 里明确说"输出格式为 Markdown,支持加粗、表格、列表",而不是"可以输出任意格式"。限定了可选范围,减少 AI 自由发挥的空间。
这四点本质上都是 Harness 的输出格式强约束思路——不是消灭随机性,而是把随机性约束在不会破坏页面结构的范围内。
模式二:信息解读(智能问答)
智能问答的核心不是多轮对话,而是帮用户理解页面上的信息。
用户在主页看到的东西很多:三大指数的实时涨跌、K 线走势、AI 分析报告、个股行情。这些信息散落在各个卡片里,用户可能知道"今天 Nasdaq 涨了"但不清楚"为什么涨"、"现在处于什么趋势"、"和历史相比算不算异常"。
智能问答的作用就是整合这些信息,用自然语言回答用户关于这些数据的提问。比如:
• "纳斯达克今天为什么涨了这么多?" • "苹果和英伟达目前哪个走势更强?" • "标普500的压力位在哪?"
关键在于,这个模式不需要用户重复描述上下文——后端已经自动把用户当前页面上展示的所有信息聚合进 prompt,AI 直接基于这些数据回答,不需要用户先把行情数据复述一遍。
三层上下文的设计与取舍
智能问答的上下文有三重,每一层都要考虑信息量和 token 消耗之间的取舍。
当前实现:简单的滑动窗口
目前的实现比较直接——从数据库里按时间倒序拉取最近 N 条问答(默认 20 条),拼进 prompt 尾部。如果总 token 数超了 context window,就从头砍问答对,直到能塞进去。
问答历史:[Q1,A1], [Q2,A2], ..., [Q_N,A_N] ← 拉最新的 N 对系统上下文:三大指数实时行情 + 个股行情 + AI分析报告用户当前问题:{这条}这个做法有两个明显问题:
1. 粒度不公平:一对完整的问答占用一个槽位,但有的问答内容很少、有的分析报告很长,粒度不均导致 token 消耗不稳定 2. 语义截断:直接砍掉开头的问答对,可能把相关上下文拆散,AI 看到问题却找不到对应的背景
这是最简单的实现方式,能跑,但有明显优化空间。
未来改进方向
方案一:基于语义相似度的上下文压缩
不做简单的滑动窗口,而是把每条历史消息做 embedding,存进向量数据库。问答时,把用户新问题做 embedding,在历史消息里做相似度搜索,只召回最相关的 Top N 条。
这样做的好处是:无论对话多长,保留的都是语义上最相关的信息,不受消息边界和长度的影响。但代价是多了 embedding 存储和向量检索的延迟,以及每次都要重新做相似度计算。
方案二:分层记忆(Hierarchical Memory)
模拟人类记忆的方式,把上下文分成三层:
短期记忆:最近 5 条对话(完整保留)中期记忆:每天/每周的市场摘要(压缩后的结构化描述)长期记忆:用户明确说过的重要偏好(如"我主要看科技股")短期记忆用滑动窗口,中期记忆由 AI 自己在每周末生成摘要("本周 Nasdaq 先涨后跌,波动较大"),长期记忆由用户主动设定或 AI 从对话中抽取。
这个方案的优点是符合人类认知模型,缺点是实现复杂,需要定期触发摘要生成。
方案三:ReAct + Tool Calling 主动获取信息
当前的方案是"先把所有上下文塞进去,让 AI 自己判断"。另一个思路是让 AI 主动决定需要什么信息:
用户:NVDA 最近走势如何?LLM 思考:我需要知道 NVDA 最近的 K 线数据和 AI 分析报告 → 调用 get_stock_kline(symbol="NVDA", period=60) → 调用 get_ai_analysis(symbol="NVDA") → 基于返回结果生成回答这个方案的优点是上下文不膨胀,AI 只取自己需要的;缺点是延迟高(多轮 tool calling),而且 AkShare 的接口格式需要标准化才能做可靠的 tool description。
目前三个方案里,分层记忆最符合 Ward 的产品形态(持续使用、多次分析),语义压缩实现成本最低,ReAct 最适合扩展到更多数据源。后续会优先尝试分层记忆。
第一层:对话历史(SQLite 持久化)
对话历史存储在 SQLite 里,每次请求时后端会把最近 N 条消息捞出来放进 prompt。
早期版本用 OFFSET/LIMIT 分页,但数据量大时性能很差(SQLite 的 OFFSET 是跳过而不是跳过,还是会扫描)。后来改成游标分页,用消息 ID 做锚点,每次只查 WHERE id < {cursor} ORDER BY id DESC LIMIT N,性能好很多。
消息写入时同时更新 conversation 的 updated_at 字段,用于对话列表按时间排序。
第二层:实时市场数据
每条用户消息发出后,后端会先拉取相关标的的实时行情,拼进 system prompt:
当前市场数据:- Nasdaq 综合:{price} ({change_pct}%)- 道琼斯:{price} ({change_pct}%)- 标普 500:{price} ({change_pct}%)个股:{stock_name} 当前价 {price},涨幅 {change_pct}%,成交量 {volume}问题是实时数据对所有标的都要拉,不能只拉用户问的那几只。解决方案是:后端记录当前页面展示的所有标的(三大指数 + 最近搜索的 5 只个股),消息进来后批量拉取,统一塞进 context。
第三层:指数 AI 分析结果
这是最有意思的部分。Ward 有一个跨功能联动设计:用户在指数卡片上点击「AI 分析」,分析报告不仅展示在卡片下方,还会同步写入对话历史的 assistant 消息。
这意味着:切换到智能问答页面时,AI 已经"读过"之前的分析结果,可以直接讨论。
技术实现是这样的:
用户点击「AI 分析 Nasdaq」 → 后端生成分析报告(Markdown) → 同时写入 SQLite:role=assistant, content=分析报告 → 前端刷新问答区,从 SQLite 拉历史(包括刚写入的分析) → 下次用户问「纳斯达克现在处于什么趋势」,AI 能感知到之前的分析这样做的好处是用户不需要重复描述上下文——分析页面看到的东西,问答页面直接能用。
Agent Harness:约束 AI 行为的几个实践
用 LLM 做工具型 Agent 和纯对话型 Agent 的一个核心区别是:你需要主动约束 Agent 的行为,否则它随时可能产出不符合预期的结果。
Ward 在这块做了几个设计:
1. 输出格式强约束
在 Prompt Schema 设计那节已经展开过,核心是四点:明确章节结构、数据格式声明、第一行标题锚点、输出格式白名单。这是在 prompt 层约束随机性的主要手段。
2. 错误时主动降级
AI 服务可能因为 API 限流、网络波动等原因失败。Ward 的处理是:AI 分析失败时在前端显示"分析生成失败,请稍后重试";问答失败时显示"抱歉,AI 服务暂时不可用"。不暴露原始错误信息给用户。
更进一步的降级策略:AI 服务不可用时,可以用规则引擎做基础回答(根据涨跌平判断"今天涨了/跌了/持平"),这样即使 LLM 挂了核心功能还能跑。
3. 数据来源的 tool calling(未做)
目前 AkShare 接口直接在 Python 端调用,没有包装成 LLM 可调用的工具。如果要做更规范的 tool calling,需要先把 AkShare 数据标准化,再用 yfinance 这类返回规范格式的库做替代。
未完成的后续想法
Ward 目前大概 70% 完成度。从 Harness 的视角看,目前缺了几块重要的实践,补上这些才能让 Agent 真正可控。
Worker / Reviewer 角色分离
现在 Ward 的 AI 分析是单 Agent 输出:prompt 进,Markdown 出,没有任何校验机制。如果 prompt 设计有偏差,或者 LLM 突发奇想改变了输出风格,页面就会渲染异常。
改进方案是拆成两个角色:
• Worker:接收 K 线数据和 prompt,生成 Markdown 分析报告 • Reviewer:读取 Worker 的输出,根据预设标准打分(格式是否正确、技术指标是否齐全、语气是否专业),不合格就打回重写
Reviewer 不应该看到 Worker 的思考过程,只看最终输出和任务目标。这样可以避免 Reviewer 继承 Worker 的生成偏见,站在新的起点上做独立判断。
从 Harness 的角度看,这是最重要的一块补全。
外部 Tool Calling:智能问答应该能自己去找答案
目前智能问答的信息聚合是"后端先拉好、prompt 里塞进去",LLM 只能消费已有的数据。如果用户问的是页面之外的东西,比如"特斯拉最近有什么新消息"或者"英伟达下周三财报预期怎么样",LLM 就会瞎猜或者直接说不知道。
一个重要的发展方向是:让智能问答拥有主动获取信息的能力。
具体来说,就是把数据接口封装成 LLM 可调用的工具:
get_stock_quote(symbol: str) -> {price, change_pct, volume, open, high, low}get_stock_news(symbol: str) -> [{title, source, time}]get_market_sentiment() -> {score: 1-9, topics: [...]}search_stock(keyword: str) -> [{symbol, name, exchange}]当用户问"特斯拉最近有什么新消息"时,LLM 会自动规划:
用户问:特斯拉最近有什么新消息?LLM 思考:我需要调用 get_stock_news(symbol="TSLA") 获取特斯拉的最新新闻 → 调用工具,获取新闻标题列表 → 基于新闻内容生成回答这个方向的核心思想是:Agent 应该能自己规划调用哪些工具来完成任务,而不是每次都把所有数据塞进上下文。它解决了当前"页面有什么就问什么"的限制,让智能问答真正变成一个可以探索和发现信息的入口。
实现这个方向的前提是先把 AkShare 和 yfinance 的接口标准化,封装成有明确输入输出定义的工具。
明确的停止条件
AI 分析目前没有停止条件——LLM 生成完就直接返回。如果质量不及格,没有自动重试的机制。
应该给每次分析加一个 Reviewer 打分环节,分数低于阈值就自动重写(最多 N 次),达到上限就放弃并提示用户。同时设定明确的退出标准:格式正确 + 技术指标覆盖完整 + 无事实性错误(涨跌幅数字与数据一致)。
外部测试验证正确性
LLM 输出的分析内容目前没有自动校验。比如 LLM 说"涨幅 3.2%",但实际数据是跌了,这个错误无法被自动发现。
一个可行的方案是:Worker 生成报告后,从报告中提取关键断言(如"Nasdaq 突破 16000 点"、"成交量较昨日放大 20%"),然后用 K 线数据做自动校验,不一致的地方标记为可疑并要求重写。
这一步比 Reviewer 成本低,因为是规则驱动、不需要再调用一次 LLM。
短反馈循环:分块校验
目前 AI 分析是等 Worker 生成完整报告后再统一 Reviewer 打分,错误要等全文完成才能发现。
可以改成:Worker 每生成一个段落(比如一段话),就触发一次轻量级 Reviewer 检查——格式是否正确、是否在胡说。如果发现问题就中断生成,而不是等全文写完才发现格式错了。
这个方案需要对当前架构做较大调整,Worker 需要支持分段输出,Reviewer 需要能随时打断执行流程。
完整的改进路线图
综合以上几点,Ward 的优化可以分三个阶段:
第一阶段(最小成本) → 加 Reviewer 打分 + 自动重写(最多3次) → 加关键断言提取 + 规则校验 → 收益:AI 分析质量可控了第二阶段(中等成本) → AkShare 数据标准化 + Tool Calling 改造 → 指数/个股分析各自有独立的 Worker + Reviewer → 收益:LLM 可以按需获取数据,扩展性提升第三阶段(较大成本) → 实现分层记忆(短/中/长期) → 支持多 Agent 协作调度 → 收益:对话上下文管理从滑动窗口升级到智能记忆项目地址:https://github.com/rainj2013/ward-agent
如果你对这个工具有任何建议,欢迎提 issue 交流。
夜雨聆风