上篇把数据闭环和策略骨架讲完之后很多朋友在后台问得最多的问题是特征到底怎么构造大模型在策略里到底扮演什么角色是真让它拿主意炒股还是只是当个花架子这篇就把这两块最核心的内容拆开揉碎讲清楚。先说结论**特征工程决定策略收益的上限大模型决策决定这个上限能不能稳定落地。**两者缺一不可但它们的坑完全不同。特征工程的坑是看起来对但实际是未来函数大模型决策的坑是看起来聪明但实际不可控。这篇我会把我自己跑通的一套流程完整展开包括特征怎么设计、为什么这么设计、大模型怎么接入决策链路、以及回测和实盘之间那些用钱换来的教训。1. 特征工程把行情快照变成模型能吃的结构化观点特征工程不是一个用 pandas 算几个指标的动作而是一套从原始数据到有效信号的完整加工流水线。A 股市场有 5000 多只股票每只股票每秒都在产生新的交易数据如果不做特征工程直接把原始行情丢给模型模型学到的多半是噪声而不是规律。1.1 特征分层量价、时序、事件三维度我做特征工程习惯分三层每层对应不同的数据源和信息维度。第一层量价特征。这是最基础的维度包括收益率、换手率、量比、振幅、价格位置等。这些特征反映的是市场参与者在这只票上做了什么。关键不是把每个指标都算一遍而是要控制特征之间的冗余。比如收盘价和均价线高度相关如果你同时把均线、收盘价、最高价、最低价全部塞进去模型会花大量参数去拟合这些冗余关系而不是真正有用的模式。我的做法是先算候选池然后用相关性矩阵做筛选两两相关性超过 0.8 的只保留一个。这一步能直接砍掉一半以上的无效特征。第二层时序特征。A 股和美股有个很大的区别——有涨跌停制度。这就导致单纯看今天涨了 5%没有意义要看这个涨幅是在什么样的换手和盘口环境下实现的。所以我习惯构造分位数类特征过去 20 天收益率的分位、过去 60 天换手率的分位、股价在近 250 天高低区间中的位置。这类特征的好处是天然做了横截面标准化不同股票之间可以直接比较。**第三层事件特征。**这是很多初学者最容易忽略的。财报发布、大单异动、龙虎榜出现、解禁日临近这类离散事件对股价的影响往往是脉冲式的。处理方式是把事件转换成距离事件最近交易日 N这样的数值特征或者用滑动窗口统计近 5 天是否有异动标记。这一层特征在 A 股尤其重要因为 A 股的散户比例高事件驱动的噪声非常显著。1.2 因子预处理去极值、标准化、中性化的先后顺序不能乱特征算出来之后不能直接用必须过预处理三件套。顺序很重要我踩过顺序颠倒的坑结果模型指标虚高得一塌糊涂。第一步是去极值。A 股数据里经常出现异常值比如某天换手率突然从 3% 跳到 25%或者收益率出现连续涨停造成的极端值。常用方法是 MAD中位数绝对偏差把均值加减 3 倍 MAD 之外的值截断。这一步的目的是防止个别极端样本主导梯度。第二步是标准化。我用的是 z-score但要注意一点不能用全历史均值和方差而是用滚动窗口的均值和方差。否则就引入了未来信息。第三步是行业中性化。A 股行业差异非常大银行股和科技股的估值体系完全不同。如果不做行业中性化模型学到的可能只是买科技股而不是买科技股里相对低估的票。具体做法是对每个特征做行业虚拟变量的回归取残差作为中性化后的特征值。1.3 标签设计未来 N 日收益怎么定义决定了模型学什么特征决定模型看到什么标签决定模型学什么。标签设计是特征工程里最容易被忽略、但影响最大的环节。我常用的标签是未来 5 个交易日收益率但这里有个细节**要不要跳过当天**如果你用今天收盘后的特征去预测今天到未来 5 天的收益其实包含了当天已经实现的收益实盘里你根本没法在收盘前拿到全部数据这就是前视偏差的重要来源之一。我的做法是收盘后计算特征用 T1 到 T6 的收益作为标签这样才和实盘时间线严格对齐。另外标签还要做样本加权。A 股有些股票长期停牌复牌后连续涨停这种样本如果权重过高模型会被带偏。我会去掉未来 N 日内停牌超过 2 天的样本并且对涨停/跌停日的样本降权。2. 大模型决策不是让 AI 炒股混合架构才是现在能落地的玩法很多人以为AI 量化策略就是把行情数据喂给大模型然后大模型告诉你买什么卖什么。这个思路在演示里很炫实盘里完全行不通。原因有三个不可解释、不可控、延迟高。金融领域和泛娱乐领域最大的区别在于你没法接受一个说不上来为什么的高频决策。不是说大模型不能用而是它不应该承担预测员的角色。我把大模型放在决策链路的最顶层做的是什么样的事件发生时才行动的推理而不是预测涨跌。2.1 为什么不让大模型直接炒股我直接说结论**大模型的核心能力不在数值预测而在语义理解和规则判断。**你让大模型从 K 线形态里找规律它大概率会一本正经地胡说八道因为它的训练语料里根本没有足够的低频行情数据。但如果你给它一段公告文本问它这份公告里的业绩预增是否包含股权转让等一次性收益它能给出相当可靠的判断。这是因为公告文本的语义模式在互联网上有大量类似的语料可以对齐。所以我的架构选择是**传统模型负责数值预测给出候选池大模型负责语义判断和规则决策。**各干各擅长的事谁也不越界。2.2 混合链路传统模型打分大模型裁决我跑通的最小链路长这样传统模型比如 LightGBM每天收盘后对全市场股票打分输出前 20 只候选股。候选池进入一个规则引擎过滤掉 ST、次新股、停牌股这些都是硬性约束。剩下的票关联最新公告、新闻、财报原文组一个结构化 Prompt 喂给大模型。大模型输出三个维度事件性质判断利好/利空/中性、风险标记是否存在监管问询、立案风险、决策建议买入/观望/回避。人工复核模块把大模型的输出和传统的风控指标做交叉验证通过后才生成交易指令。这套链路的好处是每个步骤都留了可干预的接口。大模型犯错了你能定位到是语义理解错还是规则过滤错不会黑盒到底。2.3 A 股场景下的结构化 Prompt 设计大模型落地最核心的实操就是 Prompt 设计。同一个模型Prompt 写得好不好输出质量差两倍以上。我给一个目前实测效果比较稳定的模板骨架模型角色设定你是一名持有证券从业资格证的量化研究员你的判断依据只能是公告原文和公开市场信息禁止推测内幕信息。输入数据格式化以下是 [股票名称] 在 [日期] 发布的公告摘要……。该公司近 30 日涨幅为 X%所属行业为 Y当前市盈率为 Z。**要求输出 JSON**这是关键。不要让它输出长文本而是强制输出 JSON包含event_type、risk_flags、suggestion、reason四个字段。带上自我约束如果公告信息不足以做出判断请输出 insufficient不要把猜测当作结论。这套模板的核心逻辑是把大模型的自由度限制在可控范围内它做的是判断题不是填空题。3. 决策落地从特征数据到大模型输出的完整管线设计架构思路有了接下来是落地。这部分我把自己在项目里用的数据管线、调用逻辑和风控设计完整展开重点是讲清楚每一步为什么这么做。3.1 特征平台选型别一上来就搞实时计算很多朋友一听到量化就想到高频、想到实时这是对 A 股市场的误解。A 股 T1 交易制度决定了日频策略已经足够真正的难点不是快而是准。我用的存储方案很朴素Parquet 文件 内存 DataFrame SQL 查询。每天收盘后定时任务把当日行情数据拉下来做特征计算存到按日期分区的 Parquet 里。分析需求用 DuckDB 直接查文件不用部署复杂的数据仓库。为什么这么选两个原因成本低一个人全职维护绰绰有余调试方便任何特征有问题拉出来就能查不依赖黑盒服务。等到策略资金量上来、需要分钟级频率的时候再迁移到真正的特征平台比如 Feast也不迟。3.2 大模型调用的工程化延迟、失败、缓存、成本大模型在量化链路里最大的工程问题是延迟和成本。日频策略里你每天只需要处理 20 只候选股的公告和新闻总量其实很小但必须保证调用稳定。我的做法是缓存优先。同一个股票、同一个公告日期Prompt 完全一致输出直接用缓存。除非公告有更新否则不重复调用。这一招能省 70% 的 API 费用。超时控制和重试机制。设置 30 秒超时超时后自动降级为不决策而不是强行决策。模型超时说明当前链路不可靠宁可错过机会也不能带病做判断。温度参数调低。大模型的随机性在量化场景是毒药。我把 temperature 设为接近 0确保同样的输入在绝大多数情况下输出稳定。3.3 风控闸门所有的 AI 决策必须经过硬性规则检查无论模型多聪明风控必须是一票否决制。我的风控闸门是 6 道硬性检查全部通过才允许下单单票仓位上限 10%行业总仓位上限 25%全市场组合 Beta 值控制在 0.8~1.2 之间移除预案单票回撤达到 12% 强制平仓无条件执行账户总体最大回撤达到 15%策略整体停机当日交易次数上限 10 笔防止异常循环调用流动性过滤过去 20 日日成交额低于 5000 万的票不进池这 6 条检查全部不依赖模型是纯代码逻辑。这是我最想强调的一点AI 决策只能是建议风控系统才是最终执行人。4. 回测好看实盘亏钱的真实原因四个我从坑里爬出来的教训特征、模型、管线都搭起来了回测结果也不错但一上模拟盘就开始连续打脸。这段我整理了几个高频原因每一个都是我用实打实的亏损换来的经验。4.1 前视偏差回测里的神预言都是这么来的最典型的场景你用了当天的收盘数据做特征但你的特征计算程序里调用了df.shift(-1)或者用了未来窗口的均线。这种偏差在回测里表现为模型准确率高得惊人实盘立刻失效。怎么排查我有一个笨但可靠的办法**把特征计算的基准时间全部打上日志然后随机抽 10 天的数据人工核对每个特征值在 T 日收盘时是否已经可知。**跑一遍这个流程你能抓出 90% 以上隐藏的前视偏差。4.2 滑点和成交假设过度乐观A 股的流动性比美股差很多小票尤其严重。回测里假设按收盘价成交在实盘里完全不可行。我的处理是回测统一加 0.1% 滑点涨跌停的票按无法成交处理。这样过滤掉的信号数量会很大但留下来的信号实盘可执行性高很多。宁可回测收益少 20%也不要模拟盘和实盘差距巨大。4.3 大模型的温度随机性对回测的影响如果你在回测里调用了大模型必须把它的输出日志全部存下来。因为大模型的结果是概率性的即使 temperature 很低也可能出现微小波动。这意味着你回测时的决策过程和实盘可能不完全一致。最稳妥的方案**大模型决策结果只在实盘当天调用回测的时候用历史日志中的真实输出或用一个固定规则模拟。**不要在大模型上追求回测可复现性这是反人性的。4.4 特征数量膨胀导致的隐性过拟合样本量只有几千个交易日特征加到 50 个以上就很容易过拟合。我的控制方法是特征数量不超过样本数量的 1/100 经验法则。比如你有 5000 个交易日的历史数据特征池上限控制在 50 个以内再加上交叉验证和样本外测试基本能把过拟合控制住。5. 最小可运行代码骨架特征工程 大模型决策Python前面讲的都是方法论这节给一个能直接跑的最小骨架。我用它验证了整个链路的可行性你可以在这个基础上按自己的需求扩展。5.1 特征工程核心代码import pandas as pd import numpy as np # 假设 df 包含 columns: ts_code, trade_date, close, high, low, open, vol, amount def mad_winsorize(s, n3): MAD 去极值 median s.median() mad (s - median).abs().median() upper median n * 1.4826 * mad lower median - n * 1.4826 * mad return s.clip(lower, upper) def rolling_zscore(s, window20): 滚动 z-score 标准化避免未来信息 mean s.rolling(window).mean() std s.rolling(window).std() return (s - mean) / std def compute_features(df): df df.sort_values([ts_code, trade_date]).copy() # 基础量价特征 df[ret_1] df.groupby(ts_code)[close].pct_change(1) df[ret_20] df.groupby(ts_code)[close].pct_change(20) df[ret_60] df.groupby(ts_code)[close].pct_change(60) # 换手率用 amount / 流通市值估算这里用 vol / 滚动成交量均值近似 df[volume_ma20] df.groupby(ts_code)[vol].transform( lambda x: x.rolling(20).mean() ) df[vol_ratio] df[vol] / df[volume_ma20] # 位置类特征股价在过去250日高低区间中的位置 df[high_250] df.groupby(ts_code)[high].transform( lambda x: x.rolling(250).max() ) df[low_250] df.groupby(ts_code)[low].transform( lambda x: x.rolling(250).min() ) df[price_pos] (df[close] - df[low_250]) / (df[high_250] - df[low_250] 1e-8) # 预处理 df[ret_20_winsor] df.groupby(ts_code)[ret_20].transform(mad_winsorize) df[ret_20_z] df.groupby(ts_code)[ret_20_winsor].transform(rolling_zscore) # 行业中性化假设 df 有 industry 字段用回归取残差 # 简化代码实际可用 statsmodels OLS df[ret_20_neutral] df[ret_20_z] - df.groupby([trade_date, industry])[ret_20_z].transform(mean) return df5.2 大模型决策调用核心代码import json import time class LLMDecisionEngine: def __init__(self, client, cacheNone): self.client client self.cache cache if cache is not None else {} def build_prompt(self, stock_info, announcement): system_prompt ( 你是一名持有证券从业资格证的量化研究员。 你的判断只能基于公告原文和公开市场信息禁止推测内幕信息。 如果信息不足请明确输出 insufficient。 ) user_prompt f 股票{stock_info[name]}{stock_info[ts_code]} 行业{stock_info[industry]}近30日涨幅{stock_info[ret_30]:.2f}%市盈率{stock_info[pe]} 公告摘要 {announcement[:500]} 请分析该公告对股价的潜在影响。只输出 JSON不要输出其他文本 {{ event_type: 利好 | 利空 | 中性 | 无法判断, risk_flags: [风险点数组无则留空数组], suggestion: 买入 | 观望 | 回避, reason: 简短原因50字以内 }} return system_prompt, user_prompt def decide(self, stock_info, announcement): # 缓存同一股票同一公告摘要只跑一次 cache_key f{stock_info[ts_code]}_{announcement[:100]} if cache_key in self.cache: return self.cache[cache_key] system_prompt, user_prompt self.build_prompt(stock_info, announcement) try: resp self.client.chat.completions.create( modelyour-model-name, messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature0.1, timeout30, ) content resp.choices[0].message.content.strip() # 处理返回内容可能被 markdown 代码块包裹 content content.strip( json).strip() result json.loads(content) except Exception as e: # 降级策略超时或解析失败时一律观望 result {event_type: 无法判断, risk_flags: [], suggestion: 观望, reason: fLLM调用异常: {str(e)}} self.cache[cache_key] result return result5.3 风控闸门与主循环def risk_check(candidate, portfolio, trade_history): 6 道闸门全部通过才允许下单 # 定义风控规则返回通过/拒绝 拒绝原因 checks [] checks.append((仓位限制, portfolio.current_pct(candidate) candidate.alloc_size 0.10)) checks.append((行业限制, portfolio.industry_pct(candidate.industry) candidate.alloc_size 0.25)) checks.append((回撤保护, portfolio.beta() 1.2)) checks.append((流动性过滤, candidate.avg_amount_20d 5e7)) checks.append((交易频率, len(trade_history.today) 10)) checks.append((ST/次新过滤, candidate.status normal)) failed [name for name, ok in checks if not ok] return failed def daily_pipeline(): # 1. 拉当日行情计算特征 raw fetch_market_data() features compute_features(raw) # 2. 传统模型打分取前 20 candidates model.rank(features).head(20) # 3. 拉公告大模型裁决 decisions [] for cand in candidates: ann fetch_announcement(cand[ts_code]) decision llm_engine.decide(cand, ann) decisions.append({**cand, **decision}) # 4. 风控闸门 final_picks [] for d in decisions: failed risk_check(d, portfolio, trade_history) if not failed: final_picks.append(d) # 5. 生成订单 generate_orders(final_picks)这套骨架大约能覆盖整个流程的 60%剩下的就是积攒数据、试参数、加监控。算是从 0 到 1 最有效率的一条路。5.4 代码里容易漏的三个细节第一transform和apply的区别。特征工程里大量用到groupby().transform()因为它能保留原始数据的行索引对齐而apply容易把维度搞乱。如果你发现特征值对不上行先检查这里。第二缓存键的设计。大模型缓存的 key 不能只用股票代码要加上公告摘要的前 N 个字符否则公告更新了你还在用旧结论。第三风控规则不要散落在代码各处。所有风控检查统一走risk_check()函数不要在其他地方又写一份临时判断。我吃过一次亏主流程里没走风控直接调了交易接口险些出事故。6. 合规红线与实盘前的最后提醒最后这部分不是技术问题但比技术问题更重要。做 AI 量化策略合规意识必须刻在骨子里。6.1 牌照与边界个人策略和资管产品是两回事个人用自己的钱做量化策略研究和实盘属于个人投资行为边界相对清晰。但如果是代客理财、收费荐股就触碰了红线。我做的所有项目都坚持只研究自己的账户原则。这个边界在项目启动之前就要想明白否则后面做得越大越危险。6.2 数据合规公共数据够用别碰灰色渠道特征工程需要的量价数据、公告数据、财务数据全部能通过公开渠道获取。没有必要使用任何非公开数据源。在数据这方面我的建议是**宁可数据晚一天不要数据有合规瑕疵。**研究价值最大的量价数据本身已经足够做有效的特征工程多余的数据来源只是增加风险不增加胜率。6.3 免责声明必须说清楚如果未来策略梳理成文章对外发布开头的免责声明一定写明本项目仅为个人技术研究不构成任何投资建议据此操作风险自担。这既是对读者负责也是对自己负责。AI 量化在合规框架内是正经的技术方向但绝不能变成荐股工具。说点个人体会特征工程和大模型决策的落地本质上是一个约束最大化的问题。你要在数据准确性、模型能力、交易规则、合规边界之间找平衡。最难的从来不是跑通 Demo而是让整个系统在连续几个月的时间里都不出大错。我的经验是**把每一次异常都记录成案例弱信号宁可错过强信号必须拿得住。**这套玩法不能让你一夜暴富但能把回测和实盘的差距缩到最小把一个策略变成一套可持续迭代的系统。下一篇如果有机会我会把传统模型的打分逻辑和组合构建的具体参数怎么调展开讲包括怎么用组合优化控制行业暴露以及怎么用样本外滚动测试避免看着优美、实盘打脸的窘境。先把特征和大模型这块地基打牢后面的高楼才立得稳。