量化投研最尴尬的阶段,往往是手里已经有了一堆数据、一堆因子脚本、一堆回测代码,但每天开盘前还是靠人肉在十几个窗口之间来回切换:先在终端拉一遍行情,再翻财报看行业景气,然后打开因子表手工排序,最后凭感觉决定今天调不调仓。这套流程跑一次要两三个小时,跑完之后你自己都不确定中间哪一步被昨天的缓存污染了。WorkBuddy 这类工作台出现之后,真正值得花力气的地方不是"让 AI 帮我选股",而是把上面这条链路拆成职责清晰、可以单独验证、又能串起来的 Skill,让每一步都有明确的输入输出契约。我这次拿金融量化做例子,把 10 个 Skill 串成一个四级的投研闭环——从数据底座、宏观环境,到行业轮动、标的多因子画像,再到组合构建、回测归因和风控执行,每一级都有自己的产物,上一级的输出就是下一级的输入,中间不靠"聊天上下文"传递,全部落盘成 JSON 和 Parquet。这篇文章写给三类人:刚接触 WorkBuddy 想搞明白 Skill 到底怎么用的人、有量化基础但被工程化绊住的人、以及想让团队投研流程可复现的人。文中所有代码和参数都可以直接抄,替换数据源就能跑。
1. 为什么要把量化投研拆成"四级闭环"和 10 个 Skill
1.1 从"一个大而全的提示词"到分工明确的 Skill 流水线
很多人第一次用 AI 做投研,习惯写一个超长的提示词,把"帮我分析一下现在的市场、挑几个行业、选几只票、给个仓位建议"全塞进去。我试过,结果基本是灾难。原因不复杂:单次对话里的上下文窗口有限,模型在长链路推理中会逐渐丢失前面的约束条件,到了最后一步"给仓位建议"时,它早就忘了第二步里你自己定义的行业上限是 25%。更要命的是这种流程不可复现,今天跑出来一个结果,明天同样的提示词跑出来完全不一样,你没法归因到底是数据变了还是模型抽风了。
Skill 的思路完全不同。一个 Skill 就是一段被固化的能力:它有明确的名字、明确的触发词、明确的输入参数、明确的输出格式,还有明确的失败边界。模型在这个 Skill 内部的自由度被限制在"怎么完成任务",而不是"要不要做这个任务"。把 10 个这样的单元串起来,整条链路的确定性就上来了——中间任何一环出错,你能定位到具体的 Skill,而不是对着一整段输出发懵。
打个生活化的比方:一整个大提示词像是让一个厨师从买菜、洗菜、切配到炒菜全包,中途他要是忘了放盐,你根本不知道是哪一步出的问题;Skill 流水线更像是中央厨房,切配间只负责切配,出品必须是一盒标准化的净菜,炒锅那边拿到净菜就能直接下锅,谁的问题一目了然。
1.2 四级闭环的分层逻辑:宏观、行业、标的、组合
我把这套体系分成四级,不是为了好看,而是因为这四个层级的决策频率和数据更新节奏完全不同,混在一起做就一定会互相污染。
一级是环境层,回答"现在这个市场该不该重仓"。它依赖的是利率、汇率、信用利差、指数波动率、成交额结构这类周频甚至月频想起来看一次的数据,更新慢、噪声大,但决定了后面所有仓位系数的上限。二级是行业层,回答"钱该往哪些赛道倾斜"。它依赖行业指数动量、景气度指标、估值分位、资金流向,通常是周频调仓。三级是标的层,回答"在这个赛道里买哪几个"。这部分是日频甚至更高频的,因子有效性衰减快,需要持续滚动检验。四级是组合与执行层,回答"具体买多少、什么时候调、跌到哪止损"。
这四级如果揉在一起,最典型的后果就是:你用一个日频因子去解释一个季度才变一次的宏观判断,逻辑上根本对不齐。分开之后,一级输出的"风险预算系数"会作为参数传给四级,四级再乘上自己的仓位结果,这样整条链路是可解释的。
1.3 Skill 和 Agent 的边界:什么时候该用哪个
热词里经常有人问 skill 和 agent 的区别,我自己的划分标准很粗暴:流程固定的用 Skill,需要临场判断的用 Agent。
数据清洗、因子计算、IC 检验、回测、风控检查、报告渲染,这些步骤的顺序和参数基本不会变,属于典型的 Skill 场景,写成脚本固化的收益最大。而"今天这个异动到底是不是消息面驱动的,要不要临时降仓"这种问题,需要模型去读新闻、读公告、做判断,属于 Agent 场景。
我的实际做法是:Agent 做决策点,Skill 做执行段。整条链路里只有两个地方留给 Agent:一是在二级结束、三级开始之间,让 Agent 看一眼有没有突发事件需要打断;二是在四级输出之后,让 Agent 审一遍报告有没有明显矛盾。其余八个环节全部是纯 Skill,不接受协商。
2. 10 个 Skill 的职责清单与输入输出契约
2.1 十个 Skill 一览表
先把全景摆出来,后面每一级再展开细节。这张表是我自己工作台里真实在用的版本,编号顺序就是执行顺序。
| 编号 | Skill 名称 | 所属层级 | 核心职责 | 关键输入 | 关键输出 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | >--- name: factor-forge version: 1.2.0 level: 3 triggers: ["挖因子", "因子检验", "IC分析", "分层回测"] inputs: - panel_path # 必需,一级产出的面板数据路径 - universe # 必需,标的池文件 - factor_spec # 必需,因子定义 yaml - horizon # 可选,默认 5,预测期(交易日) outputs: - factor_panel # parquet,日期 x 标的 x 因子 - ic_report # json,IC/IR/胜率 - layered_curve # parquet,分层回测净值 timeout: 900 --- # factor-forge ## 用途 根据 factor_spec 从面板数据中构造因子,做横截面标准化与去极值, 计算 Rank IC 与分层回测,输出可直接进入 signal-composer 的因子面板。 ## 执行约定 1. 所有因子一律做行业中性化与市值中性化后再算 IC。 2. 缺失值处理采用"行业内中位数填充",不允许全表填充。 3. 若 IC 样本数少于 250 个交易日,直接抛错退出,不做静默降级。 ## 失败处理 - 面板数据缺失率 > 30%:报错并提示重新运行>{ "skill": "factor-forge", "version": "1.2.0", "run_id": "20250114-093012-fg", "inputs_hash": "a3f5c9...", "started_at": "2025-01-14T09:30:12+08:00", "elapsed_sec": 214.6, "status": "success", "artifacts": { "factor_panel": "s3://local/factors/20250114/panel.parquet", "ic_report": "s3://local/factors/20250114/ic.json" }, "metrics": { "ic_mean": 0.0432, "ir": 0.61, "ic_win_rate": 0.573 } }
3. 一级实现:数据底座与宏观环境识别3.1 Skill 1:data-hub 把脏数据挡在门外>import pandas as pd import numpy as np def align_panel(price: pd.DataFrame, calendar: pd.DatetimeIndex) -> pd.DataFrame: """把价格宽表对齐到统一交易日历,停牌置 NaN 而非前向填充""" px = price.reindex(calendar) # 只做有限次数的前向填充,用于处理数据源偶发缺失,最多补 3 天 px = px.ffill(limit=3) # 标记长期停牌:连续 20 个交易日无成交 halted = px.isna().rolling(20).sum() >= 20 px = px.mask(halted, np.nan) return px
数据落地格式上,我全部用 Parquet 而不是 CSV。原因有两个:一是体积,同样一份 5000 只标的、10 年日频的价格面板,CSV 大概 300MB,Parquet 压缩后不到 40MB;二是类型保持,CSV 读回来所有列都是 object,每次都要重新解析,Parquet 直接带 schema。 3.2 Skill 2:macro-regime 用打分代替拍脑袋宏观环境判断最容易变成玄学,我的做法是把它彻底量化成几个可以计算的维度,每个维度打 0 到 100 分,加权得到总分,再映射成风险预算系数。这套方法不追求预测准确,只追求状态识别及时——它能告诉我"现在和三个月前比,环境是变好了还是变差了",这就够了。 我用的四个维度:
每个指标先算历史分位,注意方向对齐(比如信用利差是越低越好,取 这个系数会一路传到第四级的 risk-guard,直接乘在最终仓位上。它的作用不是择时,而是控制回撤的尾部风险——环境差的时候主动降仓,虽然会错过一些反弹,但长期看净值曲线的形状会舒服很多。 需要强调的是,阈值和权重必须用滚动窗口校准,不能拍死。我的做法是每年年初用过去 5 年数据重新拟合一次权重的相对大小,看哪个维度对当年回撤的解释力最强,微调但不大改。改成全自动优化很容易过拟合,反而坏事。 3.3 实操:把一级跑成一个可复用的缓存一级的两个 Skill 有一个共同特点:数据量大但计算简单,跑一次能用很久。所以我把它们做成"按周更新 + 按需触发"的模式。 具体做法是加一个轻量的调度脚本,每周一早上跑一次全量更新,把结果写到以周为单位的目录下,比如 最后输出的是一张行业得分表,配合一个硬约束:单一行业权重上限 25%,前五大行业合计不超过 70%。这个约束不是风控拍的,是回测跑出来的——放宽到 35% 之后,组合波动率上升 40% 但收益只多了 8%,性价比明显不划算。 4.2 Skill 4:chain-map 让赛道和标的真正挂钩产业链映射听起来虚,但它解决一个很实际的问题:二级选出来的是"行业",三级要选的是"个股",中间需要一个可靠的桥。 我的做法是维护一份三层结构的映射表:一级行业、二级细分、三级关键环节,每个环节下面挂具体的标的池。这份表不是自动生成的,是人工维护加定期校验。自动生成的问题在于,很多公司的业务标签和实际收入结构严重不符——一家做传统制造的公司,因为参股了一家芯片企业,就被打上"半导体"标签,这种噪声会直接毁掉下游的因子检验。 维护流程大概是:每季度跑一次收入结构校验,把主营业务收入占比低于 50% 的标签标为"弱关联",在下游打分时降低权重;同时监控新上市标的,人工确认归属。这份表更新完之后,chain-map 会输出一份"行业到标的"的映射 JSON,供三级使用。 有了这张表,三级在做因子中性化的时候就能按细分环节而不是按大行业来做,中性化的精度会明显提高。 4.3 Skill 5:valuation-band 估值分位要分场景看估值分位这个东西,直接看 PE 历史分位是最容易出错的做法。原因很简单:不同行业的估值中枢会随时间迁移,一家公司从成长期进入成熟期,PE 中枢下移是正常的,你把它当成"低估"去买,就是掉进了价值陷阱。 所以 valuation-band 里我做了三件事。第一是滚动窗口,用过去 5 年而不是全部历史,避免把十年前的估值体系带进来。第二是行业内相对分位,先看这家公司在同细分环节里排第几,再看绝对分位。第三是结合盈利趋势做交叉判断:如果估值分位低于 30% 但同时盈利预期在下调,标记为"低估值陷阱";如果估值分位在 60% 到 80% 之间但盈利在上调,标记为"合理偏贵但趋势向好"。 这套交叉判断输出的不是一个数字,而是一个带标签的结构。这些标签在三级做因子合成时能当哑变量用,效果比单纯的估值分位好不少。
5. 三级实现:标的多因子画像与信号合成5.1 Skill 6:factor-forge 因子有效性必须自己验到了三级,才真正进入因子的世界。我给 factor-forge 定了一条铁律:任何新因子必须通过三项检验才能进池子——IC 检验、分层回测、相关性检查。 IC 检验用的是 Rank IC,也就是横截面上的秩相关。为什么用秩相关而不是皮尔逊相关?因为因子的绝对数值往往有极端值,皮尔逊相关会被少数异常点带偏,秩相关看的是排序,更符合实际选股逻辑。 我这边因子入池的门槛是:IC 均值绝对值大于 0.02,IR 大于 0.3,IC 胜率大于 0.52,样本数至少 500 个交易日。这三个条件同时满足的因子其实不多,但一旦满足,在组合里的稳定性明显更好。 分层回测是第二道关。把因子值排序分成 5 组,看第 1 组和第 5 组的净值差。这里最容易犯的错是只看多空收益,不看单调性。有些因子多空收益很漂亮,但中间三组乱序跳,这种因子本质上是靠极端组撑起来的,实盘里很容易失效。我会额外算一个单调性指标,用组序和组均收益的秩相关来表示,低于 0.7 就标黄。 第三道关是相关性检查。新因子和已有因子的相关系数超过 0.85,就基本没有增量价值,直接拒绝;0.7 到 0.85 之间标为"弱增量",只在已有因子失效时启用。
5.2 Skill 7:signal-composer 加权还是等权因子池有了之后,合成方式直接决定最终信号质量。我试过三种:等权、IC 加权、最大化 ICIR 优化。 等权最稳,最大问题是没办法体现因子之间的强弱差异。IC 加权简单直接,用滚动 60 日的 IC 均值做权重,效果比等权好一些。优化法理论上最优,但实测下来最容易过拟合,样本内漂亮样本外崩塌。 我最后用的是IC 加权 + 权重上限的方案:权重按滚动 IC 均值分配,但单个因子权重上限 25%,下限 5%,同时限制同一大类因子(比如动量类、估值类)合计不超过 40%。上限约束牺牲了一点样本内表现,换来的是样本外的稳定性。 合成之后的打分表会落到三级输出目录,字段包括标的代码、综合打分、分项得分、所属细分环节、估值标签。这张表就是第四级唯一的输入,非常干净。 5.3 参数计算:IC、IR、换手率怎么联动看单看 IC 是不够的,必须把 IC、IR 和换手率放在一起看。这三个指标构成了一个三角约束,任何两个好了,第三个一定会变差。 举个我实际遇到的例子。有一个反转类因子,IC 均值 0.055,IR 0.72,看着非常优秀。但一算换手率,在每个调仓周期都是 80% 以上。按双边千分之三的成本估,一年 50 次调仓,光成本就吃掉 24% 的收益,再高的 IC 也白搭。 后来我的做法是在 factor-forge 里强制输出一个"成本后 IC":用 IC 均值乘以预测期收益的平均绝对幅度,再减掉预期换手成本。这个指标不那么好看,但更接近实盘。
这张表是我自己因子池里的真实分布,可以看到估值类因子的 IC 其实很弱,但换手极低,所以它的角色不是贡献收益,而是稳定组合、压低回撤。理解每个因子的"性格",比一味追求高 IC 重要得多。 6. 四级实现:组合、回测与风控执行6.1 Skill 8:backtest-lab 回测要防止自欺欺人回测最容易自欺欺人的地方有三个:未来函数、幸存者偏差、成本低估。backtest-lab 里我把这三个都做成了硬校验。 未来函数校验的方式是在每次取数据时强制加一个 幸存者偏差靠保留退市样本来解决。标的池是"每个时点当时存在的全部标的",而不是"今天的全部标的"。这一条听起来简单,但很多现成的回测框架默认就是后者,跑出来的收益会虚高一大截。 成本模型我用的是分段计算:固定佣金、印花税、按成交额万分之几的冲击成本,再加一个滑点估计。冲击成本这块,小资金可以直接忽略,但资金量上去之后必须建模,我用的是一个简化的平方根模型,成交额占比每提高 1%,冲击成本增加约 0.3 个基点。 归因部分我拆成三层:资产配置贡献、行业选择贡献、个股选择贡献。这样每次回测完能看清楚收益到底来自哪一层,如果收益主要来自行业配置,那说明三级选股环节还有很大优化空间。 6.2 Skill 9:risk-guard 仓位不是拍出来的risk-guard 接收的是三级打分表加二级的行业上限,输出的是最终下单权重。它要过四道检查,任何一道不通过都会强制调整。 第一道是风险预算折算。把一级给的宏观系数直接乘在目标仓位上。环境好时满仓,环境差时四成仓。 第二道是波动率目标。这是我个人认为最重要的一条。先算候选组合的预测波动率,如果超过目标波动率,就按比例缩减总仓位。目标波动率我定的是年化 15%,超过就先减总仓,而不是去砍个股,因为砍个股容易破坏分散度。 这段代码里我忽略了相关性项,看起来是偷懒,实际上是有意的。用历史相关矩阵算出来的组合波动率在极端行情下严重低估风险,因为危机时相关性会飙到接近 1。忽略相关性等于做了一个保守假设,宁可低估仓位也不要高估分散效果。 第三道是单票与行业暴露检查。单票上限 5%,单一细分环节上限 15%,单一行业上限 25%。超限部分按比例分摊到未超限标的上。 第四道是流动性检查。单日买入金额不超过该标的 20 日平均成交额的 10%,超了就按比例缩减。这条对小资金没影响,但一旦规模上去,不加上这条会直接把回测收益打回原形。
6.3 Skill 10:report-weaver 让报告自己生成最后一个是输出环节。报告这东西看起来是体力活,但它恰恰是验证整条链路有没有跑通的最佳方式——如果报告里某个数字是空的或者明显异常,说明前面某一级出了问题。 report-weaver 的输入是整条链路的所有 我特意在报告里加了一个"异常提示"区块。规则大概是:如果某个 Skill 的运行时间超过历史中位数的 3 倍、或者输出文件大小变化超过 50%、或者关键指标超过 3 个标准差,就在报告顶部标红。这个设计帮我抓到过好几次数据源的静默故障——数据接口改了字段名,返回的全是 null,但流程一路跑到底,只有这个指标突变提示暴露了问题。 报告的渲染我用的模板加变量替换,不依赖模型生成。原因很直接:数字类的内容,模型有概率把小数位搞错或者张冠李戴,用模板更安全。模型只负责写最后的"今日观察"那一段自然语言,而且要求它只能引用报告里已经存在的数字。 7. 串联实操:一条指令跑通全链路7.1 编排器怎么写10 个 Skill 串起来的方式有两种:一种是用工作流引擎按依赖关系编排,一种是写一个简单的编排脚本顺序调用。我选后者,因为依赖关系足够清晰,引入引擎反而增加调试成本。 编排脚本的核心是依赖声明和产物检查。每个步骤执行前先检查产物是否存在且新鲜,存在就跳过。 这个脚本才 40 行,但它覆盖了整条链路。 7.2 中断恢复与幂等设计长链路跑一半挂掉是常态。我的应对方式是三步: 第一步是产物目录隔离,每次运行一个 幂等性上有个细节值得说:factor-forge 这类计算密集型 Skill,我会把随机种子固定下来,写进 7.3 运行一次的真实记录说点具体的。有一次我跑了一个完整链路,时间线大致是这样:data-hub 拉取 5000 只标的、10 年日频行情和财务数据,耗时 6 分 40 秒;macro-regime 计算四个维度打分,8 秒;sector-rotation 处理 30 个行业,12 秒;chain-map 走的是查表,3 秒;valuation-band 需要计算滚动分位,55 秒;factor-forge 最重,5 个因子加 IC 加分层回测,3 分 48 秒;signal-composer 18 秒;backtest-lab 跑 5 日调仓的 10 年回测,1 分 52 秒;risk-guard 因为要模拟流动性约束,26 秒;report-weaver 渲染加 PDF,14 秒。总计大约 13 分钟出头。 这个时间比手工操作快多少?我手工跑一遍同样的流程,光是数据对齐和因子表整理就得一个半小时,还不算中间出错的返工。而且手工跑完之后,你手里只有一份结果,要改参数得从头再来;串成链路之后,改一个参数只重跑受影响的那一段,比如只换因子权重,直接从 signal-composer 开始跑,40 秒就出结果。 8. 常见问题与排查速查表8.1 问题速查表这套东西我用了小半年,踩过的坑基本都在下面这张表里。
8.2 数据源与算力的坑数据源的坑最隐蔽,也最值得提前防。我总结下来有三类:一是接口静默变更,字段名改了但接口不报错,返回一堆 null;二是历史数据修正,财务数据经常被追溯调整,导致昨天的回测今天跑不出同样结果;三是限流,短时间内大量请求被限速,返回的数据不完整但格式正确。 对应的防御措施我在>
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设
2026/9/18 8:14:30
机器学习在乳腺癌诊断中的应用与优化1. 项目背景与核心价值乳腺癌是全球女性最常见的恶性肿瘤之一,早期准确诊断对治疗方案选择和预后改善至关重要。传统病理诊断依赖医生经验,存在主观性强、效率低下的痛点。这个项目通过机器学习方法构建自动化分类模型,将乳腺肿瘤影像特征转化…
网站建设
2026/9/18 8:09:27
Ubuntu U盘挂载与卸载实战:设备识别、权限与fstab避坑1. 先把设备认明白:Ubuntu眼里的U盘到底是谁在Ubuntu下折腾U盘,十次里有八次卡在第一步——不知道U盘是哪个设备。这个坎看着小,实际是后面所有挂载、卸载、写fstab操作的地基。地基歪了,轻则报个错,重则把系统盘上的分…
网站建设
2026/9/18 8:08:45
Claude Code Harness 多宿主部署指南:Claude Code、Codex、Cursor、Grok 四端并用完全实战Claude Code Harness 多宿主部署指南:Claude Code、Codex、Cursor、Grok 四端并用完全实战 【免费下载链接】claude-code-harness Claude Code Dedicated Development Harness - Achieving High-Quality Development Through an Autonomous Plan→Work→Review Cycl…
网站建设
2026/9/18 8:07:18
QDPSK通信系统MATLAB仿真:差分相位调制与误码率分析简介:一份基于MATLAB的QDPSK通信系统仿真文档,主要面向电子信息类、通信工程等专业的学生与工程师,用于理解四相相对移相键控(QDPSK)的原理及Simulink建模仿真方法。文档基于“信号与系统”课程教学场景,阐…
网站建设
2026/9/18 8:03:55
SpringBoot+Vue构建适老化在线教育平台实践1. 项目背景与核心价值随着人口老龄化进程加速,老年教育需求呈现爆发式增长。传统线下老年大学普遍面临场地有限、课程单一、时间固定等问题,而多数在线教育平台又存在操作复杂、界面不友好等适老化不足的缺陷。我在实际调研中发现,65岁以上老…
网站建设
2026/9/18 8:03:28
前端必学Docker:从镜像容器到云服务器部署实战1. 先想清楚:前端为什么要碰 Docker说句实话,大多数前端开发者听到 Docker 的第一反应都是“这是运维的事,跟我没关系”,我当年也是这么想的。直到有一次,我把一个 Vue 项目部署到测试服务器,本地跑得好好的… |