ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

机器学习预测股票月度收益:逻辑回归、梯度提升与LSTM多模型策略解析

2026/10/3 2:46:29 拓冰建站 浏览量
机器学习预测股票月度收益:逻辑回归、梯度提升与LSTM多模型策略解析 简介一份基于逻辑回归、梯度提升与LSTM三种模型的股票收益预测项目面向金融量化爱好者、计算机相关专业学生及从业者。项目以基本面和情绪特征为输入预测下个月股票收益的正负通过每月筛选预测收益为正的前20%股票进行多头投资并对市场做空以构建市场中性组合策略思路清晰且贴近实践。资源包含可运行的Python源码、Jupyter Notebook分析说明、答辩PPT以及16张策略表现可视化图表共20个文件覆盖数据探索、模型构建、回测评估到展示汇报全流程压缩包仅1.8MB轻量易用目录结构便于按需查看。源码经测试通过适合课程设计、毕业设计或量化入门实战参考作者支持远程教学。目前已有115人学习配合英文说明文档可快速理解三种模型的预测逻辑、样本外表现及多空组合构建细节适合作为从数据到策略落地的完整参考。1. 三个模型预测下月股票收益这份源码到底能帮你搞定什么做量化的人都知道单模型预测股票收益的正负翻车概率极高——市场噪音太多逻辑回归容易欠拟合LSTM又经常过拟合到看不出规律。这份资源把逻辑回归、梯度提升和LSTM放在同一个框架里跑月度收益预测每月选出预测收益为正的前20%股票对其中一半做多头同时对市场指数做空头构建一个市场中性组合。它解决的核心问题不是“哪只股票会涨”而是“在控制市场风险的前提下下个月哪些股票有更高概率跑赢”。适合正在做课程设计、毕业设计或者刚接触基本面量化、想找一个完整可跑的基准策略的人。源码里附带策略说明文档、Pitchbook和整套回测图表下载后直接对着 Notebook 复现整个流程比自己从零搭要快得多。2. 数据准备与特征工程Quandl 数据源和月度标签怎么对齐2.1 数据源选型为什么用 Quandl 而不是 Tushare 或 yfinance这份源码的数据层用的是 Quandl 数据库。Quandl 现在已经整合进 Nasdaq Data Link但它的历史接口和表格结构在学术界和量化比赛里用得非常多尤其是 Sharadar 基本面表和价格表。选择 Quandl 而不是 Tushare 或 yfinance原因有三个第一Quandl 的表格接口对基本面数据利润表、资产负债表、现金流和价格数据做了标准化对齐字段命名统一不需要自己清洗不同来源的字段名第二它通过 API key 就能拉取适合在 Notebook 环境里快速做原型验证第三很多经典量化论文的数据集都基于 Quandl复现时对照文献更方便。不过要注意Quandl 免费层有调用次数限制而且部分表格比如 Sharadar 的 SF1 表需要订阅。如果你在运行源码时发现数据拉不下来常见做法是换成 Tiingo 或者直接读取本地 CSV。但换数据源后字段名映射要做一遍尤其是日期格式、ticker 格式和基本面指标的单位否则后面特征拼接会出问题。import quandl import pandas as pd quandl.ApiConfig.api_key YOUR_API_KEY # 拉取价格数据 prices quandl.get_table(SHARADAR/SEP, tickerAAPL, date{gte: 2015-01-01, lte: 2023-12-31}) # 拉取基本面数据 fundamentals quandl.get_table(SHARADAR/SF1, tickerAAPL, dimensionARQ, date{gte: 2015-01-01, lte: 2023-12-31})这段代码先取价格表再取季度基本面表。dimensionARQ表示按季度报告的 as-reported 数据用这个而不是MRQ最最近一个季度是因为回测时要用当时实际能拿到的数据避免前视偏差。日期范围建议稍微扩大一点给特征构造留出滞后窗口。2.2 标签构造下个月收益为什么用“符号”而不是“幅度”源码里模型的预测目标是下个月收益的正负而不是具体的涨跌幅。这是二分类问题的标准做法背后逻辑很直接预测方向比预测幅度更稳定对噪音的容忍度更高。幅度预测的误差在回归任务里会被平方放大而符号预测即使幅度错了只要方向对策略依然能赚钱。标签的具体构造方法是假设今天为 t 日取 t1 月到 t2 月的收益率作为标签。更精确地说用月末收盘价计算下个月的收益然后取符号正为 1负为 0。源码里应该是用了pct_change加shift的组合来实现这个对齐。# 月度重采样取每月最后一个交易日收盘价 monthly prices[close].resample(M).last() # 下月收益 下月收盘 / 本月收盘 - 1 monthly_ret monthly.pct_change().shift(-1) # 标签下月收益 0 记为 1否则为 0 label (monthly_ret 0).astype(int)逻辑说明shift(-1)非常关键它把收益往前移了一个月让当前月的特征去预测未来的收益。如果不做这个 shift模型就是在用本月的数据预测本月的收益等于作弊。参数说明是resample(M)用的是月末最后一个交易日如果数据里有停牌或退市需要先用dropna清洗。2.3 特征池基本面因子和情绪因子怎么组合这份策略强调同时使用基本面和情绪特征。基本面部分典型的因子包括市盈率、市净率、企业价值倍数、毛利率变化、营收增速、资产负债率等。情绪部分常见做法是用新闻情感打分、分析师评级变化或者搜索热度指数。源码里没有明确列出完整因子清单但从图表命名来看使用了 beta 控制和是否包含情绪特征的对比实验。我一般会把特征分成三组估值组、成长组、质量组每组选 3 到 5 个因子。估值组用 PE、PB、EV/EBITDA成长组用营收同比增速、利润同比增速质量组用 ROE、毛利率、资产负债率。情绪特征如果拿不到新闻数据可以先用动量因子过去 6 个月收益和波动率因子替代效果接近。features pd.DataFrame({ pe: fundamentals[pe], pb: fundamentals[pb], roe: fundamentals[roe], gross_margin: fundamentals[grossmargin], asset_turnover: fundamentals[assetturnover], mom_6m: prices[close].pct_change(126), # 6个月动量 vol_20d: prices[close].pct_change().rolling(20).std() # 20日波动 })参数说明mom_6m用 126 个交易日近似 6 个月vol_20d用 20 日滚动标准差。这些因子在进入模型前要做标准化否则逻辑回归和 LSTM 的收敛速度会受影响。特别是 LSTM输入尺度差异大时训练很容易发散。3. 三种模型的核心差异与复现逻辑回归、梯度提升、LSTM3.1 逻辑回归线性基准的意义和正则化参数选择逻辑回归在这里的角色是“线性基准”。它的优势是解释性强、训练快、不容易过拟合缺点是无法捕捉特征之间的非线性交互。源码把它作为第一个模型跑本质上是为了给梯度提升和 LSTM 一个对比锚点——如果非线性模型跑不过逻辑回归那说明特征工程本身有问题而不是模型的问题。逻辑回归的关键参数是正则化强度 C 和惩罚项类型。默认用 L2 正则化C 值越小正则化越强。在月度截面数据上特征维度通常不超过 20 个样本量可能只有几百到几千所以 C 值设在 0.1 到 1 之间比较稳妥。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X_train) lr LogisticRegression(C0.5, penaltyl2, solverliblinear, max_iter1000) lr.fit(X_scaled, y_train) print(Train acc:, lr.score(X_scaled, y_train))参数说明solverliblinear适合小数据集和二分类max_iter1000是为了确保收敛。如果数据量超过几万条可以换成solverlbfgs速度更快。C0.5是我常用的起点如果你发现训练集准确率很高但验证集崩了就把 C 调小反过来如果两个都低先检查特征标准化和标签对齐不要急着调参。3.2 梯度提升XGBoost 还是 LightGBM参数怎么设梯度提升模型在源码里对应的是 Gradient Boosting实际实现大概率是 XGBoost 或 LightGBM 之一。这类模型的核心机制是串行训练多棵决策树每棵树拟合前一棵树的残差。它对特征尺度不敏感不需要标准化而且能自动处理特征交互这是它强过逻辑回归的地方。金融时间序列上我一般优先用 LightGBM因为它的直方图算法在月度截面数据上训练速度更快。如果你是第一次跑这份源码建议保持默认参数先跑通再调n_estimators和learning_rate。import lightgbm as lgb model_lgb lgb.LGBMClassifier( n_estimators300, learning_rate0.05, max_depth4, num_leaves15, subsample0.8, colsample_bytree0.8, random_state42 ) model_lgb.fit(X_train, y_train, eval_set[(X_val, y_val)], eval_metricauc, early_stopping_rounds50)参数说明max_depth4和num_leaves15是控制树复杂度的关键金融数据信噪比低树太深容易学到噪音。subsample和colsample_bytree都是 0.8做行采样和列采样防止过拟合。early_stopping_rounds50表示验证集 AUC 连续 50 轮不提升就停止训练这是防止过拟合最有效的手段。注意eval_set必须用时间上靠后的数据不能随机切分否则会引入前视偏差。3.3 LSTM序列长度、隐藏层维度和 dropout 的取舍LSTM 在这份源码里负责捕捉时间序列上的非线性依赖。和逻辑回归、梯度提升不同LSTM 的输入是三维的样本数 × 时间步长 × 特征维度。也就是说它不是用当前月的特征做预测而是用过去 N 个月的特征序列来预测下个月的收益方向。时间步长的选择是第一个要确定的超参数。金融月度数据上时间步长取 6 到 12 比较常见。太短比如 3模型学不到中期趋势太长比如 24样本量不够月度数据的有效样本本来就不多。隐藏层维度我一般设 32 到 64 之间数据集小就取小值。import torch import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, input_size, hidden_size32, num_layers1, dropout0.3): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_size, 1) self.sigmoid nn.Sigmoid() def forward(self, x): out, _ self.lstm(x) out self.fc(out[:, -1, :]) return self.sigmoid(out).squeeze()这段代码用了 PyTorch 的nn.LSTMbatch_firstTrue让输入维度变成 (batch, seq_len, input_size)。out[:, -1, :]取最后一个时间步的隐藏状态作为全连接层的输入这是序列分类任务的常见做法。dropout0.3设置在 LSTM 层之间如果只设一层 LSTM 这个参数不生效需要num_layers2才有效。训练时建议用nn.BCELoss作为损失函数优化器用 Adam学习率从 0.001 起。LSTM 训练要特别小心月度数据样本量通常只有几百到一千多非常容易过拟合。我见过很多人把 LSTM 训到训练集准确率 98%验证集 51%这就是典型的数据量不足以支撑复杂模型的表现。缓解办法有三个——加大 dropout、减少时间步长、早停。4. 策略落地月度选股、多空组合与 beta 控制4.1 筛选逻辑前 20% 的股票为什么只做多一半源码策略的逻辑是每个月用训练好的模型预测全市场股票的收益方向概率选出预测为正且概率最高的前 20% 股票然后从中随机取一半做多同时对市场指数做等金额的空头。这样做有两个目的前 20% 的筛选保证了组合集中在模型最看好的股票上只做多一半则是为了分散个股风险避免某一只股票的黑天鹅把整个组合的 alpha 吃掉。为什么不是对全部 20% 做多因为月度调仓下模型预测的准确率通常在 52% 到 58% 之间即使是最看好的前 20%也有接近一半会预测错。集中持仓会放大单票波动分散到一半股票可以平滑收益曲线。这种做法在学术上叫 equal-weight portfolio源码里的PortfolioPerformanceBetaCap_1_DataFrame图表对应的就是不同 beta 上限下的组合表现。# 假设 pred_prob 是模型输出的下月上涨概率 top20 pred_prob.nlargest(int(len(pred_prob) * 0.2)) # 随机选一半做多 long_pick top20.sample(frac0.5, random_state42) # 等权配置 weights pd.Series(1 / len(long_pick), indexlong_pick.index) # 市场做空市值等额 market_notional portfolio_value short_weights -weights * (long_pick[mktcap] / market_notional)参数说明nlargest是 Pandas 里取前 N 大值的方法比sort_values head更简洁。sample(frac0.5)表示随机抽一半random_state固定下来是为了回测可复现。这里权重用等权不按市值加权因为月度截面下市值加权会让组合偏向大盘股失去小盘股的高 alpha 来源。4.2 市场中性空头头寸怎么对冲系统性风险市场中性策略的核心是组合的 beta 接近 0这样无论大盘涨跌组合收益主要来自选股 alpha。源码里用“对市场做相应的空头头寸”来实现具体做法是对做多股票的多头市值融券卖出等额的市场指数——通常是标普 500 或沪深 300。但实际操作中个股的 beta 不是 1所以需要对冲比例做缩放。这就是图表里PortfolioPerformanceBetaCap_1这个名字的含义——把组合的 beta 上限限制在 1。如果做多组合的 beta 是 1.3那就应该空头 1.3 倍市值的指数整体 beta 归零。不过月度调仓时 beta 会漂移所以源码里应该是做了 beta 上限约束而不是精确对冲。# 计算组合加权 beta portfolio_beta (weights * stock_beta).sum() # 目标 beta 为 0空头规模 多头市值 * portfolio_beta hedge_notional long_notional * portfolio_beta # 如果组合 beta 超过上限 1强制降低多头仓位 beta_cap 1.0 if portfolio_beta beta_cap: scaled_weights weights * (beta_cap / portfolio_beta) hedge_notional long_notional * beta_cap逻辑说明这一段是 beta cap 的实现逻辑。当组合加权 beta 超过设定上限时不对个股做调整而是直接缩仓——把多头权重按比例缩小空头规模也缩到上限对应值。这样不会改变组合内个股的相对权重只降低整体市场暴露。参数beta_cap1.0是源码图表里的默认值你可以改成 0.5 或 2.0 观察组合波动率的变化。4.3 月度调仓的回测循环注意交易成本和调仓频率月度调仓的回测框架核心是一个循环每个月末生成预测 → 筛选股票 → 调仓 → 记录组合收益 → 更新下个月的持仓。源码里的图表显示组合月收益分布和累计净值曲线说明回测是完整跑通的。但有一个很容易忽略的坑——交易成本。月度调仓一年只有 12 次成本压力比日频小很多但做多一半股票、做空指数每次调仓涉及的换手率可能达到 40% 到 60%。如果忽略交易成本年化收益可能虚高 3 到 5 个百分点。我一般会在回测里计入双边千分之二到千分之三的成本。turnover (new_weights - old_weights).abs().sum() cost turnover * 0.002 # 双边成本 0.2% net_return gross_return - cost # 月收益序列记录 monthly_returns.append(net_return)逻辑说明turnover用新旧权重的绝对差求和得到代表组合中发生变动的仓位比例。成本率取 0.2% 是 A 股和美股都相对合理的近似值如果你回测的是小盘股或低流动性股票建议提到 0.5%。这一步容易被新手漏掉但它是回测结果可信度的关键。5. 避坑与常见问题复现这份源码最容易踩的六个坑5.1 现象Quandl 数据拉取失败报错提示 api key 无效或限流原因Quandl 被 Nasdaq 收购后老接口的免费 api key 大部分失效了而且免费层有每分钟请求次数限制循环拉取多只股票时很容易触发限流。解决先检查 api key 是否还有效有效的话在拉取循环里加time.sleep(1)控制请求频率。如果接口已经废弃换成 Tiingo 或者直接用源码自带的 CSV 缓存文件。注意换数据源后日期字段要统一成datetime64类型ticker 要统一成大写字幕。5.2 现象LSTM 训练时 loss 下降很慢甚至不下降原因最常见的是特征没有做标准化。LSTM 内部的 sigmoid 和 tanh 激活函数对输入尺度敏感如果 PE 是几十倍而动量是 0.1梯度会被大数值特征主导训练不稳定。解决对所有特征做 z-score 标准化特别是进入 LSTM 之前必须有独立的 scaler。不要用全局 scaler要在训练集上 fit再 transform 验证集和测试集防止数据泄露。5.3 现象回测收益很高但实盘完全跑不出原因这是量化里最经典的坑——前视偏差。可能性有三个一是特征里用了未来数据比如用当月的完整数据预测当月收益二是没有做 survivorship bias 处理回测池里只有今天还活着的股票三是回测没有扣交易成本。解决检查标签构造的shift是否正确确认回测的股票池是当时实际可交易的。最简单的验证办法是把回测时间往前拨一年看模型是否还能稳定盈利。如果不能基本可以断定有前视偏差。5.4 现象逻辑回归和梯度提升的预测结果几乎一样原因特征池可能只有线性可分的信息或者特征之间的交互很弱。梯度提升的优势在非线性交互如果特征本身就是线性关系两个模型学到的决策边界确实高度相似。解决这不是 bug但说明特征工程做浅了。尝试加入更多交互特征比如 PE 和营收增速的比值PEG或者 ROE 的同比变化。情绪特征也是一个突破口新闻情感打分对逻辑回归的增量贡献通常更明显。5.5 现象月度调仓时某些月份完全没有可交易的股票原因筛选前 20% 时如果市场整体处于低迷期模型可能预测大部分股票下月收益为负导致通过筛选的股票数量不足组合无法分散。解决在筛选逻辑里加一个最低持仓数量限制比如至少持有 20 只。如果符合条件的股票不够就放宽到前 30% 或者持有现金。注意这会让策略的 beta 暴露变大需要重新评估市场中性效果。5.6 现象LSTM 的验证集准确率一直停在 50% 左右原因月度金融数据的信噪比极低LSTM 在这种数据上很难学到稳定的时序模式。如果时间步长取 12而有效样本只有几百模型容量和样本量严重不匹配。解决把时间步长缩短到 6隐藏层维度降到 16dropout 加到 0.5。如果验证集准确率还是不上 52%说明这个数据集不适合 LSTM直接用梯度提升的结果就好。源码里的对比图表应该也有展示不含情绪特征时 LSTM 的优势会明显缩小。6. 进阶验证怎么判断模型是真有效还是过拟合模型跑通只是第一步更重要的验证是区分真实的预测能力和随机噪音。我每次跑完这个策略会强制走一遍三个验证步骤几分钟就能暴露大部分过拟合问题。第一步是时间序列的样本外测试。不能用随机 K 折交叉验证金融数据必须按时间切分——比如 2015 到 2019 年做训练2020 到 2023 年做验证。如果训练集和验证集有重叠月模型等于开卷考试。针对这个资源我会在 Notebook 里把TimeSeriesSplit换成手动切分确保训练数据的时间截止点严格早于验证数据。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] # 训练和验证第二步是预测结果的置信区间分析。逻辑回归可以输出预测概率你要看预测概率在 0.6 以上的股票实际上涨比例是否显著高于预测概率在 0.5 附近的股票。如果两者差不多说明模型的预测概率是校准不良的高概率不代表高胜率。# 按预测概率分箱验证校准度 pred_df pd.DataFrame({prob: y_pred_prob, actual: y_test}) calibration pred_df.groupby(pd.qcut(pred_df[prob], 5)).agg( mean_prob(prob, mean), actual_rate(actual, mean) ) print(calibration)这段代码把预测概率分成五档每档取平均预测概率和实际正例占比。理想情况下两者应该接近——第一档实际正例率 50%第五档 60% 到 70%。如果第五档实际正例率不到 55%模型的排序能力就没有转化为选股胜率策略的 alpha 来源值得怀疑。参数注意qcut是按分位数切分的如果概率分布集中在中段分箱后各箱的样本量会不均衡。第三步是最关键的换手率分析。真实策略的月度调仓里每个月选出的前 20% 股票是高度重叠的——好公司的特征不会一个月内彻底变坏。如果模型每个月选出的股票重合率低于 30%说明模型在追逐短期噪音调仓成本会吃掉收益。观察重合率并画出来能看到模型预测的稳定性。# 计算相邻月份选股重合率 overlap set(month1_top20).intersection(set(month2_top20)) overlap_rate len(overlap) / len(month1_top20)如果 overlap 率很低我会考虑给预测概率加一个平滑处理比如用过去三个月的平均概率作为选股依据。这能显著降低换手率也顺便提高了预测的稳定胜率。源码里的图表不包含这个分析你可以自己在 Notebook 末尾追加这段代码。最后一个实用技巧把逻辑回归的系数打印出来看哪些特征的权重最大。如果动量因子权重过高说明策略本质是在做动量而不是基本面选股如果估值因子权重高策略会更接近价值投资风格。看完系数之后你可以决定是否要调整特征池。从那以后我每次跑完这个策略都会把这三步验证的图表存下来作为交付的一部分这东西比得到一组回测净值数字有价值得多。希望帮到你。本文还有配套的精品资源点击获取