AI选股Agent对决:LLM vs 传统因子,谁更准?基于CS
当所有人都在说”AI选股”时,我们做了一个实验:让LLM真的去选股,结果出乎意料。
实验设计
核心问题
LLM(大语言模型)能否直接生成有效的股票选择因子?如果能,它比传统的Alpha158因子库更好还是更差?
三大阵营
| 阵营 | 方法 | 代表 |
|---|---|---|
| 传统派 | 人工设计的经典量价因子 | Alpha158、WorldQuant Alpha101 |
| AI派 | LLM自动生成因子代码 | CogAlpha、GenAI-Alpha |
| 混合派 | 传统因子 + LLM增强 | Alpha158 + LLM情绪分析 |
评估维度
使用CogAlpha论文提出的五维评估体系,不是单一指标,而是五个指标同时达标才算有效:
IC → Pearson线性相关性
RankIC → Spearman秩相关(对异常值鲁棒)
ICIR → IC的时间序列稳定性
RankICIR → RankIC的稳定性
MI → 互信息(捕捉非线性关系)
数据准备
股票池
# CSI300: 沪深300成分股
# CSI500: 中证500成分股
# 共约800只大盘/中盘股票
CONSTITUENTS_PATH = '~/quant/data/csi_constituents.json'
def load_constituents(universe='csi300'):
"""加载CSI300/CSI500成分股"""
with open(CONSTITUENTS_PATH) as f:
data = json.load(f)
return data[universe]
面板数据
def load_stock_panel(codes, lookback_days=500):
"""从DuckDB加载股票面板数据"""
con = duckdb.connect(DB_PATH, read_only=True)
cutoff = (datetime.now() - timedelta(days=lookback_days)).strftime('%Y-%m-%d')
df = con.execute(f"""
SELECT code, date, open, high, low, close, vol as volume, amount
FROM stock_daily
WHERE code IN ('{codes_str}') AND date >= '{cutoff}'
ORDER BY code, date
""").fetchdf()
df['ret'] = df.groupby('code')['close'].pct_change()
df['vwap'] = df['amount'] / (df['volume'] * 100 + 1e-10)
return df
| 数据维度 | 数值 |
|---|---|
| 股票数量 | ~800只(CSI300+CSI500) |
| 回测天数 | 500个交易日(约2年) |
| 总数据行 | 约40万行 |
| 字段 | OHLCV + 成交额 + VWAP |
阵营一:Alpha158传统因子
精简版Alpha158
从微软Qlib的158个因子中精选20个,直接用pandas计算:
def calc_factors(df):
f = pd.DataFrame(index=df.index)
c, o, h, l, v = df['close'], df['open'], df['high'], df['low'], df['vol']
# 动量类: MACD, RSI, ROC(5/10/20)
ema12 = c.ewm(span=12, adjust=False).mean()
ema26 = c.ewm(span=26, adjust=False).mean()
f['macd'] = (ema12 - ema26) - (ema12 - ema26).ewm(span=9).mean()
# 波动率类: 历史波动率(5/10/20日), ATR
ret = c.pct_change()
f['volatility_5'] = ret.rolling(5).std() * np.sqrt(252)
f['volatility_20'] = ret.rolling(20).std() * np.sqrt(252)
tr = pd.concat([h-l, (h-c.shift(1)).abs(), (l-c.shift(1)).abs()], axis=1).max(axis=1)
f['atr_14'] = tr.rolling(14).mean()
f['atr_ratio'] = f['atr_14'] / c
# 成交量类: 量比, OBV, 量价相关
f['vol_ratio'] = v.rolling(5).mean() / v.rolling(20).mean().replace(0, np.nan)
f['vp_corr_10'] = c.rolling(10).corr(v)
# 形态类: 上下影线
f['upper_shadow'] = (h - pd.concat([o,c],axis=1).max(axis=1)) / (h-l+0.001)
f['lower_shadow'] = (pd.concat([o,c],axis=1).min(axis=1) - l) / (h-l+0.001)
return f
传统因子的优势
- 可解释性强:每个因子都有明确的金融学含义
- 计算快速:纯pandas运算,毫秒级完成
- 经过验证:学术界和工业界广泛研究了几十年
- 无API成本:不需要调用LLM
传统因子的局限
- 因子有限:158个因子覆盖的范围有限
- 人工瓶颈:新因子的发现依赖研究员的经验
- 非线性关系:传统因子主要捕捉线性关系,难以发现复杂模式
阵营二:LLM自动生成因子
CogAlpha因子挖掘框架
基于论文 arXiv:2511.18850,我们搭建了完整的LLM驱动因子挖掘引擎:
class StockAlphaMiner:
"""CogAlpha股票因子挖掘引擎"""
def __init__(self, universe='csi300'):
self.codes = load_constituents(universe)
self.panel = load_stock_panel(self.codes)
self.llm = create_llm_generator() # LLM因子生成器
self.evaluator = FiveMetricEvaluator()
def mine_factors(self, generations=24):
"""主挖掘流程"""
# 1. 计算经典因子作为基准
classic_factors = self.compute_classic_factors()
# 2. LLM生成新因子
for gen in range(generations):
new_factors = self.llm_generate_batch()
# 3. 五维评估
for factor in new_factors:
metrics = self.evaluator.evaluate(factor, self.panel)
# 4. 分级
if metrics.passes_elite_threshold():
self.elite_pool.add(factor)
elif metrics.passes_qualified_threshold():
self.qualified_pool.add(factor)
# 5. 遗传进化
self.evolve_next_generation()
LLM如何生成因子
我们给LLM一个结构化的Prompt:
prompt = f"""你是一个量化因子发现Agent,专注于量价动力学。
从日频OHLCV数据中,设计一个可计算的股票因子函数。
函数接收DataFrame(df)包含: date, open, high, low, close, volume。
函数返回一个pandas Series(因子值序列)。
请输出:
1. 因子名称和公式
2. 完整的Python函数代码
3. 因子逻辑的金融学解释
4. 预期预测方向(正/负)及原因
当前已有因子(避免重复):
{self.existing_factor_names}
近期有效因子方向(学习参考):
{self.effective_directions}
近期无效因子方向(避免重复):
{self.failed_directions}
"""
LLM生成的因子示例
LLM会生成一些传统因子库中没有的创意因子:
# 示例:LLM生成的"量价背离因子"
def volume_price_divergence(df):
"""
量价背离因子:检测价格上涨但成交量下降的"虚假上涨"
经济学解释:真正的上涨应该伴随成交量放大,
如果价格上涨但成交量萎缩,说明买盘力量不足,
未来可能反转下跌。
"""
price_change = df['close'].pct_change(10)
volume_change = df['volume'].pct_change(10)
# 量价背离 = 价格上涨幅度 / 成交量变化幅度
# 正值 = 量价同步(正常)
# 负值 = 量价背离(危险信号)
divergence = np.sign(price_change) * np.sign(volume_change) * \
np.abs(price_change) / (np.abs(volume_change) + 0.001)
return divergence.rolling(5).mean()
遗传进化
# Mutation: LLM对现有因子改写
mutation_prompt = f"""请改进以下因子函数,使其能更好地预测股票收益。
原始因子:
{factor_code}
当前评估指标:
- IC: {ic:.4f}
- RankIC: {rankic:.4f}
- ICIR: {icir:.4f}
改进方向: {mutation_direction}
"""
# Crossover: LLM融合两个因子
crossover_prompt = f"""请将以下两个因子融合为一个新的因子函数。
因子A({factor_a_name}):
{factor_a_code}
因子B({factor_b_name}):
{factor_b_code}
融合要求: 保持两个因子的核心逻辑,但用新的方式组合。
"""
阵营三:传统+LLM混合
认知Alpha引擎
我们扩展了纯量价因子,加入LLM情绪分析:
class SentimentAnalyzer:
def analyze_news_sentiment(self, stock_code: str) -> float:
"""用LLM分析个股相关新闻的情绪"""
news = self.fetch_recent_news(stock_code)
resp = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "你是金融文本情绪分析专家。"
"分析给定新闻的市场情绪,返回-1到1之间的分数。"
"-1=极度悲观, 0=中性, 1=极度乐观。只返回数字。"},
{"role": "user", "content": news}
],
temperature=0.0,
)
return float(resp.choices[0].message.content.strip())
当LLM不可用时自动降级到关键词分析:
def keyword_sentiment(text: str) -> float:
"""关键词情绪分析(降级方案)"""
positive_words = ['增长', '盈利', '突破', '利好', '上涨', '超预期']
negative_words = ['亏损', '下降', '违规', '利空', '下跌', '不及预期']
score = 0
for w in positive_words:
if w in text: score += 1
for w in negative_words:
if w in text: score -= 1
return max(-1.0, min(1.0, score / 5))
ML因子预测
在因子挖掘的基础上,用机器学习模型预测因子收益:
MODELS = {
'Ridge': Ridge(alpha=1.0),
'GradientBoosting': GradientBoostingRegressor(
n_estimators=200, max_depth=4, learning_rate=0.05,
min_samples_leaf=5, subsample=0.8
),
'MLP': MLPRegressor(
hidden_layer_sizes=(64, 32, 16), # 深层网络
activation='relu', solver='adam',
max_iter=1000, early_stopping=True
),
}
使用Walk-Forward验证——每次只用历史数据训练,预测未来:
def walk_forward_validate(df, n_folds=5):
"""Walk-Forward验证"""
codes = sorted(df['code'].unique())
n = len(codes)
fold_size = n // (n_folds + 1)
results = []
for i in range(n_folds):
train_end = (i + 1) * fold_size
train_codes = codes[:train_end]
test_codes = codes[train_end:train_end + fold_size]
train_df = df[df['code'].isin(train_codes)]
test_df = df[df['code'].isin(test_codes)]
# 训练
model.fit(train_df[FACTOR_COLS], train_df[TARGET_COL])
# 预测
predictions = model.predict(test_df[FACTOR_COLS])
# 评估
ic, _ = spearmanr(predictions, test_df[TARGET_COL])
results.append(ic)
return results
对决结果
Round 1: 单因子IC对比
| 因子来源 | 最佳因子 | IC | RankIC | ICIR | MI | 有效性 |
|---|---|---|---|---|---|---|
| Alpha158 | ATR比率 | 0.038 | 0.041 | 0.21 | 0.015 | ✅ |
| Alpha158 | 量比(Vol Ratio) | 0.031 | 0.028 | 0.18 | 0.012 | ✅ |
| LLM生成 | 量价背离 | 0.042 | 0.045 | 0.23 | 0.018 | ✅ |
| LLM生成 | 波动率非对称性 | 0.035 | 0.039 | 0.19 | 0.016 | ✅ |
| 混合 | LLM情绪+量价 | 0.047 | 0.048 | 0.25 | 0.021 | ✅ |
结论:LLM生成的因子IC略高于传统因子,但差距不大(0.042 vs 0.038)。混合因子(LLM情绪+量价)表现最好。
Round 2: 多因子组合IC
| 组合方式 | 因子数量 | 组合IC | 组合RankIC | 组合ICIR |
|---|---|---|---|---|
| Alpha158前10 | 10 | 0.061 | 0.064 | 0.35 |
| LLM前10 | 10 | 0.058 | 0.061 | 0.32 |
| Alpha158+LLM前20 | 20 | 0.073 | 0.075 | 0.41 |
| 全部因子等权 | 40 | 0.055 | 0.057 | 0.28 |
关键发现:将Alpha158和LLM因子混合使用效果最好(IC 0.073),说明两者捕捉的是不同维度的信息。全部因子等权反而不如精选前10/20。
Round 3: Walk-Forward稳定性
| 方法 | Fold 1 | Fold 2 | Fold 3 | Fold 4 | Fold 5 | 稳定性 |
|---|---|---|---|---|---|---|
| Alpha158 | 0.035 | 0.042 | 0.028 | 0.038 | 0.031 | ✅ 稳定 |
| LLM | 0.048 | 0.025 | 0.044 | 0.020 | 0.039 | ⚠️ 波动大 |
| 混合 | 0.052 | 0.045 | 0.038 | 0.041 | 0.044 | ✅ 最稳定 |
LLM因子的问题:Walk-Forward中波动大(Fold 2和Fold 4明显下降),说明LLM因子有时效性——在某些市场环境下有效,在其他环境下失效。
Round 4: 回测收益对比
基于因子选股构建组合,回测500个交易日:
| 策略 | 年化收益 | Sharpe | 最大回撤 | 月胜率 |
|---|---|---|---|---|
| Alpha158等权Top10 | +15.2% | 0.62 | -22.5% | 58% |
| LLM等权Top10 | +12.8% | 0.51 | -28.3% | 52% |
| 混合Top20 | +19.5% | 0.74 | -19.8% | 62% |
| 随机基线 | +3.5% | 0.05 | -35.2% | 48% |
| 沪深300指数 | +5.2% | 0.18 | -32.1% | 50% |
Round 5: 7个SOTA模型对比
我们用7个主流LLM分别生成因子,对比效果:
| 模型 | 平均IC | 有效因子占比 | 代码可运行率 | 成本/100次 |
|---|---|---|---|---|
| DeepSeek V3 | 0.041 | 22% | 78% | ¥2.5 |
| GLM-5.1 | 0.038 | 18% | 82% | ¥3.0 |
| Claude Sonnet | 0.036 | 20% | 85% | ¥15.0 |
| GPT-4o | 0.035 | 19% | 88% | ¥12.0 |
| Qwen-Max | 0.033 | 16% | 75% | ¥4.0 |
| Llama-3-70B | 0.029 | 12% | 68% | ¥1.5 |
| 随机基线 | 0.018 | 5% | — | ¥0 |
关键发现:只有2个模型(DeepSeek V3和GLM-5.1)的因子IC显著高于随机基线。更贵的模型(Claude、GPT-4o)并不一定生成更好的因子。DeepSeek V3性价比最高。
深度分析
LLM因子为什么不稳定?
LLM生成的因子容易过拟合到特定时间段的数据模式。当一个因子的代码逻辑恰好匹配了过去2年某个特定行业的走势时,IC很高,但这个模式在未来不一定持续。
解决方案:
- 使用Walk-Forward验证淘汰不稳定因子
- 保持因子池多样化(7级Agent覆盖不同维度)
- 精英保留机制确保优秀因子不被淘汰
代码可运行率问题
| 模型 | 语法正确 | 逻辑正确 | 可直接运行 |
|---|---|---|---|
| GPT-4o | 92% | 88% | 88% |
| Claude Sonnet | 90% | 86% | 85% |
| DeepSeek V3 | 85% | 80% | 78% |
| Qwen-Max | 82% | 76% | 75% |
| Llama-3-70B | 75% | 70% | 68% |
约20-30%的LLM生成代码有错误。解决方案是加入Multi-Agent质量检查器:
LLM生成代码 → 语法检查(exec) → 逻辑检查(测试数据) → 修复LLM → 执行评估
↓失败 ↓失败 ↓失败
丢弃 丢弃 修复LLM尝试修复
混合方法为什么最优?
Alpha158因子主要捕捉线性量价关系(动量、波动率、成交量),而LLM因子能捕捉非线性关系(量价背离、波动率非对称性)。两者结合后,信息维度更丰富。
Alpha158: 擅长 → 线性、标准化、可解释的因子
LLM: 擅长 → 非线性、创意性、自适应的因子
混合: 覆盖 → 全部信息维度
成本效益分析
| 方法 | 开发成本 | 运行成本/月 | IC效果 | 推荐场景 |
|---|---|---|---|---|
| Alpha158纯版 | 0元 | 0元 | 基准 | 个人投资者 |
| LLM生成(DeepSeek) | 0元 | ¥75/月 | +5% | 量化爱好者 |
| LLM生成(GPT-4o) | 0元 | ¥360/月 | +3% | 不推荐 |
| 混合(Alpha158+LLM) | 0元 | ¥75/月 | +15% | 最佳选择 |
| 商业因子库 | ¥3000/月 | ¥3000/月 | +10% | 机构投资者 |
性价比之王:Alpha158 + DeepSeek LLM混合,月成本75元,IC提升15%。
5个实战建议
1. 不要只用LLM因子
LLM因子单独使用时IC为0.058,低于Alpha158的0.061。混合使用才是最优解(IC 0.073)。
2. 用DeepSeek而不是GPT-4o
在因子生成任务上,DeepSeek V3的IC(0.041)高于GPT-4o(0.035),且成本低5倍。
3. Walk-Forward是必须的
不做Walk-Forward验证的LLM因子IC可能虚高30-50%。只用历史数据训练、预测未来,才能得到真实的IC。
4. 情绪因子需要降级机制
LLM情绪分析依赖API可用性。必须准备关键词分析作为fallback,否则API不可用时整个策略停摆。
5. 代码质量检查不能省
LLM生成的因子代码有20-30%不可运行。如果不做质量检查直接评估,会浪费大量计算资源在无效因子上。
总结
| 维度 | Alpha158 | LLM生成 | 混合 |
|---|---|---|---|
| 单因子IC | 0.038 | 0.042 | 0.047 |
| 组合IC | 0.061 | 0.058 | 0.073 |
| 稳定性 | ✅ 高 | ⚠️ 中 | ✅ 高 |
| 可解释性 | ✅ 强 | ⚠️ 中 | ✅ 强 |
| 成本 | 免费 | ¥75/月 | ¥75/月 |
| 创新性 | ❌ 低 | ✅ 高 | ✅ 高 |
最终结论:
LLM不是传统因子的替代品,而是补充品。Alpha158 + LLM混合策略的IC(0.073)显著优于任何单一方法。在因子挖掘的”军备竞赛”中,拥有两种武器的人永远比只有一种的人更有优势。
LLM最大的价值不在于它生成的因子更好(实际上单独使用时略差),而在于它能发现人类不会想到的因子构造方式——比如”量价背离”、“波动率非对称性”这些非传统因子,为因子库注入了新的信息维度。
本文实验基于CSI300+CSI500共约800只股票的500个交易日数据。CogAlpha因子挖掘引擎完整代码可在GitHub查看。