股票价格预测机器学习实战:逻辑回归、随机森林与神经网络对比
简介这份资源面向计算机、人工智能、通信工程等专业的在校学生与项目实战学习者提供一套基于多种机器学习算法进行股票价格预测的Python完整实现可用于毕业设计、课程设计或期末大作业参考。包内共20个文件以6个py源码、5个csv与3个xlsx数据文件为主另含bat批处理脚本、dot决策树结构文件、png结果图与md说明文档压缩包约3.66MB。源码覆盖人工神经网络、逻辑回归、随机森林、支持向量机等算法并配有训练测试数据、归一化数据与预测结果文件方便对比不同模型的预测表现。目前已有328人学习下载。项目代码均经测试运行成功答辩评审平均分达96.5分读者可据此理解特征处理、模型训练与结果评估的完整流程也可在现有代码基础上修改扩展实现其他预测功能。1. 股票价格预测的机器学习选型从一份源码包说起很多人第一次接触股票价格预测都是被一份「多算法对比」的源码包带进来的——人工神经网络、逻辑回归、随机森林全塞在一起跑完还能出一张对比图。但真正上手就会发现同一份数据换个算法结果能差出好几个百分点甚至方向都反了。这份源码包的价值不在于「预测准不准」而在于它把分类和回归两条技术路线摆在一起让你看清每种算法在金融时序数据上的真实边界。逻辑回归和随机森林在这里通常做的是涨跌方向分类人工神经网络既可以做分类也可以做回归取决于输出层怎么设计。适合谁适合已经会 Python 基础语法、想用机器学习做量化入门的人也适合做课程设计或期末复习时需要一个完整可跑案例的读者。下面我按自己复现这类项目的顺序把数据、特征、模型、评估和踩坑一条条拆开讲。2. 数据准备与特征工程把 OHLCV 变成模型能吃的矩阵2.1 股票数据的获取与清洗常见做法是用 yfinance 或 tushare 拉日线数据字段就是开盘价、最高价、最低价、收盘价、成交量。源码包里一般会附带一份 CSV但你要清楚它是什么周期的数据——日线、周线还是分钟线这直接决定后面标签怎么打。清洗阶段主要处理三件事停牌日的缺失值、除权除息造成的价格跳变、以及成交量单位不一致。停牌日用前向填充除权日用后复权价格成交量统一成股或手。import pandas as pd import numpy as np # 读取源码包里的 CSV假设列名为 Date, Open, High, Low, Close, Volume df pd.read_csv(stock_data.csv, parse_dates[Date]) df df.sort_values(Date).reset_index(dropTrue) # 前向填充停牌日缺失再删掉仍然为空的行 df df.ffill().dropna() # 用收盘价计算日收益率避免除权造成的绝对价格跳变 df[ret] df[Close].pct_change() # 成交量取对数压缩量纲差异 df[log_vol] np.log1p(df[Volume]) df df.dropna().reset_index(dropTrue) print(df[[Date, Close, ret, log_vol]].head())这段代码的逻辑是先保证时间有序再填充缺失然后用收益率替代绝对价格。参数上ffill()适合停牌不超过连续多日的情况如果停牌太久建议直接剔除该区间。pct_change()默认算的是相邻两行所以排序不能省。log1p而不是log是为了兼容成交量为 0 的极端情况。2.2 标签构造分类和回归的分岔口逻辑回归和随机森林做分类时标签通常是「明天涨还是跌」用 0/1 表示。人工神经网络做回归时标签是明天的收益率或收盘价。这里有个容易翻车的地方如果用收盘价做回归标签模型会学到「今天的价格约等于明天的价格」评估指标看起来很好实际没有预测能力。所以回归标签建议用收益率或者用「未来 N 日收益率」。# 分类标签明天收益率大于 0 记为 1否则为 0 df[label_cls] (df[ret].shift(-1) 0).astype(int) # 回归标签明天收益率 df[label_reg] df[ret].shift(-1) # 特征过去 5 日的收益率、成交量变化、高低价差 for lag in range(1, 6): df[fret_lag{lag}] df[ret].shift(lag) df[fvol_lag{lag}] df[log_vol].shift(lag) df[hl_range] (df[High] - df[Low]) / df[Close] df df.dropna().reset_index(dropTrue)shift(-1)是把明天的值提到今天这一行作为监督学习的标签。滞后特征用shift(lag)构造lag 取 1 到 5 是常见起点再长要考虑共线性。hl_range是当日振幅对波动率敏感的策略有用。注意最后一行因为shift(-1)会变成 NaN必须 drop 掉否则训练时会报错或静默出错。2.3 训练集和测试集的切分方式时序数据不能随机切分这是血泪经验。随机切分会让未来信息泄漏到训练集模型在测试集上表现虚高。正确做法是按时间顺序切比如前 80% 做训练后 20% 做测试。如果要做交叉验证用TimeSeriesSplit不要用KFold。from sklearn.model_selection import TimeSeriesSplit split_idx int(len(df) * 0.8) train df.iloc[:split_idx] test df.iloc[split_idx:] feature_cols [c for c in df.columns if c.startswith((ret_lag, vol_lag))] [hl_range] X_train, y_train train[feature_cols], train[label_cls] X_test, y_test test[feature_cols], test[label_cls] tscv TimeSeriesSplit(n_splits5) print(训练集样本数:, len(X_train), 测试集样本数:, len(X_test))split_idx取 0.8 是经验值数据量少时可以放到 0.7。feature_cols用前缀筛选避免把标签列误入特征。TimeSeriesSplit的n_splits决定验证轮数数据少于 1000 行时建议 3 到 5。3. 三种算法的实现逻辑回归、随机森林、人工神经网络3.1 逻辑回归做涨跌分类的最小可用配置逻辑回归在这类项目里常被当成基线模型因为它训练快、系数可解释。但金融数据噪声大默认的 L2 正则往往不够需要调C参数。C越小正则越强欠拟合风险上升C越大越容易过拟合。我一般从 0.1 到 10 之间做对数网格搜索。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.metrics import accuracy_score, classification_report pipe_lr Pipeline([ (scaler, StandardScaler()), (clf, LogisticRegression(C1.0, max_iter1000, solverlbfgs)) ]) pipe_lr.fit(X_train, y_train) pred_lr pipe_lr.predict(X_test) print(逻辑回归准确率:, accuracy_score(y_test, pred_lr)) print(classification_report(y_test, pred_lr))StandardScaler对逻辑回归是必须的因为不同特征的量纲差异会让梯度下降收敛慢甚至不收敛。max_iter1000是防止默认 100 次迭代不够导致警告。solverlbfgs适合中小规模数据数据量大时换saga。注意classification_report里的 precision 和 recall 比 accuracy 更有参考价值因为涨跌样本可能不平衡。3.2 随机森林的参数怎么设才不白跑随机森林需要跑多长时间取决于树的数量和最大深度。n_estimators从 100 起步max_depth不设限时树会一直长到叶子纯净训练慢且容易过拟合。我一般先设max_depth5到10min_samples_leaf设 10 到 50控制单棵树复杂度。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV rf RandomForestClassifier(random_state42, n_jobs-1) param_grid { n_estimators: [100, 200], max_depth: [5, 8, 12], min_samples_leaf: [10, 30] } grid_rf GridSearchCV( rf, param_grid, cvTimeSeriesSplit(n_splits3), scoringf1, n_jobs-1 ) grid_rf.fit(X_train, y_train) best_rf grid_rf.best_estimator_ pred_rf best_rf.predict(X_test) print(最优参数:, grid_rf.best_params_) print(随机森林准确率:, accuracy_score(y_test, pred_rf))random_state42保证结果可复现。n_jobs-1用满 CPU 核数但注意在共享服务器上可能被限制。scoringf1而不是 accuracy是因为涨跌分类中如果跌的样本多模型全预测跌也能有高 accuracy但 f1 会暴露问题。TimeSeriesSplit在 GridSearchCV 里同样要传不能默认 KFold。3.3 人工神经网络输出层决定分类还是回归人工神经网络在这份源码包里通常用 Keras 或 PyTorch 实现。分类任务输出层用 sigmoid 加一个神经元损失用 binary_crossentropy回归任务输出层不加激活函数损失用 mse。隐藏层结构不用太深金融数据信噪比低两层各 32 或 64 个神经元往往就够了。import tensorflow as tf from tensorflow.keras import layers, models def build_cls_model(input_dim): model models.Sequential([ layers.Dense(64, activationrelu, input_shape(input_dim,)), layers.Dropout(0.3), layers.Dense(32, activationrelu), layers.Dropout(0.3), layers.Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) return model model_nn build_cls_model(X_train.shape[1]) history model_nn.fit( X_train, y_train, validation_split0.2, epochs50, batch_size32, verbose0 ) pred_nn (model_nn.predict(X_test) 0.5).astype(int).flatten() print(神经网络准确率:, accuracy_score(y_test, pred_nn))Dropout(0.3)是防过拟合的常用手段比例太高会导致欠拟合。validation_split0.2从训练集尾部切验证集时序数据里这相当于用最近的一段做验证比随机切合理。epochs50配合早停更稳但源码包里常省略早停你需要自己加EarlyStopping。predict输出是概率要手动卡 0.5 阈值转成 0/1。3.4 三个模型的统一评估与对比评估不能只看准确率。分类任务看 precision、recall、f1 和 AUC回归任务看 MAE、RMSE 和方向准确率。方向准确率是预测收益率符号和真实符号一致的比例对交易策略更有意义。from sklearn.metrics import roc_auc_score, mean_absolute_error, mean_squared_error # 分类模型 AUC auc_lr roc_auc_score(y_test, pipe_lr.predict_proba(X_test)[:, 1]) auc_rf roc_auc_score(y_test, best_rf.predict_proba(X_test)[:, 1]) auc_nn roc_auc_score(y_test, model_nn.predict(X_test).flatten()) print(fLR AUC: {auc_lr:.4f}, RF AUC: {auc_rf:.4f}, NN AUC: {auc_nn:.4f}) # 回归任务示例用随机森林回归预测收益率 from sklearn.ensemble import RandomForestRegressor reg RandomForestRegressor(n_estimators200, max_depth8, random_state42) reg.fit(X_train, train[label_reg]) pred_reg reg.predict(X_test) print(MAE:, mean_absolute_error(test[label_reg], pred_reg)) print(RMSE:, np.sqrt(mean_squared_error(test[label_reg], pred_reg)))predict_proba取第二列是正类概率AUC 对类别不平衡不敏感。回归的 MAE 和 RMSE 单位是收益率数值通常很小不要因为 0.01 就以为模型没用要看和基线比。方向准确率可以自己算(np.sign(pred_reg) np.sign(test[label_reg])).mean()。4. 避坑与排查这类项目最容易翻车的五个地方4.1 未来函数泄漏现象是回测收益高得离谱现象模型在测试集上准确率 70% 以上实盘一跑就亏。原因特征里混入了未来信息比如用当日收盘价算的指标去预测当日涨跌或者标准化时用了全量数据的均值和方差。解决所有特征计算只用当前及之前的数据标准化在训练集上 fit再 transform 测试集。用Pipeline可以避免手动操作出错。4.2 标签不平衡现象是模型全预测一个方向现象涨跌样本比例 7:3模型全部预测涨accuracy 仍有 70%。原因准确率在不平衡数据上有欺骗性。解决用class_weightbalanced或调整阈值评估改用 f1 和 AUC。随机森林和逻辑回归都支持class_weight参数神经网络可以在fit时传class_weight字典。4.3 过拟合现象是训练集表现远好于测试集现象随机森林训练集准确率 95%测试集 52%。原因树太深、叶子样本太少模型记住了噪声。解决限制max_depth和min_samples_leaf神经网络加 Dropout 和早停。判断标准是训练集和验证集的损失曲线是否分叉分叉明显就是过拟合。4.4 数据频率和标签周期不匹配现象用日线数据预测明天涨跌但特征里混了周线指标。原因不同频率的数据对齐时产生错位。解决统一到同一频率日线就全部用日线需要周线特征就做重采样并对齐到日线。重采样时注意用resample后的最后一个值或聚合值不要用未来数据。4.5 随机种子和版本差异导致结果不可复现现象同一份代码换台机器跑出来结果不一样。原因随机种子没固定或者库版本不同导致默认参数变化。解决random_state在所有模型和切分里都设成固定值记录 Python、sklearn、tensorflow 版本。源码包里的requirements.txt如果有优先按它装。5. 进阶技巧用滚动窗口和概率阈值把预测变成可执行信号最后一章说一个我实际用下来最稳的技巧滚动窗口重训练加概率阈值过滤。固定训练集在金融数据上很快失效因为市场结构会变。滚动窗口是每次用最近 N 天数据训练预测下一天然后窗口向前滑动。N 取 250 到 500 个交易日比较常见对应一年到两年。def walk_forward(df, feature_cols, label_col, window300, threshold0.55): preds, truths [], [] for i in range(window, len(df) - 1): train_w df.iloc[i - window:i] test_w df.iloc[i:i 1] model RandomForestClassifier( n_estimators100, max_depth8, min_samples_leaf20, random_state42, n_jobs-1 ) model.fit(train_w[feature_cols], train_w[label_col]) prob model.predict_proba(test_w[feature_cols])[0, 1] # 只有概率超过阈值才认为有信号否则视为不交易 pred 1 if prob threshold else 0 preds.append(pred) truths.append(test_w[label_col].values[0]) return np.array(preds), np.array(truths) preds, truths walk_forward(df, feature_cols, label_cls) signal_acc (preds truths).mean() print(滚动窗口方向准确率:, signal_acc)window300是训练窗口长度太小模型不稳太大跟不上市场变化。threshold0.55是过滤阈值只有模型比较确信时才输出 1减少无效交易。这个函数跑起来慢因为每滑一步都要重训但比固定训练集靠谱得多。你可以把RandomForestClassifier换成逻辑回归或神经网络逻辑回归最快神经网络最慢。验证方法上除了看方向准确率还要看信号发出频率。如果一年只发几次信号准确率再高也没有统计意义。我一般要求信号频率在 20% 到 40% 之间低于 10% 就调低阈值高于 50% 就调高阈值。另外滚动窗口的起始位置要留够训练数据range(window, len(df)-1)里len(df)-1是因为最后一行没有标签。我自己的习惯是每次拿到一份新的股票预测源码先不跑模型而是花半小时检查标签构造和切分方式。这两处对了后面调参才有意义这两处错了准确率 90% 也是假的。希望帮到你。本文还有配套的精品资源点击获取