拓冰建站拓冰建站
首页 / 资讯中心 / 正文

PyTorch高频波动率预测:特征工程与LSTM+Attention实战

简介本资源是一份面向量化研究初学者与金融科技从业者的专题文档聚焦PyTorch在股票价格波动率预测中的特征工程设计覆盖高频交易背景、模型选型、统计与技术指标提取、特征选择与转换、训练优化及回测实践等完整知识链条。全包仅1个PDF文件大小2.15MB共45页自带目录与章节大纲便于按需跳转阅读。文档从张量操作、自动求导等PyTorch基础讲起逐步深入到移动平均线、RSI、布林带等特征构建并给出MSE、RMSE、MAE等评估指标及模型比较方法兼顾理论与代码思路。内容组织上从特征工程基本流程到案例复盘逐层递进适合希望系统搭建量化预测模型、梳理特征工程方法的读者参考学习。目前已有222人浏览学习兼具入门导览与实操参考价值。1. 高频交易里最稳的预测目标不是涨跌而是波动率一分钟K线的下一次收盘价涨跌在高频数据里几乎逼近随机游走预测准确率做到52%都算吃力但同一份数据的波动率却呈现出极强的自相关和聚集性——大波动后往往跟大波动这是ARCH效应的经典表现。这句话把四个东西串在一起PyTorch是建模工具量化交易是落点股票价格波动率预测是中间目标特征工程设计则是决定模型上限的核心环节。所以这篇内容讲的是「不预测价格而是预测波动率」这条高频策略路径把原始行情表变成结构化特征再交给PyTorch模型最后用预测出的波动率反推仓位和止损位。适合已经会跑LSTM但觉得特征总差一口气的人也适合传统因子研究员想往深度学习迁移时直接抄一套可落地的框架。2. 波动率特征工程的分层从分钟K线到Tick微观结构2.1 预测目标先定标签已实现波动率怎么算才不歪波动率是隐变量模型训练前得先用一个统计量把它刻画出来。常见做法有三种已实现波动率Realized Volatility、Parkinson高低价波动率、以及GARCH类模型估计。高频场景下我一般首选已实现波动率因为它只依赖收盘价的平方收益计算简单且没有参数估计偏差日内重采样后天然包含高频信息。import pandas as pd import numpy as np # 假设df是1分钟bar包含close列按时间升序 df[log_ret] np.log(df[close]).diff() # 10个1分钟bar滚动平方和再开方得到10分钟已实现波动率 df[rv_10] np.sqrt((df[log_ret] ** 2).rolling(10).sum()) # 换成5分钟已实现波动率窗口调成5 df[rv_5] np.sqrt((df[log_ret] ** 2).rolling(5).sum())这段代码里最关键的是rolling(10).sum()它把过去10个bar的平方收益加总对应10分钟窗口的已实现方差。注意这里刻意没有做年化处理因为在高频波动率预测里模型学习的是相对水平和时序模式年化只是一个常数缩放不影响特征和标签的排序关系反而会把数值推到极其接近0的小数区间让PyTorch里的BatchNorm和损失函数数值都变得难调。下表是三类波动率标签的对比选型时可以直接按数据粒度决定标签类型计算公式适用粒度优点坑已实现波动率sqrt(sum(r^2))1分钟及以上简单、无参数、高频信息足对零成交区间敏感Parkinsonsqrt((1/(4ln2))*mean(ln(H/L)^2))任意OHLC利用高低价信息量更大高低价含噪声tick级失真GARCH(1,1)极大似然估计条件方差日线或低频有理论基础、可预测性解释强高频拟合慢收敛不稳高频策略里我默认用已实现波动率做标签Parkinson可以当作第二个预测目标做多任务学习的辅助输出而不是替代主目标。2.2 高频特征分四层价格、时间、微观结构与盘口特征工程不能一把梭把所有列都塞进模型。高频波动率预测的特征可以按来源分四层每一层解决一类信息缺失问题。第一层是价格衍生特征包括收益率、动量、相对强弱、距离均线的偏离度第二层是时间特征比如星期几、当日第几分钟、距离开盘和收盘的秒数第三层是微观结构特征包括买卖价差、每笔成交均额、成交笔数、主动买卖占比第四层是盘口特征包括委买委卖挂单量差、盘口深度、大单净流入。实际从tick数据合成分钟bar时微观结构特征往往比价格特征更有信息量。量化交易平台的数据接口里tick数据通常包含bid_price,ask_price,bid_volume,ask_volume,last_price,volume这些字段合成特征时可以这样处理# 从tick聚合成1分钟bar并生成微观结构特征 tick[mid] (tick[bid_price] tick[ask_price]) / 2 tick[spread] tick[ask_price] - tick[bid_price] tick[spread_pct] tick[spread] / tick[mid] bar tick.set_index(time).resample(1min).agg({ mid: last, spread_pct: mean, bid_volume: sum, ask_volume: sum, volume: sum, last_price: last }) bar[volume_imbalance] (bar[bid_volume] - bar[ask_volume]) / (bar[bid_volume] bar[ask_volume] 1e-8)spread_pct是相对买卖价差波动率高的时候做市商普遍扩大价差它本身就是波动率的领先信号volume_imbalance衡量买卖力量的失衡程度大值往往伴随后续剧烈波动。这两个特征计算成本极低但对PyTorch模型的边际提升通常非常明显。值得注意合成bar时用resample(1min)默认右闭合标签列也必须用同一套对齐逻辑否则特征和标签错位一个周期等于把整个模型变成了对未来信息的偷看。2.3 特征计算里三个隐蔽的高频陷阱高频数据上的特征工程第一个坑是前视偏差。比如用rolling(10).mean()算均线时pandas默认窗口包含当前bar而标签是未来10分钟的波动率模型看到的是「当前bar的均值」预测未来这没问题但如果你在t时刻用shift(-1)把下一根bar的数据挪进来泄漏就发生了。PyTorch模型训练时loss会异常低实盘却一塌糊涂。第二个坑是停牌和零成交区间。A股涨跌停或临时停牌时价格不变已实现波动率变成0这个0不是真实波动率而是没有交易产生的伪信号。处理方式是在特征表里加一列「成交笔数」并把零收益的bar标记出来让模型自己去学「零波动零成交」和「零波动正常成交」的区别。第三个坑是归一化用了全样本统计量。StandardScaler用全部训练数据拟合等于让模型在训练时“看到”了验证集的均值和方差。正确做法是用滚动窗口拟合归一化参数或者至少用TimeSeriesSplit折内fit。后面第四章会专门讲这个问题因为它是特征工程做完后最容易被忽视、又最能毁掉模型的一步。3. 用PyTorch搭建波动率预测模型LSTM加注意力是起步配置3.1 特征张量的构造滑动窗口与实例归一化特征工程产出的是逐行特征表交给PyTorch前要切成[batch, seq_len, feature_dim]的三维张量。seq_len代表用过去多少个时间步做上下文我一般取30分钟或60分钟。这个超参数和交易品种的波动率衰减速度有关沪深300指数期货的波动率半衰期约20分钟取60略保守但稳妥。import torch from torch.utils.data import Dataset class VolatilityDataset(Dataset): def __init__(self, features, labels, seq_len30): self.features torch.tensor(features, dtypetorch.float32) self.labels torch.tensor(labels, dtypetorch.float32) self.seq_len seq_len def __len__(self): return len(self.features) - self.seq_len def __getitem__(self, idx): x self.features[idx: idx self.seq_len] y self.labels[idx self.seq_len] return x, y这里__getitem__返回idx到idx seq_len的特征窗口标签取窗口结束后的下一条。注意窗口内是历史标签是未来边界划分必须用这种「左闭右开」的切法。InstanceNorm是一个在高频场景里很好用的小技巧对每个样本窗口单独做减均值除标准差能消除不同时间段波动水平整体漂移的影响等价于让模型专注学习波动形态而不是绝对数值。3.2 LSTM加Attention的PyTorch实现高频波动率预测模型不需要多复杂LSTM加时间维度的注意力在绝大多数品种上都能稳定超过纯LSTM和纯Transformer的基线。注意力的作用是让模型自己决定过去60分钟里哪几个关键时刻对预测未来波动率最有用而不是机械地把最后一个时间步当作总结。import torch.nn as nn class VolatilityLSTM(nn.Module): def __init__(self, input_size, hidden_size, num_layers2, dropout0.3): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout) self.attn nn.Linear(hidden_size, 1) self.head nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): out, _ self.lstm(x) # out: (batch, seq_len, hidden_size) attn_w torch.softmax(self.attn(out).squeeze(-1), dim1) # attn_w: (batch, seq_len) context torch.bmm(attn_w.unsqueeze(1), out).squeeze(1) # context: (batch, hidden_size) return self.head(context).squeeze(-1)attn_w是用一个线性层把每个时间步的隐向量压成标量再在时间维度做softmax得到的是归一化的注意力权重。torch.bmm是批矩阵乘法attn_w.unsqueeze(1)变成[batch, 1, seq_len]与out相乘得到[batch, 1, hidden_size]的加权和也就是用注意力权重把所有时间步的隐状态做加权平均。hidden_size我习惯设64到128之间超过128对分钟级数据提升微乎其微反而增加过拟合风险。num_layers2够捕获两层时间依赖再深就不好训了。3.3 训练循环与损失函数选型训练高频波动率模型损失函数建议用分位数损失而不是纯MSE。波动率预测在交易里的真实用途是仓位管理低估波动率会带来超预期的亏损高估则减少收益机会。分位数损失能分别惩罚高估和低估让模型偏向保守。def quantile_loss(pred, target, tau0.7): err target - pred loss torch.where(err 0, tau * err, (tau - 1) * err) return loss.mean() model VolatilityLSTM(input_sizeX.shape[2], hidden_size64) optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) for epoch in range(50): model.train() for x, y in train_loader: optimizer.zero_grad() pred model(x) loss quantile_loss(pred, y, tau0.7) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step() scheduler.step()tau0.7意味着高估误差的惩罚更轻、低估误差惩罚更重模型会倾向于给出稍微偏高的波动率预测这对做风险管理是安全的。weight_decay1e-5对LSTM的权重施加轻微L2正则能有效抑制高频噪声过拟合。clip_grad_norm_设5.0是为了防止个别极端bar产生的大梯度把整个序列学习打乱。如果发现训练loss震荡很剧烈优先降低学习率到3e-4而不是增大batch size去硬扛。关于PyTorch环境搭建直接用Anaconda建一个Python 3.10的虚拟环境CPU版本跑分钟级数据完全够用只有数据量到Tick级且需要大量调参时才值得配GPU版本。4. 高频量化模型的过拟合与特征穿越排查4.1 特征穿越的四种隐蔽形态回测曲线漂亮到不真实时我首先怀疑的不是模型强而是特征穿越。第一种形态是归一化泄漏整个数据集一起fitStandardScaler验证集的均值方差被偷看。第二种形态是标签窗口重叠预测未来10分钟波动率时相邻样本的标签窗口重叠了9/10训练集和验证集之间信息高度重叠验证误差严重虚低。第三种形态是特征使用了未来聚合值比如把当日全天的成交量当成特征回测时数据完整所以没问题实盘时下午两点根本不知道全天的值。第四种形态是价格特征的shift方向搞反把当前bar的收盘价当成下一bar的已知信息。试想下面这段代码它就是最经典的归一化穿越from sklearn.preprocessing import StandardScaler # 错误做法全数据集上拟合 scaler StandardScaler() X_scaled scaler.fit_transform(X)这个fit用的是包括验证集在内的全部数据。回测时模型的输入分布与真实分布完全一致实盘线上数据却会因为行情水平漂移而偏离训练分布表现断崖式下跌。正确做法是把归一化参数放进训练集拟合并在验证集上只做transform。4.2 用手写的walk-forward验证替代KFold时序数据不能用KFold随机打乱这一点大家都知道但很多人不知道标签窗口重叠的问题依然存在于按时间切分的验证里。假设预测未来10分钟波动率验证集从第1000分钟开始训练集到第990分钟结束两者之间其实只隔了10分钟训练集最后一个样本的标签窗口已经伸进了验证集区域相当于模型回测时提前见过了一部分“未来”。解决方法是给训练集和验证集之间加一段缓冲带也就是embargo机制。下面这个walk-forward切分函数把缓冲显式写出来def walk_forward_split(n, train_len3000, val_len500, embargo30): folds [] start 0 while start train_len val_len embargo n: train_idx list(range(start, start train_len)) val_idx list(range(start train_len embargo, start train_len embargo val_len)) folds.append((train_idx, val_idx)) start val_len return foldsembargo30代表训练集末端与验证集起点之间空出30个bar用来消化标签窗口的重叠影响。这个值必须大于等于预测窗口长度才有意义。高频数据里我通常把embargo设为预测窗口的1.5倍比如做10分钟波动率预测时设15。验证集的评价指标要用每折独立计算后取中位数而不是把多折的预测结果拼在一起算否则折与折之间的重叠会再次引入数据泄漏。4.3 按列置换做特征重要性验证PyTorch模型不像树模型有现成的feature_importances_但可以用置换法验证每个特征是否真正起作用原理是把验证集上某一列特征的时间顺序打乱如果模型预测误差显著变差说明模型依赖了这个特征如果误差几乎不变说明这列特征在模型里是废的甚至是噪声来源。model.eval() baseline 0.0 with torch.no_grad(): for x, y in val_loader: baseline quantile_loss(model(x), y).item() * len(x) baseline / len(val_dataset) scale torch.std(features, dim0) importance {} for col in range(features.shape[1]): perm features.clone() perm[:, col] perm[torch.randperm(perm.shape[0]), col] x_perm build_windows(perm) loss_perm 0.0 with torch.no_grad(): for i in range(0, len(x_perm), 128): loss_perm quantile_loss(model(x_perm[i:i128]), labels[i:i128]).item() importance[col] (loss_perm / len(x_perm)) - baseline这段代码对特征矩阵的第col列做整列行置换破坏特征与标签的对应关系同时保持其他特征不变。置换发生在样本维度而不是时间维度这是一个容易搞错的细节如果沿时间维度整体平移会连带破坏其他特征的时间结构导致重要性估计失真。判断标准是相对值重要性得分超过baseline的10%以上才算有效特征低于5%的特征可以考虑直接删掉减少模型输入维度能同时降低过拟合和推理延迟。5. 波动率预测结果如何落成高频交易信号5.1 把预测波动率映射到目标仓位与止损宽度模型输出的预测波动率本身不是交易信号它要通过风险预算的换算变成仓位。固定总风险预算的框架里目标仓位与预测波动率成反比预测波动率越高仓位越低这是波动率目标策略的核心思想。target_vol 0.10 # 年化目标波动率按个人风险偏好设 annual_factor np.sqrt(252 * 240) # 1分钟线一年约240个交易日 pred_vol model.predict(last_window) # 模型输出分钟级 position target_vol * annual_factor / (pred_vol * np.sqrt(10)) stop_loss_pct 3 * pred_vol这里position是名义仓位倍率stop_loss_pct用3倍预测波动率作为止损宽度相当于某种动态ATR止损。预测波动率忽然放大时仓位自动缩小止损带宽自动放大避免了固定止损在高波动行情里被噪声反复扫掉的问题。本质上波动率预测模型是在做风险调节器而不是方向预测器它的稳定收益来源于「避开大波动、吃透小波动」的复利结构。5.2 从预测到执行的延迟预算高频落地时模型推理延迟直接影响交易质量。LSTM加注意力在CPU上的单条推理一般是1到3毫秒叠加特征计算和行情推送只要控制在一个K线周期内就不会影响策略有效性。用分钟级bar做决策时完全不需要GPU如果真的升级到了tick级逐笔模型才需要考虑TensorRT或ONNX导出把推理压缩到微秒级。验证模型是否退化的方式有两种一是每根bar计算预测波动率和当时已实现波动率的Spearman秩相关这个指标相对MSE对极端值更鲁棒能更快暴露分布漂移二是监控注意力权重的分布如果权重从集中在某个时段变成均匀分布说明模型已经失去了对关键波动时刻的识别能力这时候重新训练通常比调参有效。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门