简介:这份资源面向具备一定Python基础、希望入门数据挖掘与机器学习实战的学习者,聚焦线性回归在股票预测中的完整落地流程。内容围绕数据获取与清洗、特征工程、模型训练、预测与评估展开,涉及pandas数据处理、NumPy特征计算,以及移动平均、MACD、RSI等技术指标的构建思路,并借助sklearn线性回归完成拟合,通过MSE、RMSE与R²衡量预测效果,同时点明线性假设的局限与组合其他模型的改进方向。压缩包共2个文件,包含1个py源码与1个pdf讲解文档,源码对应完整实现过程,文档辅助理解原理与步骤,整体约2.34MB,结构精简便于快速上手。目前已有4741人学习下载,适合作为课程设计、项目练手或进一步研究股票预测的参考案例,帮助读者把数据预处理、特征选择、模型训练与结果评估串联成一条可复用的实践路径。
1. 从一份股票预测源码说起:线性回归到底能挖出什么
很多人第一次接触「Python数据挖掘与机器学习开发实战」,都是从一份股票预测的源码压缩包开始的。解压、装依赖、跑通、看到一条拟合曲线,然后心里冒出一个问题:这条线真能预测明天的涨跌吗?我当年也是这么入门的,跑完第一遍兴奋,跑完第十遍就冷静了——因为每次结果都不一样,模型像在掷骰子。这份以线性回归为核心的股票预测案例,真正值钱的地方不是那条线本身,而是它把数据挖掘的完整链路摊开了:怎么取数、怎么构造特征、怎么切分训练测试集、怎么评估、怎么避免自欺欺人。它适合两类人:刚学完 Python 语法、想找一个能跑通的机器学习入门项目的新手;以及做过业务数据分析、想把线性回归真正用到时间序列场景的从业者。下面我不复述某份源码,而是按这个标题最常见的落地路径,把每一步讲清楚,让你能自己复现、自己调参、自己判断值不值得继续投入。
2. 数据挖掘到线性回归:股票预测的链路拆解与选型理由
2.1 为什么股票预测常从线性回归起步
线性回归是机器学习里最容易被低估的算法。它假设目标值 y 和特征 X 之间存在线性关系,形式是 y = wX + b,训练过程就是找一组权重 w 和偏置 b,让预测值和真实值的残差平方和最小。放到股票场景里,y 通常是「下一日收盘价」或「下一日涨跌幅」,X 是各种技术指标、成交量、历史价格衍生出来的特征。
选它起步有三个现实理由。第一,可解释性强,每个特征的系数直接告诉你它和预测目标的正负相关以及影响幅度,这在金融场景里比黑盒模型更容易被接受。第二,训练快,几千到几万条样本在普通笔记本上秒级完成,方便你快速验证特征工程有没有效果。第三,它是理解过拟合、正则化、交叉验证这些概念的绝佳载体,Lasso 和 Ridge 就是在它基础上加惩罚项。
但必须说清楚边界:股票价格序列是非平稳的,今天和半年前的统计规律可能完全不同,线性回归假设的独立同分布在这里并不成立。所以这个案例的正确用法是「学习完整流程」,而不是「直接拿去实盘」。我一般会把它当成一个教学骨架,跑通之后再往上加滚动窗口、加正则化、换模型对比。
2.2 数据获取与特征构造的最小可用方案
数据来源常见做法是用公开的行情接口拉日线数据,字段包括日期、开盘、最高、最低、收盘、成交量。拿到原始数据后,第一步是排序和去重,确保按日期升序且没有重复交易日。第二步是构造特征,这一步决定了模型上限。
import pandas as pd import numpy as np # 假设 df 已包含 date, open, high, low, close, volume 列 df = df.sort_values("date").drop_duplicates("date").reset_index(drop=True) # 基础衍生特征 df["ret1"] = df["close"].pct_change(1) # 日收益率 df["ma5"] = df["close"].rolling(5).mean() # 5日均线 df["ma20"] = df["close"].rolling(20).mean() # 20日均线 df["vol_ma5"] = df["volume"].rolling(5).mean() # 成交量5日均 df["hl_range"] = (df["high"] - df["low"]) / df["close"] # 振幅 # 预测目标:下一日收盘价 df["target"] = df["close"].shift(-1) # 去掉滚动窗口和 shift 产生的空值 df = df.dropna().reset_index(drop=True)这段代码的逻辑是:用历史窗口统计量刻画趋势,用收益率和振幅刻画波动,用 shift(-1) 把「明天」对齐到「今天」这一行。参数上,5 日和 20 日是常见短中期组合,你可以改成 10 和 60 观察效果变化。注意 target 用 shift(-1) 之后,最后一行没有未来数据,必须 dropna 掉,否则会引入空值报错。
提示:特征里不要直接放「当日收盘价」去预测「下一日收盘价」,那样模型会学到 close 和 target 高度接近的伪关系,评估指标好看但毫无预测意义。这是新手最容易翻车的地方。
2.3 训练测试切分与评估指标怎么选
时间序列不能随机切分,这是血泪经验。随机切分会让未来数据泄漏到训练集,模型在测试集上表现虚高。正确做法是按时间顺序切,比如前 80% 做训练,后 20% 做测试。
from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score feature_cols = ["ret1", "ma5", "ma20", "vol_ma5", "hl_range"] split = int(len(df) * 0.8) train = df.iloc[:split] test = df.iloc[split:] X_train, y_train = train[feature_cols], train["target"] X_test, y_test = test[feature_cols], test["target"] model = LinearRegression() model.fit(X_train, y_train) pred = model.predict(X_test) print("MAE:", mean_absolute_error(y_test, pred)) print("RMSE:", np.sqrt(mean_squared_error(y_test, pred))) print("R2:", r2_score(y_test, pred))评估指标里,MAE 和 RMSE 衡量误差绝对大小,单位是价格单位,直观;R2 衡量拟合优度,但股票预测里 R2 经常很低甚至为负,不要因为 R2 低就否定整个流程。更贴近实战的做法是把预测价格转成涨跌方向,算方向准确率。
direction_acc = ((pred > test["close"].values) == (y_test.values > test["close"].values)).mean() print("方向准确率:", direction_acc)参数说明:split 比例 0.8 是常见起点,样本少时可以试 0.7;feature_cols 要和你实际构造的列名一致,多一列少一列都会报 KeyError。
2.4 每次结果不一样:随机性到底来自哪里
热搜里有个高频疑问「模型预测股票涨跌每次结果不一样」,这背后通常有三个来源。第一,如果你用了随机切分或打乱数据,每次划分不同,结果自然不同。第二,如果特征里有随机初始化或用了带随机性的模型(比如 SGDRegressor),随机种子没固定。第三,数据本身在更新,你两次跑用的数据范围不同。
解决办法是固定随机种子并固定切分方式。LinearRegression 本身是确定性解法,正常不会随机,但只要你用了 train_test_split 且没设 random_state,就会变。
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, shuffle=False )注意 shuffle=False 在时间序列里很关键,它保证按顺序切分。random_state=42 只是让任何内部随机过程可复现。把这两个参数固定后,同一份数据跑出来的系数和指标应该完全一致。如果还不一致,去检查数据读取环节有没有排序不稳定或去重逻辑有随机性。
3. 从零复现:环境、代码与参数调优的完整步骤
3.1 环境准备与依赖安装
新手最容易卡在环境上。推荐用 Python 3.9 到 3.11,太新的版本某些科学计算库轮子还没跟上。用虚拟环境隔离依赖,避免和系统 Python 冲突。
python -m venv venv # Linux / macOS source venv/bin/activate # Windows venv\Scripts\activate pip install pandas numpy scikit-learn matplotlib如果你用 VSCode,装好 Python 插件后按 Ctrl+Shift+P 选择解释器,指向 venv 里的 python。这一步没做对,后面 import 报 ModuleNotFoundError 会浪费你半小时。参数上,pandas 和 numpy 版本建议配套,scikit-learn 用 1.2 以上即可,不需要追最新。
3.2 数据清洗与缺失值处理
真实行情数据常见问题有三类:停牌导致某天缺失、字段类型是字符串、除权除息造成价格跳变。处理顺序是先转类型,再补缺失,最后检查异常值。
df["date"] = pd.to_datetime(df["date"]) for col in ["open", "high", "low", "close", "volume"]: df[col] = pd.to_numeric(df[col], errors="coerce") # 前向填充停牌日,再用后向填充兜底 df[["open", "high", "low", "close", "volume"]] = ( df[["open", "high", "low", "close", "volume"]].ffill().bfill() ) # 简单异常值检查:单日涨跌幅超过 20% 标记出来人工确认 df["pct"] = df["close"].pct_change() outliers = df[df["pct"].abs() > 0.2] print(outliers[["date", "close", "pct"]])逻辑说明:errors="coerce" 把无法转换的值变成 NaN,避免字符串混入计算。ffill 用前一个有效值填充,符合停牌期间价格不变的现实。异常值不一定要删,但必须知道它们存在,否则模型会被极端值带偏。参数 0.2 是经验阈值,不同市场可以调整。
3.3 特征标准化与多重共线性处理
线性回归对特征尺度敏感,尤其是你用了正则化的时候。标准化让每个特征均值为 0、方差为 1,系数之间可比。另外 ma5 和 ma20 高度相关,会带来多重共线性,导致系数不稳定。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 用方差膨胀因子检查共线性 from statsmodels.stats.outliers_influence import variance_inflation_factor import statsmodels.api as sm X_const = sm.add_constant(X_train) vif = pd.DataFrame({ "feature": X_const.columns, "VIF": [variance_inflation_factor(X_const.values, i) for i in range(X_const.shape[1])] }) print(vif)关键点:scaler 只能在训练集上 fit,然后 transform 测试集。如果对整个数据集 fit,测试集的统计信息就泄漏进训练过程了。VIF 大于 10 通常认为共线性严重,可以考虑删掉其中一个均线或改用均线差值作为特征。参数上,VIF 阈值 5 到 10 都是常见判断区间。
3.4 用 Ridge 和 Lasso 抑制过拟合
普通最小二乘在特征多、样本少时容易过拟合。Ridge 加 L2 惩罚让系数整体收缩,Lasso 加 L1 惩罚能把部分系数压到 0 实现特征选择。
from sklearn.linear_model import Ridge, Lasso ridge = Ridge(alpha=1.0) ridge.fit(X_train_scaled, y_train) print("Ridge R2:", ridge.score(X_test_scaled, y_test)) lasso = Lasso(alpha=0.001, max_iter=10000) lasso.fit(X_train_scaled, y_train) print("Lasso R2:", lasso.score(X_test_scaled, y_test)) print("非零系数特征:", [f for f, c in zip(feature_cols, lasso.coef_) if abs(c) > 1e-6])参数说明:alpha 是惩罚强度,越大正则化越强。Ridge 的 alpha 从 0.1、1、10 试起;Lasso 的 alpha 要小一些,0.001 到 0.1 之间,太大会把所有系数压成 0。max_iter 调大是防止 Lasso 在数据尺度大时不收敛。判断标准是看测试集指标,不要只看训练集。
3.5 滚动窗口验证:更接近实盘的评估方式
单次切分只能看一个时间段的表现,滚动窗口能看模型在不同市场阶段的稳定性。做法是用固定长度训练窗,向后滚动预测下一段。
def walk_forward(df, feature_cols, train_size=250, test_size=20): results = [] for start in range(0, len(df) - train_size - test_size, test_size): train = df.iloc[start:start + train_size] test = df.iloc[start + train_size:start + train_size + test_size] model = LinearRegression() model.fit(train[feature_cols], train["target"]) pred = model.predict(test[feature_cols]) mae = mean_absolute_error(test["target"], pred) results.append({"start": start, "mae": mae}) return pd.DataFrame(results) res = walk_forward(df, feature_cols) print(res["mae"].describe())逻辑说明:train_size=250 约等于一年交易日,test_size=20 约一个月。每滚一次重新训练,模拟「用历史数据预测未来一个月」的真实节奏。看 mae 的均值和标准差,如果某些窗口误差突然飙升,说明那段行情模型不适应,这比单一 R2 有信息量得多。
4. 避坑与排查:股票预测里最容易翻车的五个地方
4.1 未来函数泄漏:指标好看但实盘必亏
现象:测试集 R2 高达 0.95 以上,方向准确率 80%,兴奋地以为找到了圣杯。原因:特征里混入了未来信息,比如用当日最高价预测当日收盘价,或者标准化时对全量数据 fit。解决:逐列检查特征生成时间戳,确保每个特征在预测时刻只依赖历史数据;标准化、填充、编码全部只在训练集 fit。我一般会写一个时间对齐检查函数,把特征列和 target 的日期错位打印出来核对。
4.2 用收盘价预测收盘价:伪相关陷阱
现象:模型系数里 close 的权重接近 1,其他特征权重接近 0。原因:target 是下一日收盘价,而 close 和它数值上高度接近,模型直接抄近路。解决:要么把 target 改成收益率或涨跌方向,要么把 close 从特征里彻底移除,只保留衍生指标。这个坑非常隐蔽,因为指标不会报错,只会让你误判模型能力。
4.3 数据频率与对齐错误
现象:回测收益异常高,或者出现未来日期。原因:不同数据源日期格式不一致,或者用了未复权的价格导致除权日出现巨大跳空。解决:统一日期格式为 YYYY-MM-DD,合并数据时用 merge 并检查行数变化;价格数据确认是前复权还是后复权,同一份分析里不要混用。参数上,merge 之后用 df.shape 和 df.isna().sum() 快速体检。
4.4 过拟合调参:在测试集上反复试
现象:换一组参数测试集指标就涨一点,最后参数多到记不清。原因:把测试集当成了验证集反复使用,信息泄漏。解决:从训练集里再切一段验证集,或者直接用滚动窗口的均值做选型依据。测试集只在最后用一次。这是方法论层面的坑,比代码 bug 更难自查。
4.5 忽略交易成本与滑点
现象:方向准确率 55%,感觉能赚钱。原因:没算手续费和滑点,高频调仓下成本吃掉全部利润。解决:在回测里加入单边千分之一到千分之三的成本假设,并统计换手率。如果策略在加入成本后收益归零,说明它赚的是噪声不是规律。这一步不做,前面所有指标都是自嗨。
5. 把线性回归股票预测用出价值:三个进阶技巧
第一个技巧是把预测目标从价格改成方向,并做概率校准。价格回归的误差在震荡市里很难压低,但方向分类配合阈值过滤能筛掉低置信度信号。做法是用 LogisticRegression 或对线性输出做符号判断,只在高置信区间交易。
# 用线性回归输出构造方向信号,设置阈值过滤 pred = model.predict(X_test) signal = np.where(pred > test["close"].values * 1.005, 1, 0) # 预测涨幅超0.5%才看多 print("有效信号数:", signal.sum(), "总样本:", len(signal))阈值 1.005 表示预测涨幅超过 0.5% 才产生信号,这个参数要根据波动率调整,高波动市场可以放宽。
第二个技巧是特征重要性分析配合业务解释。线性回归系数标准化后可以直接排序,找出对预测贡献最大的因子。我一般会把系数和 VIF 放一起看,系数大但 VIF 也大的特征要警惕,它可能只是共线性的代表。
| 特征 | 标准化系数 | VIF | 处理建议 |
|---|---|---|---|
| ret1 | 0.42 | 1.8 | 保留 |
| ma5 | 0.31 | 12.5 | 与 ma20 二选一 |
| ma20 | 0.28 | 11.9 | 与 ma5 二选一 |
| vol_ma5 | -0.15 | 2.1 | 保留 |
| hl_range | 0.09 | 1.5 | 保留 |
第三个技巧是模型集成与对比基线。永远保留一个「预测明天等于今天」的朴素基线,如果你的模型连这个基线都跑不赢,说明特征工程没带来增量信息。把线性回归、Ridge、Lasso、朴素基线放同一张表对比,才能判断投入是否值得。
baseline_pred = test["close"].values # 朴素预测:明天等于今天 baseline_mae = mean_absolute_error(y_test, baseline_pred) print("基线 MAE:", baseline_mae, "模型 MAE:", mean_absolute_error(y_test, pred))如果模型 MAE 只比基线低一点点,别急着高兴,先检查这点差距在滚动窗口里是否稳定。我自己踩过的最大坑就是单窗口指标好看就上线,结果换一段行情直接失效。后来养成习惯:任何结论至少跑三个不同时间窗口,指标一致才认。这套流程不保证你赚钱,但能保证你不被自己骗。希望帮到你。
本文还有配套的精品资源,点击获取