简介Python实现机器学习股价预测源码以LR、LSTM、ARIMA、KNN等多种模型为核心围绕股票价格预测场景提供了从数据读取、特征处理、模型训练到结果评估的完整实验链路适用于毕业设计、期末大作业、课程设计等需要快速做出可运行项目的学习者。资源压缩包内共31个文件包括Python源码模型构建、回测、预测、可视化、CSV股票与账户数据、Excel月度队列、HTML可视化图表、依赖清单及说明文档等总大小仅1.45MB轻量而完整便于本地部署与二次开发。目前已有372人学习下载项目均经过调试可直接运行能有效降低上手门槛。源码注释详细新手也能看懂项目功能完善提供趋势图、自相关图、ARIMA/LSTM预测图、每日资金情况图等多种可视化结果便于理解不同模型在股价预测中的表现差异。稍加调整即可适配其他股票数据具备较强的实用性与展示价值可直接用于毕业设计或期末大作业答辩。1. 为什么股价预测要用多模型而不是碰运气调参当你在检索框里输入“Python实现机器学习股价预测源码”的时候大概率已经经历过一次挫折从某个网页下载了一份代码跑起来准确率不错换一只股票、换一段行情结果完全对不上。这不是代码写得烂而是股价预测这件事天然不适合用单一模型硬扛。线性回归能抓住缓慢趋势却抓不住突发反转KNN能匹配相似形态却总是慢半拍ARIMA对长期走势有解释力但面对波动率聚集就失去脾气LSTM在足够数据下能学非线性关系可训练成本最高翻车也最隐蔽。于是我把“到底用哪个模型”换成了“让每个模型各自打分”于是有了这篇可以一步步落地复现的实战笔记。这套方案的定位很简单它不承诺“预测明天涨跌”而是给你一条相对可靠的实验路径——数据怎么预处理、特征怎么构造、LR/ARIMA/KNN/LSTM各自的实现要点、怎么公平对比、哪些坑必须避开。适合的人群包括刚刷完机器学习入门课程、想拿金融数据做毕业设计或期末作业的同学也包括已经在做股票量化分析、想用传统机器学习模型加深度学习模型做基准对比的从业者。无论你从哪一个入口进来这套流程都希望帮你少走弯路。2. 数据准备是预测的命根子从数据源到防泄露的完整流水线很多初学量化的人会犯一个顺序错误先选模型再找数据最后才发现数据根本对不上。我一般会把数据准备放到整个流程的起点因为后面所有模型都吃同一份数据数据如果有坑LR翻车、LSTM也翻车没有任何模型能救得回来。2.1 数据源怎么选本地CSV日线、时间对齐与清洗股价预测最常见的数据粒度是日线包含日期、开盘、最高、最低、收盘、成交量六列就够用。我建议你把数据落成本地CSV再开始实验而不是每次都在代码里实时下载。原因有两个其一实时接口可能因为网络或限流中断导致实验不可复现其二本地保存的CSV可以固定一个区间方便你反复对比不同模型的表现。import pandas as pd import numpy as np df pd.read_csv(price_data/TSLA.csv, parse_dates[Date], index_colDate) df df.sort_index() df df[[Open, High, Low, Close, Volume]].dropna() # 检查重复索引 dup_idx df.index[df.index.duplicated()] if len(dup_idx) 0: df df[~df.index.duplicated(keeplast)] # 检查停牌缺口连续交易日应该没有超过5天的间隔 gap_days df.index.to_series().diff().dt.days print(最大间隔天数:, gap_days.max())这段代码做的事情很直白读CSV、按时间排序、去掉空值、处理重复日期。最后一步打印时间间隔最大值用来发现停牌或数据缺失。如果你的数据源只有交易日那么间隔最大值通常是3天比如周五到下周一如果出现超过5天的缺口就要回源头检查是否漏了交易日数据。这里我一般不会直接fillna因为股价序列的空缺和普通表格不一样补进去一个假价格会影响后续所有特征计算。数据源的选择上免费渠道比如akshare、baostock或者财经网站导出的交易数据都可以关键不在于数据源多权威而在于你记录了数据区间和数据来源保证别人拿到同一份CSV能复现你的结果。环境配置我就不展开了python安装教程和vscode python环境配置网上已经非常成熟装个Anaconda基本不折腾。2.2 特征构造与标签定义让LR和KNN能吃同一份数据股价预测的输入特征不需要多玄乎常用的是几类收益率、波动率、成交量变化、高低振幅、均线乖离。这些特征有一个共同特点——只用历史信息就能计算不会用到未来数据。这是构造特征的第一原则。# 标签未来T日收益方向1为上涨0为下跌 T 1 df[future_ret] df[Close].shift(-T) / df[Close] - 1 df[label] (df[future_ret] 0).astype(int) # 特征 df[ret_1d] df[Close].pct_change(1) df[ret_5d] df[Close].pct_change(5) df[vol_20d] df[Close].rolling(20).std() df[volume_ratio] df[Volume] / df[Volume].rolling(20).mean() df[high_low_ratio] df[High] / df[Low] - 1 df[ma_gap] df[Close] / df[Close].rolling(10).mean() - 1 feature_cols [ret_1d, ret_5d, vol_20d, volume_ratio, high_low_ratio, ma_gap] df df.dropna(subsetfeature_cols [label])这里的逻辑要解释清楚future_ret用shift(-T)拿到未来第T天的收盘价算出的收益才是标签。用T1就是预测“明天是否涨”如果你想预测未来5天方向把T改成5即可。特征侧全部使用历史窗口计算rolling函数不会往过去泄漏未来。末尾的dropna很重要因为pct_change和rolling会让前20行产生NaN标签也会让最后T行变成NaN这些样本必须丢掉否则模型会学到一堆空值。不同模型对特征的预处理要求差别很大。LR要求特征在同一量纲下否则大数值特征主导权重KNN对量纲更敏感因为距离计算完全依赖尺度ARIMA根本不看这些特征它只吃一条价格或收益率序列LSTM虽然对量纲有一定鲁棒性但训练时如果不做归一化loss可能震荡得厉害。所以通用做法是通过“训练集拟合scaler、测试集应用同一个scaler”来标准化下面这段代码示范了正确姿势。from sklearn.preprocessing import StandardScaler split_idx int(len(df) * 0.7) train_df df.iloc[:split_idx] test_df df.iloc[split_idx:] scaler StandardScaler() train_scaled scaler.fit_transform(train_df[feature_cols]) test_scaled scaler.transform(test_df[feature_cols]) X_train, y_train train_scaled, train_df[label].values X_test, y_test test_scaled, test_df[label].values注意fit_transform和transform的配合scaler只在训练集上拟合出均值和方差测试集直接转换。如果你对全量数据先标准化再切分测试集的信息已经混入训练过程回测指标会虚高这是最常见的“未来函数”之一。2.3 切分与滑动窗口验证集怎么切才不会偷看未来时间序列切分和普通机器学习不一样不能使用train_test_split里的默认随机抽样。随机打乱会破坏时间顺序模型等于通过未来片段去预测过去。正确做法是按时间顺序切成三段训练集、验证集、测试集。# 80%训练10%验证10%测试 train_ratio, val_ratio 0.8, 0.1 train_idx int(len(df) * train_ratio) val_idx int(len(df) * (train_ratio val_ratio)) train_part df.iloc[:train_idx] val_part df.iloc[train_idx:val_idx] test_part df.iloc[val_idx:]这个切分看起来简单但有两个细节容易被忽略。第一特征计算要在切分之前完成这样每条样本的特征窗口是历史的不依赖切分边界第二验证集和测试集的scaler都必须由训练集拟合。如果验证集和测试集用过自己拟合的scaler虽然误差可能不大但它违背了“测试环境未知”的原则严谨性会被扣分。至于滑动窗口它有两种作用一种是给LSTM生成样本序列一种是在滚动回测中模拟逐步预测。后者我放到最后一章展开这里只说LSTM的序列生成。常见做法是用过去window天数据预测未来1个点窗口默认值我一般取30因为对应一个月的交易日有足够形态信息。代码在第四章给出这里先记住结论窗口太短学不到中期趋势窗口太长会把远古信息当噪音30到60是大多数LSTM时间序列预测python实践的起步区间。3. LR、KNN和ARIMA先用三个经典模型把基线建立起来很多人一上来就上LSTM最后陷入调参泥潭。我的顺序建议是先跑LR、KNN、ARIMA这些基础模型得出一个可复现的基线再让深度学习模型去挑战这个基线。如果LSTM连LR都打不过调参就没有意义模型选择本身可能就错了。3.1 逻辑回归LR标准化、类别权重与基线意义逻辑回归虽然名字带“回归”但做的是分类。我们把“明天涨跌”当成一个二分类问题y等于1表示上涨0表示下跌。LR训练快、可解释性强最适合当第一个基线。from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, confusion_matrix lr_model LogisticRegression( C0.1, max_iter1000, class_weightbalanced, random_state42 ) lr_model.fit(X_train, y_train) y_pred_lr lr_model.predict(X_test) print(LR准确率:, accuracy_score(y_test, y_pred_lr)) print(混淆矩阵:\n, confusion_matrix(y_test, y_pred_lr))参数说明C是正则化强度的倒数C越小正则化越强。股价特征间相关性高过拟合风险不小我一般先把C设为0.1再网格搜索。class_weightbalanced是因为上涨和下跌样本比例可能接近五五开但也可能某一阶段上涨天明显多于下跌天动平衡可以避免模型偷懒全部预测多数类。max_iter设1000是给收敛充裕空间默认100偶尔会警告未收敛尤其在特征没标准化的时候。LR的意义不只在于它的准确率更在于逻辑回归系数能告诉我们每个特征的贡献方向。比如ret_5d系数为正说明过去5天涨幅越大明天上涨概率越高——这不一定符合实际市场规律但至少模型没有学到反直觉的模式。3.2 KNN做形态匹配近邻参数与时序约束KNN算法常被应用于股票量化分析因为它没有显式训练过程思想是“找到历史上最像当前时点的T天看看次日涨跌”。距离度量默认用欧式距离这与2.2里做的标准化直接相关——如果不标准化价格从几百到几千成交量从几万到几亿欧氏距离会被大数值特征完全支配。from sklearn.neighbors import KNeighborsClassifier knn_model KNeighborsClassifier( n_neighbors5, weightsdistance, p2, metricminkowski ) knn_model.fit(X_train, y_train) y_pred_knn knn_model.predict(X_test) print(KNN准确率:, accuracy_score(y_test, y_pred_knn))这里n_neighbors5表示取最近5个历史样本投票weightsdistance让距离更近的邻居权重更大而不是简单多数投票p2就是欧氏距离p1是曼哈顿距离两者可以都试。KNN最大的坑在于时间顺序。sklearn的KNN在fit之后查询X_test时会在整个训练集里找邻居这本身没有泄露因为训练集时间都在测试集之前。但如果你做滚动预测每次预测时训练集必须只包含“当前时刻之前”的样本否则测试期之后的数据会被当成邻居这种未来数据泄漏会让回测准确率虚高实盘一塌糊涂。后面第五章我会给出一个具体的错误范例。3.3 ARIMA做单变量序列ADF检验、AIC定阶与滞后问题ARIMA模型和机器学习分类器不是一个路子它只吃一条时间序列比如每天的收盘价建模目标是序列自身的时间依赖。对价格序列直接建模通常不稳最常见的诊断工具是ADF检验。from statsmodels.tsa.stattools import adfuller close_series df[Close] adf_stat, p_value adfuller(close_series)[:2] print(ADF p值:, p_value) if p_value 0.05: diff_series close_series.diff().dropna() print(价格序列非平稳做一阶差分) else: diff_series close_seriesp值大于0.05说明序列非平稳必须先差分。ARIMA(p, d, q)中的d就是差分阶数常见取1就够。定阶我很少纯靠眼睛看ACF/PACF图而是跑一个小网格用AIC挑选既省事又相对客观。from statsmodels.tsa.arima.model import ARIMA train_close close_series.iloc[:split_idx] test_close close_series.iloc[split_idx:] best_aic float(inf) best_order None for p in range(0, 4): for d in range(0, 2): for q in range(0, 4): try: model ARIMA(train_close, order(p, d, q)).fit() if model.aic best_aic: best_aic model.aic best_order (p, d, q) except Exception: continue print(最优阶数:, best_order, AIC:, best_aic) arima_model ARIMA(train_close, orderbest_order).fit() forecast arima_model.forecast(stepslen(test_close))这个网格很小p和q各取0到3d取0或1总共32个组合训练速度快适合日线数据。AIC越低模型越优但它只代表样本内拟合与复杂度的折中不能保证样本外预测好。ARIMA预测有一条近似平移的曲线学名是“预测均值回归”直观表现就是它基本把历史走势往右平移等到序列真正发生趋势变化时反应很慢。这是ARIMA自身的局限不算bug决策时不要指望它抓住拐点。4. LSTM的实现要点从DataFrame到PyTorch训练闭环市面上关于LSTM做预测的源码并不少但多数只是“能跑”对数据格式、窗口长度、训练参数这些决定成败的细节一带而过。这一章我把从DataFrame到PyTorch训练的完整链路拆开讲清楚每一步在做什么以及参数为什么这么设。4.1 序列张量化与窗口长度LSTM时间序列预测python第一步LSTM的输入是三维张量形状是(batch, seq_len, features)。seq_len就是前面说的windowfeatures是特征数量。和LR不同LSTM不拿一堆独立样本喂模型而是每条样本本身就是一条连续的历史片段。import numpy as np import torch window 30 feature_data df[feature_cols].values def create_sequences(data, window): X, y [], [] for i in range(len(data) - window): X.append(data[i:iwindow]) y.append(data[iwindow]) # 这里用未来一行的特征做回归目标 return np.array(X), np.array(y) X_seq, y_seq create_sequences(feature_data, window) print(输入张量形状:, X_seq.shape, 标签形状:, y_seq.shape)这段代码里有一个容易被忽视的问题y取的是data[iwindow]而不是“未来收益”。如果目标是回归一般会构造未来收益率作为标签如果目标是分类则把未来涨跌方向作为标签。上面的示例是为了方便解释张量维度实际使用时要替换成df对应行的label。窗口长度选30的理由前面提过——月度交易周期恰好能覆盖一轮短期形态超过60天历史形态的参考价值会衰减。张量划分与前面保持一致先切分时间再生成序列切分边界要落在样本索引上而不是落在窗口中间。训练数据有必要转成torch.float32张量LSTM内部计算不接受float64这是新手最容易碰到的报错。4.2 模型结构设计hidden、layers、dropout怎么配模型结构不需要复杂一个LSTM层加一个全连接输出层就能解决大多数单步预测问题。hidden_dim控制记忆容量num_layers表示堆叠层数dropout用于防止过拟合层数多了反而难收敛。import torch.nn as nn class PriceLSTM(nn.Module): def __init__(self, feature_dim, hidden_dim64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizefeature_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Linear(hidden_dim, 1) def forward(self, x): out, _ self.lstm(x) last_step out[:, -1, :] return self.fc(last_step) feature_dim len(feature_cols) model PriceLSTM(feature_dimfeature_dim, hidden_dim64, num_layers2, dropout0.2)参数说明batch_firstTrue表示输入形状是(batch, seq_len, features)这更符合我们直觉如果不设则默认是(seq_len, batch, features)容易搞混。hidden_dim64是常用的起始大小数据量大可以升到128num_layers2通常够用超过3层在小数据集上基本只会过拟合。dropout只在多层lstm的层间生效单层LSTM设dropout不会有任何作用这是PyTorch实现细节。网上能找到的Pytorch LSTM源码非常多但很多时候换一个数据集就失灵原因是骨架只是骨架输入输出维度、标签对齐、归一化策略才是真正的灵魂。这一段代码不追求花哨就是要把结构稳下来。4.3 训练闭环学习率、梯度裁剪与早停训练LSTM最典型的失败是loss不降反涨或者训练loss低、验证loss剧烈震荡。除了数据问题最重要的两个控制手段是梯度裁剪和早停。criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) X_train_tensor torch.tensor(X_train_seq, dtypetorch.float32) y_train_tensor torch.tensor(y_train_seq, dtypetorch.float32).view(-1, 1) X_val_tensor torch.tensor(X_val_seq, dtypetorch.float32) y_val_tensor torch.tensor(y_val_seq, dtypetorch.float32).view(-1, 1) epochs 100 best_val_loss float(inf) best_model_path best_lstm.pth for epoch in range(epochs): model.train() optimizer.zero_grad() pred model(X_train_tensor) loss criterion(pred, y_train_tensor) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() if (epoch 1) % 10 0: model.eval() with torch.no_grad(): val_pred model(X_val_tensor) val_loss criterion(val_pred, y_val_tensor).item() print(fEpoch {epoch1:3d} | train loss: {loss.item():.6f} | val loss: {val_loss:.6f}) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model_path)这里的关键是clip_grad_norm_。LSTM对梯度爆炸尤其敏感RNN系模型在长序列上梯度传播容易失控把梯度范数裁剪到1.0能有效防止训练发散。学习率从1e-3起步如果训练loss一直在0.1量级反复横跳不下降可以把lr降到3e-4再跑一遍。早停逻辑很简陋但有效每10个epoch对比一次验证loss只保留最好的参数。很多进阶方案会加ReduceLROnPlateau这种学习率调度器效果更好但核心仍然是保留最优验证loss。5. 模型对比与避坑为什么回测漂亮一上模拟盘就翻车特征也构造了模型也训练了准确率看着还行一放到实盘模拟却完全不是那么回事。这一章把最容易踩的坑逐一挑明。不夸张地说这里每一条我都亲眼见过有人踩进去。5.1 公平对比的前提同数据、同窗口、同指标做过课程设计或者期末作业的人应该理解这种感觉明明每个模型单独跑的时候都说得过去写进报告里一对比却发现LR和KNN的测试集不一样ARIMA用的区间又比LSTM多了一段。模型对比只要有一个条件不同结论就不成立。我的一般做法是先统一训练、验证、测试区间再分别计算准确率、F1、年化收益率、最大回撤这类指标。准确率只适合初步观察在涨跌分布不均衡时容易产生误导。年化收益率容易极端需要配合最大回撤一起看。无论用哪个指标四条铁律必须遵守一是同一份CSV二是同一个切分点三是同一个预测目标四是同一个时间粒度。5.2 常见问题一九成准确率其实是类别失衡加数据泄露现象模型在测试集上准确率超过0.9你觉得“稳了”放到模拟盘却连续亏损。原因通常有两个叠加第一未来函数泄漏常见的是标准化时用了全样本统计量或者特征构造时用了未来价格第二类别极度不平衡某个时段上涨或下跌样本占比超过90%模型只要一直预测多数类就能得到漂亮准确率。解决检查两个地方。一是scaler是否只在训练集上fit二是打印测试集标签的分布如果某类占比超过80%准确率就没有参考价值此时要看F1或AUC。如果你想要一个更可信的基准可以做一个“预测昨天涨跌方向”的哑模型它的准确率大概等于上涨样本占比任何模型都必须在这个基准之上才算有效。5.3 常见问题二KNN把未来样本当邻居现象KNN在回测里准确率特别高但在实盘模拟中表现平平甚至差于LR。原因很多人用sklearn的KNN时写的是整个数据集fit再predict。如果训练集时间窗口覆盖到测试集之后的数据KNN查询时就会把“未来”样本纳入邻居集合这意味着它偷看了未来。KNN因为没有显式训练参数这类错误特别隐蔽。解决滚动预测每次只取当前时刻之前的数据重新fit。误区是追求效率用全量数据一次fit但KNN本身没有训练过程每次预测前临时构建训练集也不算慢代价完全可以接受。# 错误写法KNN能偷看到未来样本 knn.fit(X_all, y_all) knn.predict(X_test) # 正确写法只在当前时刻之前的历史样本中找邻居 knn.fit(X_train, y_train) knn.predict(X_test)这两行对照是最典型的时间序列KNN坑比任何参数调优都重要。5.4 常见问题三ARIMA预测出一条“平移曲线”现象ARIMA的预测曲线在图上和真实曲线高度相似但整体向右平移了一段看起来准确率不错实际上没有任何可用信号。原因ARIMA的预测本质是条件均值当序列非平稳或模型以低阶AR为主时预测值会趋向于最后几个观测值的加权重平均。股价序列的随机性很强条件均值预测天然会倒退到“最近的值就是最佳预测”的逻辑于是曲线就产生了滞后。解决ARIMA适合做数据的基线理解不适合直接作为股价预测信号。用它验证残差是否为白噪声比用它预测涨跌更有价值。如果你看到残差序列还有明显自相关说明模型结构没定对需要回到d阶数和p、q选择上重新检查。5.5 常见问题四LSTM训练loss在测试集上发散现象训练loss一路下降验证loss却从第30个epoch开始剧烈上升最后彻底发散成NaN。原因学习率过高、没有梯度裁剪、没有早停三者至少中了一个。时间序列预测的LSTM对学习率非常敏感1e-2都可能直接发散1e-3是相对安全起点梯度爆炸在序列长度长、数值范围大时更容易发生早停则是最后的防火墙。解决把学习率降到3e-4或者1e-4重新跑在backward之后加clip_grad_norm_只保留验证loss最小的模型参数不要用最后一个epoch的权重。这里的实现细节已经在第四章给出关键在于真正用起来而不是读一遍就过。6. 把模型组合起来从多模型投票到实盘纪律单个模型各有脾气那就让他们投票。常用的办法是少数服从多数LR、KNN、LSTM、ARIMA各给一个明日的涨跌观点当两个或三个以上模型一致时才产生信号。这个做法的逻辑不是每时每刻都要交易而是过滤掉模型分歧大的日期只做大家都同意的方向。import numpy as np # 假设四个模型都输出0或11表示看涨 votes np.array([lr_signal, knn_signal, arima_signal, lstm_signal]) final_signal 1 if votes.sum() 3 else 0这个代码片段简单到不需要解释真正要理解的是背后的纪律把“每天都要预测”变成“只在模型共识时做决策”。四个模型经常有分歧所以信号数量会少但胜率通常更稳定。除了投票我还保持一个习惯把每次预测的概率和对应的特征日期存到本地每周复盘一次预测质量计算IC也就是预测方向与真实方向的相关性。如果连续几周IC都在零附近徘徊说明特征失效需要重新切数据或者更新训练集。这种做法比盯着一两个回测指标更真实。LSTM这类深度学习模型需要定期重训市场环境变了训练数据也要滚动更新。我总会先用LR跑通完整流程再逐步加KNN、ARIMA、LSTM先在模拟盘上跑三个月再谈实盘。这套流程说不上温柔但至少能让你少交学费。希望帮到你。本文还有配套的精品资源点击获取
