BP神经网络预测股票:原理、Python/MATLAB实现与避坑指南
简介面向股票价格预测的BP神经网络MATLAB实现示例包适合机器学习初学者与金融量化爱好者快速上手。压缩包内共2个文件一个MATLAB脚本用于构建、训练及评估BP网络一个CSV数据文件提供历史行情样本整体仅23KB结构紧凑。已有5759人学习下载代码虽短但覆盖了数据预处理、归一化、训练集划分、网络创建、反向传播训练及预测输出等完整流程CSV中包含开盘价、最高价、最低价、收盘价等常用字段可直接运行复现也可替换为其他股票数据来验证模型泛化能力。脚本内附参数注释便于调整隐藏层节点数以适配不同数据。借助这份精简实现读者能直观理解BP神经网络如何通过误差反向传播拟合价格走势并认识到股票预测存在内在不确定性为后续扩展到Python或其他工具打下坚实基础。1. BP神经网络预测股票先别急着拿代码想清楚这三件事“BP神经网络预测股票”这个标题看着像是一条通向财富自由的简洁路径实际做下来更像是在修一座需要自己搭脚手架的桥。BP神经网络做的事情并不神秘拿历史行情数据当输入经过隐藏层的非线性变换输出一个对未来的预测值它能解决的是“从量价数据里找到某种非线性映射”这个回归问题适合想验证自己选的特征到底有没有用、想跑通一套量化研究流程的人不适合指望它直接给出买卖点就算完事的人。这篇文章会把 Python 和 MATLAB 两条路都走一遍告诉你参数怎么调、回测怎么做才不算自欺欺人、坑都埋在哪里。读完之后你能自己复现一套从取数到评估的完整流程也能判断别人发的那些“预测准确率 95%”的图到底可不可信。2. BP凭什么能预测股价从网络结构到数据特性的三层拆解2.1 BP神经网络结构图拆解输入层、隐藏层、输出层各自在做什么BP 神经网络的全称是 Backpropagation Neural Network它的信号流可以拆成两段前向传播计算预测值反向传播更新权重。前向传播时输入特征向量先经过权重矩阵加权求和再通过激活函数做非线性变换逐层传递到输出层得到预测结果接着拿预测值和真实值算损失误差再沿着网络反向传递用梯度下降法逐层修正权重这就是“误差反向传播”的全部含义。对应到股价预测场景里每一层都有它明确的职责。输入层接收当天的特征值比如开盘价、收盘价、成交量、涨跌幅、均线值隐藏层相当于在自动组合这些特征尝试拟合出它们和未来价格之间的函数关系输出层就是预测目标本身比如次日收盘价。用一张表可以把这套映射关系说清楚网络部分在股价预测里的角色常见参数设置输入层当日特征向量量价、技术指标神经元数 特征维度隐藏层特征组合与非线性映射1~2 层每层 16~128 个神经元输出层预测价格回归任务1 个神经元激活函数用 linear激活函数给网络引入非线性能力隐藏层用 relu 或 tanh输出层用 linear损失函数衡量预测价与真实价的差距回归任务用 mse 或 mae优化器决定权重怎么更新Adam 或 SGD学习率 0.001~0.01这里有个新手最容易忽略的点BP 网络的作用是拟合映射关系不是“学习规律”本身。输入特征决定了它能拟合的上限网络结构决定它能逼近这个上限到什么程度。如果特征里根本没有有效信息隐藏层再多也只是在一个空房间里堆家具。2.2 股票数据适合做BP输入的特征量价、技术指标与序列构造BP 对输入特征的态度是“给什么学什么”它不会自己判断特征有没有未来函数也不会分辨当前行情是牛市还是熊市。所以做这个方向特征工程的重要性不亚于模型调参。常见做法是把三类特征组合起来用基础量价开盘价、收盘价、最高价、最低价、成交量、成交额衍生特征涨跌幅、振幅、换手率技术指标MA5、MA10、MACD 柱、RSI、KDJ 的 J 值序列构造把最近 N 天的特征拼成一个向量比如用过去 5 天的数据预测明天序列构造这一步直接决定样本的组织方式。以 5 天窗口为例一条样本就是 5 天 × 特征维度的数值拼接成一维向量标签是第 6 天的收盘价。窗口太小模型看不到趋势窗口太大样本量会迅速缩水训练效率下降。我一般先用 5 到 10 天起调效果不明显再往上加。技术指标的计算顺序也要注意MA20 这类窗口指标需要前 20 天数据必须先把指标算完再去切样本否则样本开头一段全是空值。常见的翻车现场是 rolling 计算之后没有 dropna带着 NaN 进网络训练结果损失函数一路飘红。2.3 BP预测股价的可行性边界非线性拟合与过拟合的拉锯为什么说 BP 预测股价是“有边界的可行”因为股价序列是非平稳的统计特征随时间变化今天学到的规律明天可能就失效同时信噪比极低有效信号可能只占数据的很小一部分。BP 在训练时很容易把噪声当成规律学进去典型表现是训练集损失降得很低、测试集损失却很高这就是过拟合。所以做 BP 预测股价重点不是把训练集损失压到多小而是控制模型复杂度让它学到的是相对稳定的映射而不是对某一段行情的死记硬背。控制手段包括减少隐藏层神经元数量、加 Dropout、早停EarlyStopping以及最关键的——用时间顺序划分训练集和测试集而不是随机打乱。还有一个常见困惑有序列特性的数据为什么不用 LSTM 而用 BP我的看法是如果数据量不大、特征维度不高BP 训练更稳定、调参更快、部署也更轻量LSTM 在理论上更适合序列建模但参数更多、训练更慢在小数据集上未必打得过 BP。先把 BP 跑通拿到基线结果再决定要不要上 LSTM这是最务实的路线。3. 用Python跑通BP神经网络预测股票价格完整代码与参数说明3.1 准备数据取行情、算特征、构造样本先用 akshare 取日线行情然后构造基础特征。这一节的逻辑是拿到原始数据 → 算衍生指标 → 按时间顺序排列 → 构造“过去 N 天预测下一天”的样本。import akshare as ak import pandas as pd import numpy as np # 拉取日线行情这里以平安银行为例 df ak.stock_zh_a_hist(symbol000001, perioddaily, start_date20180101, end_date20241231, adjustqfq) df.columns [date, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover] df df[[date, open, close, high, low, volume]].copy() df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) # 衍生特征涨跌幅、MA5、MA10 df[ret] df[close].pct_change() df[ma5] df[close].rolling(5).mean() df[ma10] df[close].rolling(10).mean() # 特征列与目标列 feature_cols [open, close, high, low, volume, ret, ma5, ma10] target_col close # 删除开头因 rolling 产生的 NaN df df.dropna().reset_index(dropTrue)这段代码里有几个关键点。akshare 返回的列名会随版本略有变化拿到数据后先用df.columns核对一遍否则后面按列名取数据会直接报 KeyError。pct_change()算出的第一天涨跌幅是 NaN滚动均线的前几天也是 NaN最后统一用dropna()清掉避免把空值送进神经网络。3.2 序列样本切分与标准化fit在训练集上不要提前看测试集数据准备好之后下一步是把数据切成“特征 标签”的样本再做训练集/测试集划分和标准化。这里有一个新手最容易犯的错误先标准化整份数据再划分导致测试集信息提前泄漏到训练过程中。seq_len 5 # 用过去 5 天预测下一天 X, y [], [] for i in range(len(df) - seq_len): X.append(df[feature_cols].iloc[i:iseq_len].values.reshape(-1)) y.append(df[target_col].iloc[iseq_len]) X np.array(X, dtypenp.float32) y np.array(y, dtypenp.float32) # 按时间顺序划分前 80% 训练后 20% 测试 split int(len(X) * 0.8) X_train_raw, X_test_raw X[:split], X[split:] y_train_raw, y_test_raw y[:split], y[split:] # 标准化scaler 只在训练集上 fit再应用到测试集 from sklearn.preprocessing import StandardScaler scaler_X StandardScaler() X_train scaler_X.fit_transform(X_train_raw) X_test scaler_X.transform(X_test_raw) scaler_y StandardScaler() y_train scaler_y.fit_transform(y_train_raw.reshape(-1, 1)).ravel() y_test scaler_y.transform(y_test_raw.reshape(-1, 1)).ravel()标准化这步值得多说两句。StandardScaler会把数据变成均值为 0、方差为 1 的分布这对神经网络训练几乎必不可少不然量纲差异大的特征成交量 vs 收盘价会让梯度更新被大数值特征主导。但scaler只能fit在训练集上测试集只能用transform原因在于测试集模拟的是未来未知数据如果拿测试集的均值方差去归一化等于让模型提前知道了未来数据的分布范围回测结果会偏乐观。seq_len 5的意思是每条样本包含连续 5 天的数据标签是第 6 天的收盘价。这个窗口大小是超参数可以调成 10 或 20但要注意窗口越大样本越少训练数据不够时模型容易欠拟合。3.3 用Keras搭建BP神经网络层数、神经元数与Dropout取值这一节直接搭模型。用 Keras 的 Sequential 容器堆三个全连接层中间加 Dropout 做正则化配合 EarlyStopping 防止过拟合。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model Sequential([ Dense(64, activationrelu, input_shape(X_train.shape[1],)), Dropout(0.2), Dense(32, activationrelu), Dense(1, activationlinear) # 回归任务输出层不用激活 ]) model.compile(optimizeradam, lossmse, metrics[mae]) early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit( X_train, y_train, validation_split0.1, epochs100, batch_size32, callbacks[early_stop], verbose1 )模型结构先从 64 个神经元的隐藏层起步第二层收窄到 32 个形成一种“宽进窄出”的结构让网络先充分展开特征组合再压缩到单一输出。Dropout(0.2)表示训练时随机丢弃 20% 的神经元输出这是应对股价数据过拟合最直接的手段。输出层用linear激活函数因为回归任务要求输出连续值如果用 sigmoid 或 relu 会把预测值限制在某个区间内导致价格永远预测不准。EarlyStopping监控验证集损失连续 10 轮不下降就停止训练并自动恢复到验证集损失最低的那一轮权重。patience10这个值要结合数据量看数据量大时可以放宽到 20数据量小时 5 到 10 就够。batch_size32是内存和梯度稳定性之间的折中显存小就调小一点。3.4 训练与回测损失曲线怎么看方向准确率才算数训练完成后要做的不是看一眼 loss 就完事而是把预测结果反归一化回真实价格再算几个有交易含义的指标。import matplotlib.pyplot as plt # 预测并反归一化 y_pred_scaled model.predict(X_test) y_pred scaler_y.inverse_transform(y_pred_scaled).ravel() # 回归指标MSE 和 MAE mse np.mean((y_test_raw - y_pred) ** 2) mae np.mean(np.abs(y_test_raw - y_pred)) print(fMSE: {mse:.4f}, MAE: {mae:.4f}) # 方向准确率预测涨跌方向和真实涨跌方向一致的比例 direction_acc np.mean((np.diff(y_pred) 0) (np.diff(y_test_raw) 0)) print(fDirection Accuracy: {direction_acc:.4f}) # 画训练损失曲线 plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.show()MSE 反映整体误差大小但股价动辄几十上百元MSE 数值大并不一定代表模型差要和 MAE 一起看比如 MAE 是 1.5 元意思是平均每天预测偏差一块五。方向准确率是我更看重的指标它计算的是模型预测的涨跌方向和真实涨跌方向是否一致。股价预测的误差再小如果方向判断不对落在交易上依然是亏钱方向准确率如果稳定超过 52% 到 55%才有实际参考价值。损失曲线要重点观察两个东西训练损失和验证损失之间的 gap以及验证损失是否在下降后反弹。训练损失低、验证损失高说明过拟合两者都高说明模型容量不足或特征没有信息量。4. 用MATLAB做BP神经网络股价预测从newff到nntool的落地路径4.1 MATLAB里建BP的两种方式命令行newff和图形化nntoolMATLAB 做 BP 神经网络有两条经典路径。一条是命令行方式核心函数是newff新版推荐feedforwardnet配合train和sim完成训练与预测另一条是图形化工具nntool适合不想写代码、想快速验证数据能不能跑通的情况。对于股票预测这种需要反复调参的任务我更推荐命令行方式原因有三个可复现、可批量调参、不会因为手动点错导致结果对不上。nntool适合第一次接触的人去理解网络结构长什么样真要做回测对比还是代码更可靠。MATLAB 在神经网络训练上有个别的环境没有的优势内置的trainlmLevenberg-Marquardt优化算法在小数据集上收敛速度非常快不需要像 Python 那样手动调 Adam 的学习率。代价是内存占用高数据量大时容易内存溢出。如果样本量超过几万条建议换成trainscg。4.2 MATLAB的BP预测代码骨架newff建网、train训练、sim预测下面是一份 MATLAB 实现 BP 预测股价的代码骨架。数据从 Excel 读入假设每行是一天的行情列依次为开盘价、收盘价、最高价、最低价、成交量。% 读取行情数据 data xlsread(stock_data.xlsx); % 列open close high low volume % 计算衍生特征涨跌幅、MA5、MA10 ret [0; data(2:end, 2) ./ data(1:end-1, 2) - 1]; ma5 movmean(data(:, 2), 5); ma10 movmean(data(:, 2), 10); % 拼装特征矩阵并去掉指标窗口不完整的行 feat [data(:, 1:5), ret, ma5, ma10]; feat feat(11:end, :); % 前10行MA10算不完整 price data(11:end, 2); % 对齐后的收盘价 % 构造样本过去5天特征预测下一天收盘价 seq_len 5; X []; y []; for i 1:length(price) - seq_len X [X; reshape(feat(i:iseq_len-1, :), 1, [])]; y [y; price(i seq_len)]; % 标签是第i5天的收盘价 end % 归一化到 [0, 1]mapminmax 对列操作注意转置 [X_norm, X_ps] mapminmax(X, 0, 1); [y_norm, y_ps] mapminmax(y, 0, 1); X_norm X_norm; y_norm y_norm; % 按时间划分训练集/测试集 split round(size(X_norm, 1) * 0.8); train_x X_norm(1:split, :); train_y y_norm(1:split, :); test_x X_norm(split1:end, :); test_y_real y(split1:end, :); % 未归一化的真实值用于反归一化对比 % 建立BP网络输入层-隐藏层[32,16]-输出层 net newff(train_x, train_y, [32, 16], {tansig, tansig, purelin}, trainlm); net.trainParam.epochs 100; net.trainParam.lr 0.01; net.trainParam.goal 1e-5; % 训练 net train(net, train_x, train_y); % 预测并反归一化 pred_norm sim(net, test_x); pred mapminmax(reverse, pred_norm, y_ps);这份代码有四个需要特别说明的地方。第一特征矩阵的切分对齐由于 MA10 需要前 10 天数据前 10 行是 NaN所以统一从第 11 行开始取目的是保证所有特征在同一时间点都有效。第二mapminmax是按行操作的所以传入时要对矩阵做转置归一化后再转置回来这个细节漏掉会得到一堆莫名其妙的 NaN。第三newff的隐藏层参数[32, 16]表示隐藏层 1 有 32 个神经元、隐藏层 2 有 16 个激活函数分别对应tansig和tansig输出层用purelin与 Python 代码中的 relu linear 设计思路一致。第四trainlm在训练时会自动用验证集做早停但默认划分比例可能不是 80/20如果想严格控制需要手动切数据并设置net.divideFcn。4.3 Python与MATLAB怎么选数据生态、调参效率和部署成本对比同一个 BP 预测任务Python 和 MATLAB 都能做但适合的场景不同。我自己的使用习惯是快速验证想法、需要频繁改特征、要接入实时行情时用 Python做学术实验、需要和论文里的经典实现对齐、或者手头已经有 MATLAB 数据处理流水线时用 MATLAB。对比维度PythonMATLAB数据获取akshare / tushare 直接拉行情手动导出或用 Datafeed Toolbox特征工程pandas 处理灵活生态丰富矩阵操作强但日期处理不如 pandas模型搭建Keras / PyTorch文档多newff / feedforwardnet上手快调参效率需要自己写循环调参trainlm 自动收敛省心部署落地Flask / 定时任务 / 云函数都方便部署依赖 MATLAB Runtime较重适合人群量化研究者、需要落地的人学术验证、习惯 MATLAB 的工程师有一个场景我会明确推荐 MATLAB样本量只有几千条、特征维度不高trainlm的收敛速度和稳定性往往比 Python 里手调 Adam 更好几乎不用怎么调学习率就能拿到不错的损失。反之如果后续要接实时行情、要做滚动训练、要把模型嵌入交易系统Python 的工程优势就体现出来了。不要两个都学得很深把一条路径跑透够用就行。5. BP预测股票最容易翻车的5个坑每条都是实盘前的血泪教训5.1 未来函数回测曲线完美到不真实实盘却一塌糊涂现象回测时预测值和真实值几乎重合看起来模型神准一放到实盘或者换成滚动预测误差立刻变大完全不可用。原因特征或标签里混入了未来信息最常见的是直接用当天的收盘价预测当天的收盘价、用了未复权数据导致的跳空、或者把标准化时的全局统计量用在了样本构造之前。只要模型在训练阶段“见过”答案回测一定会好看。解决严格按时间顺序切分样本保证标签永远是样本窗口之后的数据对特征做标准化时只在训练集上fit测试集只transform构造样本时把df.iloc[i:iseq_len]对应的数据全部限定在iseq_len之前标签取iseq_len当天的值两者不能有重叠。5.2 归一化反归一化错位预测结果和前一天收盘价几乎一模一样现象预测曲线看起来就是真实曲线整体滞后了一天方向准确率勉强过半MSE 却不低进一步检查发现预测值约等于前一天的收盘价。原因股价序列本身具有很强的自相关性今天的价格大概率接近昨天。如果模型发现了这个“规律”它只需要复制昨天的收盘价就能把损失压得比较低。这在特征缺乏有效信息时非常常见模型不是在预测而是在做“价格惯性复制”。解决不要只看 MSE要看预测的涨跌方向是否真的来自特征信息。另一个有效的诊断方法是把训练数据的标签做差分改造成预测涨跌幅而不是预测绝对价格。这样模型必须学习真实的变化规律无法靠复制前一日价格蒙混过关。5.3 随机打乱训练集模型偷学了“未来”却毫无察觉现象训练损失下降很快测试集表现也好到反常但没人能解释模型到底学到了什么。原因做机器学习时习惯了train_test_split默认的随机打乱这个操作放在普通表格数据上没有大问题但放在股价序列上就是灾难。随机打乱后训练集里会出现“用 2023 年的数据预测 2021 年价格”的样本模型学会了跨时间的映射本质上是在作弊。解决股票数据只能用按时间顺序的切分方式例如前 80% 训练、后 20% 测试或者用TimeSeriesSplit做时间序列交叉验证。这一条是硬性的数据纪律没有任何商量余地。5.4 训练集和测试集分布不一致牛市的规律用在震荡市里直接失效现象训练集是某只股票 2019 到 2021 年的数据包含一段明显上涨行情测试集是 2022 到 2024 年处于震荡或下跌行情结果测试集损失远高于训练集方向准确率跌到 50% 以下。原因股价数据非平稳不同行情阶段的数据分布完全不同。BP 学到的是训练集所在行情阶段的映射关系跨阶段使用时自然水土不服。解决常规做法是缩小训练窗口只用最近 1 到 2 年的数据训练同时配合滚动训练策略每隔一段时间就重新训练一次。如果数据集跨度很大可以按行情阶段分段验证分别在上涨段、下跌段、震荡段上评估模型表现而不是只看整体指标。5.5 损失不降或震荡剧烈先查数据再查学习率别急着改网络现象训练了 50 个 epoch损失曲线像锯齿一样上下抖动或者完全不动有时干脆变成 NaN。原因最常见的三类原因——学习率设置过大导致梯度震荡特征数据没有标准化某些特征数值范围过大导致梯度爆炸数据里还有 NaN 值没清理干净loss 计算时直接算出 NaN。解决按照固定顺序排查第一步检查数据里是否有 NaN 和 inf第二步确认所有特征都经过了标准化或归一化第三步把学习率调到 0.001 甚至 0.0001 重新训练。大部分损失不降的问题都出在这三处不要一上来就盲目加深网络或者换优化器。6. 让BP预测真正可用的三个进阶动作滚动训练、多步预测与方向评估6.1 滚动窗口重训让模型跟上行情变化BP 网络不是一次训练完就一劳永逸的股价规律会漂移旧模型的预测能力会随时间衰减。常见做法是每 20 到 60 个交易日用最新数据重新训练一次窗口滑动权重更新。具体实现就是在训练循环里把时间窗口向后推进代码上只需要在样本切分时加上一个步长参数训练频率视数据更新速度而定。6.2 从单步预测到多步预测直接多步和迭代多步的取舍预测明天价格只是起点很多场景需要预测未来一周的走势。两条技术路线一是直接多步把输出层改成 5 个神经元同时预测未来 5 天的价格二是迭代多步每次预测一天把预测结果作为特征拼回去再预测下一天。直接多步训练更简单但误差在各步之间独立迭代多步更符合交易逻辑但存在误差累积问题。我的建议是先做迭代多步因为它能复用单步模型而且更容易观察预测衰减的速度。6.3 方向准确率比MSE更贴近交易的评价指标股价预测的终极评价不该只是误差大小还要看方向判断对不对。方向准确率计算的是预测涨跌方向与真实涨跌方向一致的比例超过 52% 才说明模型有微弱优势配合手续费和滑点真正能稳定盈利的门槛通常更高。这也是我评价任何预测模型的习惯回测报告先看方向准确率再看 MAE最后才看训练损失。这个思路来自一次教训有段时间我把训练损失压得非常漂亮回测 MSE 也很低结果放出去跑模拟盘连续亏损回头一查方向准确率只有 49%。从那以后我再也不信损失函数的绝对值只信方向准确率和样本外的稳定性。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →