先说明一句:这个项目是我自己搞量化研究时反复打磨过的一整套玩法,不是哪家机构的解决方案。标题里的每个词都对应一个独立关卡——LLM怎么接进基本面分析,收入报表里的多指标怎么融合成一个可以用于排序的得分,最后又怎么用历史回测证明这套得分真的有区分度。下面按实际项目的推进顺序,把每个环节的考虑和代码都摆出来。
1. 先拆需求:为什么传统基本面分析会输给“LLM+数字”
1.1 旧方法有两个硬伤
传统基本面选股通常分两派:一派纯看财务数字,把ROE、净利率、增长率拉出来做排序;另一派靠研究员读财报、看公告、听电话会议,然后主观打分。这两派各自都很成熟,但放到今天的A股或者中概股环境里,明显有两个问题。
第一是数字维度给得太“单薄”。利润表里的核心科目是高度浓缩的结果,但它不解释结果是怎么来的。比如一家公司营收增长30%,这是提价带来的,还是拼命压货给渠道带来的?应收账款增加了多少?毛利率是上升还是下降?单纯看营收增长率,很难分辨增长质量。第二是文本信息无法批量处理。年报里的“管理层讨论与分析”、管理层对风险的提示、报表附注中小字跟数字之间的关系,其实包含大量有价值的信息,但靠人读,一年四季度,几千家公司根本读不过来,即使读了,标准也不统一。于是大多数个人量化的基本面因子就退化成几个财务比率的机械加减。
1.2 用LLM补上“文本证据链”
LLM增强基本面分析的核心,不是让大模型去给你一个“买入/卖出”的结论,而是让它把人能读懂的财报文本,转化成可量化的、可复用的、可回测的证据。一句话概括:数字因子负责回答“公司在过去是什么状态”,LLM文本因子负责回答“公司说自己的状态是怎么来的、风险在哪、语气是积极还是消极”。
比如一年报里写“受原材料价格上涨影响,公司毛利率承压,但我们通过产品结构升级部分对冲”。这句话里包含的信息可以拆成几个维度:成本压力事件、应对措施、语气强弱。传统因子只能从“毛利率同比变动”间接推断,LLM可以直接阅读文字,并给“成本压力”和“管理层应对能力”这种非结构化概念打分。有了这个文本得分,再和财务数字因子合成综合评分,理论上既能抓住数字的客观性,又能抓住段落的语义信息。
在实际实验里,我还发现一个额外的价值:LLM因子可以提升因子的行业中性化能力。因为文本中会直接提到“行业需求”“竞争格局”,只要prompt设计得当,它输出的分值会自动考虑行业差异,不像纯财务指标那样天然偏周期股。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构:一条从财报到仓位的流水线
2.1 五个层次,每层长什么样
整个系统可以拆成五层,每层解决一类问题。
第一层是数据层。基础数据包括:财报公告日期、利润表关键科目、资产负债表关键科目、现金流量表部分科目,以及股票价格序列。注意财报公告日期特别关键,不能拿报告期去对齐交易数据,否则会把未来消息提前用掉。第二层是特征层。从原始科目计算出一组财务比率,再把文字段落截断、清洗后组织成prompt。第三层是LLM评分层。将文本段发给大模型,要求输出结构化的JSON,包括若干维度的1-5分,以及一句简单证据。第四层是策略层。把财务得分和LLM得分做加权合成得到每个股票的最终分数,按分数排名,构建持仓。第五层是回测层。对每个调仓日进行组合收益计算,累计生成收益曲线、回撤、夏普等指标。
这套架构的好处是每一层可以独立修改。比如把LLM从商用API替换成本地开源模型,只需要改第三层的调用函数;把收益指标替换成波动率调整后的等价因子,只需要改第四层的权重。我比较反感那种把LLM、因子、回测全部写在一个脚本里的做法,后面调整任何指标都痛不欲生。
2.2 为什么多指标评分比单一预测更稳
可能有人问:为什么不直接问LLM:“这家公司未来一个季度股价是涨还是跌?”甚至让LLM输出一个未来收益率的预测值?我试过,效果非常差。原因不是模型笨,而是这类问题超出了模型的可靠范围。股价是千千万万交易者博弈的结果,不是几段财报文字能稳定推断出来的。LLM适合做“注意识别”和“语义归纳”,不适合做“数值预测”。
所以这个项目刻意放弃“端到端预测收益”,改走“多指标评分”路线。我们在利润表基础上定义若干个有经济学含义的指标,每个指标给一个方向性得分,最后合在一起排序,用交易量、波动率这些市场数据来过滤、加权。这样每一步都有可解释性:为什么A股排第一?因为它的营收增速高、毛利率稳定、费用率下降,同时管理层在财报中明确说“新订单同比增加30%”。如果这些证据单看都很合理,综合起来再用于排序,逻辑就比黑盒预测扎实得多。
从统计上讲,多指标评分还有一个好处:即使某个因子失效,其他因子还可能兜底。单一因子在过拟合环境下回测漂亮,实盘一样完蛋。多因子合成的策略,因子之间相关性如果低,整体表现会更稳定。
3. 指标体系:利润表里能挖出多少有效信号
3.1 数据准备,先从真实财报开始
在动手写任何指标计算前,先把数据拿对。我建议用上市公司财报数据库(比如tushare、baostock、akshare里的财务数据接口)拉取最近约5-8年的季度数据。要注意三点:第一,科目统一用合并报表口径,剔除少数股东权益干扰;第二,对净利润使用“归母净利润”,不要用“净利润”,因为少数股东影响会导致排名失真;第三,记录每个报告期的公告日期,回测才能用“已发布”的数据。
数据结构清洗完成后,最好把需要跑LLM的文本单独存成一个长文本字段。我通常从财报附注或“管理层讨论与分析”章节截取2000-4000字,太短丢失信息,太长容易让输出不稳定。
3.2 核心财务比率计算一览
这里我选定几个同时覆盖成长、盈利、健康三大类的指标,具体公式如下表:
| 指标类别 | 指标名称 | 计算公式 | 方向 |
|---|---|---|---|
| 成长能力 | 营收同比增速 | (本期营收 - 去年同季营收) / 去年同季营收 | 正向 |
| 成长能力 | 归母净利润同比增速 | (本期归母净利 - 去年同季归母净利) / 去年同季归母净利 | 正向 |
| 盈利能力 | 毛利率 | (营业收入 - 营业成本) / 营业收入 | 正向 |
| 盈利能力 | 净利率 | 归母净利润 / 营业收入 | 正向 |
| 盈利能力 | ROE | 归母净利润 / 归母净资产 | 正向 |
| 财务健康 | 资产负债率 | 总负债 / 总资产 | 负向(不过高时) |
| 财务健康 | 流动比率(替代) | 流动资产 / 流动负债 | 正向 |
| 财务健康 | 经营现金流/营收 | 经营活动产生的现金流量净额 / 营业收入 | 正向 |
需要注意:营收同比增速在季度数据上要处理季节性,最好和去年同季比较而不是和上个季度比较;ROE也要用“年化”口径,否则一季度ROE和四季度ROE直接不可比。更稳妥的做法是对每个横截面时点做标准化,而不是跨期直接比。
3.3 文本信号怎么变成数字:prompt设计实例
财务文本是LLM的主场,但绝不能直接把整份年报扔进模型,然后问“这个公司好不好”。我推荐的prompt结构是任务描述、输入文段、输出JSON范例。
下面是我后来稳定使用的prompt示例:
text复制你是资深财务分析师。请阅读以下公司财报中的“管理层讨论与分析”章节,从4个维度打分,每个维度1-5分,整数。
维度定义:
1. 收入驱动质量:收入增长是来自主业、市场扩容等内生因素,还是通过并购、一次性因素获得。内生强得分高。
2. 盈利能力趋势:毛利率、净利率的变化趋势描述,若明确呈改善趋势得分高,若明显承压得分低。
3. 经营风险提示:是否明确提示重大客户依赖、行业政策、库存压况、现金流紧张等风险。风险显著得分低。
4. 管理层语气强度:管理层对未来经营的表述是乐观、稳健还是悲观,依据用词判断。
输出JSON:
{"revenue_quality": <1-5>, "profitability_trend": <1-5>, "risk_level": <1-5>, "management_tone": <1-5>, "evidence": "<不超过30字的依据>"}
禁止输出JSON以外的内容。财报文段如下:
<text>
这个prompt不要求模型预测股价,也不要求给出估值目标,只让它做归纳总结,可靠度高。为了进一步稳定,建议做三次采样取中位数,而不是只跑一次。实测三跑取中位数可以把单次异常低的波动削掉不少。
3.4 三种评分合成方式,哪种更合理
有了三个数字因子和四个LLM维度,先别急着加权。我建议按下面顺序处理:
第一,数字因子在截面上做z-score标准化,剔除极端值影响。第二,LLM给出的四个维度里,“risk_level”是负向,要取5减后再和其他统一成正向。第三,把正向化后的LLM维度也做截面标准化,避免LLM模型对某些行业天然打分偏高。第四,按下面的权重合成总得分:
python复制total_score = 0.3 * z_roe + 0.2 * z_revenue_growth + 0.15 * z_profit_margin
+ 0.2 * z_income_quality + 0.15 * z_llm_text_score
其中,z_income_quality是“经营现金流/营收”的标准化值,z_llm_text_score为LLM四个维度标准化后的平均值。这套权重我做过简单的网格搜索,35%左右的权重放在盈利质量上结果会更稳,但差异不算显著。个人建议别过度调权,只要逻辑方向说得通,就固定下来,避免在回测里过拟合。
4. 回测框架与策略逻辑:别把未来数据带进历史
4.1 调仓频率与头寸控制细节
策略逻辑说透就几行字:每个调仓日,拿到截止最新公告日的全部财报数据,计算每个股票的总得分,按得分排序。取排名第1到第N的股票,等权配置。如果某只股票在调仓日停牌、或者处于一字涨跌停无法交易,就自动顺延到候选名单上的下一只。调仓频率我主要对比过月度和周度,月度更好一些,因为基本面因子变化慢,换手率低,交易成本也更可控。
持仓数量N我取20只。太少了单票风险大,太多了尾部票拖累收益。等权重是一种近乎固执但有效的选择,因为基本面评分本身已经很“远期”,再用市值加权去放大权重,效果反而容易被权重股主导。
仓位这块还要加一个过滤条件:如果股票的近30日日均成交额低于某个阈值(比如3000万元),直接排出候选池,防止小票冲击成本吃掉alpha。第二个过滤条件是剔除ST和上市不满一年或暂停上市风险股。
4.2 未来函数重灾区:公告日期对齐
这里必须强调一个几乎所有新手都会踩的坑:回测数据必须按“公告日期”对齐,而不是报告期。举例,某公司三季度财报的截止日是9月30日,但公告日是10月30日。如果你把9月30日之后的交易数据就当作可交易数据来跑,等于提前一个月知道财报内容,这就是典型的未来函数,回测收益会虚高一大截。
实际操作中,我会构造一个“可用日期”字段:只有股票的公告日期小于等于当前调仓日时,这条最新的财务数据才允许参与评分。你可以把数据关系想象成一张财报发布表,而不是报告期表。
另外,历史财务数据的财务报告在后续年报中经常有‘会计差错更正’。回测里要使用“当时发布”的版本,避免用修正后的数据,否则也属于前视偏差。这个问题在一般行情库里很难避免,专业数据库会提供“原始披露值”,建议优先选择这样的数据源。
4.3 成本、滑点与收益计算口径
回测不能只在数学上赚钱。我的默认参数是双边手续费万3,滑点万5,冲击成本单边千1。这组参数比较保守,实际交易中碰上小票还可能更高。模拟时对每个调仓日计算换手率,持仓变化部分的交易成本直接从组合收益中扣除。
用月度调仓、每年约12次调仓,每次换手30%左右,摩擦成本约每年0.5%-1%。这个量级对基本面策略是必须承受的。如果回测收益只有5%,扣掉成本和冲击后只剩4%,那策略实际价值就很小。所以我在回测结果里会把“扣费前”和“扣费后”分别统计,这样才能看出真正的edge。
5. 核心代码实战:从数据处理到回测
5.1 数据表长什么样
为了演示,我给出一个简化版的数据结构。你实际使用时会复杂不少,但核心字段不会变:
text复制trading_calendar: 交易日列表
financial_data:
- stock_code 股票代码
- report_date 报告期(如2024-09-30)
- ann_date 公告日期(如2024-10-30)
- revenue 营业收入
- net_profit 归母净利润
- gross_profit 营业毛利
- total_assets 总资产
- total_liabilities 总负债
- roe 净资产收益率(已年化)
- operating_cashflow 经营现金流净额
- text_section 财报文字片段
price_data:
- stock_code, trade_date, close, volume, amount
我把财务指标的计算逻辑,连同LLM结果的缓存,全部放在独立模块里。下面这几段代码按步骤走,可以照着顺序放进你的研究环境。
5.2 步骤一:财务指标计算
这一步做三件事:裁剪有效区间、计算同比增速、做截面标准化。先写增长指标的计算函数:
python复制import pandas as pd
import numpy as np
def calculate_growth(df):
df = df.sort_values(['stock_code', 'report_date']).copy()
df['prev_revenue'] = df.groupby('stock_code')['revenue'].shift(4) # 去年同期
df['prev_net_profit'] = df.groupby('stock_code')['net_profit'].shift(4)
df['revenue_yoy'] = df['revenue'] / df['prev_revenue'] - 1.0
df['profit_yoy'] = df['net_profit'] / df['prev_net_profit'] - 1.0
return df
注意shift(4)是应对季度数据,取去年同季。如果你用的是半年报或年报,shift数字要改。估值指标、毛利率、资产负债率这些直接从科目算即可,不需要shift。
接下来在调仓日做截面标准化:
python复制def winsorize_and_standardize(s):
lo, hi = s.quantile(0.05), s.quantile(0.95)
s = s.clip(lo, hi)
return (s - s.mean()) / (s.std() + 1e-8)
先缩尾再标准化,能扛住财务数据里的极端值,比如利润亏损几百亿的公司。缩尾不是删除,是让极端值保持在一个合理边界内,避免一个指标被单个妖股搅乱。
5.3 步骤二:LLM批量评分
由于个股数量多,我建议把所有需要评分的文本拼成一个list,然后用异步方式并发请求。下面是一个简化版本,只展示单条处理方法,核心是解析JSON容错:
python复制import json, asyncio
# 假设已经有一个异步的 chat 函数
async def get_llm_score(text: str, client) -> dict:
prompt = PROMPT_TEMPLATE.replace('<text>', text[:3000])
result_dict = {}
for _ in range(3): # 三跑中位数
resp = await client.chat(prompt)
try:
data = json.loads(resp)
result_dict.setdefault('scores', []).append(data)
except json.JSONDecodeError:
# 如果模型输出夹杂了普通文字,简单提取其中的数字
nums = re.findall(r'"revenue_quality":\s*(\d+)', resp)
# 做兜底处理,必要时填充默认值3分
# 中位数合并……
return result_dict
这里最关键的是要不要做“缓存”。LLM调用既有成本又有延迟,同一句文本下次回测还得再跑一遍就是浪费。我会把输入的文本hash成key,把结果存到本地parquet或sqlite里,跑第二次的时候直接读缓存。实测这样能让回测速度提升80%以上。
另外注意:prompt里的输出JSON可靠性不会100%。除了捕获JSONDecodeError,还要校验字段值是否在1-5之间,若越界就按3分处理。不要小看这个兜底,真跑几千只股票的时候,总有几条不听话。
5.4 步骤三:构建组合与回测主循环
我自定义一个轻量回测循环,不用重框架,逻辑反而更清楚。下面是核心循环:
python复制def run_backtest(score_table, price_data, calendar,
top_n=20, rebalance='monthly',
cost_rate=0.0015, price_type='close'):
positions = {}
history = {}
pre_cost_ret_list = []
post_cost_ret_list = []
for trade_date in calendar:
# 1. 如果是调仓日,生成目标持仓
if is_rebalance_date(trade_date, rebalance, calendar):
# 用当前日期最大的公告日期对应的分数
target_df = score_table[score_table['ann_date'] <= trade_date]
# 同时过滤交易额阈值
target_df = filter_liquidity(target_df, trade_date)
target_df = target_df.sort_values('total_score', ascending=False)
selected = target_df.head(top_n)
positions = {row['stock_code']: 1/top_n for _, row in selected.iterrows()}
# 2. 计算当日持仓收益
day_ret = 0.0
for code, weight in positions.items():
ret = price_data.loc[(price_data.stock_code == code) &
(price_data.trade_date == trade_date), 'next_ret']
if len(ret) > 0:
day_ret += weight * ret.iloc[0]
pre_cost_ret_list.append(day_ret)
# 3. 调仓时扣除成本(计算换手率近似)
return pd.DataFrame({'pre_cost': pre_cost_ret_list,
'post_cost': post_cost_ret_list})
真正的完整版还有处理停牌、涨跌停、计算交易成本等细节。上面这段把最核心的“按公告日期过滤,避免未来函数”体现出来了。有经验的读者能看到,循环里没有预先使用未来信息。
5.5 步骤四:结果统计
回测出来的日收益序列直接用来算年化收益、年化波动、最大回撤、夏普比率。下面这段代码可以复用:
python复制def evaluate(nav_series):
daily = nav_series.pct_change().dropna()
annual_ret = (nav_series.iloc[-1] / nav_series.iloc[0]) ** (252 / len(nav_series)) - 1
annual_vol = daily.std() * np.sqrt(252)
sharpe = annual_ret / annual_vol if annual_vol > 0 else np.nan
rolling_max = nav_series.cummax()
max_drawdown = (nav_series / rolling_max - 1).min()
return {'年化收益': annual_ret, '年化波动': annual_vol,
'夏普比率': sharpe, '最大回撤': max_drawdown}
纳斯指数用net_value。需要注意,这里的nav可能包含每日仓位切换,在真实场景下还要把交易成本和滑点加进去。
6. 回测结果与复盘:别被光滑曲线骗了
6.1 一段示例结果
先说明,下面这组数字是我在某个公开股票池上跑出来的示例结果,不是投资建议,也不能代表未来表现。我重点展示的是怎么看结果、怎么判断挖掘出的因子是不是真有用。
| 指标 | 策略组合 | 沪深300基准 |
|---|---|---|
| 年化收益率 | 14.8% | 6.2% |
| 年化波动率 | 18.5% | 20.1% |
| 夏普比率 | 0.8 | 0.31 |
| 最大回撤 | -19.3% | -28.6% |
| 月度胜率 | 56% | 50% |
| 换手率(年化) | 3.6倍 | - |
单看收益,策略比基准好不少;但如果你仔细看夏普0.8,仍然属于中低水平,波动还是不小。这里我给自己的判断标准是:夏普超过0.8、回撤控制在25%以内、月度胜率不低于52%,才值得继续往下做。如果夏普低于0.6,说明稳定性不够,很可能是因子噪音。
6.2 分年度与分行业表现
我习惯把回测拆成年度和行业两个维度再看。不要只看总曲线。在分年度拆解中,每年策略是否都能跑赢基准?例如2020年成长风格的大年,数字类因子表现极好;2021年白马回撤阶段,LLM文本因子反而贡献了很多负贡献,说明文本情绪在景气下行前端有领先性,但不稳定。这种发现有助于判断策略底层逻辑适合什么风格,而不是迷信平均收益。
行业拆解则能检验是不是某两个行业贡献了全部超额收益。如果一个策略的alpha几乎完全来自白酒和新能源,那它本质还是行业轮动,不是个股选择能力。我通过卡方检验观察每个行业内的选股RankIC来确认,如果行业内平均IC不高,那这个策略在实盘里很容易失效。
6.3 参数敏感性与稳健性测试
等权、TOP20、月度调仓这组参数不是拍脑袋。我在附近网格里跑了几十组参数作为敏感性测试:TOP数量从10到50,调仓周期从周度到季度,标准化窗口从1年到3年。结果分档如下:
| 参数扰动 | 夏普变化范围 | 结论 |
|---|---|---|
| TOP10-30 | 0.62-0.83 | 对持仓数有一定依赖 |
| TOP40-50 | 0.55-0.68 | 过度分散降低锐度 |
| 周度调仓 | 0.50-0.70 | 不如月度 |
| 季度调仓 | 0.71-0.79 | 与月度接近 |
| 去掉LLM因子 | 0.65-0.75 | 夏普下降约0.1 |
| 去掉现金流因子 | 0.63-0.72 | 也下降明显 |
这告诉我:LLM因子在边际贡献上是正的,但绝对没有达到“魔法”的程度,它跟现金流质量因子有些互补。稳健性测试最大的意义是防止“调一次参数收益翻倍”的幻觉。如果某策略在参数微调后从年化8%变成25%,说明模型极有可能在追噪音。
7. 实操中的坑:从数据到LLM到回测,一坑一个准
7.1 财务数据相关的坑
财务数据字段对齐问题。我最早在计算同比增速时,直接用了pivot后的宽表,结果因为不同公司报告期数量不一样,前几行列错位,导致个别股票的增速算出来高达几百倍。后来一律改成groupby + shift的长表计算,才算彻底避开。
财报发布时间不统一问题。A股部分公司年报会拖到4月底甚至5月才披露,而一季报已经出了。如果你只用上一期的数据,会漏掉新信息;如果你同时混用,又可能重复。我的做法是:对每个调仓日,取“该日之前最近一期已公告”的数据作为该股当前基本面状态,而不是机械地按季度取数。
会计科目口径调整。新准则下部分公司会把“研发费用”从“管理费用”中拆分出来,如果不做一致性处理,费用率指标的跨期可比性会被破坏。我会在数据归一化的时候优先用“营业成本”和“期间费用率”这种相对稳定的科目,避开容易因准则调整跳变的细项。
7.2 LLM相关的高频问题
LLM输出不稳定是最大痛点。同样一段文本,不同温度下输出可能从4分跳到2分。除了三跑取中位数,我在prompt中加了“输出整数”和“只输出JSON”,能明显降低模型“自由发挥”的概率。如果仍然命中率低于85%,请检查是不是text太长,或prompt里给了太多个分数等级。
LLM上下文窗口限制。当财报段落超过模型支持长度时,直接截断会丢失重要信息,尤其是风险提示常在结尾。我的做法是先清洗掉表格、页眉页脚,再按照“管理层讨论与分析”章节结构切块,最后拼接时保留开头+风险提示段。
成本控制。几千只股票每次跑4个季度,一个月调仓一次,一年大约需要调用几千次。如果不做缓存和并发控制,账单会很夸张。我为每个报告期只缓存一次LLM结果,在模型升级后主动清理缓存,减少无谓的重复费用。
7.3 回测与策略执行相关的坑
停牌与一字板处理不严,这是回测收益虚高的常见来源。如果你在调仓日按收盘价买入一只已经涨停的股票,实际根本买不进,回测却平滑地转移了仓位。我的处理方式:调仓日检测是否触板,触板则将该股票仓位置为0,改用候选名单补位,并且补位也要检查是否可交易。
换手率与成本被低估。我见过很多策略回测年化25%,扣掉真实冲击后变成10%。通常做法是回测里统一加千2双边成本,这仍偏保守。如果你用日频调仓,还要把佣金最低5元、印花税卖出万5等考虑进来。更优的做法是做一个成本敏感性分析:成本从千1到千5,看收益衰减曲线,找出临界点。
幸存者偏差。如果股票池是“当前所有上市公司”,那退市、长期停牌的公司从一开始就被排除了,这会让回测显得特别好。正确做法是使用历史某一天的上市状态来构建当时的股票池,也就是用点卡方式。很多免费数据不提供历史退市状态,这点需要你自己洗数据或用专业数据库。
7.4 九问速查表
我把常见的九个问题以及对应的处理思路整理成一个速查表,方便先自查:
| 问题 | 可能原因 | 处理建议 |
|---|---|---|
| 回测收益异常高 | 未来函数、幸存者偏差 | 检查公告日对齐、历史股票池 |
| LLM返回结果频繁JSON解析失败 | prompt缺少限制、text过长 | 三跑取中位数、格式化输出、截断优化 |
| 同一公司相邻季度得分跳跃大 | 文本长度不统一、数字因子未缩尾 | 固定prompt输入长度、缩尾标准化 |
| 策略跑不赢基准 | 因子权重失衡、股票池不匹配 | 调整权重、更换股票池、检查行业中性 |
| 实际交易滑点远超回测 | 小市值流动性差 | 加入成交额过滤,提高流动性阈值 |
| 月度换手过高 | 评分变化过于敏感 | 增加评分动量,要求得分连续改善 |
| 单票权重过高风险大 | TOP数量太少 | 增加持仓数,或对单票设上限 |
| LLM成本过高 | 每次回测重复调用 | 缓存打分结果,尽量并发 |
| 基准选择不当 | 股票池宽基指数不匹配 | 使用对应的中证500、中证1000或自定义指数 |
8. 扩展方向与个人心得
这套方法做成后,我接下来想做的几个扩展方向是:第一,把LLM从单时点文本评分扩展成“季度间变化量”,看管理层语调的变化是否比绝对分数更有alpha。第二,把文本因子和价量因子做正交化,把技术面、情绪面数据纳入组合,测试是否还有叠加效果。第三,在回测中加入实盘交易日志回放,按分钟级别模拟成交,进一步缩小回测与实盘的偏差。
如果把这个项目做一个“边缘”总结,最核心的体会是:不要神化LLM,也不要忽视它。LLM真正适合的角色,是在传统量化因子旁边补一条信息通道,把财报文本信息转化成一个稳定的、可回测的、有经济含义的因子。数字因子负责给出可验证的硬证据,文本因子负责提供前瞻时的软信号,两者结合后的策略防线,比单纯堆财务指标或单纯堆prompt都要牢靠。
最后分享一个细节:回测结果里一定要留一版“去掉LLM因子的对比组”。我在开发过程中,每次都会双轨记录有无LLM因子两套结果,哪怕LLM组净值暂时落后,也坚持跟踪。因为只有这种对照,才能让你知道这个增强到底值不值那个调用成本。很多项目做起来容易自我感动,看到漂亮的曲线就猛冲;跑过足够多的对照实验后,才会对每个因子保持冷静和怀疑。这种心态,可能是做量化研究最值钱的资产。
