尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于STGNN的沪深300成分股走势预测与量化策略实战

基于STGNN的沪深300成分股走势预测与量化策略实战 简介金融时间序列分析是量化投资的重要基础但传统预测模型常将股票视为独立个体难以捕捉市场中的联动效应。图神经网络GNN通过将股票构建为图结构结合时序特征与空间关联为股价预测提供了新思路。基于时空图神经网络STGNN以沪深300成分股为节点利用图卷积与时间卷积联合建模能够同时提取价格走势的动态规律与板块间的传导关系。该方法不仅提升了预测的区分度和稳定性还为量化交易中的候选池筛选、组合构建等场景提供了可靠的alpha信号。本文结合深度学习与PyTorch实现详细拆解了从数据工程、图构建、模型训练到策略回测的完整流程帮助读者掌握这一前沿技术在实际量化研究中的应用。 沪深300成分股走势预测这个方向我前后折腾了小半年。一开始我也走的是老路——把每只股票当成独立个体用LSTM、Transformer挨个跑时间序列结果复盘时发现一个尴尬的问题预测结果是完全割裂的。同一只股票价格异动板块内其他股票往往跟着联动产业链上下游的传导、机构资金的抱团进出一环扣一环这种市场结构信息被模型直接忽略了。直到我把图神经网络GNN引入框架把沪深300成分股建模成一张动态关联图同时抽取时序特征和空间关联特征模型的表现才真正起了质变。这个项目就是围绕这个思路落地的基于时空图神经网络STGNN对沪深300成分股做多维度走势预测输出个股未来N日的趋势概率或收益率排序再对接量化交易策略做候选池筛选。标题里的核心技术栈包括深度学习、金融时间序列分析、图神经网络、时空特征提取、PyTorch下面我把整个系统的设计思路、实现细节、训练陷阱和策略对接完整拆开来讲。适合正在做量化研究、股价预测、图神经网络应用的同学参考尤其是那些已经跑通单股LSTM、想进一步提升预测区分度的人。1. 项目定位与整体思路拆解1.1 为什么要用STGNN做沪深300走势预测沪深300指数由全市场市值大、流动性好的300只股票组成覆盖金融、消费、科技、工业、医药等主要行业。这些股票之间存在大量可见或潜在的关联同属一个行业板块、处于同一条产业链的上下游、被同一批机构重仓持有、对宏观流动性敏感度相似。传统时序模型把每只股票当作独立样本输入是它自己的历史价格和成交量输出是它自己的未来走势完全忽略了“股票之间相互影响”这个基本事实。STGNNSpatial-Temporal Graph Neural Network的出发点正好相反把300只股票看作图上的节点把它们的相关性看作节点之间的边把历史量价数据看作节点上的时序信号。模型的目标是同时捕获两个维度的模式时间维度上的趋势延续、均值回归、波动聚集空间维度上的板块联动、资金轮动、风险传染。用图卷积网络GCN做空间消息传递用Gate-CNN、GRU或Transformer做时间建模两者叠加成时空模块一层层堆叠下去。一句话概括单股时序模型回答“这只股票历史上怎么走”STGNN回答“这只股票在现在的市场环境、板块环境、个股环境下下一天/下一周最可能怎么走”。后者明显更贴近真实的交易决策逻辑。1.2 与传统时间序列方案的核心差异传统方案我在早期项目里测试过很多组合单股LSTM、CNNLSTM、AttentionLSTM还包括直接用XGBoost/LightGBM做手工特征的监督学习。这些方案有一个通病即使把全市场股票都灌进去训练模型也只是在“批量学习300条独立序列”特征交叉只发生在单条序列内部跨股票的互动完全靠线性的市场因子比如大盘涨跌幅间接表达。STGNN最本质的区别是把“股票网络”作为一个显式的结构先验放进了模型。市场拓扑结构不再依赖于人工构造的统一大盘因子而是通过邻接矩阵让信息沿真实的联动关系流动。举个例子某日政策利好新能源板块宁德时代和比亚迪的股价快速反应传统的单股LSTM只能等信号传导到目标股票的价格上才能做出反应而图模型第一时间把板块内龙头的异动通过图传播到关联节点上预测的领先性就出来了。此外STGNN在训练中还天然具备样本间交换信息的能力。图卷积层里每个节点更新后的特征包含了邻居节点的信息这使得模型不是孤立地预测300只股票而是全局联合优化。这在处理沪深300这类成分股高度关联的市场时信息利用效率远高于独立建模。注意STGNN不是万能的它解决的是“个股关联性导致的预测偏差”问题并不会自动创造预测能力。如果输入特征本身没有预测信息任何模型都白搭。后面我会详细讲特征工程怎么做才有效。2. 技术选型与系统架构设计2.1 模型选型凭什么选STGNN而不是纯Transformer做金融时间序列近两年很多人直接上Transformer。Transformer的优势是注意力机制可以捕捉长距离依赖但它有两个问题一是标准Transformer是序列模型本身没有图结构的概念要让股票之间交互只能把300只股票当作token做full attention那是300×300的大矩阵训练开销大而且注意力的含义是隐式的缺乏可解释性二是金融时序信噪比极低Transformer参数量大在几千根日K线的训练集上很容易过拟合。STGNN里的图卷积部分相对更“克制”。图卷积本质是沿着确定的邻接矩阵做信息的加权聚合参数只在特征变换上共享这相当于给模型加了一个强正则你只能沿着预设的股票关系传播信息不能自由地学习任何两个股票之间的交互。这在样本量有限的金融场景是个重要的归纳偏置。当然自适应邻接矩阵版本可以让模型在预设关系基础上微调边的权重达到结构先验和数据驱动之间的平衡。我用的是自适应邻接矩阵方案后面会详述。项目里我对比过三种方案基线LSTM、时间Transformer、STGNN基于GCNGRU。在相同的特征和训练集下STGNN的验证集AUC和RankIC都明显优于前两者尤其是RankIC提升比较稳定。原因是股票池本身高度关联图结构带来的空间信息与时间信息互补而Transformer把序列特征吃得很透却很难利用横截面上的结构关系。2.2 系统整体流程设计整个系统在工程上分成四个串行阶段我直接按流程画清楚数据采集与预处理拉取沪深300成分股的历史K线、日频基本面指标、行业板块分类清洗缺失值、剔除停牌异常数据做前后填充和截尾处理。特征工程与图构建在滚动窗口内计算每只股票的时序特征收益率、换手率、技术指标、波动率等和因子特征市值、估值分位等同时计算股票间相关矩阵构建图邻接矩阵。STGNN模型训练将过去一段时间窗口的特征张量N×T×C输入模型预测下一个时段的收益或方向标签。训练过程用时间序列验证集早停保留效果最好的checkpoint。策略对接与回测模型输出全股票池的前瞻收益率预测排序打分作为量化选股的alpha信号输入回测框架执行TopK选股、定期调仓、风控约束等逻辑。这个流程本质上是一个标准alpha信号挖掘管线STGNN本身只是其中的信号生成器。我见过很多做图模型的人只在1-3步打转模型在测试集上表现不错但迟迟无法落地到策略就是因为没有把4的工程链路打通。2.3 环境与工具链准备整个项目基于PyTorch实现完整依赖清单大致如下Python 3.10PyTorch 2.xCUDA版本视显卡驱动而定NumPy、Pandas、Scikit-learn数据处理与指标计算DGL或PyG图神经网络库本项目只用PyG因为算法结构不算复杂tqdm、TensorBoard训练进度与指标监控tushare pro / akshareA股数据源港股、美股同构可替换如果是第一次搭PyTorch环境建议先用Anaconda创建独立虚拟环境再按官网命令安装CUDA版PyTorch。很多人卡在GPU版本装不上核心是先核对显卡驱动支持的CUDA版本再选择对应的PyTorch版本。条件有限的话纯CPU也能跑通模型只是训练速度慢一些300只股票的日线数据量不算大实测一个Epoch在CPU上也能几十秒内完成。3. 数据工程多维度特征与图结构构建3.1 数据源与行情数据处理沪深300成分股的数据我主要用tushare pro接口拉取日线行情包含开高低收、成交量、成交额、换手率再配合每日成分股列表动态处理。这里有个非常关键的细节沪深300的成分股是定期调整的回测时必须用历史时点的成分股列表而不是用最新的列表去回溯历史否则会引入前视偏差。具体做法是在历史回测的每一天使用该日期实际有效的成分股权重和列表再取近60个交易日的数据构成特征窗口。对于停牌股票我的做法是保留它的最后有效行情并打上停牌标记而不是直接删掉这样模型可以学到停牌状态本身的信息。数据清洗上还要注意复权问题。A股有除权除息直接用原始价格会有跳空必须用前复权或后复权数据。我统一用前复权日线保证计算收益率时口径一致。3.2 时序特征与标签构造预测目标我选了两个一个是回归任务预测个股未来5个交易日的累计收益率另一个是分类任务预测未来5日收益率是否超过中位数。实际策略对接时以回归输出为主因为可以对全市场300只股票直接排序。特征构造分三组量价基础特征过去N日收益率、成交量变化率、换手率、振幅、最高最低价比例技术指标特征短期均线偏离度MA5/MA10、RSI、MACD的DIF与DEA差、布林带位置、ATR波动率横截面特征个股PE、PB、市值在沪深300内的分位数排名、行业动量行业指数过去20日收益特征矩阵的形状是[N, T, C]N300是股票数T20是回看窗口长度C是特征维度。窗口长度20日的含义是过去一个月内的技术形态与交易行为能基本反映短期走势的预测信号。窗口也不是越长越好太长会把久远且失效的信息混进来导致模型拟合历史噪音。标签计算时必须错开时间窗口特征截至T日收盘标签是T2到T6的5日累计收益为了考虑交易执行延迟T1日开盘买入所以从T1收盘或T2开始都行我习惯错开一天更贴合实际。这里最容易犯的错误是标签和特征重叠导致未来信息泄漏模型看起来准确率很高一上实盘就崩。3.3 股票关系图的三种构造方式图结构是STGNN里最需要决策的部分。节点就是300只股票边怎么定义直接决定了模型能学到什么样的股票关系。我尝试过三种方式实际效果差异很大第一种是行业板块图同属一个申万一级行业的股票之间连边。这是最直观的结构信息在同板块内传播模块轮动、板块内联动都能捕捉到。但问题是同一行业里的股票数量差别很大银行、食品饮料个股很多通信、传媒很少导致图的度数不均衡训练时容易偏向热门大行业。第二种是相关系数图计算过去60个交易日个股收益率的两两相关系数保留相关性高于阈值的边。这个做法能捕捉到跨行业的资金联动比如科技股和券商股在某些行情下高度同步。缺点是相关系数不稳定市场风格切换后图结构要频繁更新。第三种是自适应邻接矩阵不预先设定边而是让模型自己学一个300×300的权重矩阵作为图卷积的邻接矩阵使用。我最终采用的是“行业先验图自适应图”融合的方式即用行业图提供一个稀疏、可解释的基础结构再叠加一个通过节点Embedding学习的稠密矩阵让模型在行业约束之外自行发现隐含的联动关系。实操建议图结构权重不要每个交易日都变否则模型输入分布变化太剧烈训练不稳定。我按“季度更新行业约束、滚动20日更新相关边、每日平滑更新自适应节点Embedding”的节奏做兼顾时效性和稳定性。4. 模型实现与训练细节4.1 核心模型结构解析模型主干我参考了STGCN和GraphWaveNet的思路做了一定简化更适配A股日频数据量和样本量。整体框架如下输入层形状为(B, N, T, C)的张量B为batch sizeN为节点数300T为时间窗口20C为特征维度约30维空间编码层对每一时间步的特征矩阵做图卷积聚合邻居节点信息输出更新后的节点特征时间编码层使用一维因果卷积TCN或者GRU对每个节点的时间序列做时序特征提取。我最终用两层TCN替代GRU因为TCN可以并行训练速度更快且感受野通过膨胀系数控制对20个时间步的窗口完全够用跳跃连接与残差空间和时间模块交替堆叠每个模块外接残差连接避免深层网络训练退化输出层对最后一个时间步的节点特征做全连接映射输出每只股票的预测收益回归或涨跌概率分类图卷积的通俗理解其实就一句话把每个节点的新特征更新为“自身特征邻居特征的加权求和”权重来自邻接矩阵。如果你熟悉图像里的卷积图卷积只是把“固定网格”换成了“任意拓扑结构”由邻接矩阵决定哪些像素是邻居。4.2 PyTorch实现关键代码图卷积部分我直接手写没有依赖PyG因为邻接矩阵就是300×300的稠密矩阵用PyTorch的矩阵乘法就够了不需要特殊的图采样操作。核心代码如下import torch import torch.nn as nn import torch.nn.functional as F class GraphConvLayer(nn.Module): def __init__(self, in_dim, out_dim, num_nodes, embed_dim16): super().__init__() self.fc nn.Linear(in_dim, out_dim) # 自适应邻接矩阵的节点嵌入 self.nodevec1 nn.Parameter(torch.randn(num_nodes, embed_dim)) self.nodevec2 nn.Parameter(torch.randn(num_nodes, embed_dim)) def get_adaptive_adj(self): # 通过节点嵌入内积学习边权重加ReLU保证非负 adj F.relu(self.nodevec1 self.nodevec2.T) # 按行做softmax归一化让消息传递的权重和为1 return F.softmax(adj, dim-1) def forward(self, x, static_adj): x: 节点特征 (B, N, C) static_adj: 静态邻接矩阵 (N, N)作为先验信息 adaptive_adj self.get_adaptive_adj() adj static_adj * 0.5 adaptive_adj * 0.5 # 图卷积沿邻居加权聚合 x_agg torch.einsum(bnc,nm-bmc, x, adj) return self.fc(x_agg)时间卷积层用因果膨胀卷积实现class TemporalConvLayer(nn.Module): def __init__(self, in_channels, out_channels, kernel_size3, dilation1): super().__init__() self.padding (kernel_size - 1) * dilation self.conv nn.Conv1d( in_channels, out_channels, kernel_sizekernel_size, paddingself.padding, dilationdilation ) def forward(self, x): # x: (B, N, T, C) - 转换为 (B*N, C, T) 做一维卷积 B, N, T, C x.shape x x.reshape(B * N, C, T) x self.conv(x)[:, :, :-self.padding] # 因果裁剪 return x.reshape(B, N, -1, T).permute(0, 1, 3, 2)完整网络就是把空间卷积和时间卷积交替堆叠两层再接一个输出全连接层。训练脚本中最关键的是数据加载器要按时间顺序滑动窗口生成样本不能随机打乱否则训练集和验证集之间会出现特征重叠。4.3 训练策略与参数调优训练细节上我的经验是金融时序预测模型的成败一半在数据管线一半在训练纪律。数据划分上我用严格的时间序列切分前70%时间段的样本做训练接下来15%做验证最后15%做测试。随机K折在这里不适用因为股票数据有时间依赖随机划分会严重泄漏未来信息导致验证集结果虚高。每次模型迭代时都必须在验证集上监控RankIC和AUC。损失函数分任务回归任务用Huber Loss分位数任务用分位数损失Quantile Loss分类任务用BCE Loss。注意不能用纯MSE因为金融收益率尾部厚、极端值多MSE会被少数暴涨暴跌样本主导模型学不到稳定的中线规律。其他训练配置优化器AdamW初始学习率3e-4权重衰减1e-5配合CosineAnnealing学习率调度器Batch size32每个batch随机采样32个连续时间步包含全部300只股票的特征张量早停策略验证集RankIC连续10个epoch不上升就停止保留最佳checkpoint正则化Dropout 0.1 图卷积层输出后的LayerNorm防止训练震荡训练300只股票、20个时间步、30个特征一个batch的张量大小约32×300×20×30数据量不大单张RTX 3090就能在半小时内跑完60个epochs。真正的瓶颈反而在数据预处理尤其是特征对齐、滚动相关性计算这些部分。重要提醒股票预测模型在训练集上的AUC可以随便做到0.7以上但验证集AUC在0.55-0.6区间就是正常的。不要追求过高的验证集指标那通常意味着模型已经过拟合了市场噪音实盘会快速失效。要关注的是RankIC的稳定性和不同行情片段下的表现差异。5. 从预测到策略回测与策略对接5.1 回测框架与交易逻辑设计模型训练完成后输出的结果是每只股票的预测收益率。但光有预测值还不够必须把它落成可模拟交易的策略逻辑。我的策略设计如下调仓频率每周调仓一次每次持有TopKK30股票等权配置买入规则调仓日开盘买入新入选且当前未持有的股票卖出规则调仓日开盘卖出跌出候选池的持仓单票仓位初始等权约3.3%仓位风控约束个股单日跌幅超7%强制止损行业持仓不超过总仓位的30%停牌或涨跌停无法成交时顺延下一交易日处理交易成本双边手续费万3滑点单边0.1%印花税卖出千1回测框架我直接用backtrader做二次开发。回测时间窗口从2018年到2023年覆盖了下跌、震荡、上涨三种行情。回测结果我在这里不过多吹嘘因为换个时间段、换一组参数结果会差很多。但趋势上可以说明的是加入STGNN信号的策略在年化收益和夏普比率上比单纯使用RSI、MACD等技术指标选股的基线策略有稳定提升尤其是在市场风格快速切换的震荡行情中图模型的横截面排序能力会体现得更明显。5.2 绩效评价与风控要点模型输出的信号不能只用一个指标评价我同时跟踪几个口径RankIC预测值与未来5日收益的Spearman相关系数衡量模型排序能力要求稳定大于0.03以上分层回测把预测值分成5层观察多层收益是否单调递减。如果Top层收益不是最高说明模型排序能力不稳定需要检查标签或特征换手率模型每周调仓产生的持仓变动比例。换手率过高说明预测不稳定频繁买卖只会让交易成本吃掉收益最大回撤单周净值最大回撤超过8%时触发策略暂停检查信号是否失效风控上有个很实用的技巧不要只交易TopK的预测结果而要做“多空组合”或“行业内中性化”。沪深300的beta波动比较大如果只看绝对收益很多时候赚的是大盘beta而不是alpha。我的做法是在每个行业内预测排名靠前的股票多配靠后的股票不配甚至低配这样能把行业beta剥离掉留下的才是模型的alpha。5.3 模型上线与监控模型上线不只是加载一个checkpoint就完事。我建议把预测流程做成每日定时任务每天收盘后拉取最新行情数据→更新特征和邻接矩阵→跑模型推理→输出全股票池的预测排序→推送预警信号到交易系统。上线后要持续监控两个维度一是RankIC是否衰减每周滚动计算一次如果连续一个月RankIC的均值跌破0.02就要考虑重新训练二是特征分布漂移如果图卷积输入的特征均值和方差相对训练集发生明显变化说明市场风格变了需要扩充训练集或者调整模型结构。监控面板可以直接用TensorBoard或者MetaBase做核心是把RankIC、TopK组合收益、换手率、回撤这几个指标放在一个页面里每天打开扫一眼就能判断系统是否还处于健康状态。6. 实战踩坑记录与常见问题速查6.1 数据层面的坑这个项目踩到的坑有一半都在数据上。第一个坑是复权数据的误用。我刚开始用的是不复权价格导致除权日出现假跳空模型误以为出现大幅波动信号预测结果完全跑偏。换成前复权数据后这个问题立刻消失。第二个坑是成分股列表的实时性问题。用当前时刻的沪深300成分股去算历史特征相当于把未来才知道的信息用在了过去。回测结果虚高但实盘做不出来因为当时的成分股和现在的并不完全一致。必须维护一份历史成分股调整时间表。第三个坑是停牌股的处理。停牌期间成交量为0直接在收益率里表现为0会让模型学到“停牌之后大概率不动”的错误规律。我给每个样本增加一个“停牌天数”的标识特征同时把停牌超过10个交易日的股票在训练和预测时直接剔除。6.2 模型层面的坑模型层面的坑主要集中在过拟合和图结构的选择上。过拟合的典型表现是训练集AUC不断上升而验证集RankIC已经停滞或者下降。我花了很多时间调参最后发现早停策略和数据增强反而比换模型结构更有效。图结构的选择上最开始我用纯相关系数图每20日滚动更新一次结果模型在2018年熊市表现尚可到了2021年结构性行情中RankIC剧烈震荡。后来改成行业图自适应图融合稳定性明显提升。原因是相关系数在牛熊切换时变化很大而行业关系相对稳定自适应图又能捕捉到市场当时的潜在联动三者结合才平衡了稳定性和灵活性。还有一个很隐蔽的过拟合来源标签设计和特征窗口重叠。如果T日特征包含了过去5日的收益率而标签是未来5日收益率模型很容易学到短期动量或者反转效应但这种效应在不同年份极不稳定。我试过把动量因子单独剥离出来对比发现STGNN确实是学到了部分图结构信息但也会偷懒去拟合动量因子。解决办法是在特征里保留动量同时把标签窗口拉长到10日以上让模型的预测目标更偏向中期趋势而非短期噪音。6.3 工程层面的坑工程上的坑主要来自PyTorch版本和数据管线。PyTorch 2.x的编译模式和旧版有一些差异我遇到过一个诡异的bug训练时loss正常下降但同一个checkpoint在推理时输出结果和训练时不一致最后定位到是BatchNorm在训练和推理模式下的行为差异。解决办法是在推理前显式调用model.eval()并且在图卷积层里用LayerNorm代替BatchNorm彻底规避这个问题。数据管线上pandas的索引对齐是个容易出大错的地方。股票代码有时带后缀有时不带索引如果对不上特征矩阵就会错位模型等于在乱序数据上训练。我的做法是强制用MultiIndex日期股票代码做数据对齐每一步操作后都校验索引是否完整。GPU显存不足也是个常见问题。300×300的稠密邻接矩阵加上batch 32的张量整个图注意力计算并不占太多显存但如果把自注意力机制加进去就很容易爆显存。我的经验是先用torch.cuda.amp混合精度训练显存不够再减小batch size。下面我把常见问题整理成速查表供大家直接对照排查问题可能原因解决方案训练loss不下降学习率过大或特征未归一化调低学习率检查特征标准化验证集RankIC接近0标签与特征存在延迟或对齐错误检查特征截断日期和标签区间是否重叠回测收益高但实盘失效前视偏差或未考虑交易成本检查成分股列表和除权数据回测中加入滑点和冲击成本图卷积效果不如无图邻接矩阵噪声太大换成行业图先验或降低自适应边的学习率模型推理结果与训练不一致BatchNorm模式切换问题统一使用LayerNorm推理前调用model.eval()预测结果频繁换仓模型对短期波动过拟合拉长标签窗口或对预测序列做指数平滑后再排序最后再说一个我在实战中体会最深的事做金融预测模型最难的从来不是模型结构而是你对“什么样的信号是真实规律、什么样的信号是过拟合噪音”的判断。STGNN的图结构给了模型更丰富的表达空间但同时也给了模型更多过拟合的维度。我的习惯是每次迭代后都做一次历史片段回溯把2018年下跌段、2020年疫情段、2021年白马股行情段、2022年震荡段分别拉出来看RankIC如果模型只在某一段行情中表现好那就说明它学到的是特定市场环境的产物而不是普适规律。如果你准备复现这个项目我的建议是先把数据管线和回测框架跑通再进模型。哪怕用最简单的线性回归在同样的特征上跑一版基线也能帮你建立对数据质量的基本信任。STGNN是一把好用的工具但前提是你手里的数据没有泄漏、没有错位、没有前视否则模型越复杂错误传播得越远。本文还有配套的精品资源点击获取
返回列表