1. 从研报 PDF 到可运行因子为什么我放弃了全自动方案把一篇几十页的投资研报变成一组能跑在本地数据上的股票因子这件事听起来像是量化研究员日常里最枯燥、最重复、最该被自动化的一段。我最初的想法很直接既然大模型已经能读懂 PDF、能写 Python那为什么不干脆让它一口气从研报跑到因子代码于是我去找了现成项目试了 RDAgent 这类研报进、因子出的全自动框架结果发现它确实能跑通但代价是环境重、耗时长、中间过程黑盒。5 个因子跑了一个半小时还有 1 个没生成成功最要命的是——代码跑通了我却不确定它到底是不是研报里那个因子。这就是本文要解决的问题用 Codex 的 Skill 机制把投资研报 → 股票因子做成一条半自动、可控、可复现的链路。它适合三类人一是手头有本地行情数据、想批量沉淀因子的个人量化研究者二是想用 AI 辅助读研报、但不愿把关键判断交给黑盒的投研同学三是已经在用 Codex 或 Claude Code、想把重复的因子抽取工作标准化下来的开发者。整条链路的核心不是全自动生成 40 个因子而是让 AI 干重复劳动、让人卡关键节点最后产出一份字段清晰、窗口明确、能直接接到本地数据上的因子代码。我试过把整篇研报直接丢给模型让它生成代码结果字段名对不上、窗口期理解错、复权口径偷换问题一堆。后来我把流程拆成提取候选因子 → 生成结构化摘要 → 人工 review → 生成代码 → 运行检查五步用 Codex Skill 固化下来效率反而高很多。下面我把这套 Skill 的配置、TaoToken 统一 Key 的接入方式以及用样例研报跑通因子输出的完整动作写清楚你可以照着复现。2. TaoToken 统一 Key 接入让 Codex Skill 稳定调用模型Skill 要跑起来第一步是让 Codex 能稳定调用大模型。这里我用 TaoToken 做统一接入层原因是它把多个模型的调用收敛成一个 Base URL 一个 KeySkill 里不用为每个模型改配置换模型只改一个 Model ID 就行。对因子抽取这种需要反复调试提示词的场景统一入口能省掉大量环境切换的麻烦。先说清楚 TaoToken 是什么、能做什么。它是一个模型 API 聚合服务对外提供 OpenAI 兼容的接口你拿到一个 Key 之后就能用同一套调用方式访问不同模型。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。适合谁适合像我这样需要在 Codex、脚本、Skill 之间共享一套凭证、又不想每个工具单独配一遍的人。接入的核心是三件套Base URL、API Key、Model ID。这三样在 Codex 的配置里必须写全缺一个就会报连接或鉴权错误。Base URL 用https://taotoken.net/api注意这个地址后面不加任何 UTM 参数保持干净。API Key 在控制台的 API Keys 页面创建路径是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。Model ID 按你实际要用的模型填比如做因子抽取这种需要长上下文和结构化输出的任务选一个擅长 JSON 输出的模型会顺很多。这里有个容易踩的坑很多人把 Base URL 写成带/v1或者带一堆参数的地址结果请求 404。TaoToken 的 API 根地址就是https://taotoken.net/apiOpenAI 兼容路径由客户端自己拼。如果你用的是 Codex 的auth.json或settings.jsonBase URL 字段填这个根地址即可。另外Key 不要硬编码进 Skill 的提示词文件里放在环境变量或 Codex 的凭证配置里避免提交到仓库泄露。我建议在正式写 Skill 之前先用模型对话页面手动验证一次 Key 是否可用地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。在页面里发一句返回一个 JSON包含字段 name 和 window如果能正常返回结构化内容说明 Key 和模型都通了再往下写 Skill 就少一层排查。这一步花两分钟能省掉后面半小时的到底是 Key 错还是 Skill 错的纠结。3. 可复制的 Skill 配置从研报解析到因子字段定义这一节是全文的核心我给你一份可以直接抄的 Skill 配置。Codex 的 Skill 本质是一个带元信息的提示词工程文件通常放在项目的.codex/skills/目录下用 Markdown 加 frontmatter 描述触发条件和执行步骤。下面这份配置我按研报解析 → 因子字段定义 → 批量抽取三段来组织路径和原文保持一致。先看 Skill 的目录结构建议这样放zer0factor/ ├── .codex/ │ └── skills/ │ └── report-to-factor/ │ ├── SKILL.md │ └── factor-spec.json ├── data/ │ └── sample_report.md └── factors/ └── output/SKILL.md是主文件内容如下。注意 frontmatter 里的name和description决定了 Codex 什么时候触发这个 Skill描述里要写清楚从投资研报提取股票因子这类关键词方便检索命中。--- name: report-to-factor description: 从投资研报中提取候选股票因子生成结构化因子摘要与字段依赖输出可运行的因子代码。适用于动量、波动率、量价类研报的因子抽取。 --- # 研报转因子 Skill ## 执行步骤 ### 第一步研报解析 读取输入研报提取以下内容 - 因子名称中文 英文缩写 - 因子类别动量 / 波动率 / 量价 / 基本面 - 计算逻辑的文字描述 - 涉及的输入字段如 close、volume、high、low - 窗口期参数如 20 日、60 日 - 是否需要复权价格 输出格式为 JSON 数组每个因子一个对象。 ### 第二步因子字段定义 对每个因子按 factor-spec.json 的规范补全 - factor_id唯一标识小写下划线 - category因子类别 - inputs输入字段列表 - window最小依赖窗口整数 - adjust是否需要复权true/false - formula计算逻辑的伪代码 - future_risk是否可能引入未来函数true/false ### 第三步批量抽取 遍历所有因子生成 Python 代码要求 - 因子逻辑与数据源解耦不直接读写文件 - 每个因子声明最小依赖窗口 - 输出结构化元数据配套的factor-spec.json定义字段规范这份文件是保证 AI 生成代码风格统一的关键{ factor_id: string, 小写下划线, name_cn: string, 中文名, category: momentum | volatility | volume_price | fundamental, inputs: [close, volume], window: 20, adjust: true, formula: string, 伪代码, output_desc: string, 因子含义, frequency: daily, future_risk: false }如果你用的是 Codex 的auth.json来管理凭证配置长这样Base URL、Key、Model ID 三件套都在里面{ base_url: https://taotoken.net/api, api_key: sk-你的TaoToken密钥, model: 你的模型ID }把这份配置放进项目后在 Codex 里输入用 report-to-factor 处理 data/sample_report.mdSkill 就会按三步走。这里的关键设计是第一步和第二步的输出是显式的 JSON人可以在这里 review确认因子定义没被理解错再进入第三步生成代码。这就是半自动和全自动的区别——中间结果可见、可改。4. 验证请求用样例研报跑通因子输出配置写好了得跑一遍才知道通不通。我准备了一份样例研报data/sample_report.md内容是一段关于动量因子的描述模拟真实研报的写法## 动量类因子 本文讨论三种动量因子 1. 收益率动量过去 20 个交易日的累计收益率使用前复权收盘价计算。 2. 成交量动量过去 20 日成交量均值与过去 60 日成交量均值之比。 3. 风险调整动量过去 60 日收益率除以同期收益率标准差。在 Codex 里触发 Skill 后第一步应该输出这样的 JSON。如果模型返回的是自然语言而不是 JSON说明提示词里的输出格式为 JSON 数组没被遵守可以在 Skill 里加一句只输出 JSON不要任何解释文字。[ { name_cn: 收益率动量, category: momentum, inputs: [close], window: 20, adjust: true, formula: sum(return, 20) }, { name_cn: 成交量动量, category: volume_price, inputs: [volume], window: 60, adjust: false, formula: mean(volume, 20) / mean(volume, 60) }, { name_cn: 风险调整动量, category: momentum, inputs: [close], window: 60, adjust: true, formula: mean(return, 60) / std(return, 60) } ]第二步补全字段后第三步生成的因子代码大概是这样。注意这里因子逻辑和数据读取是分开的compute只接收 DataFrame不关心数据从哪来import pandas as pd import numpy as np FACTOR_META { factor_id: return_momentum_20d, name_cn: 收益率动量, category: momentum, inputs: [close], window: 20, adjust: True, future_risk: False, } def compute(df: pd.DataFrame) - pd.Series: 过去 20 日累计收益率输入需为前复权收盘价。 ret df[close].pct_change() return ret.rolling(window20).sum()跑通之后用一段最小验证脚本确认因子值不是全空、没有明显异常import pandas as pd from factors.output.return_momentum_20d import compute, FACTOR_META df pd.read_parquet(data/sample_ohlcv.parquet) factor compute(df) print(窗口:, FACTOR_META[window]) print(非空数量:, factor.notna().sum()) print(前 5 个值:\n, factor.dropna().head()) print(是否全空:, factor.dropna().empty)如果非空数量大于 0、是否全空为 False说明这条研报 → 因子的链路已经跑通。实测下来三个因子从解析到出代码加上人工 review 的时间大概十几分钟比全自动方案快而且每一步都看得见。5. 常见报错排查401、local proxy failed 与 choices 解析失败跑这条链路时报错基本集中在接入层和输出解析层。我把踩过的几个典型错误列出来对照着排查能省不少时间。401 Unauthorized。这是最常见的鉴权错误原因通常是 Key 没填、填错或者 Base URL 和 Key 不匹配。先检查auth.json里的api_key是不是从 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建的那一串注意别把前后空格带进去。如果 Key 确认没问题再看base_url是不是写成了https://taotoken.net/api多一个斜杠或者少一个/api都会导致 401 或 404。local proxy failed。这个报错一般出现在客户端尝试走本地网络配置的时候。先确认你的运行环境没有设置奇怪的网络变量比如HTTP_PROXY、HTTPS_PROXY指向了一个不存在的本地端口。可以在终端里echo $HTTPS_PROXY看一下如果有值但你不确定它是否可用先清掉再试。另外Codex 的配置文件里如果残留了旧的代理设置也会触发这个错误检查settings.json里有没有多余的proxy字段。reading choices 解析失败。这个错误说明客户端拿到了响应但结构不对通常是模型返回的内容不是预期的 JSON或者返回体里choices字段为空。原因有两个一是 Model ID 填错了调到了一个不存在的模型二是提示词里没强制 JSON 输出模型返回了带解释的自然语言。解决办法是在 Skill 的提示词里明确写只输出 JSON 数组不要 markdown 代码块不要解释同时在auth.json里确认model字段是有效的 Model ID。OAuth 相关报错。如果你用的是需要 OAuth 的客户端报错里出现OAuth字样通常是凭证过期或授权范围不对。这种情况建议改用 API Key 方式接入也就是本文用的 Base URL Key 三件套绕开 OAuth 流程。TaoToken 的 API Key 方式不依赖 OAuth配置更简单适合 Skill 这种自动化场景。排查顺序建议是先看 HTTP 状态码401 查 Key404 查 URL再看响应体结构choices 为空查 Model ID 和提示词最后看本地环境proxy 相关查环境变量。按这个顺序走大部分问题五分钟内能定位。6. 把链路沉淀下来从单次抽取到可复用工作流跑通一次不代表能长期用真正有价值的是把这条链路沉淀成可复用的工作流。我的做法是把 Skill、因子规范、验证脚本三样东西固定在一个项目里每次拿到新研报只需要替换输入文件其余步骤复用。具体来说factor-spec.json是规范层保证不同研报生成的因子字段一致SKILL.md是流程层固定解析 → 定义 → 抽取三步验证脚本是质量层每次生成代码后自动跑一遍非空检查和窗口检查。这三层分开之后换模型、换数据源、换研报都只动其中一层不会牵一发动全身。如果你想让这条链路更进一步可以接上因子质量检测和本地回测。质量检测主要看三件事因子值是否全空、因子之间相关性是否过高、是否存在未来函数风险。回测则把因子接到你的本地行情数据上看 IC、分层收益这些指标。这部分我还在做但思路是一样的——AI 负责生成和检查人负责判断因子逻辑是否可信。最后给一个实用建议Skill 的提示词不要一次写太满先跑通最小闭环再逐步加约束。我一开始把字段规范、代码风格、异常处理全塞进提示词结果模型顾此失彼输出反而乱。后来拆成三步、每步只干一件事稳定性明显提升。这条研报 → 因子的链路核心不是让 AI 替你做研究而是让它替你做那些重复、机械、容易出错的搬运工作把判断权留给你自己。