最近在复盘自己用 AI 辅助投研的工作流时,有个很深的感受:真正让效率翻倍的并不是某个大模型的提示词,而是一套被固化成“Skill”的标准化流程。所谓 AI 投研 Skill,就是把数据采集、财务指标计算、估值分析和研报草稿生成这些环节,封装成一个 AI 编程助手能自动调用的工作包。你只需要输入一个股票代码,它会按预设步骤取数、算指标、生成一份结构完整的投研纪要。这篇文章会完整分享这套 Skill 的设计思路、目录结构、核心代码以及实操中踩过的坑,适合自己做基本面研究、但不想把时间耗在复制粘贴上的个人投资者,也适合想给团队沉淀研究流程的初级研究员。如果你正在用 Claude Code、Codex 或 Cursor 这类 AI 编程工具,这套方法基本开箱即用。
1. 为什么需要把投研流程装进 Skill
1.1 传统投研的信息处理痛点
做个股基本面研究,大致要经历这样几个步骤:先找到公司近几年的年报和季报,整理出营收、净利润、毛利率、经营现金流等关键数据;再算 ROE、估值分位、自由现金流,做横向纵向对比;最后把结论写成一份可读的研究纪要。听起来不复杂,但实际做一遍非常耗时。以我自己的经验,一只票从零开始整理数据到出草稿,至少需要两到三个小时,其中一半时间花在“打开年报,定位科目,复制数据,核对单位”这种机械劳动上。
更大的问题是标准不统一。今天分析 A 公司用归母净利润增速,明天分析 B 公司又用营业总收入增速,不同时候做出来的底稿格式五花八门,复盘的时候根本没法横向比较。我早先试过用对话式 AI 来解决,但每次都要把“请帮我分析 XX 公司基本面,重点关注盈利质量和现金流”这段背景重新输入一遍,而且它给出的指标经常对不上财报口径,数据来源也不明不白。用过几次之后我就意识到,缺的不是生成能力,而是一套能让 AI 重复执行、不偏题的流程外壳。Skill 正好补上这个缺口。
1.2 Skill 和 Agent 的分工关系
很多人会把 Skill 和 Agent 混在一起,其实两者是不同层的东西。Agent 是一个能自主规划、调用工具的智能体,它负责拆解任务、决定下一步做什么;而 Skill 是给它的一套标准化能力包,里面写清楚特定场景下应该执行什么步骤、调用哪些脚本、按什么模板输出。用一个生活化的类比:Agent 是厨师,Skill 是菜谱和预处理好的配菜包。厨师可以临场发挥,但如果想稳定出品,就必须有一份标准化配菜。
目前主流 AI 编程工具都在逐步支持这类机制。比如 Claude Code 会读取用户目录下.claude/skills中的 Skill 定义,Codex 也能通过项目说明文件加载自定义技能,Cursor 的规则目录同样可以达到类似效果。因为核心都是“描述文件 + 可执行脚本 + 模板”,所以这套投研 Skill 可以在不同工具间复用,只需要调整目录位置和描述格式。这也是我选择把它做成 Skill 而不是一段固定 prompt 的原因:可复用、可分发、还能用 Git 做版本管理。另一个关键点是,Skill 能强制 AI 去调用本地脚本,而不是靠自己的语言模型“脑补”财务数字,这直接解决了 AI 在数据准确性上的最大短板。
1.3 为什么选择自定义 Skill 而不是固定提示词
有一段时间,我在各种收藏夹里攒了大量“AI 投研提示词”,比如“你是一名资深分析师,请从盈利能力、成长性、估值水平三方面分析 XXX 公司”。说实话,这类提示词有一定效果,但问题也很明显:第一,每次使用都要复制粘贴,不同成员之间版本还会漂移;第二,prompt 里没法内置可执行的取数逻辑,AI 只能基于训练数据里的旧信息回答,无法拿到最新财报;第三,输出格式完全看模型心情,上一份报告是表格,下一份就可能变成小作文。
自定义 Skill 把这些问题都解决了。你可以把 Python 脚本放在 Skill 目录里,让 AI 按步骤执行;可以把模板文件放在同一个目录下,规定输出结构;还可以把整个目录提交到 Git,团队里所有人拉下来就是同一套研究标准。而且 Skill 本身可以迭代,今天发现某个指标算错了,改一次脚本,以后所有分析都自动修正。这种“一次建设、长期复用”的体验,是用普通提示词无法获得的。
2. Skill 的整体设计与目录结构
2.1 功能模块划分
我把整个投研流程拆成三个模块:取数模块、计算模块、输出模块。取数模块负责拿到股票代码后拉取最新的财务数据和行情数据;计算模块负责算出核心指标,比如 ROE、毛利率、净利率、资产负债率、PE、PB、自由现金流;输出模块则把指标和模板结合,生成一份带数据来源和风险提示的研报草稿。
这样拆是有原因的。三个模块可以独立测试和替换,比如换一个数据源时不需要动计算逻辑;计算脚本用 Python 写死公式,能避免模型在对话里“心算”出错;模板独立出来之后,想调整研报格式只需要改 markdown 文件,不用重新解释需求。模块之间的数据流也很清晰:取数脚本落地 CSV,计算脚本读 CSV 产出 JSON,最后模板渲染成 Markdown。任何一个环节出问题,都能快速定位。
| 模块 | 主要职责 | 对应文件 |
|---|---|---|
| 数据获取 | 拉取行情、财务报表、基础信息 | scripts/fetch_data.py |
| 指标计算 | 计算盈利质量、成长、估值、现金流指标 | scripts/calc_metrics.py |
| 报告生成 | 根据模板生成结构化研报 | scripts/generate_report.py |
| 流程编排 | 让 AI 按步骤执行并调用脚本 | SKILL.md |
2.2 目录结构与配置说明
实际目录长这样:
equity_research_skill/ ├── SKILL.md ├── scripts/ │ ├── fetch_data.py │ ├── calc_metrics.py │ └── generate_report.py ├── templates/ │ ├── report_template.md │ └── checklist.md └── data/ ├── balance.csv ├── income.csv ├── cashflow.csv └── price.csvSKILL.md 是这个 Skill 的核心描述文件,建议用 YAML front matter 写清楚技能名称和触发描述。它相当于“使用说明书”,AI 会先读这个文件,才知道该按什么顺序执行任务。下面是我在项目里实际使用的 SKILL.md 简化版:
--- name: equity_research description: 用于个股基本面投研分析。输入股票代码或公司名称,自动获取财务数据、计算核心指标,并生成结构化研报草稿。 --- # Equity Research Skill ## 执行步骤 1. 用户输入股票代码(如 600519.SH)后,先运行 `python scripts/fetch_data.py <代码>` 获取数据。 2. 数据拉取成功后,运行 `python scripts/calc_metrics.py <代码>` 计算指标。 3. 结合计算结果和 `templates/report_template.md` 生成研报。 4. 生成内容必须在末尾注明“本内容仅供研究参考,不构成投资建议”和取数时间。放在哪个目录取决于工具。Claude Code 通常读取~/.claude/skills/和项目内.claude/skills/;Codex 可以通过AGENTS.md或对应技能目录加载;Cursor 的规则目录也能放自定义 skill。我自己的习惯是放在项目.claude/skills/下,这样不同机器、不同项目拉下来就能用。写完目录后记得重启会话,或者在工具里执行列出 skills 的命令确认已经加载。
2.3 环境准备与安装
在使用这套 Skill 之前,需要保证本机有可用的 Python 环境。我的建议是用虚拟环境隔离依赖,避免污染系统 Python。命令很简单:
mkdir -p equity_research_skill/{scripts,templates,data} cd equity_research_skill python -m venv .venv source .venv/bin/activate # Windows 下是 .venv\Scripts\activate pip install akshare pandasakshare 是取数用的开源库,pandas 用于数据清洗。如果你还想让报告生成脚本更优雅一点,可以再加一个 jinja2,不过我在示例里用的是纯字符串替换,不依赖额外库。装好依赖后,先手动跑一遍 fetch 脚本,能正常打印“done”再交给 AI 使用。否则 AI 调脚本时报错,排查起来会多一层噪音。
3. 核心能力实现与实操要点
3.1 数据获取与清洗
取数我选了 akshare。原因很简单:免费、覆盖 A 股主要行情和财务报表、接口调用门槛低。缺点也有,接口字段偶尔会变,后面常见问题部分会专门讲。以下是我在 fetch_data.py 里用的一个简化版本,足以跑通全流程:
import sys import akshare as ak import pandas as pd code = sys.argv[1] # 取资产负债表、利润表、现金流量表(最近几期) for table, name in [ ("资产负债表", "balance"), ("利润表", "income"), ("现金流量表", "cashflow"), ]: df = ak.stock_financial_report_sina(stock=code, symbol=table) df.to_csv(f"data/{name}.csv", index=False) # 取日线行情,用于计算 PE/PB 的实时值 price = ak.stock_zh_a_hist(symbol=code.split(".")[0], period="daily", start_date="20240101", adjust="qfq") price.to_csv("data/price.csv", index=False) print("done")注意几点:财务报表的单位一般是“元”,但有的接口返回的是“万元”,写计算脚本之前先打印几行看看;stock_financial_report_sina返回的是按报告期排列的明细表,里面包含公告日期和报告期,后续计算要用最新报告期数据;股票代码格式在不同接口里不统一,有的要600519,有的要600519.SH,建议在 SKILL.md 里约定统一格式,再由脚本内部转换。
清洗数据这一步往往决定成败。原始财务表里会有不少空列和重复列名,例如“净利润”可能出现“归属于母公司所有者的净利润”和“净利润”两个字段。我一般会根据字段名关键词优先选择更严格的口径,例如要算归母净利润时,匹配“归属于母公司所有者的净利润”,实在找不到再用“净利润”。如果关键字段缺失,宁可让脚本报错,也不要让后续计算带着 NaN 继续跑。
3.2 财务指标与估值计算
拿到原始数据后,重点不是把所有财务指标都算一遍,而是围绕“盈利质量、成长性、财务健康、估值”四个维度选最核心的指标。我这里固定算这几个:
| 指标 | 公式 | 用途 |
|---|---|---|
| ROE | 净利润 / 净资产 | 衡量股东回报效率 |
| 毛利率 | 毛利 / 营收 | 衡量商业模式与护城河 |
| 净利率 | 净利润 / 营收 | 衡量整体盈利效率 |
| 资产负债率 | 总负债 / 总资产 | 衡量财务风险 |
| 经营现金流/净利润 | 经营现金流绝对值 / 净利润 | 检验利润含金量 |
| PE | 总市值 / 净利润(TTM) | 估值水平 |
| PB | 总市值 / 净资产 | 估值水平 |
| 自由现金流 | 经营现金流 - 购建固定资产支出 | 企业可自由支配的现金 |
calc_metrics.py 里比较关键的是数据处理部分。因为原始表里包含多个报告期,必须按报告期排序后取最新一行,同时注意字段名在不同年份可能有差异,建议用“包含某个关键词”的方式去做字段匹配。具体片段:
def get_field(df, keywords): for col in df.columns: if any(k in col for k in keywords): return col return None latest = df.sort_values("报告期").iloc[-1] net_income_col = get_field(df, ["归属于母公司所有者的净利润", "净利润"]) equity_col = get_field(df, ["归属于母公司股东权益合计", "股东权益合计"]) roe = latest[net_income_col] / latest[equity_col]从实际使用的角度,这些口径问题比指标本身更容易把人绊倒。ROE 建议用归母净利润除以归母净资产,而不是用含少数股东权益的合并净资产,否则不同公司之间差异很大;PE 建议用最近 12 个月净利润的 TTM 口径,避免只拿单季净利润导致估值失真;PB 用最新报告期的净资产,不能用上一年年报的净资产,因为年中分红和增发都会直接影响净资产。还有一个细节是自由现金流:很多人直接用“经营现金流净额 - 投资现金流净额”,但投资现金流里包含理财产品的买卖,会把计算口径搞混。更实用的口径是用“经营现金流净额 - 购建固定资产、无形资产和其他长期资产支付的现金”,这样更接近企业真实的扩张消耗。
所有计算结果最终可以汇总成一个 JSON 文件供后续报告生成使用。格式类似:
{ "code": "600519.SH", "report_date": "2024-09-30", "roe": 0.286, "gross_margin": 0.917, "net_margin": 0.523, "debt_ratio": 0.214, "ocf_to_net_profit": 1.17, "pe_ttm": 26.5, "pb": 8.4, "fcff": 52300000000 }3.3 研报生成与输出
计算脚本只负责产出结构化数据,真正的投研观点仍由 AI 生成。为了让输出稳定,模板必须足够细。report_template.md 我是这样组织的:
# {公司简称} ({股票代码}) 投研简报 ## 业务概况 - 主营产品/服务: - 主要客户与行业地位(如有已知信息): ## 财务表现(数据截至 {取数日期}) - 最新报告期: - 营业收入及增速: - 归母净利润及增速: - 毛利率、净利率: - ROE: ## 估值与现金流 - PE/PB: - 自由现金流: - 经营现金流/净利润: ## 核心看点 (基于以上数据总结 2-3 个关键点) ## 风险提示 (列出财报数据中值得警惕的信号) > 免责声明:本内容仅供研究参考,不构成投资建议。generate_report.py 可以写成一个简单的模板渲染器,从 JSON 中读取指标,替换模板里的占位符。核心逻辑如下:
import json, pathlib, sys metrics = json.loads(pathlib.Path("output/metrics.json").read_text()) template = pathlib.Path("templates/report_template.md").read_text() for k, v in metrics.items(): template = template.replace("{" + k + "}", str(v)) pathlib.Path("output/report.md").write_text(template) print("report generated")不过生成报告这个环节,我更推荐让 AI 来做最终润色,而不是完全依赖脚本。脚本填充的是一份信息准确的初稿,AI 负责把“核心看点”和“风险提示”写成通顺的自然语言。SKILL.md 的执行步骤里要特别强调两点:一是所有数据必须来自/data目录下的 CSV 或 JSON,AI 不能自己“编”一个数出来;二是模板里的每一项都要写,不允许漏项。实测下来,只要把这两点写进 Skill 描述,生成质量会稳定很多。
3.4 让 Skill 适配不同 AI 编程工具
这套 Skill 不是某个工具独有的,但不同工具的加载方式略有差异。Claude Code 中,放在~/.claude/skills/下的技能全局可用,放在项目.claude/skills/下的技能只对当前项目生效;配置好之后可以在会话里用命令查看已加载技能,确认描述是否被识别。Codex 的做法通常是依赖项目说明文件,例如在AGENTS.md中写明“当前项目包含 equity_research_skill,使用时请先阅读 SKILL.md”;也有的团队会直接用官方支持的自定义技能目录,以最新文档为准。Cursor 用户则可以把 SKILL.md 的核心指令复制进.cursor/rules或.cursorrules,让对话模型知道遇到个股分析任务时该调用哪些脚本。
不管用哪种工具,核心逻辑是一样的:让 AI 知道有一个可调用的技能,并且明确脚本路径和执行顺序。如果不想维护多份配置,一个偷懒的办法是在项目根目录写一个简短的AI_AGENTS.md,把“投研分析请参考.claude/skills/equity_research_skill/SKILL.md”这句说明写清楚,大部分工具都能读懂这种约定式指引。
4. 常见问题与排查技巧
4.1 Skill 没有生效
新手最容易遇到的就是技能放好了但 AI 不调用。先检查目录位置:有的工具只读取项目内.claude/skills,有的读~/.claude/skills,放错位置当然不会生效。其次检查 SKILL.md 开头的 YAML front matter,name和description缺一不可,description 要写清楚“输入股票代码”这个触发条件。最后,改完文件后必须重启会话,之前我因为没重启反复怀疑脚本写错,浪费了不少时间。
还有一类情况是 AI 虽然读了 SKILL.md,但执行顺序不对。比如它直接开始写报告,跳过了取数和计算。这时候要在执行步骤里加更明确的指令,例如“必须运行脚本,禁止在没有脚本输出的情况下生成财务数据”。如果工具支持调试模式,可以先让 AI 输出它认为应该执行的命令,再手动干预纠正。
4.2 数据源不稳定
akshare 的接口属于“免费但会变”,最典型的问题是某个财务表字段名变了,导致脚本取值取到 NaN。我的处理方式是把数据抓取和指标计算解耦,抓到 CSV 后脚本先做一次空值检查,如果发现关键字段缺失,就中止流程并输出提示,而不是带着错误继续往下算。日常使用还可以在 fetch 脚本里加一个--cache参数,当天数据缓存下来,重复分析同一只票就不必重复拉接口,也能减少被数据源限流的情况。
当接口失效时,不要硬等修复,可以直接准备本地 CSV 作为降级方案。从行情软件或公司公告里手动导出最近几期财务报表,放到data/目录下,再让脚本带一个--local参数读取本地文件。这样即使数据源接口临时挂了,投研流程也能继续跑。数据合规上也要注意,只使用公开披露的财报和行情数据,不要抓取非公开信息,也不要大规模高频请求接口,个人研究频率下基本不会踩线。
4.3 结果不准 / 幻觉
AI 生成研报时最容易出现的问题是“一本正经地编指标”。比如我遇到过它把上年同期净利润当成最新值,还写进了摘要。解决办法主要靠两点:第一,把计算全部交给脚本,计算结果以 JSON 形式给到 AI,并且要求它只能引用这份 JSON 里的数字;第二,在生成结果末尾强制带上取数时间,这样复盘时能判断是不是数据过期导致的偏差。另外,所有涉及个股的结论我都让模型在“风险提示”里加上一句“数据可能存在口径差异,请以公司公告为准”,这个习惯帮我避免了好几次“数据被误读”的尴尬。
还有一种比较隐蔽的错误是“字段匹配错误”。比如有些数据接口会同时返回“净利润”和“归属于母公司所有者的净利润”,如果脚本用模糊匹配匹配到了前者,算出来的 ROE 就会偏高。我的经验是在 get_field 里把更精确的字段放在关键词列表最前面,优先匹配长字段名。同时建议每个指标在 JSON 里附带一个unit字段,标明单位是元、万元还是亿元,上游单位错了,后面所有结论都会跟着错。
4.4 离线场景下怎么办
如果你是出差途中或者网络不稳定,A 股实时接口可能拉不到数据。我的解决办法是提前把重点关注池的财报数据导出成 CSV,存到data/history/目录下,然后修改 fetch 脚本支持--local模式。本地模式不再调用任何网络接口,而是从 CSV 里读取最近报告期数据,同样可以完成指标计算和报告生成。虽然拿不到最新的行情,但基本面分析本来就更依赖季报年报,一两天内的离线不会让你的研究结论失效。
最后再分享一个小技巧:把你在实战中反复踩过的检查项沉淀成模板里的 checklist,比如商誉占比超过净资产 30% 要单独提示、应收账款增速远高于营收增速要警惕、经营现金流连续为负必须重点说明。Skill 最大的价值不在省几分钟,而在于它会逼着 AI 每次都用同一套标准帮你扫描这些信号。我实际跑了大半年之后,这个投研 Skill 已经从最早的取数和计算,慢慢长成了包含检查清单、风险提示模板、多数据源降级方案的研究框架。对我来说,它带来的不是某一次分析有多惊艳,而是每一次分析都不会再漏掉那些本来应该被看见的细节。