ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python股票价格走势预测:从数据清洗到回测的完整实践指南

Python股票价格走势预测:从数据清洗到回测的完整实践指南 简介面向Python入门后想尝试量化交易的开发者这份资源是一套基于TensorFlow框架的股票价格走势预测小项目目标是用已有行情数据推测次日开盘价。项目实际代码借助tushare获取股票数据pandas负责清洗与特征构造matplotlib在同一张图中绘制真实价格与预测价格曲线方便直观评估回归效果虽然当前仅覆盖单日开盘价预测但作者表示后续会继续完善整体结构简洁适合作为学习LSTM或时序回归的起点。压缩包共5个文件包含两个Python脚本、一个TensorFlow checkpoint以及配套的data与index数据文件整包仅456KB下载后即可快速加载模型并运行测试。目前已有2142人学习浏览适合想在股票数据上动手实践深度学习的初学者。运行test.py可完成模型加载、预测与可视化配合自带权重可直接跳过训练过程快速看到当前模型的预测表现并对特征工程、网络结构及预测步长做进一步调优。1. 用Python做股票价格走势预测拆开这个zip先看四块硬骨头拿到一个基于python实现股票价格走势预测.zip多数人第一反应是找里面有没有LSTM模型其实真正让这个方向翻车的从来不是模型而是数据、标签和回测口径。Python做价格走势预测的完整链路是把历史行情、特征工程、预测模型、回测评估四块串起来缺一块都跑不出可信结果。这个方向适合三类人想入门量化交易的开发者、做数据分析或毕业设计的学生、以及想验证技术指标到底有没有用的投资者。先泼一盆冷水单靠历史价格预测未来价格准确率天花板就在那里但把这条研究管道搭好你手里就多了一套可反复验证的策略实验台这个价值比预测准一天大得多。2. 数据是第一个黑匣子行情取数、标签对齐和复权边界价格预测模型的本质是用历史和当前的信息预测未来某个时点的价格或方向。这句话落地到实现就是数据准备阶段必须回答三个问题数据从哪来、标签怎么打、复权和停牌怎么处理。很多新手在这一步就把顺序搞错——先去找模型代码结果模型跑通后怎么验都不对回头查才发现是标签错位或者复权不一致。数据阶段不像建模那么性感但它决定了后面所有步骤能不能成立。2.1 用akshare把日线拉回本地三行代码和参数说明常见做法是通过akshare从东方财富接口取A股日线这个库不需要注册token适合本地实验。先把行情拉到DataFrame里再做字段标准化和排序import pandas as pd import akshare as ak df ak.stock_zh_a_hist( symbol000001, perioddaily, start_date20180101, end_date20231231, adjustqfq ) df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume }) df df[[date, open, high, low, close, volume]] df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) print(df.head())这段代码的逻辑是akshare返回的中文列名先映射成英文标准字段再按日期升序排列并重置索引。symbol传的是不带交易所前缀的代码000001是平安银行period只取日线避免分钟线带来的数据量和噪声翻倍adjustqfq表示前复权这是计算技术指标时最常用的口径因为前复权价格在历史区间内是连续的均线、MACD这类指标不会因为分红除权出现虚假跳空。如果akshare接口偶尔抽风或者返回空值备用路径是baostockimport baostock as bs lg bs.login() rs bs.query_history_k_data_plus(sz.000001, date,open,high,low,close,volume, start_date2018-01-01, end_date2023-12-31, frequencyd, adjustflag2) bs.logout()这里adjustflag2对应前复权1是后复权3是不复权。数据拉到本地后第一件事不是建模而是检查有没有停牌导致的空行、重复日期、成交量异常为零的情况。我一般会先打印df.isnull().sum()和df[date].duplicated().sum()这两行排查能省掉后面大量莫名其妙的报错。2.2 构造标签预测涨跌方向还是预测收益率数据清洗完接下来是打标签。预测涨还是跌是分类问题预测涨多少是回归问题。两者都可以做但标签构造的方式直接决定模型学什么。为了防止信息泄漏标签必须用shift(-1)也就是未来的值来构建同时特征里不能携带未来信息df[ret] df[close].pct_change() df[label_up] (df[ret].shift(-1) 0).astype(int) # 明日是否上涨 df[label_ret] df[ret].shift(-1) # 明日收益率 df df.dropna().reset_index(dropTrue)逻辑说明pct_change()计算的是当日相对前一日的收益率shift(-1)把明天的收益率搬到今天这一行这样模型用今天的特征去预测明天的涨跌方向或收益率。这里最大的坑是把ret直接当成标签——ret是今天的收益率预测今天没有任何决策价值必须取shift(-1)。label_up适合做分类模型和准确率统计label_ret适合做回归模型和排序类指标IC两者可以都保留模型选型时再决定用哪个。2.3 停牌、复权和涨跌停样本的取舍复权问题是回测翻车的第一大来源。如果做技术指标和特征工程用前复权因为指标要求价格连续如果做收益计算和资金回测用后复权更准因为后复权价格包含了分红再投资的收益计算账户净值时不会因为除权产生虚假亏损。我见过不少人研究代码没问题净值曲线却莫名向下跳一查就是回测用了前复权数据分红日被当成了下跌。停牌样本的处理要看场景停牌日成交量通常为0有的数据源会直接留空行。这类样本要删除否则pct_change()会把复牌日的收益率算成从0涨到开盘价产生一个离谱的异常值。涨跌停样本更微妙——涨停板次日不一定买得进跌停板次日不一定卖得出如果不标记这类样本回测里会出现大量完美抄底逃顶的假交易。我一般会给每行打一个is_limit标记规则是涨幅超过9.8%记为涨停、跌幅超过9.8%记为跌停回测阶段单独处理。3. 特征工程用Python算出MA、RSI、MACD并防好信息泄漏特征工程阶段的目标不是堆指标而是构造出今天收盘后就能算出来、且对未来收益有预测能力的变量。均线、RSI、MACD这类技术指标是数据从业者最常用的起点因为它们基于收盘价就能算天然满足不用未来数据的约束。这一章的难点不在于公式本身而在于实现时容易把rolling窗口用错、把归一化的range算错导致泄漏混进模型。3.1 用pandas算均线、RSI和MACDrolling与ewm的坑用pandas计算技术指标的代码很简洁但有一处特别容易错——rolling(window)默认是对当前值以及之前的window-1个值求窗口统计不包含未来这是它适合做特征的根本原因。常见的均线、RSI、MACD代码如下df[ma5] df[close].rolling(5).mean() df[ma20] df[close].rolling(20).mean() delta df[close].diff() up delta.clip(lower0) down -delta.clip(upper0) roll_up up.rolling(14).mean() roll_down down.rolling(14).mean() df[rsi14] 100 - 100 / (1 roll_up / roll_down) ema12 df[close].ewm(span12, adjustFalse).mean() ema26 df[close].ewm(span26, adjustFalse).mean() df[macd_dif] ema12 - ema26 df[macd_dea] df[macd_dif].ewm(span9, adjustFalse).mean() df[macd_hist] df[macd_dif] - df[macd_dea]逻辑说明MA5和MA20分别是5日和20日收盘均线反映短期和中期趋势RSI14用14日涨跌幅均值之比衡量超买超卖MACD是经典的12日EMA减26日EMA得到DIF再对DIF做9日EMA得到DEA。ewm(span..., adjustFalse)是很多新手容易踩的点——adjustFalse保证EMA从第一行开始就递推计算而不是用前一段数据的加权平均做初始化否则特征的前几十行会出现莫名的大幅波动。参数上这些窗口值都是行业常用默认值不要为了拟合历史而频繁改动否则就滑向了过拟合。3.2 归一化不要在整段数据上做泄漏从这开始特征算好之后LSTM或神经网络类模型需要做归一化。这里的泄漏陷阱非常隐蔽如果先对整段数据fit_transform测试集的均值和最大值已经参与了训练数据的缩放计算等于模型在训练时偷看了测试集分布。正确做法是只在训练段上fit再用同一个scaler去transform测试段from sklearn.preprocessing import MinMaxScaler features [ma5, ma20, rsi14, macd_dif, macd_dea, macd_hist] train_size int(len(df_scaled) * 0.8) scaler MinMaxScaler(feature_range(0, 1)) df_scaled df.copy() df_scaled.loc[:train_size - 1, features] scaler.fit_transform( df_scaled.loc[:train_size - 1, features] ) df_scaled.loc[train_size:, features] scaler.transform( df_scaled.loc[train_size:, features] )逻辑说明第一行fit_transform只用了训练段的数据去计算min和max第二行transform把同样的min和max套到测试段上。feature_range(0, 1)是LSTM的常规输入区间如果你用ARIMA或XGBoost这类模型树模型对量纲不敏感不做归一化也没关系。判断有没有泄漏的方法很简单训练段的min/max和解压出的scaler属性打印出来如果测试段出现了超出(0,1)区间范围的值那就是正常的因为测试段的极值本来就不该参与训练。3.3 特征相关性排查热力图之外看互信息特征算完不是直接灌进模型要先做一轮相关性排查。常见做法是画seaborn热力图看皮尔逊相关系数但相关性矩阵只能抓线性关系价格序列里很多有效信号是非线性的。互信息可以弥补这一点from sklearn.feature_selection import mutual_info_classif mi mutual_info_classif( df_scaled[features], df_scaled[label_up], random_state42 ) print(dict(zip(features, mi)))逻辑说明mutual_info_classif计算每个特征与标签明日是否上涨的互信息值数值越大说明特征与标签的非线性关联越强。random_state42是为了结果可复现。这一轮排查的价值在于互信息接近0的特征可以直接丢弃相关性极高的一对特征比如MA5和MA20相关系数超过0.95保留一个即可否则回归类模型会把多重共线性放大预测值变得忽高忽低。4. 三套模型最少代码ARIMA、LSTM、XGBoost怎么落地模型选型是这个zip里最容易被神化的部分。价格走势预测在从业者手里通常有三条路ARIMA这类统计模型、LSTM这类深度学习模型、XGBoost这类梯度提升树模型。它们不是竞争关系而是三种不同假设——ARIMA假设线性自相关LSTM假设长短期时序依赖XGBoost假设特征与标签存在可学习的非线性映射。选型看数据量和任务样本少于500条先跑ARIMA和XGBoost样本充足再上LSTM。4.1 ARIMAstatsmodels最小可跑代码ARIMA是老牌时间序列模型核心是order(p, d, q)三个参数。它只吃单变量序列所以这里用收盘价本身来建模不掺特征。代码如下from statsmodels.tsa.arima.model import ARIMA series_close df_scaled[close].iloc[:train_size] model_arima ARIMA(series_close, order(5, 1, 0)) model_arima_fit model_arima.fit() forecast model_arima_fit.forecast(steps5) print(forecast)逻辑说明series_close是训练段的收盘价序列order(5, 1, 0)表示5阶自回归、1阶差分、0阶移动平均。d1是因为原始收盘价通常非平稳一阶差分后序列才会稳定p5是粗定的自回归阶数准确做法是先画plot_acf和plot_pacf两张图看截尾和拖尾特征来定阶。forecast(steps5)预测未来5个交易日。这段代码的价值是当baseline——如果后面LSTM和XGBoost连ARIMA都跑不赢说明特征或标签构造有问题而不是模型不够高级。4.2 LSTMkeras里最容易写错的三行LSTM做价格预测的标准姿势是滑动窗口监督学习用过去20个交易日的特征预测下一个交易日的收益。最容易写错的地方集中在三处input_shape不带batch维度、第一层return_sequencesTrue、训练时shuffleFalse。最小代码如下import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense seq_len 20 X, y [], [] for i in range(seq_len, train_size): X.append(df_scaled[features].iloc[i - seq_len:i].values) y.append(df_scaled[label_ret].iloc[i]) X np.array(X) y np.array(y) model Sequential([ LSTM(50, return_sequencesTrue, input_shape(seq_len, len(features))), LSTM(50), Dense(1) ]) model.compile(optimizeradam, lossmse) model.fit(X, y, epochs30, batch_size32, validation_split0.1, shuffleFalse)逻辑说明X的形状是(样本数, 20, 特征数)每个样本是连续20天的特征矩阵y是对应第21天的收益率。input_shape(seq_len, len(features))不需要写batch维度。第一层return_sequencesTrue是因为后面还要接第二层LSTM如果写成False会直接报维度错误。shuffleFalse是必须的——价格序列有强时序性随机打乱样本等于把未来样本混进训练集模型会在测试集上表现得忽好忽坏。epochs30、batch_size32是入门参数实际调参看验证集loss曲线如果验证loss先降后升就该减少epochs或调小学习率。4.3 XGBoost特征矩阵直接喂效果往往最稳XGBoost在这个任务上经常被低估。它不需要滑动窗口构造三维输入直接拿今天的特征预测明天的收益训练速度快、不需要归一化而且对特征中的非线性关系拟合能力很强。最小代码如下from xgboost import XGBRegressor X_train df_scaled[features].iloc[:train_size] y_train df_scaled[label_ret].iloc[:train_size] X_test df_scaled[features].iloc[train_size:] model_xgb XGBRegressor( n_estimators200, learning_rate0.03, max_depth4, subsample0.8, random_state42 ) model_xgb.fit(X_train, y_train) pred_xgb model_xgb.predict(X_test)逻辑说明X_train是训练段的特征矩阵y_train是训练段的明日收益率X_test是测试段特征。n_estimators200控制树的数量learning_rate0.03是收缩步长max_depth4限制单棵树深度来防过拟合subsample0.8让每棵树只用80%的样本训练增加随机性。这组参数对日频价格预测来说是一个稳定起点。XGBoost最实用的点在于它能输出feature_importances_跑完直接看哪些特征在起作用而不是把模型当黑匣子。4.4 三者对比选型看数据量、任务类型、可解释性三套模型各有边界下面这张表整理了最关键的区别模型输入形式适合数据量是否吃多特征可解释性常见翻车点ARIMA单变量序列500条以下否高非平稳、阶数定错LSTM滑动窗口三维张量2000条以上是极低归一化泄漏、shuffle错开XGBoost特征矩阵500条以上是中学习率过大、特征泄漏选型建议很直接数据量不足500个交易日ARIMA做baseline、XGBoost做主力LSTM基本不用碰数据量超过2000个交易日、你有GPU或耐心调参再让LSTM上场。另外把预测当成回归任务还是分类任务也影响选型——预测收益率用XGBRegressor或LSTM回归头预测方向用XGBClassifier输出概率。实操里我见过最稳的组合是XGBoost回归预测收益率再用阈值过滤信号这个思路在下一章回测环节会体现价值。5. 回测里的常见问题为什么预测准了却亏钱预测模型在测试集上表现不错放到回测里却亏钱这是价格走势预测项目最经典的玄学时刻。原因通常不在模型本身而在回测口径和交易约束。这一章集中写回测阶段最常见的5个坑每条都是现象、原因、解决三步覆盖了新手90%的翻车现场。5.1 回测口径预测对了不代表能按这个价成交回测的第一原则是成交价不能偷看未来。常见错误是模型预测明天上涨回测里用今天的收盘价买入第二天用预测日的收盘价卖出。这等于提前知道明天的收盘价去成交回测收益天然虚高。正确口径是今天收盘后产生预测信号明天开盘价买入持有若干天后用卖出日的开盘价或收盘价卖出。用开盘价成交已经是最乐观的假设了更保守的做法是用次日开盘价加一个滑点来模拟冲击成本。回测的净值计算也要统一复权口径。用后复权数据算累计收益最稳妥因为后复权价格天然包含了分红再投资不会因为除权造成净值跳变。代码上净值曲线的计算方式是df_strategy[position] df_strategy[signal].shift(1) df_strategy[strategy_ret] df_strategy[position] * df_strategy[ret] df_strategy[nav] (1 df_strategy[strategy_ret]).cumprod()逻辑说明signal是当日收盘后产生的信号1表示持仓、0表示空仓shift(1)是为了让信号在第二天才生效对应次日开盘执行的规则。strategy_ret是持仓状态下能拿到的收益率nav是策略净值曲线。如果省略shift(1)信号当天就成交等于提前使用了收盘后的信息这就是未来函数的一种。5.2 高频踩坑清单5.2.1 未来函数回测完美、实盘崩盘现象训练loss很低回测净值曲线漂亮得像教科书一上实盘就亏。原因归一化用了全样本的min和max或者特征里混入了当日的未来统计量模型在测试阶段偷看了不该看的信息。解决归一化只fit训练段所有特征在构造时做shift(1)确保第T天的特征值在第T天收盘后才能算出来回测信号必须shift(1)再用于次日成交。5.2.2 时序样本被随机打乱现象LSTM训练过程正常但预测曲线是锯齿状完全不像连续的价格序列。原因model.fit里shuffleTrue随机打乱了时间顺序模型学到的是乱序样本之间的统计关系失去了时序依赖。解决LSTM训练必须设置shuffleFalse如果用了validation_split要确认验证集是训练集末尾的连续切片而不是随机抽取。5.2.3 涨停买不进、跌停卖不出现象回测里盈利最多的几笔交易都发生在涨停板上。原因没有处理涨跌停约束模型预测涨停次日还大涨回测默认按开盘价成交但真实情况是涨停封单巨大根本买不进。解决在数据里标记涨跌停样本回测中对一字涨停样本禁止买入、一字跌停样本禁止卖出或者直接把这类样本从交易时段中剔除。5.2.4 前复权与后复权混用现象同一策略换一个数据源跑净值曲线差异巨大。原因技术指标用前复权计算收益回测却用前复权数据算资产变动分红除权日被误判为下跌。解决特征计算用前复权资金回测改用后复权数据两者分开维护混用时至少要做收益率的对齐校验。5.2.5 用整段数据做特征筛选现象测试集IC高得离谱但滚动回测收益为负。原因先用全样本算互信息或相关性筛完特征再切训练测试集特征选择过程已经偷看了测试集。解决特征筛选只在训练段内完成或者使用下一章的walk-forward方式每一段重训时重新选特征。5.3 评估指标怎么选准确率、IC与最大回撤评价预测模型和评价策略是两件事。预测层面方向准确率要超过53%才有统计意义上的优势随机猜测是50%收益率预测可以用IC来衡量也就是预测值与真实收益率的秩相关。策略层面只看累计收益不够最大回撤和夏普比率更关键。计算IC的最小代码from scipy.stats import spearmanr y_test df_scaled[label_ret].iloc[train_size:].values ic, p_value spearmanr(pred_xgb, y_test) print(fIC: {ic:.4f}, p-value: {p_value:.4f})逻辑说明spearmanr计算的是秩相关系数不要求预测值和真实值呈线性关系IC绝对值超过0.05就说明预测有微弱但有效的排序能力p_value用来判断显著性小于0.05才值得继续往下做。如果IC接近0别浪费时间调参回头查特征构造和标签对齐。6. 进阶验证用walk-forward滚动回测把预测驯化成信号静态切分训练集和测试集只能证明模型在历史数据上没翻车不能证明模型在未知未来上能持续工作。价格预测项目最接近实盘环境的验证方式是walk-forward也叫滚动训练每过一段时间就用最近一段数据重新训练模型然后用新模型预测下一段模拟边更新边交易的真实节奏。这里以XGBoost为例因为它训练快、不需要滑窗最适合滚动验证import xgboost as xgb retrain_window 250 retrain_step 5 preds [] test_dates [] for end in range(retrain_window, len(df_scaled) - retrain_step, retrain_step): train_df df_scaled.iloc[end - retrain_window:end] test_df df_scaled.iloc[end:end retrain_step] model XGBRegressor( n_estimators200, learning_rate0.03, max_depth4, subsample0.8, random_state42 ) model.fit(train_df[features], train_df[label_ret]) preds.append(model.predict(test_df[features])) test_dates.extend(test_df[date].tolist())逻辑说明retrain_window250表示每次用最近250个交易日约一年的数据训练retrain_step5表示每5个交易日滚动一次。循环内部重新训练模型并预测接下来5天预测结果拼成一条完整的预测序列。这样得到的预测曲线每个点都是用当时能拿到的最新数据算出来的不存在任何未来信息评估结果才可信。滚动预测跑通之后下一步是把预测值转成可执行的信号。不要每天交易而是设阈值过滤预测收益率大于0.5%才开仓预测收益率低于负阈值才平仓或做空。这样做的本质是牺牲交易频率换取胜率模型预测不准的日子直接空仓。阈值的选择看回测结果一般取预测分布的上分位数比如只交易预测收益排在前20%的日子。我早期在这个项目上最大的教训就是急着上线LSTM花了大量时间调参回测却跑不过一个简单的XGBoost加阈值方案。后来我才意识到价格走势预测项目的难点从不在单一模型的准确率而在于数据是否干净、回测是否诚实、信号是否有过滤机制。把这三个问题解决好哪怕用最简单的模型也能搭出一个逻辑自洽、可验证的策略框架。希望帮到你。本文还有配套的精品资源点击获取
返回列表