
简介面向金融时序预测与机器学习实践的完整算法包整合了股票价格预测中常用的多种建模方法包括LSTM、Prophet、AutoARIMA、SVM、随机森林等并自带基础回测系统便于读者在同一框架下对比不同模型的预测表现。压缩包共386个文件以357个csv历史行情与训练数据为主配合18个Python算法与回测脚本、4个HTML可视化页面以及少量JS、Excel、Markdown辅助文件整体大小仅1.14MB结构紧凑、数据齐全适合用于课程设计、量化入门或个人研究。目前已有306人学习下载。除算法代码外包内还保留了数据清洗、模型调用与回测结果组织方式可帮助读者快速搭建自己的股价预测流程省去从零收集数据和编写框架的时间。对于想深入理解LSTM门控机制或对比传统机器学习与深度学习方法差异的读者这是一份可直接运行的参考资料。1. 基于机器学习的股票价格预测一份自带回测系统的算法集合拿到这份资源的时候我第一反应是“又一份调包预测脚本”但拆开看完才发现它把 LSTM、Prophet、AutoARIMA、朴素贝叶斯、SVM、随机森林六类算法全塞进了一个项目里还配了一套基础回测系统。对做量化入门的人来说最头疼的不是跑通单个模型而是不知道同一份数据在不同算法下表现差多少、回测怎么搭才不算自欺欺人。这份资源恰好把这两件事焊在了一起。它适合两类人一是想对比各类机器学习算法在股价预测上效果的从业者二是刚开始搭个人量化框架、需要一套可复现基线的新手。下面我按自己拆项目的习惯从数据、预处理、单模型、LSTM 细节、到回测踩坑一层层给你过一遍。2. 把 CSV 变成模型能吃的时间序列数据规范与预处理2.1 数据文件长什么样资源里放了一堆 CSV文件名像data2.csv、data3.csv、2020年08月16日.csv这种。日期命名的文件通常是按天导出的行情快照而 data2/data3 这类更可能是连续日线或分钟线。我当时先打开了data2.csv看表头发现列基本逃不开date/open/high/low/close/volume这五六个字段。预测任务做的是下一日收盘价或未来几日的趋势方向所以预处理的第一步就是把日期列转成datetime索引并按时间升序排好。import pandas as pd df pd.read_csv(data2.csv, parse_dates[date], index_coldate) df df.sort_index() # 去掉停牌或空值行 df df[[open, high, low, close, volume]].dropna() print(df.head())这里parse_dates可以直接把中文字段名转成标准时间索引。sort_index很容易被忽略但原始 CSV 可能不是严格按时间排序尤其从多个文件拼接时漏掉这一步后续切 train/test 会直接造成数据泄漏。dropna()是底线操作停牌日或者数据源缺行时后面模型会报维度错误提前清掉能省一堆调试时间。2.2 特征构造与归一化的选择原始行情只有 OHLCV直接丢给 LSTM 或 SVM 效果很糙。我一般会再算几列常见技术特征涨跌幅、5 日均线、20 日均线、成交量变化率。注意这些特征必须在每个时间步用“当时已知”的数据计算不能用未来数据。df[return] df[close].pct_change() df[ma5] df[close].rolling(5).mean() df[ma20] df[close].rolling(20).mean() df[vol_ratio] df[volume] / df[volume].rolling(5).mean() df df.dropna()pct_change()计算的是相对前一日的收益率rolling(5).mean()是过去 5 天收盘均值vol_ratio用于捕捉放量缩量。这些特征对后面所有模型共用LSTM 用归一化后的数值树模型用原始数值也行但 SVM 对尺度敏感必须缩放。归一化我建议用MinMaxScaler或StandardScaler。对股票序列MinMaxScaler会把价格压缩到 [0,1]避免 LSTM 激活函数饱和。这里有一个关键细节scaler 只能用训练集的统计量拟合再分别 transform 训练集和测试集千万别在划分之前对全量数据 fit。from sklearn.preprocessing import MinMaxScaler feature_cols [open, high, low, close, volume, return, ma5, ma20, vol_ratio] scaler MinMaxScaler() scaled scaler.fit_transform(df[feature_cols])参数说明MinMaxScaler默认feature_range(0,1)股价波动大时需要注意是否有极端异常值如果有建议先做 winsorize 截断不然某个涨停日会把其他所有数据压到一坨模型直接废掉。2.3 滑窗样本生成LSTM 和 Prophet 这类模型需要把连续序列切成固定长度样本。我一般用lookback30意思是拿过去 30 天预测第 31 天收盘价。滑窗步长取 1样本会有重叠但对时间序列来说重叠是正常的不会像交叉验证那样造成严重泄漏。import numpy as np def make_sequences(data, lookback30): X, y [], [] for i in range(lookback, len(data)): X.append(data[i - lookback:i]) y.append(data[i, 3]) # close 所在列索引按 feature_cols 顺序 return np.array(X), np.array(y) X, y make_sequences(scaled, lookback30)这里的data[i, 3]对应的就是close列。如果你把feature_cols顺序改了索引要同步改。常见错误是把整个scaled数组拿去划分结果 model 的输入维度从(样本数, 时间步, 特征数)变成了(样本数, 特征数)维度对不上就报错。生成样本后按时间顺序前 80% 做训练后 20% 做测试不要随机打乱。3. 传统时序模型先上场Prophet、AutoARIMA 与朴素基准3.1 为什么要先跑一遍简单模型遇到算法集合我习惯先跑一个“朴素基准”——拿昨天的收盘价当今天的预测。这个基准的意义是任何模型如果连这个都跑不过说明它在当前数据上没有学习到有效规律。很多新手上来直接调 LSTM最后效果差得怀疑人生回头一对比不如拿昨天价格直接复制一份。# 朴素基线预测值 当前收盘价 pred_naive close_test.shift(1)用 RMSE均方根误差和 MAE平均绝对误差评估shift(1)的含义是取前一天的真实值作为后一天的预测。逻辑很简单但它是后面所有模型的天花板参照系。如果 LSTM 的 RMSE 和它差不多那说明 LSTM 其实没学到东西只是记住了上一个价格。3.2 Prophet 的用法与坑资源里包含 Prophet 算法Facebook 开源的时序预测工具对节假日和趋势的处理非常强但它默认是加法趋势。股票数据通常有周期性波动我一般会把yearly_seasonality关掉因为股票没有固定年度周期开了反而会拟合出假周期。from prophet import Prophet train_df df.reset_index()[[date, close]].rename(columns{date: ds, close: y}) model Prophet(daily_seasonalityFalse, weekly_seasonalityFalse, yearly_seasonalityFalse) model.fit(train_df) future model.make_future_dataframe(periodslen(test_df), freqD) forecast model.predict(future)daily_seasonalityFalse是因为日频交易数据在一天内没有多尺度周期weekly_seasonality对股票也不稳定A 股没有周末竞价周内效应不明显。make_future_dataframe必须传入与训练数据相同的日期频率不然预测会错位。Prophet 对缺失日期会自动填充但如果你用了交易日历直接按自然日扩展会引入非交易日导致预测结果看起来漂移很大。3.3 AutoARIMA让模型自己选差分阶数AutoARIMA 是自动搜索(p,d,q)参数的工具来自pmdarima库。它对股票这种非平稳序列会自动做差分。我在用的时候会限制max_d2不要让它过度差分不然会把原本的趋势信息差分成噪声。from pmdarima import auto_arima model auto_arima( train_series, start_p1, max_p5, start_q0, max_q3, max_d2, seasonalFalse, stepwiseTrue, traceTrue ) pred model.predict(n_periodslen(test_series))参数含义start_p和max_p是自回归项搜索范围max_q是移动平均项stepwiseTrue使用逐步搜索代替穷举速度能快几倍。traceTrue会把每一轮搜索的 AIC 打印出来方便你确认它选了哪个模型。AutoARIMA 的坑是输入的train_series必须是pd.Series索引要是日期否则它内部的时间索引会错乱预测长度可能与测试集对不上。3.4 三个模型的对比策略我一般会把 Prophet、AutoARIMA、朴素基线的预测结果画在同一张图上看拐点和滞后性。朴素基线天然滞后一天AutoARIMA 本质也是线性模型对突变的跳空缺口反应慢。Prophet 的预测曲线非常平滑经常预测出接近直线的水平线。对比的意义在于判断数据到底有没有可预测性。如果这三个模型全部打平那就别挣扎了LSTM 大概率也救不回来。4. 核心戏肉LSTM 的构建、训练与预测细节4.1 LSTM 为何适合股价预测股价序列有长期依赖关系比如某个支撑位形成的逻辑可能依赖过去几十天的价格轨迹。LSTM 通过输入门、遗忘门、输出门控制信息保留和丢弃理论上能在长序列里保持梯度流通这是 RNN 不具备的优势。但要注意LSTM 只是“更不容易忘”不是“一定记得住”。对股票这种信噪比极低的序列它学到的往往是短期动量。我在用 LSTM 时输入特征除了 OHLCV还会把上面构造的 return、ma5、ma20 一起塞进去让模型更容易发现趋势变化。4.2 搭建一个能跑的 LSTM 预测模型我用 Keras 搭一个最简单但有效的双层 LSTM第一层返回完整序列第二层只返回最后时间步然后接全连接层回归。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(30, X.shape[2]))) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizeradam, lossmse)参数说明第一层return_sequencesTrue是因为下一层还是 LSTM需要保留时间步输出第二层False只输出最后一个隐藏状态。Dropout(0.2)是时序 Dropout随机丢弃部分神经元输出防止过拟合千万别用普通 Dropout 在 LSTM 层之间效果会差很多。损失函数用mse而不是mae因为 MSE 对大偏差惩罚更大能逼模型拟合突变点。训练时我用validation_split0.1从训练集尾部切一串当验证不能用随机切分。4.3 训练与早停训练 LSTM 最怕两件事过拟合和学到了噪声。我一般加EarlyStopping监控验证集损失如果连续 10 轮不下降就停。from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit(X_train, y_train, epochs100, batch_size32, validation_split0.1, callbacks[early_stop], verbose1)patience10表示允许验证损失连续 10 轮不创新低再强行停掉。restore_best_weightsTrue会把模型权重恢复到验证损失最低的那一轮不然你拿到的是最后 10 轮里已经过拟合的权重。batch_size32对日线数据来说够用如果你的机器内存小可以降到 16。训练完必须检查history里有过有一个先降后升的拐点如果有说明早停没拦住还得调低学习率或者增大 Dropout。4.4 预测与反归一化LSTM 输出的是归一化后的值要看真实价格必须反变换回来。因为close在feature_cols的索引是 3所以只取 scaler 的对应列做 inverse。pred_scaled model.predict(X_test) pred_close scaler.inverse_transform( np.concatenate([np.zeros((len(pred_scaled), 3)), pred_scaled, np.zeros((len(pred_scaled), 5))], axis1) )[:, 3]这段代码有点绕思路是把预测的 close 列放回原来的特征矩阵位置其余列补零再用inverse_transform最后只取第 3 列。注意scaler是在全特征上拟合的所以补零的位置不在乎只要列数和顺序对就行。更稳妥的办法是单独为close做一个 MinMaxScaler就不用来回拼接了。4.5 LSTM 的常见翻车点我拆过不少股票 LSTM 项目发现翻车集中在三处。第一处是忘记对测试集做transform用了全局 scaler 导致信息泄漏测试 RMSE 低得离谱实盘却拉胯。第二处是把lookback设得太短比如 5 天模型只能看到非常局部的波动交易逻辑稍长一点就失效。第三处是不做差分直接把价格序列丢给 LSTM模型容易学成“预测值约等于上一个价格”此时 RMSE 看着比朴素基线好一点但其实是对趋势的延迟复制不是真预测。5. 机器学习家族集合随机森林、SVM 与朴素贝叶斯5.1 从序列到二维特征把时序转成监督学习随机森林和 SVM 不能直接吃三维序列需要把每个时间步的特征展平成一条样本。常见做法是用前 30 天的所有特征拼接成一个长向量作为该样本的输入。def flatten_sequences(X): n_samples, n_timesteps, n_features X.shape return X.reshape(n_samples, n_timesteps * n_features) X_flat flatten_sequences(X_train) X_test_flat flatten_sequences(X_test)展平后每个样本有 30×9270 个特征。这样随机森林和 SVM 就能跑了。代价是特征维数膨胀对 SVM 来说可能引发维数灾难所以我会先对展平后的特征做 PCA 降维保留 95% 方差。5.2 随机森林逆天的 Baseline随机森林是集成树模型对非线性关系拟合能力强对异常值不敏感而且几乎不需要调参。用它做股价预测经常比 LSTM 效果还好因为股价噪声大树模型天然抗噪。from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators200, max_depth10, min_samples_leaf5, random_state42 ) rf.fit(X_flat, y_train) pred_rf rf.predict(X_test_flat)n_estimators200表示 200 棵树再多对性能提升不大训练时间却会线性增长。max_depth10限制树深度防止单棵树记住样本噪声。min_samples_leaf5每个叶节点至少 5 个样本也是正则化。随机森林最大的优势是能输出特征重要性我跑完会看rf.feature_importances_如果最有效的特征是最近 12 天的return说明市场确实只有短期动量。5.3 SVM对尺度极其敏感SVM 在时序预测里通常是配角因为它是对偶求解样本量稍大就慢得不行。但资源里既然给了我一般会用来做涨跌分类而不是回归。做回归时必须先把目标值缩放而且核函数选rbf时要小心gamma值。from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler scaler_y MinMaxScaler() y_svm scaler_y.fit_transform(y_train.values.reshape(-1, 1)).ravel() X_svm StandardScaler().fit_transform(X_flat) # 注意已经展平 svr SVR(kernelrbf, C100, epsilon0.01) svr.fit(X_svm, y_svm) pred_svm scaler_y.inverse_transform(svr.predict(X_test_svm).reshape(-1, 1)).ravel()StandardScaler把每个特征变成均值 0、方差 1这一步对 SVM 是必须的因为 RBF 核计算的是样本距离不同特征的量纲不一致会导致距离被价格主导。C100是惩罚参数越大越强调分类对所有样本的拟合epsilon0.01是回归管道的宽度允许预测值偏离真实值 0.01 时不计算损失。SVM 训练数据量一上去就很折磨人我用前 500 个样本训练时速度还能接受超过 2000 个就想去换模型了。5.4 朴素贝叶斯硬凑还是有用朴素贝叶斯是生成模型特征独立假设很严格股价特征几乎不独立。但把它用在“明天涨还是跌”的二分类上倒也能跑出一个准确率底数。它在资源里出现的意义更多是当对照实验证明特征相关性对模型影响有多大。from sklearn.naive_bayes import GaussianNB # 把 y 转成分类1 表示下一日收盘价高于今日0 表示跌 def to_label(close_series): return (close_series.shift(-1) close_series).astype(int)[:-1] y_cls to_label(df[close]) X_cls df[[return, ma5, ma20]].fillna(0)[:-1] gnb GaussianNB() gnb.fit(X_cls, y_cls)to_label这一步用了未来一天的 close 与当前比较所以构造样本时要去掉最后一行避免标签里混入 NaN。高斯朴素贝叶斯会把每个特征独立拟合高斯分布对分类的高斯概率取对数求和。实际准确率通常就在 0.5 左右晃因为涨跌接近随机。如果它能稳定超过 0.55说明构造的特征有信息量这时候再去看随机森林和 LSTM 才有意义。5.5 对比什么时候用哪个我的习惯是先跑随机森林和朴素贝叶斯分类器快速判断数据中是否有可学习的信号。如果信号弱SVM 和 LSTM 大概率也救不回来。如果随机森林的回归 RMSE 显著低于朴素基线再投入时间调 LSTM。SVM 适合小样本数据集比如只有几百条日线时表现稳定大数据量下树提升更吃香。而朴素贝叶斯基本看作是随机预测的对照线不是实际可用的预测模型。6. 回测系统怎么搭避免自欺欺人的四个关键6.1 回测的基本框架资源里的回测系统本质是一条资金曲线模拟器。核心逻辑是预测模型给出每天的预测信号交易规则决定什么时候买入卖出最后统计累计收益和最大回撤。我拆到的这个版本里是按信号在每日收盘价买入次日收盘价卖出的 T1 模式。class Backtester: def __init__(self, initial_cash100000): self.cash initial_cash self.position 0 self.equity_curve [] def run(self, signals, prices): for i in range(1, len(signals)): if signals[i] 1 and self.cash prices[i]: self.position self.cash / prices[i] self.cash 0 elif signals[i] -1 and self.position 0: self.cash self.position * prices[i] self.position 0 self.equity_curve.append(self.cash self.position * prices[i]) return self.equity_curvesignals为 1 表示买入-1 表示卖出0 表示持仓不动。这里忽略手续费和滑点如果用于实盘评估必须加入成本模型。参数initial_cash是初始资金prices[i]是当日收盘价。整个回测不涉及未来数据因为信号是用预测模型当天的输出生成的价格也是当前已收盘的价格。6.2 关键坑一未来函数泄漏最常见的问题是在预测今天信号时用了今天收盘后才知道的数据。比如用当日close计算 5 日均线并产生买入信号这在收盘后没问题但如果你的特征里包含当日最高价、最低价的一些未来统计比如“当日最高价比昨日高 5%”这种实盘时当天盘中根本不知道会不会更高。我检查数据里的rolling(5)等操作是否在切割样本前就做完了如果做完了会包含未来窗口的信息必须重新按时间顺序逐日滚动计算。6.3 关键坑二训练集包含测试集时间很多人在做模型迭代时先把全量数据喂给模型训练再拿同一段数据的区间来回测。这等于考试先给答案回测曲线当然漂亮。正确做法是把数据按时间线切成三段最前面 60% 训练中间 20% 作为验证调参与早停最后 20% 回测期间模型权重不能再更新。split_idx int(len(df) * 0.6) val_idx int(len(df) * 0.8) train_df df.iloc[:split_idx] val_df df.iloc[split_idx:val_idx] test_df df.iloc[val_idx:]回测只能基于test_df来跑。如果你看到别人的项目里用同一批数据同时做训练和回测直接拉黑。6.4 关键坑三只盯着累计收益率单看累计收益一个在尾部暴涨 50% 的模型会让前 300 天的亏损显得无关紧要。但真实交易中最大回撤决定了你能否撑到收益来的时候。回测必须同时输出年化收益、夏普比率、最大回撤三项。import numpy as np def max_drawdown(equity_curve): peak np.maximum.accumulate(equity_curve) drawdown (equity_curve - peak) / peak return drawdown.min() def sharpe_ratio(returns, rf0.02): return (np.mean(returns) - rf / 252) / np.std(returns) * np.sqrt(252)max_drawdown用累积峰值减去当前值再除峰值得到最惨时刻的亏损比例。sharpe_ratio里rf0.02是 2% 年化无风险利率/252换算到日收益率。如果模型收益曲线上涨很猛但最大回撤超 30%短线资金根本扛不住波动。6.5 关键坑四信号与实际交易不同步我踩过一个坑模型在 T 日收盘后预测T1 日开盘才执行买卖但回测代码里却用了 T 日收盘价成交。这会造成一种“买了就涨”的幻觉原因是预测的强势延续在 T 日收盘后已经透支了部分收益。正确做法是预测 T1 日收益率信号产生后按 T1 日收盘价成交或者至少按 T1 日开盘价成交。我后来统一改成“预测当日信号次日开盘价成交”这样更接近实盘。从那以后我每次把一份新资源跑起来都会强制走一遍这个流程先看数据有没有未来函数再构造朴素基线然后训练模型并检查测试集是否严格滞后最后用买入信号与成交价错位一天的方式回测。即使某个算法被吹得天花乱坠我也只信经过这四层拷打的结果。这套资源和它的回测系统能帮你把以上每一个坑都踩一遍并留好防坑代码省掉最折磨人的前期重复劳动。希望帮到你。本文还有配套的精品资源点击获取