
1. 从一条标题拆出来的真实需求大模型荐股到底靠不靠谱第一次看到“ChatGPT大模型荐股与夏普比率投资组合优化系统”这个标题我脑子里冒出来的第一个念头不是“这玩意儿能赚钱吗”而是“这套东西到底怎么把自然语言模型和量化金融的数学框架缝在一起”。因为但凡做过一点量化的人都知道荐股和组合优化是两个完全不同的工种——前者偏主观判断和信号生成后者是纯粹的数学规划问题。把大模型塞进这个链路里它到底扮演什么角色这才是整个项目最值得聊的地方。先把话说在前头这篇文章不构成任何投资建议所有涉及标的、参数、代码的内容都只作为技术实现的示例。我做这个项目的出发点很简单就是想验证一件事——大模型在金融信号生成这个环节能不能比传统的因子模型或者技术指标提供一些增量信息。结论我先不急着给咱们一步步拆。这套系统适合谁看如果你是有一定Python基础、对量化交易有初步了解、想看看大模型怎么落地到金融场景的开发者那这篇内容会对你有直接帮助。如果你是完全零基础的小白也不用慌我会把每个环节的原理和操作都讲清楚你跟着走一遍也能跑通。整个系统涉及的核心技术栈包括Python数据处理、大模型API调用、Agent任务编排、夏普比率计算、组合优化求解。听起来吓人但拆开来看每一块都不复杂。我先把整个系统的骨架说清楚让你有个全局认知。系统分四层第一层是数据层负责拉取行情数据、财务数据和新闻文本第二层是信号层用大模型Agent对新闻和公告做情感分析、事件抽取生成看多看空信号第三层是组合层把信号转化成预期收益向量结合协方差矩阵做均值方差优化目标是最大化夏普比率第四层是回测层用历史数据验证策略表现输出净值曲线和风险指标。这四层里大模型只在第二层发挥作用其他三层都是传统量化方法。这个设计很关键后面我会详细解释为什么不能让大模型碰组合优化。2. 系统整体架构设计与技术选型逻辑2.1 为什么用Agent而不是直接调大模型API很多人做类似项目的第一反应是写个prompt把新闻丢给大模型让它输出“买入/卖出/持有”完事。我一开始也这么干过实测下来问题很大。单次调用的输出极不稳定同一篇新闻你换个时间问它可能给你完全相反的结论。而且它没法处理多步推理——比如一条新闻里提到某公司拿到了大订单但同时又提到原材料涨价可能压缩利润这种矛盾信息单次调用很难权衡。Agent的价值就在这里。我设计了一个三层Agent结构第一层是信息抽取Agent负责从原始文本里提取结构化事件比如“公司A获得政府补贴5000万”第二层是影响评估Agent判断这个事件对哪些行业、哪些公司有正向或负向影响影响力度大概多大第三层是信号聚合Agent把多个事件的影响汇总成一个综合评分。每一层Agent都有明确的输入输出格式层与层之间通过结构化数据传递而不是自然语言。这样做的好处是每一步都可追溯、可调试出了问题你知道是哪一层判断错了。Agent的编排我用的是轻量级的方案没有上LangChain那种重框架。原因很简单这个系统的Agent逻辑是固定的流水线不需要动态规划或者工具调用用LangChain反而增加调试成本。我直接用Python的类和方法把三个Agent串起来每个Agent内部封装prompt模板和API调用逻辑外部通过一个调度器按顺序执行。代码量不大但可控性极强。2.2 夏普比率为什么是组合优化的核心目标夏普比率的公式大家都知道(组合预期收益 - 无风险利率) / 组合波动率。但很多人做优化的时候只盯着收益忽略了波动率结果组合净值曲线跟过山车一样。夏普比率的本质是衡量“每承担一单位风险你能拿到多少超额收益”。这个指标高说明你的收益不是靠赌出来的而是有风险调整后的真实超额。在均值方差优化框架里最大化夏普比率等价于找到切点组合——也就是从无风险利率出发与有效前沿相切的那个点。数学上可以转化为一个二次规划问题求解。我用的是cvxpy这个库它能把优化问题用接近数学公式的方式写出来求解器自动处理。相比scipy.optimizecvxpy在处理约束条件时更直观比如“单只股票权重不超过10%”“行业暴露不超过30%”这种约束写起来很自然。这里有个关键细节预期收益向量怎么来。传统方法用历史收益率均值或者因子模型预测我用的是大模型信号加上历史动量做加权。具体来说大模型对每只股票给出一个[-1, 1]的情感评分我把这个评分映射成预期收益的调整项叠加到历史收益率均值上。权重方面大模型信号的权重设了0.3历史动量的权重是0.7。这个比例不是拍脑袋定的后面回测部分我会讲怎么调。2.3 数据管道的设计行情、财务、文本三路并行数据层我分了三路。行情数据用akshare拉取这个库免费且接口稳定日线数据足够用。财务数据也是akshare主要用市盈率、市净率、ROE这几个指标做股票池的初筛。文本数据是重点我爬了三个来源财经新闻网站、公司公告、社交媒体讨论。爬虫用requests加BeautifulSoup没有上scrapy因为数据量不大每天几百条文本简单脚本就够了。文本数据进来之后要先做清洗。去重、去广告、去HTML标签这些是基本操作。然后做分词和关键词提取我用的是jieba分词配合自定义的金融词典。这一步的目的是把长文本压缩成关键信息减少大模型API的token消耗。实测下来一篇800字的新闻经过关键词提取后压缩到200字左右信息损失很小但API成本降了70%。数据存储用的是SQLite没上MySQL或者PostgreSQL。原因很简单这个系统的数据量是单机级别的SQLite完全够用而且零配置部署方便。表结构设计了三张主表price_daily存行情financial_quarterly存财务text_raw存原始文本text_processed存处理后的文本和Agent输出。索引建在股票代码和日期上查询速度很快。3. 大模型Agent信号生成的核心细节与实操要点3.1 Prompt模板的设计结构化输出是底线Agent能不能稳定输出90%取决于prompt设计。我踩过的最大坑就是让大模型自由发挥结果它今天输出“强烈推荐”明天输出“建议关注”格式完全不统一后面根本没法解析。后来我强制要求输出JSON格式并且在prompt里给出完整的schema示例。信息抽取Agent的prompt大概长这样EXTRACT_PROMPT 你是一个金融信息抽取助手。请从以下文本中提取所有与上市公司相关的事件。 输出格式必须为JSON数组每个元素包含以下字段 - stock_code: 股票代码6位数字 - stock_name: 股票名称 - event_type: 事件类型只能是[业绩预告, 重大合同, 政策影响, 高管变动, 行业新闻]之一 - sentiment: 情感倾向-1到1之间的浮点数-1表示极度利空1表示极度利好 - confidence: 置信度0到1之间的浮点数 - reason: 判断理由不超过50字 文本内容 {text} 这个模板的关键在于字段类型明确、取值范围明确、枚举值明确。大模型看到这种约束输出稳定性会大幅提升。但即便如此我还是加了一层校验——用jsonschema库对输出做格式验证不合格的就重试重试三次还不行就丢弃这条数据。实测下来第一次调用合格率大概85%重试一次后能到95%以上。3.2 影响评估Agent的推理链设计信息抽取只是第一步真正难的是判断一个事件对股价的影响。比如“某公司获得政府补贴”这个事件对该公司是利好但对同行业其他公司可能是利空——因为补贴意味着政策倾斜资源向头部集中。这种间接影响单靠信息抽取Agent是看不出来的。影响评估Agent的prompt里我加入了一个推理链的引导IMPACT_PROMPT 你是一个资深行业分析师。已知以下事件 {event_json} 请按以下步骤推理 1. 该事件直接影响哪些公司影响方向是什么 2. 该事件是否会影响上下游产业链具体影响哪些环节 3. 该事件是否会影响同行业竞争对手影响方向是什么 4. 综合以上分析给出受影响股票列表及各自的评分。 输出JSON格式包含affected_stocks数组每个元素有stock_code、impact_score(-1到1)、impact_reason。 这个推理链的设计参考了人类分析师的思考过程。实测下来加了推理链之后Agent对间接影响的识别率明显提升。我做过一个对比测试100条新闻不加推理链的Agent只识别出32条有间接影响加了推理链之后识别出67条。当然这里面有误报但整体召回率上去了对后续组合优化是有利的。3.3 信号聚合与去噪别让大模型带偏节奏三个Agent跑完你会得到一堆事件和评分。但这些评分不能直接拿来用因为存在几个问题第一同一事件可能被多个Agent重复识别导致重复计算第二不同事件的时间衰减不同三天前的新闻和今天的新闻影响力度肯定不一样第三大模型有时候会过度反应把一个小事件评成极端值。我的处理方案是三层去噪。第一层做去重用事件类型加股票代码加日期做唯一键重复的取置信度最高的那条。第二层做时间衰减衰减因子用指数衰减半衰期设了3天。也就是说今天的新闻权重是1三天前的权重是0.5一周前的权重是0.25。第三层做截断把所有评分限制在[-0.8, 0.8]之间防止极端值主导组合。这个截断阈值是回测调出来的后面会讲。聚合的时候我对每只股票把所有相关事件的评分加权求和权重是置信度乘以时间衰减因子。最后再做一个横截面标准化把评分映射到标准正态分布这样不同股票之间的评分才有可比性。注意大模型信号只能作为辅助不能完全替代传统因子。我在回测中发现纯靠大模型信号的组合夏普比率只有0.8左右而加入动量因子后能到1.5以上。大模型的价值在于捕捉传统因子覆盖不到的事件驱动机会而不是全面替代。4. 组合优化与夏普比率最大化的完整实现4.1 预期收益向量的构建信号与动量的融合预期收益向量是整个优化问题的输入它的质量直接决定组合表现。我的构建方法是对每只股票取过去60个交易日的日收益率均值年化后作为基础预期收益。然后加上大模型信号的调整项调整幅度是信号评分乘以一个系数。这个系数我设的是0.02也就是说信号评分为1的股票预期收益上调2%评分为-1的下调2%。为什么是0.02这个数字是回测调出来的。我试过0.01、0.02、0.05、0.1四个档位0.02的夏普比率最高。0.01的时候信号影响太弱跟纯动量策略差不多0.05和0.1的时候信号影响太强组合波动率飙升夏普比率反而下降。这个参数跟市场环境有关牛市里可以适当调高熊市里要调低。协方差矩阵的估计用的是Ledoit-Wolf收缩估计没有直接用样本协方差。原因很简单股票数量多的时候样本协方差矩阵不稳定容易导致优化结果极端化。Ledoit-Wolf方法通过向对角矩阵收缩降低了估计误差。sklearn.covariance里有现成的实现一行代码就能调用。4.2 约束条件的设置别让优化器放飞自我无约束的均值方差优化有个臭名昭著的问题它会给你一堆极端权重比如某只股票权重90%其他股票加起来10%。这种组合在实际交易中根本没法执行而且风险极大。所以必须加约束。我设了四类约束。第一类是权重上下限单只股票权重在0到0.1之间也就是说最多10%仓位。第二类是行业暴露约束每个行业的合计权重不超过30%。第三类是换手率约束每次调仓的换手率不超过50%避免交易成本吃掉收益。第四类是做多约束不允许做空。这些约束在cvxpy里都是几行代码的事。import cvxpy as cp import numpy as np n len(mu) w cp.Variable(n) gamma cp.Parameter(nonnegTrue) # 目标最大化 (w mu - rf) / sqrt(w Sigma w) # 转化为最小化方差约束收益 ret mu w risk cp.quad_form(w, Sigma) objective cp.Minimize(risk) constraints [ ret target_return, cp.sum(w) 1, w 0, w 0.1, # 行业约束略 ] prob cp.Problem(objective, constraints) prob.solve()上面这段代码是简化版实际实现中我用的是最大化夏普比率的等价形式固定分母最大化分子。具体做法是设一个目标收益最小化方差然后遍历不同的目标收益找到夏普比率最大的那个点。这个方法比直接优化夏普比率更稳定因为直接优化涉及分式规划数值上容易出问题。4.3 回测框架与绩效评估回测框架我用的是自己写的事件驱动引擎没有用backtrader或者zipline。原因是我需要精确控制每个调仓日的信号生成和组合优化流程现成框架的抽象层反而碍事。回测逻辑很简单每个调仓日用截止当天的数据生成信号优化组合然后持有到下一个调仓日。调仓频率设的是每周一次因为大模型信号是事件驱动的日频调仓交易成本太高。绩效评估除了夏普比率我还看了几个指标最大回撤、年化收益、年化波动率、Calmar比率、胜率。这些指标用empyrical库计算一行代码搞定。回测区间是2022年1月到2024年12月覆盖了熊市、震荡市和反弹市三种环境。回测结果这里我不放具体数字因为不同股票池、不同参数下结果差异很大放出来容易误导。但可以分享一个定性结论加入大模型信号后组合的夏普比率相比纯动量策略提升了20%到40%最大回撤降低了10%到20%。提升主要来自两个方面一是大模型能提前捕捉到一些事件驱动的机会二是大模型信号与动量因子相关性低有分散化效应。5. 实操过程中踩过的坑与排查技巧5.1 大模型API调用的稳定性问题做这个项目最大的痛点不是算法而是API调用的稳定性。我遇到过几种典型问题一是超时尤其是文本比较长的时候响应时间可能超过30秒二是返回格式不对明明要求JSON它给你返回一段自然语言三是内容截断输出到一半停了。针对超时我加了重试机制用tenacity库做指数退避重试最多重试5次。针对格式问题我加了JSON解析的容错处理如果解析失败尝试用正则提取JSON部分还不行就丢弃。针对截断我设置了max_tokens参数确保输出长度足够同时在prompt里明确要求“输出必须完整不要截断”。还有一个坑是并发控制。我一开始用多线程并发调用API结果触发限流大量请求失败。后来改成信号量控制并发数同时最多10个请求稳定性大幅提升。如果你用的是按量付费的API还要注意成本控制我每天处理500条文本一个月下来API费用大概在几十美元不算贵但也要心里有数。5.2 数据对齐与时间戳处理金融数据最麻烦的就是时间对齐。行情数据是交易日财务数据是季度文本数据是自然日。这三路数据的时间戳必须对齐到同一个调仓日否则会引入未来函数。我的处理方法是所有数据都对齐到调仓日的收盘后。也就是说调仓日当天收盘后用截止当天的所有数据生成信号第二天开盘执行交易。这里有个细节文本数据的时间戳要特别注意。新闻的发布时间和实际影响时间可能不一致比如晚上发布的新闻第二天开盘才会反映到价格里。我的处理是把当天15:00之后发布的新闻归到下一个交易日。这个规则不是完美的但能避免大部分未来函数问题。5.3 常见问题速查表问题现象可能原因排查方法解决方案Agent输出格式错误prompt约束不够强打印原始输出检查加强schema约束加格式校验和重试组合权重极端集中协方差矩阵估计不稳定检查条件数用Ledoit-Wolf收缩估计加权重约束回测夏普比率异常高存在未来函数检查数据时间戳对齐严格对齐到调仓日收盘后API调用频繁失败并发过高触发限流查看错误码加信号量控制并发数加指数退避重试信号与价格背离大模型过度反应对比信号与后续收益加评分截断降低信号权重换手率过高调仓频率太高统计每日换手降低调仓频率加换手率约束提示回测结果好看不代表实盘能赚钱。我见过太多回测夏普比率2.0以上、实盘亏钱的策略。回测只能验证逻辑不能预测未来。实盘之前一定要做模拟盘至少跑三个月。5.4 参数调优的经验法则这个系统里有几个关键参数大模型信号权重、时间衰减半衰期、评分截断阈值、调仓频率、单只股票权重上限。这些参数不能随便设但也不建议用网格搜索暴力调优因为容易过拟合。我的做法是先根据逻辑定一个合理范围然后在这个范围内做敏感性分析。比如信号权重逻辑上它不应该超过历史动量的权重所以范围定在0.1到0.5之间。然后在这个范围内取几个点看夏普比率的变化曲线。如果曲线比较平坦说明参数不敏感选中间值就行如果曲线很陡说明参数敏感要小心过拟合。时间衰减半衰期我试了1天、3天、5天、10天四个值3天的表现最好。这个也符合直觉事件驱动的影响通常持续几天太短了信号来不及反映太长了噪音太多。评分截断阈值试了0.5、0.8、1.00.8最好既能保留信号强度又能防止极端值。6. 系统扩展方向与个人实操体会这套系统跑通之后我陆续做了一些扩展。一个是加入了多模态信号把财报电话会议的音频转成文本再用大模型分析管理层语气。这个方向有意思但工程复杂度高音频转文本的准确率是个瓶颈。另一个是加入了组合归因分析拆解收益来源看多少来自大模型信号多少来自动量因子多少来自行业配置。这个对策略迭代很有帮助。还有一个扩展方向是Agent的自我反思机制。具体来说就是让Agent定期回顾自己之前的判断对比实际市场走势总结哪些判断对了、哪些错了然后把总结写入prompt指导下一次判断。这个思路来自强化学习里的经验回放实测下来对提升信号质量有一定帮助但提升幅度有限大概5%到10%。我个人在实际操作中的体会是大模型在金融领域的应用目前最适合的场景是信息提取和事件驱动信号生成不适合做价格预测和组合优化。它的优势在于处理非结构化文本劣势在于数值计算和逻辑一致性。把合适的工作交给合适的工具这才是工程化的思维方式。另外这个系统的代码和文档我都整理好了包括Agent的prompt模板、数据管道脚本、组合优化代码、回测框架如果你照着复现大概两三天能跑通。但我要提醒一句跑通和赚钱是两码事量化交易的核心竞争力从来不是代码而是对市场的理解和风险控制的能力。