ChatGPT提示工程挖掘高频选股因子:从原理到回测复现
简介《Alpha掘金系列之五如何利用ChatGPT挖掘高频选股因子》是一份面向量化研究员、金融工程从业者与策略学习者的专题报告聚焦大语言模型在高频选股因子挖掘中的落地方法。资源包仅含1个PDF文件大小约2.21MB结构覆盖ChatGPT原理、Transformer与LSTM差异、RLHF训练流程、提示工程及思维链提示等基础内容。正文进一步给出周频变异系数因子、高频买卖盘力量因子的构建与测试并讨论中证1000指数增强策略、代码输出校验及模型随机性、回测失效等风险点。已有1359人学习适合用来理解提示设计如何转化为因子研究流程并借鉴因子评价、调仓测试与策略复现思路。1. 一份研报拆到最后LLM 挖高频因子的产出边界在哪把一份研报丢给模型让它照着里面的字段吐一个因子出来我试过不止一次多数时候吐回来的是“市盈率倒数”这类换皮货。直到把这份以 ChatGPT 挖高频选股因子为主线的 Alpha 掘金系列报告拆完才看到一条能跑通的路把数据范围限制在委托价和委托量用思维链提示逼模型把构造逻辑一步步写出来最后落地成买卖盘力量因子。日频测试多头年化超额 17.29%多空夏普 4.51降到周频做小盘宽基指数增强单边千二费率下年化超额 7.17%、信息比 0.57。这份 PDF 适合两类人手里有 tick 快照但不知道怎么造因子的量化从业者以及想把大语言模型真正接进研究流水线、而不是只拿来写周报的人。2. LSTM 的短板与 Transformer 的自注意力原理层绕不开的三件事很多人拿到一段提示模板就开问问完发现因子不能用问题往往不在提示词本身而在不清楚模型是怎么处理输入文本的。这一章不铺开讲深度学习只讲三件和因子挖掘直接相关的事LSTM 为什么处理不了十档委托量之间的关系、RLHF 怎么让模型“听话”、zero-shot 到 few-shot 该怎么选。把这三件事弄清楚后面写提示才知道哪些约束必须加、哪些话说了也白说。2.1 为什么 LSTM 记不住十档委托量之间的权重差异传统 LSTM 处理语言模型任务有两个硬伤。一是它对文本里不同词无法区分重要程度每个位置的输入基本等权参与运算模型没法自己决定“哪个词更关键”二是长距离信息在门控结构里逐层衰减序列一长早期信息就被冲淡。放到高频数据上这两个缺陷会被放大一只股票一天 3 秒快照约 4800 条每条含十档买价、十档买量、十档卖价、十档卖量近盘口的一档和远盘口的十档信息量完全不同LSTM 很难学出这种差异而日内早盘的委托行为对尾盘因子的影响又恰恰需要长距离记忆。Transformer 的自注意力机制解决的就是权重分配问题。它把序列里每个位置映射成查询、键、值三组向量用查询和键的点积算出位置之间的相关性再按相关性对值加权求和。翻成白话模型自己决定当前这个位置该看序列里的哪些位置、各看多重。2017 年那篇提出 Transformer 的论文之后GPT 系列从 GPT-1 的 1.17 亿参数、GPT-2 的 15 亿、GPT-3 的 1750 亿一路走到 GPT-4 支持图像输入底座都是这套结构。做因子挖掘时选择 ChatGPT 而不是自己训 LSTM理由很实际通用语言能力已经覆盖了金融文本和代码语料省掉标注成本代价是模型不懂你的表结构字段、频率、方向都得靠提示约束。能力维度LSTMTransformer / ChatGPT对因子挖掘的影响位置权重基本等权自注意力动态分配能区分近盘口与远盘口信息长序列记忆逐层衰减全局可见日内早盘行为可被尾盘因子利用领域知识需自建语料训练预训练已覆盖省标注但需字段白名单约束输出稳定性确定性较高有随机性同一提示需多次生成取交集2.2 Scaling Law 与 RLHF 三步模型“听话”的工程来源ChatGPT 表现出的智能水平报告归因于两个因素。第一是参数数量提升带来的涌现现象也就是 Scaling Law参数、数据、算力同时上去模型能力会出现非线性的跳变一些小模型做不了的任务在大模型上突然就能做。第二是 RLHF人类反馈的强化学习让模型的输出更贴近人类偏好。RLHF 的训练分三步报告写得很具体我整理成表步骤输入产出对因子挖掘的意义监督微调组织 40 名标注员对指令集做标注初步会按指令回答的模型能听懂“用委托量构造因子”这类任务描述奖励模型同一提示的多个输出人工排序一个能给回答打分的模型输出更像研究员写的少答非所问强化学习优化新采样指令 奖励模型最终模型在“专业性”上更稳定这套流程解释了一个现象模型给的因子往往“看起来很像那么回事”公式齐整、逻辑自洽、还带几句解释。但偏好对齐不等于可回测。模型优先优化的是人类觉得好不是字段存在、频率对得上、方向没搞反。我在复现时遇到过模型给出bid_px_11这种字段表里只有十档它按语义补了第十一档。RLHF 让模型更会说话也让这种“合理编造”更隐蔽所以提示里必须把字段白名单写死。2.3 Zero-shot、One-shot、Few-shot先定样本量再提问提示工程里常提三种样本模式放到因子挖掘场景差别非常直接。Zero-shot 是不给任何示例直接问比如“请给一个高频选股因子”模型通常回“量价背离”“大单净流入”这类烂大街的东西逻辑对但没增量。One-shot 是给一个完整示例让模型照着格式来输出的结构会规整很多但容易抄示例的逻辑。Few-shot 给两到三个不同逻辑的示例输出格式和逻辑多样性都最好代价是提示变长模型可能把示例改个参数就交回来。我一般用 few-shot但会在要求里加一句“因子逻辑不能与示例重复”。下面这段提示模板可以直接抄你是一名量化研究员。下面给出两个因子示例请参照它们的格式再生成一个新因子。 示例 1 因子名开盘半小时成交量占比 字段volume_0930_1000, volume_day 公式volume_0930_1000 / volume_day 频率日频 方向值越大当日动量越强 示例 2 因子名尾盘委托价偏离 字段last_px_1457, vwap_day 公式(last_px_1457 - vwap_day) / vwap_day 频率日频 方向值越大尾盘越强 要求 1. 只能使用字段字典中出现的字段 2. 输出 JSON包含 name/formula/frequency/direction/reason 五个键 3. 因子逻辑不能与示例重复。这段模板的关键参数有三个。字段字典要单独贴给模型不能默认它知道你的表结构JSON 的五个键固定下来后面可以用脚本直接解析不用人工摘公式direction必须让模型写回测时方向搞反是高频事故先让它自己表态再拿 IC 符号去对。示例数量控制在两到三个再多提示会太长模型容易只做表面改写。3. 提示工程落地角色、任务、指令三段式怎么写才可回测提示工程不是玄学落到因子挖掘上就三件事角色定得够不够具体、任务描述有没有可验收的标准、指令有没有把边界封死。报告给了一个基础公式我在复现时又补了几个约束条件这一章按“公式—思维链—校验”三步走每一步都给能直接用的写法。3.1 基础公式与角色强化把“资深研究员”拆成字段级约束报告里的提示公式是“作为一个【角色】按照这些指示生成【任务】【指令】”。客服场景的例子是“作为一名客服人员请生成客户询问的回复这些回复应当专业、准确、简洁”。搬到因子挖掘第一版很容易写成“作为一名资深量化研究员请给出一个行之有效的选股因子要求具有原创性、有逻辑支撑、业绩出色”。这个提示能用但输出通常还是反转、低波动这些老面孔因为“资深研究员”这个角色太宽模型没有被逼到具体的数据维度上。角色强化的方向是往数据字段上靠。把角色改成“在境内市场做日频选股因子、熟悉 Level-2 快照十档委托价与委托量字段的量化研究员”任务改成“构造一个尚未被广泛使用的高频选股因子”指令里加五条硬约束角色你在境内市场做日频选股因子熟悉 Level-2 快照中的十档委托价与委托量字段。 任务构造一个尚未被广泛使用的高频选股因子。 指令 1. 只能使用以下字段bid_px_01..10、bid_vol_01..10、ask_px_01..10、ask_vol_01..10、last_px、volume 2. 因子按日频聚合每个交易日一个值 3. 说明因子值越大代表什么方向必须明确 4. 给出构造公式和一句话逻辑不要给代码 5. 如果字段不足以支撑你的想法直接说缺什么不要编造字段。五条约束各有作用。字段白名单是防编造的核心模型一旦被允许“自由发挥字段”就会按语义补出不存在的列日频聚合把频率钉死避免它给一个需要逐笔撮合数据的因子方向必须明确是为了回测时不用猜符号不要代码是因为模型写的代码常在小处出错先要公式再自己实现更稳最后一条“缺什么直接说”能反向暴露数据缺口比它硬编一个字段有价值。3.2 思维链提示用 IC 加权那个案例走一遍纠偏报告里有个很典型的例子两个因子 IC 分别是 0.22 和 0.25但 A 因子多头表现好、B 因子多头组最差。IC 高的反而不能买这说明 IC 作为评价指标在分组非线性时会失真。直接问模型“IC 有什么缺陷”它给的是教科书答案认识不到问题出在分组权重上。报告用思维链提示一步步逼我按它的对话顺序整理成表轮次提示内容模型表现问题在哪1IC 作为因子评价指标有什么缺陷泛泛谈秩相关、线性假设没落到分组2两个因子 IC 0.22 与 0.25多头表现相反为什么指出分组内 IC 可能不同接近问题3每组内分别算 IC 再求均值行不行给出横截面 IC 方案还不是加权4只能做多权重该怎么调多头组权重更高得到加权 IC目标达成这个案例的可复用点是节奏每一步只推进一步不要一次把“分组、权重、做空限制”全塞进去。条件给太多模型会拼凑一个看起来完整的答案反而不去推理。思维链提示的价值不在“让模型想得更久”而在把复杂问题拆成可验证的小步每一步都能人工判断对错错了就在下一步纠回来。3.3 输出校验字段、频率、方向三查模型输出因子后进回测前必须过三道检查。这三道检查用脚本做不靠肉眼检查项常见错误校验方法字段出现bid_px_11、order_imbalance这类表里没有的列拿白名单做字符串匹配不在名单里的直接打回频率公式是日频逻辑但提示里提了周频调仓模型按周频给检查公式里的窗口长度与调仓频率是否匹配方向低波动因子写成“值越大越好”用历史 IC 符号反推IC 显著为负说明方向反了聚合顺序先日频均值再算比值与先逐笔算再均值结果能差一倍固定聚合顺序并写进提示聚合顺序这条最容易被忽略。报告里的买卖盘力量因子是先在每条快照上算 BForce、SForce再按日求均值最后算 BSForce如果反过来先求日均价再算比值得到的因子值域和分布都会变回测结果对不上是常事。提示里写清楚“先逐条计算再按日聚合”比事后调参省事得多。4. 因子复现变异系数与买卖盘力量的代码、参数与回测口径报告给了两组因子一组是中低频的价量变异系数一组是高频的买卖盘力量。前者的价值在于让你看清模型的能力边界——IC 好看但多头不赚钱后者才是能落地的。复现要盯三件事公式对不对、聚合顺序对不对、回测口径对不对。这一章按这两组因子给代码和参数。4.1 中低频5 日滚动 LI/VLI 的代码实现变异系数因子的构造很朴素价格和成交量各自算变异系数再相乘或相除。公式是LI σ_price / μ_priceVLI σ_vol / μ_volLIxVLI LI × VLILI2VLI LI / VLI。报告用过去 5 个交易日的日频数据滚动计算。下面这段代码可以直接跑import numpy as np import pandas as pd def cv_factor(df, window5): df: 列包含 code, date, close, volume 返回: 每只股票每日的 LI / VLI / LIxVLI / LI2VLI df df.sort_values([code, date]).copy() g df.groupby(code, group_keysFalse) # 5 日滚动均值与标准差min_periods 必须等于 window避免前 4 日用不完整窗口 roll g[[close, volume]].rolling(window, min_periodswindow) mu_p roll[close].mean().reset_index(level0, dropTrue) sd_p roll[close].std().reset_index(level0, dropTrue) mu_v roll[volume].mean().reset_index(level0, dropTrue) sd_v roll[volume].std().reset_index(level0, dropTrue) df[LI] sd_p / mu_p df[VLI] sd_v / mu_v df[LIxVLI] df[LI] * df[VLI] df[LI2VLI] df[LI] / df[VLI].replace(0, np.nan) # 分母为 0 置空不补 0 return df这段代码有两个坑。groupby之后rolling返回的是多层索引赋值回原表前必须reset_index(level0, dropTrue)否则索引对不齐因子会串到别的股票上min_periodswindow保证前四个交易日是 NaN 而不是用不完整窗口算出一个偏小的标准差后者会让因子在样本前期虚高。参数上window5对应报告的 5 日滚动想试更长周期直接改这个数但要注意窗口越长因子换手越低、IC 衰减越慢。回测口径按报告来周频调仓每周第一个交易日开盘价买入最后一个交易日收盘价卖出股票池是小盘宽基指数成份股按因子值分十分位。报告的 IC 结果如下因子IC 均值T 统计量多头组合年化超额LI-5.36%-8.07-1.09%VLI-2.79%-7.532.75%LIxVLI-5.29%-10.740.74%LI2VLI-3.11%-4.690.74%IC 均值为负、T 统计量绝对值全部大于 2说明方向稳定价格和成交量的波动率越低越好符合低波动异象。但多头超额只有 VLI 的 2.75% 能看LI 甚至是负的。这个落差不是数据问题是指标口径问题——IC 衡量全样本秩相关多头只取前 10%两者不是一回事。低波动异象在小盘池的多头端被压缩空头端贡献更多所以多空净值曲线好看多头组合却赚不到钱。4.2 高频十档快照聚合成 BForce/SForce/BSForce高频这组是报告的核心产出。提示里限定数据范围只能用委托价和委托量模型经过引导后给出买卖盘力量因子公式是BForce BVol × (1 − mean(bid_px) / last_px)SForce SVol × (mean(ask_px) / last_px − 1)BSForce (BForce − SForce) / (BForce SForce)其中 mean(bid_px) 是十档买价均值BVol 是十档买量之和卖盘同理。第一个式子的含义是买盘挂单离现价越近、量越大买盘力量越强第二个式子反过来衡量卖压第三个式子做标准化让买卖两个方向可比。代码按“先逐快照、再按日聚合”的顺序写import pandas as pd def order_force(snap): snap: 单只股票单日 tick 快照列含 bid_px_01..10, bid_vol_01..10, ask_px_01..10, ask_vol_01..10, last_px 返回: 该股票该日的 BForce / SForce bid_cols [fbid_px_{i:02d} for i in range(1, 11)] ask_cols [fask_px_{i:02d} for i in range(1, 11)] bvol_cols [fbid_vol_{i:02d} for i in range(1, 11)] svol_cols [fask_vol_{i:02d} for i in range(1, 11)] bid_px snap[bid_cols].mean(axis1) ask_px snap[ask_cols].mean(axis1) bid_vol snap[bvol_cols].sum(axis1) ask_vol snap[svol_cols].sum(axis1) px snap[last_px] # 逐快照计算避免先日均价再算比值带来的量纲偏差 bforce bid_vol * (1 - bid_px / px) sforce ask_vol * (ask_px / px - 1) return pd.DataFrame({ BForce: [bforce.mean()], SForce: [sforce.mean()], })三个参数要盯住。十档字段名按bid_px_01到bid_px_10的格式对齐不同数据源命名可能是bidprice1或BP1取列前先统一last_px用最新成交价不用收盘价因为盘口偏离是相对现价算的聚合顺序是逐快照算完再按日求均值报告对 BSForce 还做了一步标准化修正因为买盘力量和卖盘力量的量纲不同直接相减没有横截面可比性。缺失值处理报告没细写常见做法是十档字段有任一缺失就把该条快照置 NaN不参与当日均值停牌和集合竞价时段直接剔除。日频测试结果如下股票池同上次日开盘价买入| 因子 | IC 均值 | T 统计量 | 多空年化 | 多空夏普 | 多头年化超额 | | --- |本文还有配套的精品资源点击获取