每天收盘后把自己钉在电脑前,翻公告、扒财报、对数据的日子,做投研的朋友应该都不陌生。我自己的工作时间被大量重复动作吃掉之后,终于下决心把整套流程做成了一套 AI skill。这里说的 skill,不是一句聪明的提示词,而是能在 Claude Code、Cursor 这类编程助手里被自动加载、按固定流程执行的可复用技能包。这篇文章就把我这个投研 skill 的完整设计思路、目录结构、模板和踩过的坑都摊开来讲,希望对同样在折腾 AI 投研工具的人有点帮助。
1. 投研流程里真正值得固化的部分:为什么做成 skill 而不是万能 prompt
我先说一个自己的真实感受:直接用 prompt 让大模型"帮我分析某家公司",十次里有八次会得到一个看似全面、实则空泛的结论。模型会把营收、利润、估值这些词轮番抛出来,但当你追问"数据是哪来的""和上年同期比到底差在哪"的时候,它就开始含糊了。问题不在于模型笨,而在于投研本身是一套强流程的活儿,流程没被固化,模型自然只能自由发挥。
1.1 skill 和普通提示词的本质区别
普通提示词是"一次性对话",每次都要重新描述背景、格式、数据口径;skill 则是一个目录,里面装着模型需要遵守的指令、可执行的脚本、预先设计好的模板。模型一旦识别到当前任务匹配某个 skill,就会自动读取目录里的说明,按说明一步步执行。
用一个生活类比:普通 prompt 像你临时指挥一个实习生"去查一下这家公司",对方可能东一榔头西一棒槌;skill 则像你交给这位实习生一份标准作业手册,手册上写着先做什么、后做什么、数据从哪个库拿、报告按什么结构写、哪些风险项必须核对。同样的实习生,拿到手册之后产出的质量会稳定非常多。
1.2 投研场景的天然优势:流程固定、数据公开、输出有章法
投研这件事天生适合做成 skill,原因有三个。
第一,流程高度固定。不管是分析个股还是行业,总是先拉数据、再看财务、然后估值、最后给风险结论,这个线性流程不太依赖灵感。第二,数据源相对公开。财报、公告、行情数据都有约定俗成的获取渠道,只要把脚本写好,模型可以自己拉取,不必每次从零开始。第三,输出结构可预期。投研报告有固定的框架,读者在乎的是数字准确、逻辑清晰,而不是文采飞扬。
所以我把"基本面分析""财务指标对比""风险检查""持仓跟踪"这些固定动作全部拆成了 skill 子模块。模型负责调用工具、读取数据、生成报告,我负责审核结论。这比让它自由回答可靠得多。
2. 从 0 到 1 搭建投研 skill:目录、声明文件与运行机制
先看一个最简目录结构。以 Claude Code 的 skill 机制为例,我会在~/.claude/skills/下建一个名为finance_research的目录:
~/.claude/skills/finance_research/ ├─ SKILL.md ├─ scripts/ │ ├─ fetch_quotes.py # 拉行情K线 │ ├─ fetch_financials.py # 拉财务摘要 │ └─ parse_report.py # 解析年报PDF ├─ templates/ │ ├─ company_analysis.md # 个股分析模板 │ ├─ industry_analysis.md # 行业分析模板 │ └─ risk_checklist.md # 风险检查清单 └─ references/ └─ data_sources.md # 数据源说明SKILL.md是这个技能包的核心说明文件,它决定了模型在什么情况下调用该 skill、以及调用后执行的规则。用 YAML frontmatter 开头,正文用自然语言描述操作流程,直接给出一份可以抄作业的版本:
--- name: finance_research description: 投资研究分析,覆盖公司基本面分析、行业对比、财务指标体检、估值判断、风险清单梳理。当用户询问某家公司的投资价值、财务状况、行业前景,或者要求生成投研报告时,优先使用该技能。 allowed-tools: bash, python, web_search --- # 投研分析执行流程 1. 先判断任务类型:个股分析 / 行业对比 / 风险检查 / 财报解读。 2. 使用 scripts/fetch_financials.py 获取财务数据,优先使用本地缓存。 3. 若用户提供年报PDF,使用 scripts/parse_report.py 提取文本。 4. 所有关键财务数字必须标注数据来源和截止日期。 5. 按 templates/company_analysis.md 的结构生成报告,不得跳过风险清单。 6. 报告结尾必须给出"数据局限性说明",包括数据滞后性和模型可能存在的理解偏差。2.1 description 怎么写才能让模型准时命中
很多人在这一步随手写一句"帮我做投研",结果模型经常不触发 skill。关键规则是:description 要描述"用户的需求场景",而不是"你的功能"。
比如你写"本技能用于股票分析",当用户问"帮我看看这家公司值不值得买"时,模型可能觉得这不是"股票分析"而不敢触发。但如果你写"当用户询问某公司投资价值、财务健康状况、行业前景,或要求生成投研报告时使用",模型就能更准确地匹配。
我还会刻意在 description 里加一些"触发词":比如"财报""估值""基本面""仓位建议""减持""商誉"。这些词在不同模型上的命中率略有差异,实测多写几类场景能明显提高触发率。
2.2 数据脚本的边界:抓什么、缓存什么、不碰什么
写脚本时最容易犯的错是把所有数据源都塞进去。我的原则是:行情和财务摘要走脚本,深度内容走人工提供文件。
fetch_quotes.py我一般这样写,用 akshare 拉日线数据并缓存到本地 CSV:
import akshare as ak import pandas as pd import os from datetime import datetime def fetch_daily(symbol: str, start: str = "20200101", end: str = None): """拉取日线行情,缓存到本地 data/ 目录。 symbol 格式:sh600000 或 sz000001。""" end = end or datetime.now().strftime("%Y%m%d") cache_path = f"data/{symbol}_{start}_{end}.csv" if os.path.exists(cache_path): return pd.read_csv(cache_path) df = ak.stock_zh_a_hist(symbol=symbol.split("sh|sz")[1], period="daily", start_date=start, end_date=end, adjust="qfq") df.to_csv(cache_path, index=False) return df这里有个细节值得说:只要接口允许,一定设置adjust="qfq"(前复权)。如果不复权,分红送股会导致历史价格出现跳空,后面算涨跌幅、画趋势图全是错的。第一次踩这个坑时,我以为某公司股价"暴跌",实际只是除权除息。
财务摘要我用ak.stock_financial_abstract_ths或直接解析年报 PDF,二选一。脚本层面我只负责把数据落成结构化表格,不做任何"建议"。判断和建议留给模型结合分析模板来做,这样分工清晰,出问题也好排查。
2.3 不碰什么
这个边界必须提前写进 skill 说明里:不对未公开信息做预测,不输出任何形式的"确定性内幕"。模型只能基于公开数据和用户提供的材料分析,所有结论都是概率性判断。我在SKILL.md里专门加了一条:
本技能所有输出均基于公开信息,不构成投资建议。禁止编造未公开的业绩数据,禁止使用"主力资金意图""庄家动向"等无法验证的说法。
这条规则既是合规需要,也实际提高了报告质量。没有了神秘化表述,模型反而会把注意力放在财报数字和估值逻辑上。
3. 把分析框架内嵌到 skill 里:一套可复用的投研模板
模板是整个 skill 的灵魂。模型能不能稳定产出专业报告,取决于你给它的框架够不够细。我把所有模板放在templates/目录下,模型输出时严格按模板结构填充。
3.1 公司分析模板:先财务、再估值、后结论
这是我平时最常用的一份模板,核心是不允许模型跳过任何一节:
# 公司分析简报 ## 一、业务画像 - 公司主营业务(一句话) - 收入结构(按产品线/地区,最近年度) - 商业模式特点(To B / To C / 平台型) ## 二、财务体检(最近三年) - 营收及同比增速 - 归母净利润及同比增速 - 毛利率、净利率变化 - ROE(加权)及变化趋势 - 经营现金流 / 净利润(比值低于1要重点提示) - 资产负债率、有息负债规模 ## 三、估值分析 - 当前 PE / PB / PS(标注数据日期) - 近三年估值分位数 - 与同行业可比公司的估值对比 ## 四、风险清单 - [ ] 应收账款是否异常增长 - [ ] 商誉占净资产比例是否超过30% - [ ] 大股东质押比例是否超过50% - [ ] 近一年是否有大额减持公告 - [ ] 审计意见是否为非标准无保留意见 ## 五、结论 - 核心看点(最多3条) - 核心风险(最多3条) - 适合进一步研究的触发条件 ## 数据来源与局限性说明这份模板最关键的改动是加了"经营现金流/净利润"这个比值。很多新手看财报只看利润表,结果被应收账款和存货欺骗。现金流比值连续多年低于 0.7,通常意味着利润只是账面数字,这个信号在模板里出现后,模型每次都会主动计算,不会遗漏。
3.2 行业对比模板:同口径比才有意义
做行业分析时,最大的坑是口径不一致。比如"归母净利润"和"净利润"两个词在财报里经常被混用,增速算出来可以差十几个百分点。所以行业对比模板里我会强制要求:
- 所有公司使用同一指标口径(统一用"归母净利润")
- 财务截止日期必须一致
- 若数据来自不同年份年报,在表格中标注年报期
下面是模板中的对比表格:
| 公司 | 营收(亿) | 营收同比 | 归母净利润(亿) | 净利同比 | 毛利率 | ROE | PE | |------|---------|---------|---------------|---------|--------|-----|-----| | A公司 | 数据 | 数据 | 数据 | 数据 | 数据 | 数据| 数据| | B公司 | 数据 | 数据 | 数据 | 数据 | 数据 | 数据| 数据|我见过模型自动把"扣非净利润"和"归母净利润"混在一列里的情况,导致对比结果完全失真。所以模板顶部用加粗标注了一行提示:「所有利润类指标默认使用归母净利润,除非用户特别说明」。
3.3 风险检查清单:让模型像审计员一样逐项确认
风险清单如果只是模型"想想有什么风险",输出会很发散。我把它做成逐项核对的检查表,模型必须调用web_search或读取用户提供的公告文件来确认每一项。
清单里固定包含这几项:
- 商誉占净资产比例超过 30%,有减值风险
- 应收账款增速连续两年超过营收增速
- 大股东质押比例过高,需关注流动性
- 关联交易占营收比例超过 20%
- 审计师出具了带强调事项段或者非标意见
- 存货周转天数连续上升,且存货增速快于营收
每项后面我要求模型给出"通过 / 未通过 / 数据不足"三选一结论。数据不足时不允许自行猜测,必须在报告中明确提示。这份检查清单落地之后,模型报告的质量提升非常明显,因为它从一个"写作文"的模式切到了"填表格"的模式,后者很少遗漏关键风险。
4. 实测中最容易翻车的环节与处理经验
再好的结构,跑起来也会遇到实际问题。我把自己在真实使用中踩过的坑整理成一段排查链路,每一个都对应着具体解决方案。
4.1 财报 PDF 解析:表格错位与口径陷阱
第一次让 skill 直接读上市公司年报 PDF,我天真地以为pdfplumber能把表格完美提取出来。结果模型生成的营收数据出现明显错误——核对之后发现,PDF 里表格跨页时表头会重复,提取出来的列错位了。
处理办法分成两层。第一层是脚本层面,我对parse_report.py做了规则:只提取目标 PDF 的钱前 80 页关键段落,碰到表格线条复杂的页直接跳过,不强行解析。第二层是模型层面,在SKILL.md的流程里写死一条规则:
从 PDF 提取的财务数字,必须与用户口头提供或行情接口返回的数据交叉验证。若不一致,以接口数据为准,并在报告中标注差异。
从那以后,模型再也不会拿着一个错位的数字一本正经地分析。顺带说一句,PDF 解析后的人工复核仍然不能省,我现在每周固定花十分钟抽查三份报告里的关键财务数字,成本很低,但安全感高很多。
4.2 数据时效性:一次过期数据导致的分析偏差
有段时间我让 skill 做每日复盘,模型偶尔会拿三天前的行情数据当最新价。原因很简单:脚本有缓存机制,当天数据接口还没刷新的时候,模型直接读了旧 CSV 文件。
我的解决方式是在缓存逻辑里加入时间校验:
def cache_fresh(path: str, expire_hours: int = 2) -> bool: """判断缓存文件是否在有效期内""" import time if not os.path.exists(path): return False age = time.time() - os.path.getmtime(path) return age < expire_hours * 3600同时对 skill 的流程做了一条硬性要求:报告里标注的行情日期,必须等于或晚于模型上次刷新数据的日期,否则视为无效输出,重新执行数据获取步骤。有了这道校验,模型的"睁眼说瞎话"问题基本绝迹。
4.3 模型幻觉治理:强制引用与数据校验
所有用过 AI 做数据工作的人都会遇到幻觉问题,投研场景尤其危险,因为数字错了就是真金白银的损失。我的治理思路是三板斧:
- 强制引用原始数据文件。指令中明确要求:报告中每个关键财务数字后面,必须用括号标注数据来源,比如
(来自 fetch_financials.py 输出,截止2024年报)。 - 要求模型自我校验。报告生成前先按流程生成一遍
data_check.md,里面只放数字和来源,再由模型把正文和这个检查文件逐项比对。 - 对"数据不足"诚实输出。禁止模型脑补任何指标,未知就写"未获取到该项数据"。
这套流程实际跑下来,模型预测性幻觉大幅减少。曾经有一次它想当然地把"毛利率约40%"写进结论,被校验规则拦住了,因为它找不到对应的数据文件来源——这就是规则生效的例子。
5. 从单票到组合:skill 的复用与扩展思路
投研 skill 做成了之后最爽的部分是可以横向复制。同一个框架,稍微改改模板就能支撑完全不同的工作流。
5.1 持仓周报自动化
我把公司分析模板改成了持仓周报格式:周一早上自动拉取持仓列表里每只股票的周涨幅、成交额、最新财务风险开关,生成一张总览表。
关键是把"持仓列表"做成一个简单的 CSV 维护文件,skill 脚本只负责读取和刷新,模型只负责基于表格写解读。每周一份周报,内容包括:
- 持仓整体涨跌幅和基准对比
- 个股异动原因摘要(结合 tushare 公告和新闻检索)
- 风险清单状态变化
- 下周关注事件日历
这套流程跑起来之后,我的周一早上直接省掉了一个多小时的机械梳理工作。
5.2 公告监控与事件驱动检查单
另一个我常用的子技能是"事件驱动检查单"。当某家公司发布重大公告(比如大额减持、重大合同、更名),模型不会盲目解读消息是好是坏,而是按检查单逐项判断:
- 这是否改变公司基本面逻辑(利润、收入、现金流)
- 公告中的关键数字是否与模型已知数据一致
- 该事件是否触发风险清单中的某一项
- 需要进一步验证的信息点是什么
这样生成的不是"利空利多"式的武断结论,而是一份带着验证步骤的工作底稿。我拿着底稿再去做深度调研,效率和准确率都比以前高不少。
5.3 多 agent 分工:把 skill 拆成子技能
单纯一个finance_researchskill 承担太多职责后,触发判断会变得迟钝。我后来把它拆成了三个更小的技能:
| 子技能 | 职责 | 触发场景 |
|---|---|---|
financial_radar | 拉取行情与财务数据,产出数据核对文件 | 需要数据、涨跌分析、财务指标计算 |
report_draft | 基于数据文件生成结构化分析报告 | 需要报告、结论、行业对比 |
risk_audit | 独立运行风险清单,输出风险开关状态 | 需要对风险的单独核验 |
拆分的收益不仅是触发更精准,更重要的是可以让两个 agent 同时工作:一个跑数据、一个审风险,最后合并输出。虽然实际操作起来需要处理并发冲突,但思路一旦跑通,投研流水线的雏形就出来了。
最后说一点我的个人体会。做这套 skill 最值得的投资不是模型提示词技巧,而是把"分析框架"本身变成代码的一部分。框架在,才谈得上工具化;框架乱,再强的模型也只是个聪明的聊天机器。
如果你也想复现这套东西,我建议从最常用的"公司分析模板"开始,先跑通单票流程,再加数据脚本,最后再慢慢加行业对比和风险审计。别想着一步到位,投研 skill 是一块一块拼出来的,每加一块你自己会更清楚下一块是什么。