数据获取、特征工程、时间切分、神经网络集成与诚实评估
实验数据截止:2026年6月24日
本文使用 MATLAB R2025b、Tushare 日线行情和 fitrnet 神经网络,对深南电路 002916.SZ的下一交易日收益率进行预测。重点不是展示一条“神奇曲线”,而是完整说明数据处理、时间序列切分、特征工程、模型选择、集成预测、朴素基线和结果局限。
建议标签: MATLAB、Tushare、A股、神经网络、量化分析、时间序列、股票预测
一、项目背景
股票预测最容易出现两个误区:
- 直接拟合不断上涨的价格,使模型只学会长期趋势;
- 随机打乱训练集和测试集,让模型在训练时“看到未来”。
因此,本项目没有直接预测股价绝对值,而是预测下一交易日的对数收益率:
y(t) = log(Close(t+1) / Close(t))
预测出收益率后,再用当天收盘价还原下一交易日价格:
ForecastClose(t+1) = Close(t) × exp(y_hat(t))
这样既能减弱价格非平稳趋势的影响,也方便与“明天价格等于今天”的朴素基线公平比较。

图:模型总体流程
二、运行环境与项目文件
本文实验环境如下:
|
项目 |
配置 |
|
MATLAB |
R2025b |
|
数据源 |
Tushare 日线行情 |
|
股票 | 深南电路 002916.SZ |
|
历史范围 |
2017-12-13 至 2026-06-24 |
|
有效日线数量 |
2065 条 |
|
建模函数 |
`fitrnet` |
|
预测目标 |
下一交易日对数收益率 |
项目核心文件:
E:\A股\
├─ tushare_matlab_sdk\
├─ data\002916_daily_raw.csv
├─ fetch_shennan_history.m
├─ analyze_predict_shennan.m
├─ generate_blog_figures.m
└─ analysis_results\
三、通过 Tushare 获取历史行情
Tushare MATLAB SDK 原始版本较老,内部使用的 urlread2 已不适合当前 MATLAB 和 HTTPS 接口。因此我将网络层改成 MATLAB 自带的 webwrite,同时保留原有调用方式:
token = string(getenv("TUSHARE_TOKEN"));
api = pro_api(char(token));
data = api.query("daily", ...
"ts_code", "002916.SZ", ...
"start_date", "20171213", ...
"end_date", "20260624");
Token 不写入源代码,而是放入当前 MATLAB 环境变量:
setenv("TUSHARE_TOKEN", "你的Tushare Token")
这种方式可以避免将密钥提交到 Git 仓库或者截图中。
原始数据包含:
- 开盘价、最高价、最低价、收盘价;
- 前收盘价和涨跌幅;
- 成交量与成交额;
- 交易日期和证券代码。
四、为什么要重建连续价格
A股会发生送股、分红、拆分等除权除息事件。若直接对未复权收盘价求差,模型可能把机械除权误认为真实暴跌。
本文利用 Tushare 返回的日涨跌幅重建连续价格序列,并将最新价格作为锚点:
dailyReturn = log1p(T.pct_chg / 100);
logContinuousClose = zeros(height(T),1);
logContinuousClose(end) = log(T.close(end));
for k = height(T)-1:-1:1
logContinuousClose(k) = ...
logContinuousClose(k+1) - dailyReturn(k+1);
end
continuousClose = exp(logContinuousClose);
这个连续价格用于计算均线距离和趋势特征;最终预测价格仍然以最新真实收盘价为基准。
五、特征工程设计
模型共使用 15 项特征,所有特征都只使用预测时点及之前的数据。
1. 动量特征
- 1日、3日、5日、10日、20日累计对数收益率;
- 描述短期和中期价格惯性。
2. 均线偏离特征
- 当前连续价格相对 MA5、MA20、MA60 的偏离;
- 用于识别短线跌破均线或中期过热状态。
3. 波动特征
- 5日收益率波动率;
- 20日收益率波动率;
- 当日最高价与最低价形成的日内振幅。
4. 强弱与成交特征
- RSI(14);
- 成交量对数变化;
- 当前成交量相对20日平均成交量;
- 开盘价相对前收盘价的跳空幅度。
核心代码如下:
XAll = table( ...
dailyReturn, ret3, ret5, ret10, ret20, ...
continuousClose ./ ma5 - 1, ...
continuousClose ./ ma20 - 1, ...
continuousClose ./ ma60 - 1, ...
volatility5, volatility20, rsi14 / 100, ...
logVolumeChange, volumeRatio20, intradayRange, openGap);
六、时间序列切分:绝不能随机打乱
样本按时间先后划分:
|
数据区间 |
比例 |
用途 |
|
训练集 |
70% |
拟合网络参数 |
|
验证集 |
15% |
选择网络结构和正则化参数 |
|
测试集 |
15% |
最终样本外评价 |
nTrain = floor(0.70 * n);
nValidation = floor(0.15 * n);
iTrain = 1:nTrain;
iValidation = nTrain + (1:nValidation);
iTest = (nTrain+nValidation+1):n;
测试集时间约为 2025年3月25日至2026年6月24日,共302个交易日。测试集不参与网络结构选择。
七、网络结构选择
这是一个表格回归任务,因此采用 MATLAB 推荐的 fitrnet,而不是旧版的 fitnet 或 trainNetwork。
候选结构包括:
architectures = {8, 16, [16 8], [32 16]};
lambdas = [1e-3, 1e-4];
每个候选网络都只根据验证集 RMSE 排名:
model = fitrnet( ...
X(iTrain,:), Y(iTrain), ...
Standardize=true, ...
LayerSizes=architectures{a}, ...
Activations="relu", ...
Lambda=lambdas(l), ...
ValidationData={X(iValidation,:),Y(iValidation)}, ...
ValidationPatience=20, ...
IterationLimit=500);
最终选中的结构为:
输入层:15项特征
隐藏层:32 → 16
激活函数:ReLU
L2正则化:0.0001
输出层:下一交易日对数收益率
八、为什么采用15个模型集成
神经网络的训练结果会受到随机初始化影响。对于股票这种噪声很高的数据,单次训练可能给出偶然的极端预测。
因此,最终阶段使用15个不同随机种子的同结构网络,并取预测中位数:
ensembleSize = 15;
ensembleReturns = zeros(ensembleSize,1);
for member = 1:ensembleSize
rng(2000 + member);
finalModels{member} = fitrnet( ...
X, Y, ...
Standardize=true, ...
LayerSizes=[32 16], ...
Activations="relu", ...
Lambda=1e-4);
ensembleReturns(member) = ...
predict(finalModels{member}, latestFeatures);
end
nextReturn = median(ensembleReturns);
本次15个模型的预测收益率范围为约 -3.84% 至 +0.13%,标准差为1.23个百分点,说明模型对随机初始化仍然比较敏感。
九、必须设置朴素基线
股价预测图往往看起来很贴合,因为:
明天的价格通常与今天的价格非常接近
因此真正需要击败的基线是:
naivePrediction = 0; % 下一日收益率为0
naivePrice = todayClose; % 下一日价格等于今日价格
测试集结果
|
指标 |
神经网络 |
朴素基线 |
|
收益率 RMSE |
3.953% |
3.953% |
|
价格 MAE |
6.50元 |
6.46元 |
|
方向准确率 |
48.34% |
随机方向约50% |

图:样本外评估
结果非常重要:虽然预测价格曲线在视觉上紧跟真实价格,但模型没有稳定战胜朴素基线,方向准确率也低于50%。
这意味着该模型适合用作 MATLAB 时间序列和机器学习实验,不应直接作为交易系统。
十、2026年6月25日预测结果
截至2026年6月24日:
- 最新收盘价:431.90元;
- 当日涨跌幅:+1.38%;
- 5日累计收益率:+6.94%;
- 20日累计收益率:+3.58%;
- 60日累计收益率:+82.58%;
- RSI(14):62.48;
- 相对MA20:+7.31%;
- 相对MA60:+30.80%;
- 20日年化波动率:75.43%。
集成模型给出的下一交易日预测为:
预测日期:2026-06-25
预测收盘价:423.80元
预测收益率:-1.87%
经验80%区间:407.62元至448.92元

图:次日价格预测
需要强调:经验区间由测试集残差的10%和90%分位数构造,并不是概率保证区间。
十一、为什么模型预测回调
为了理解预测结果,我进行了一次局部归因实验:
- 保持其他特征不变;
- 将某一项最新特征替换为历史中位数;
- 比较替换前后的预测变化。

图:局部归因
主要下行因素如下:
|
因素 |
模型解释 |
|
5日波动率偏高 |
当前短期波动率比历史均值高约2.2个标准差 |
|
跌破5日均线 |
最新价格仍低于MA5约2.43% |
|
高于60日均线 |
最新价格高于MA60约30.80%,存在均值回归压力 |
|
日内振幅较大 |
6月24日日内振幅约5.56%,多空分歧较强 |
|
10日涨幅较高 |
短期上涨后,模型倾向给出回撤修正 |
其中,短期波动率是最大影响因素。若仅将5日波动率恢复为历史中位数,模型预测会由约 -1.9% 改善到接近持平。
但这属于模型关联解释,不代表现实中的因果关系。
十二、MATLAB预测图代码
以下代码绘制最近90个交易日价格、次日预测点和经验区间:
recent = T(end-89:end,:);
futureDate = datetime(2026,6,25);
figure(Color="w");
plot(recent.Date, recent.close, ...
Color=[0.08 0.26 0.55], LineWidth=2);
hold on;
errorbar(futureDate, forecastClose, ...
forecastClose-forecastInterval(1), ...
forecastInterval(2)-forecastClose, ...
"o", Color=[0.82 0.20 0.18], ...
MarkerFaceColor=[0.82 0.20 0.18], ...
MarkerSize=8, LineWidth=2);
yline(T.close(end), "--", "最新收盘价");
grid on;
xlabel("交易日期");
ylabel("价格(元)");
title("深南电路下一交易日收盘价预测");
十三、模型目前有哪些不足
1. 单只股票样本较少
2065条日线对于神经网络来说并不算多,模型容易学习到阶段性规律。
2. 市场状态会变化
训练期内有效的关系,可能在新的政策、产业周期和市场风格中失效。
3. 缺少外部变量
当前模型主要使用个股价格和成交量,没有加入:
- 沪深300、电子行业指数和PCB板块走势;
- 公司财务指标和估值;
- 公告、新闻和机构一致预期;
- 市场资金、北向资金和融资融券数据。
4. 评价优势不显著
模型在测试集上没有击败朴素基线。若把交易成本、滑点和涨跌停限制加入回测,实际表现可能更差。
5. 区间仍然很宽
407.62元至448.92元的经验区间说明单日噪声很大。相比点预测,风险区间更值得关注。
十四、可继续改进的方向
- 使用滚动窗口或扩展窗口进行 Walk-Forward 验证;
- 加入沪深300和行业指数的相对强弱特征;
- 将预测目标改为“未来5日超额收益”;
- 将回归问题改为上涨、震荡、下跌三分类;
- 使用梯度提升树、线性模型和神经网络进行模型融合;
- 加入交易成本后进行策略级回测;
- 使用 SHAP 或置换重要性进行更系统的解释。
十五、总结
本文完成了一套较完整的 MATLAB A股预测实验:
- 使用 Tushare 获取深南电路历史行情;
- 根据涨跌幅重建连续价格;
- 构造动量、均线、波动率、RSI和成交量等15项特征;
- 按时间顺序划分训练、验证和测试数据;
- 使用 fitrnet 训练和选择神经网络;
- 使用15模型中位数降低随机初始化影响;
- 与“明天等于今天”的朴素基线比较;
- 输出点预测、经验区间和局部归因。
本次模型预测2026年6月25日收盘价约为423.80元,但测试集方向准确率只有48.34%,且未稳定战胜朴素基线。因此,最有价值的结论不是“明天一定下跌”,而是:
当前股价处于高涨幅、高波动和较大均线偏离状态,
模型认为短期回撤风险上升,但预测信号本身并不可靠。
免责声明:本文仅用于 MATLAB、时间序列分析和机器学习技术研究,不构成任何投资建议。股票市场存在较高风险,请独立判断并自行承担风险。

2232

被折叠的 条评论
为什么被折叠?



