Python股票价格预测教学闭环:数据清洗到滚动验证

发布时间:2026/9/10 22:07:11
Python股票价格预测教学闭环:数据清洗到滚动验证 简介本资源是一份面向高校计算机与金融工程专业学生的Python课程设计项目聚焦股票价格预测这一典型时间序列建模任务提供从数据获取、特征工程、模型训练到结果可视化的完整实现方案。压缩包共24个文件含17个CSV格式的多支A股历史行情数据如000001、600015等代码6个核心Python脚本涵盖数据预处理、LSTM/ARIMA模型构建、评估与预测主流程以及1个占位文件整体体积仅2.95MB轻量易部署。已有215人学习下载项目经导师指导并获97分高分评价代码结构清晰、注释完整、依赖明确开箱即用无需修改即可在本地Python环境含pandas、numpy、scikit-learn、tensorflow等常见库中成功运行。读者可直接复现高分课程设计全流程深入理解金融时序数据建模的关键环节与工程实践规范。1. 这不是“AI炒股”而是用 Python 复现课程级股票价格预测闭环从原始 CSV 数据加载、特征工程、模型训练到滚动预测验证全程可复现、可调试、可教学很多初学者下载了“股票价格预测源码.zip”后直接运行报错——不是模型不准而是连数据路径都写死在 C:\Users\Admin\Desktop\stock_data\或者 pandas 读取时因编码问题卡在第一行。这个标题里的“课程设计”是关键线索它不追求实盘收益而聚焦于教学完整性——必须包含真实可得的股票历史数据如 Yahoo Finance 格式、明确的时间序列处理逻辑、可解释的特征构造非黑盒、以及能跑通的最小预测 pipeline。适合计算机/金融工程专业大三以上学生也适合想补足时间序列建模基础的转行者。它解决的不是“明天涨跌”而是“如何把收盘价序列变成一个可训练的监督学习问题”核心能力包括用pandas清洗 OHLCV 数据、用sklearn构建滞后特征、用LSTM或RandomForest建模、用matplotlib可视化预测残差。所有依赖仅限numpy,pandas,scikit-learn,tensorflow或pytorch等主流包无需额外 API 密钥或付费数据源。2. 用 pandas 加载并清洗股票历史数据从原始 CSV 到结构化 DataFrame 的 5 步标准化流程课程设计中最大的隐性门槛往往卡在第一步数据加载失败。常见问题包括中文列名乱码、日期格式不统一、缺失值分布不均、复权处理缺失。以下流程基于 Yahoo Finance 下载的典型 CSV含 Date, Open, High, Low, Close, Adj Close, Volume 列适配 Python 3.8 环境。2.1 验证数据文件结构与编码避免 read_csv 报错提示不要假设文件是 UTF-8。Yahoo Finance 下载的 CSV 在 Windows 上常为 GBK 编码直接pd.read_csv(data.csv)会触发UnicodeDecodeError。import pandas as pd import chardet # 自动检测编码对小文件有效 with open(stock_data.csv, rb) as f: raw_data f.read(10000) # 读前 10KB encoding chardet.detect(raw_data)[encoding] print(f检测到编码: {encoding}) # 通常输出 GBK 或 utf-8-sig # 按检测结果读取 df pd.read_csv(stock_data.csv, encodingencoding, parse_dates[Date], index_colDate)parse_dates[Date]将字符串列转为 datetime 类型index_colDate设为索引——这是时间序列操作的前提。若列名含空格或中文如“收盘价”需提前重命名df.columns [Open, High, Low, Close, Adj_Close, Volume]。2.2 处理缺失值与异常值针对金融数据的业务敏感清洗股票数据缺失通常集中在停牌日或数据源断点。简单df.dropna()会丢失关键时间点应按业务逻辑填充# 检查缺失值分布 print(df.isnull().sum()) # 输出示例Volume 3, Close 0 → Volume 缺失更需关注 # 对 Volume 缺失用前后 3 日均值线性插值比前向填充更合理 df[Volume] df[Volume].interpolate(methodlinear, limit_directionboth) # 对价格异常值如单日涨跌幅 15%用滚动窗口中位数替代 def remove_price_outliers(series, window20, threshold3): rolling_med series.rolling(windowwindow).median() rolling_std series.rolling(windowwindow).std() outliers (series - rolling_med).abs() threshold * rolling_std return series.where(~outliers, rolling_med) df[Close] remove_price_outliers(df[Close]) df[Adj_Close] remove_price_outliers(df[Adj_Close])interpolate(methodlinear)保证成交量连续性rolling median std比全局 IQR 更适应股价波动周期。注意Adj_Close 必须用于建模因它已校正分红送股影响避免模型学习到人为价格跳变。2.3 构建基础时间特征将 Date 索引转化为可学习的数值变量纯日期索引无法被模型直接使用需提取周期性特征df[DayOfWeek] df.index.dayofweek # 0周一, 6周日 df[DayOfMonth] df.index.day # 1~31 df[Month] df.index.month # 1~12 df[Quarter] df.index.quarter # 1~4 df[IsMonthEnd] df.index.is_month_end.astype(int) df[Year] df.index.year这些特征对捕捉季节性如财报季效应、年末资金面至关重要。is_month_end转为 int 后模型可识别“月末效应”是否显著——实证中该特征常与波动率正相关。2.4 计算技术指标作为衍生特征MACD、RSI、布林带的向量化实现课程设计要求体现“金融特征工程”而非仅用原始价格。以下用ta库pip install ta实现避免手动循环from ta import add_all_ta_features from ta.utils import dropna # 添加全部技术指标含 40 特征 df add_all_ta_features( df, openOpen, highHigh, lowLow, closeClose, volumeVolume, fillnaTrue ) # 删除含 NaN 的行fillnaTrue 仍可能残留 df dropna(df)add_all_ta_features自动生成volatility_bbm布林带上轨、trend_macdMACD 线、momentum_rsiRSI等列。fillnaTrue用前向填充处理指标初始 NaN。关键参数说明open/high/low/close/volume必须严格匹配 DataFrame 列名fillnaTrue是必需项否则 LSTM 输入会因 NaN 中断。2.5 保存清洗后数据生成课程设计标准输入文件清洗后的数据需导出为.csv供后续模型模块调用# 保留核心列价格、成交量、技术指标、时间特征 feature_cols [Close, Volume, volatility_bbm, trend_macd, momentum_rsi, DayOfWeek, Month, IsMonthEnd] df_clean df[feature_cols].copy() # 按日期升序排列确保时间顺序 df_clean df_clean.sort_index() # 保存为 UTF-8 编码兼容所有环境 df_clean.to_csv(stock_clean.csv, encodingutf-8-sig) print(f清洗完成共 {len(df_clean)} 条记录特征维度: {df_clean.shape[1]})encodingutf-8-sig解决 Excel 打开中文乱码问题sort_index()确保时间序列单调递增——这是LSTM训练的硬性要求。3. 构建监督学习样本将时间序列转换为特征-标签对的滑动窗口策略股票预测本质是回归问题用过去 N 天数据预测未来 M 天价格。课程设计常用N60, M1即用 60 日数据预测次日收盘价。难点在于避免未来信息泄露和窗口边界对齐。3.1 定义滑动窗口函数支持多步预测与特征对齐import numpy as np def create_sequences(data, seq_length60, pred_horizon1): data: DataFrame索引为日期列包含特征 seq_length: 输入序列长度天数 pred_horizon: 预测步长1次日5下周 返回: X (samples, seq_length, features), y (samples, pred_horizon) X, y [], [] # 遍历所有可构成完整窗口的起始索引 for i in range(len(data) - seq_length - pred_horizon 1): # 取 [i:iseq_length] 作为输入窗口 seq_x data.iloc[i:(i seq_length)].values # 取 [iseq_length:iseq_lengthpred_horizon] 作为标签未来价格 seq_y data[Close].iloc[(i seq_length):(i seq_length pred_horizon)].values X.append(seq_x) y.append(seq_y) return np.array(X), np.array(y) # 加载清洗后数据 df_clean pd.read_csv(stock_clean.csv, index_col0, parse_datesTrue) X, y create_sequences(df_clean, seq_length60, pred_horizon1) print(f生成样本数: {X.shape[0]}, 输入形状: {X.shape[1:]}, 标签形状: {y.shape[1:]}) # 输出示例生成样本数: 1200, 输入形状: (60, 8), 标签形状: (1,)iloc确保按位置索引非标签索引避免因日期跳跃导致窗口错位pred_horizon1时y为一维数组便于RandomForestRegressor直接拟合。3.2 划分训练/验证/测试集按时间顺序切分禁用随机打乱金融时间序列严禁train_test_split(random_state...)必须按时间切分# 按 70%-15%-15% 划分保证时序连续 split1 int(0.7 * len(X)) split2 int(0.85 * len(X)) X_train, y_train X[:split1], y[:split1] X_val, y_val X[split1:split2], y[split1:split2] X_test, y_test X[split2:], y[split2:] print(f训练集: {X_train.shape[0]} 样本) print(f验证集: {X_val.shape[0]} 样本) print(f测试集: {X_test.shape[0]} 样本)split1和split2用int()截断避免浮点索引错误。验证集用于早停Early Stopping测试集仅最后评估绝不参与超参调优。3.3 特征缩放对 LSTM 使用 MinMaxScaler对树模型禁用不同模型对尺度敏感度差异巨大from sklearn.preprocessing import MinMaxScaler # LSTM 必须缩放否则梯度爆炸 scaler_X MinMaxScaler(feature_range(0, 1)) scaler_y MinMaxScaler(feature_range(0, 1)) # 重塑为 2D(samples*seq_length, features) → 缩放 → 重塑回 3D X_train_2d X_train.reshape(-1, X_train.shape[-1]) X_train_scaled scaler_X.fit_transform(X_train_2d).reshape(X_train.shape) X_val_scaled scaler_X.transform(X_val.reshape(-1, X_val.shape[-1])).reshape(X_val.shape) X_test_scaled scaler_X.transform(X_test.reshape(-1, X_test.shape[-1])).reshape(X_test.shape) # y 缩放仅对 LSTM y_train_scaled scaler_y.fit_transform(y_train) y_val_scaled scaler_y.transform(y_val) y_test_scaled scaler_y.transform(y_test)MinMaxScaler将所有特征压缩至 [0,1]避免LSTM中 sigmoid/tanh 激活函数饱和。注意RandomForest不需要缩放其分裂基于特征排序尺度不影响结果。3.4 构建多模型基线RandomForest 与 LSTM 的代码对比课程设计需对比不同范式。以下提供两个可直接运行的基线模型3.4.1 RandomForestRegressor无需缩放训练快可解释性强from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error # 展平 X 为 2D(samples, seq_length * features) X_train_flat X_train.reshape(X_train.shape[0], -1) X_val_flat X_val.reshape(X_val.shape[0], -1) X_test_flat X_test.reshape(X_test.shape[0], -1) rf RandomForestRegressor(n_estimators100, max_depth10, random_state42) rf.fit(X_train_flat, y_train.ravel()) # ravel() 确保 y 为 1D # 预测 y_pred_rf_val rf.predict(X_val_flat) y_pred_rf_test rf.predict(X_test_flat) print(RandomForest 验证集 MAE:, mean_absolute_error(y_val, y_pred_rf_val)) print(RandomForest 测试集 MAE:, mean_absolute_error(y_test, y_pred_rf_test))n_estimators100平衡速度与精度max_depth10防止过拟合ravel()将(n,1)数组转为(n,)适配sklearn接口。3.4.2 LSTM 模型Keras 实现含早停与学习率调度import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau model Sequential([ LSTM(50, return_sequencesTrue, input_shape(X_train_scaled.shape[1], X_train_scaled.shape[2])), Dropout(0.2), LSTM(50, return_sequencesFalse), Dropout(0.2), Dense(25), Dense(1) ]) model.compile(optimizeradam, lossmse) # 回调函数 early_stopping EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.5, patience5) history model.fit( X_train_scaled, y_train_scaled, batch_size32, epochs100, validation_data(X_val_scaled, y_val_scaled), callbacks[early_stopping, reduce_lr], verbose1 ) # 预测并反变换 y_pred_lstm model.predict(X_test_scaled) y_pred_lstm scaler_y.inverse_transform(y_pred_lstm) # 反归一化return_sequencesTrue用于堆叠 LSTM 层Dropout(0.2)防止过拟合patience10允许验证损失停滞 10 轮后停止避免冗余训练。4. 滚动预测与可视化用测试集模拟实盘推演生成可交付的课程报告图表课程设计验收关键不仅给出最终误差更要展示模型在未知时间窗口的动态预测能力。需实现滚动预测Rolling Forecast即每预测一日后将新观测加入训练集重新拟合或更新状态。4.1 实现滚动预测框架以 RandomForest 为例的在线更新逻辑def rolling_forecast_rf(model, X_full, y_full, start_idx, window_size60, horizon1): model: 已训练的 RandomForest X_full: 全量特征序列 (samples, seq_len, features) y_full: 全量标签序列 (samples, horizon) start_idx: 开始滚动的索引通常为测试集首索引 window_size: 每次使用的训练窗口长度 返回: 预测值列表、真实值列表 predictions [] actuals [] # 初始化训练数据用 start_idx 前 window_size 个样本 train_end start_idx X_train_roll X_full[:train_end].reshape(-1, X_full.shape[-1]) y_train_roll y_full[:train_end].ravel() # 滚动预测 for i in range(start_idx, len(X_full)): # 当前输入最近 window_size 个样本 X_current X_full[i-window_size:i].reshape(1, -1) if i window_size else X_full[:i].reshape(1, -1) # 预测 pred model.predict(X_current)[0] predictions.append(pred) actuals.append(y_full[i][0]) # y_full[i] 是 (1,) 数组 # 更新训练集添加最新样本仅当有新标签 if i len(y_full) - 1: new_X X_full[i].reshape(1, -1) new_y y_full[i][0] X_train_roll np.vstack([X_train_roll, new_X]) y_train_roll np.append(y_train_roll, new_y) # 重训模型课程设计中可简化为每 10 步重训 if (i - start_idx) % 10 0: model.fit(X_train_roll, y_train_roll) return np.array(predictions), np.array(actuals) # 执行滚动预测 start_idx len(X_train) len(X_val) # 测试集起始位置 y_pred_roll, y_true_roll rolling_forecast_rf(rf, X, y, start_idx, window_size60)window_size60模拟“始终用最近 60 天数据训练”(i - start_idx) % 10 0控制重训频率平衡计算开销与模型新鲜度。4.2 绘制三线对比图真实值、LSTM 预测、RandomForest 预测import matplotlib.pyplot as plt # 创建日期索引从测试集开始 test_dates df_clean.index[start_idx:start_idx len(y_true_roll)] plt.figure(figsize(15, 6)) plt.plot(test_dates, y_true_roll, labelActual Price, linewidth2, colorblack) plt.plot(test_dates, y_pred_lstm.flatten()[:len(y_true_roll)], labelLSTM Prediction, linestyle--, alpha0.8) plt.plot(test_dates, y_pred_roll, labelRandomForest Prediction, linestyle-., alpha0.8) plt.title(Stock Price Prediction Comparison (Rolling Forecast)) plt.xlabel(Date) plt.ylabel(Adjusted Close Price) plt.legend() plt.grid(True, alpha0.3) plt.xticks(rotation45) plt.tight_layout() plt.savefig(prediction_comparison.png, dpi300) plt.show()flatten()[:len(y_true_roll)]确保 LSTM 预测长度与滚动预测一致linestyle区分模型类型dpi300保证课程报告打印清晰。4.3 计算滚动预测误差指标MAPE、Directional Accuracy课程设计需量化预测质量不能只看 MSEdef calculate_metrics(y_true, y_pred): mae np.mean(np.abs(y_true - y_pred)) rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) # MAPE避免除零 mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100 # 方向准确率预测涨跌方向是否正确 true_dir np.diff(y_true) 0 pred_dir np.diff(y_pred) 0 dir_acc np.mean(true_dir pred_dir) * 100 return {MAE: mae, RMSE: rmse, MAPE (%): mape, Directional Accuracy (%): dir_acc} metrics_rf calculate_metrics(y_true_roll, y_pred_roll) metrics_lstm calculate_metrics(y_true_roll, y_pred_lstm.flatten()[:len(y_true_roll)]) print(RandomForest 滚动预测指标:) for k, v in metrics_rf.items(): print(f {k}: {v:.3f}) print(\nLSTM 滚动预测指标:) for k, v in metrics_lstm.items(): print(f {k}: {v:.3f})MAPE衡量相对误差Directional Accuracy衡量趋势判断能力——这两项比RMSE更符合课程设计对“预测实用性”的考察要求。5. 课程设计交付技巧3 个让答辩老师眼前一亮的细节优化课程设计评分常取决于细节呈现。以下三个技巧无需增加模型复杂度但能显著提升专业感与可信度。5.1 用shap解释 RandomForest 特征重要性可视化哪几个指标真正在驱动预测import shap # 计算 SHAP 值需安装 shap: pip install shap explainer shap.TreeExplainer(rf) shap_values explainer.shap_values(X_test_flat[:100]) # 取前 100 样本加速 # 获取原始特征名展平后需映射 feature_names [] for i in range(60): # 60 天窗口 for j, col in enumerate([Close, Volume, volatility_bbm, trend_macd, momentum_rsi, DayOfWeek, Month, IsMonthEnd]): feature_names.append(f{col}_t-{60-i}) # 绘制摘要图 plt.figure(figsize(10, 6)) shap.summary_plot(shap_values, X_test_flat[:100], feature_namesfeature_names, plot_typebar, showFalse) plt.title(RandomForest Feature Importance (SHAP)) plt.tight_layout() plt.savefig(shap_importance.png, dpi300) plt.show()shap.summary_plot(..., plot_typebar)直接显示各特征平均 |SHAP| 值。结果通常显示Close_t-1昨日收盘价权重最高trend_macd次之——这符合金融直觉证明模型未学偏。5.2 生成预测置信区间用 RandomForest 的predict_quantilessklearn 1.4# 需 sklearn 1.4 from sklearn.ensemble import QuantileRegressor # 训练分位数回归森林替代 RandomForest qr QuantileRegressor(quantiles[0.1, 0.5, 0.9], alpha0.05) qr.fit(X_train_flat, y_train.ravel()) # 预测 10%/50%/90% 分位数 y_pred_q10 qr.predict(X_test_flat, quantile0.1) y_pred_q50 qr.predict(X_test_flat, quantile0.5) y_pred_q90 qr.predict(X_test_flat, quantile0.9) # 绘制带状图 plt.fill_between(test_dates, y_pred_q10, y_pred_q90, alpha0.2, colorblue, label90% CI) plt.plot(test_dates, y_pred_q50, labelMedian Prediction, colorblue) plt.plot(test_dates, y_true_roll, labelActual, colorblack) plt.legend() plt.title(Prediction with 90% Confidence Interval) plt.show()QuantileRegressor提供概率化预测比点估计更具说服力。alpha0.05控制正则化强度避免过拟合。5.3 自动化报告生成用pandas_profiling现为ydata-profiling分析数据质量# 安装pip install ydata-profiling from ydata_profiling import ProfileReport # 对清洗后数据生成交互式报告 profile ProfileReport(df_clean, titleStock Data Quality Report, explorativeTrue) profile.to_file(stock_data_report.html)生成 HTML 报告自动包含缺失值热力图、特征相关性矩阵、时间序列趋势图。答辩时打开网页即可展示数据清洗成果无需手动截图。注意ydata-profiling会消耗内存建议在df_clean.sample(10000)子集上运行或设置minimalTrue参数加速。本文还有配套的精品资源点击获取