多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

随机过程+Transformer:波动率曲面预测的残差学习方案

随机过程+Transformer:波动率曲面预测的残差学习方案 简介一份共二十七页的PDF技术资料聚焦于衍生品定价模型中的随机过程Transformer在期权波动率曲面预测的应用。文档支持目录章节跳转阅读器左侧大纲也可快速定位适合系统阅读或按需查阅。资源包仅包含一个PDF电子文档压缩后大小约二兆已有七十一人学习浏览。内容从布莱克-斯科尔斯模型与波动率微笑等期权定价基础切入系统介绍随机过程Transformer的建模动机、网络架构、长距离依赖捕捉与随机市场适应性并完整覆盖数据清洗、特征选择与组合、模型训练、超参数调整、正则化、早停策略以及均方误差、平均绝对误差、均方根误差等评估指标。实验部分包含对比模型、定量与定性结果分析并延伸到金融机构风险管理、投资组合优化和交易策略制定等实际案例最后讨论挑战与未来方向。适合量化金融、金融工程及机器学习领域的研究者与从业者参考既可了解前沿技术落地路径也可作为实验复现与模型改进的借鉴。1. 曲面每天都在变随机过程负责“骨”Transformer负责“肉”期权波动率曲面的预测是衍生品定价模型里最让人头疼的一个环节。曲面不是一组静态报价它每天在动而且动得不只是水平短端可能因为日内事件陡起来长端可能纹丝不动同一个行权价附近两边尾部的形状也各自独立变形。经典随机过程模型SABR、Heston、rough Bergomi能把这种演化的大骨架描述出来但对“今天到底偏离骨架多少”这种问题线性方法基本没有好办法。把Transformers加进这个环节正是用它来学曲面在随机过程先验之外的那段残差这也是“随机过程Transformer”这类方案在期权定价场景里最实际的落点。这篇内容写给三类人量化研究员想看它能否替代手工调SVI插值期权系统开发想知道参数怎么设计做波动率交易的想搞清楚预测结果能不能直接拿去做风险对冲。2. 把曲面先压成能学的形状随机过程视角下的波动率曲面表示2.1 隐含波动率曲面不是一张照片而是带漂移的动力学过程第一步要扭转一个思维习惯波动率曲面不是一张静态的三维照片而是函数空间里的一个动态对象。横轴是行权价K纵轴是到期日T第三维是当前时间t。我们每天看到的其实是整个“曲面过程”在当天的切片预测明天本质上是在估计这个切片如何沿时间轴演化。很多团队一开始都会犯同一个错把曲面网格拆成一个个独立的到期日行权价点位对每个点位单独训练一个时间序列模型。ARIMA、LSTM这么干的都有结果是惨淡的。原因很简单曲面在行权价方向上有强结构约束——同一个到期日下各个行权价的隐波不会自由漂移否则立刻出现日历套利或蝶式套利窗口。一个模型如果不懂“曲面是曲面”它学到的全是局部噪声而不是市场微结构里的规律。这个前提决定了后面所有设计曲面预测必须先做表示压缩再谈模型。为什么要用随机过程来做这个压缩因为随机波动率模型本身就是对“曲面如何演化”的生成式描述。SABR、Heston这些模型的参数初始方差、均值回复速度、波动率随机性直接对应着曲面形态的宏观特征。拿它们做先验相当于告诉Transformer曲面不会乱来它大体沿着某个随机过程在走你只需要去看过程之外的偏差。这一步看着简单实际决定了整个方案的稳定边界。2.2 SVI参数化与降维5个参数比一个30×30网格好管在曲面表示上我一般会推荐先用SVI参数化做压缩。SVI针对某个到期日T把无风险利率下的对数行权价 k log(K/F) 映射为隐含方差w(k) a b·(ρ·(k − m) sqrt((k − m)² σ²))这里有五个参数a控制整体水平b控制两翼斜率总幅度ρ控制左右翼的不对称m控制曲线最低点的位置σ控制过渡的缓和程度。同一个交易日里把各个到期日分别拟合成一组SVI参数就得到一个随时间变化的参数序列[a₁,b₁,ρ₁,m₁,σ₁, a₂,b₂,ρ₂,m₂,σ₂, …]每个到期日占5列。为什么先走参数这条路而不是直接把30×30的隐波网格喂给Transformer因为原始网格的噪声太大了。期权链上的报价受买卖价差、做市商持仓、市场深度不足等因素影响很多网格点本来就是插值插出来的“伪数据”。拿它们训练模型会把插值壳子也当成规律学进去。SVI参数是压缩过的曲面骨架五个参数之间在不同到期日上有缓变结构预测它们的演化远比预测一个高维网格稳定。我之前做最小可行版本时就是从SVI压缩开始的效果立竿见影直接上网格头的版本在样本量不足时几乎都会翻车。2.3 随机波动率模型负责“物理”Transformer负责“修正”随机过程在这个方案里有三种用法它们的角色完全不同第一种直接用SABR或Heston参数做动态外推。这是一种纯“物理”做法假设参数服从某个扩散过程或均值回归过程然后推明天。它的优点是稳定、可解释缺点是模型形式太简单抓不住regime切换。第二种用随机过程做数据生成器。真实市场曲面样本就那么几千天不够Transformer吃。常见做法是拿rough Bergomi这类过程拟合历史参数然后蒙特卡洛生成大量合成曲面路径扩充训练集。这个做法有效但要小心合成数据的分布和真实市场的差异生成器越精致引入的偏差越大。第三种也是我更推荐的把随机过程当作baseline预测器。先拟合一个简单的均值回归过程比如对SVI参数做AR过程外推或者直接用SABR隐含参数做迷你外推得到“明天曲面”的粗估计然后把Transformer的目标改成“市场实际曲面 − baseline预测”。这样Transformer学的是残差不是完整曲面。原因很朴素曲面的大尺度演化已经被随机过程描述掉了残差通常幅度小、结构相对简单靠有限的真实样本就能学好。这个“随机过程当骨架、Transformer学残差”的思路是整个方案的核心。后面第4章的代码和第6章的上线流程都围绕它来展开。3. 把Transformer改造成曲面预测器架构选择、位置编码与校准约束3.1 自注意力为何适合曲面演化建模曲面预测本质上是带横截面结构的时间序列预测。LSTM、GRU这类循环模型的问题是记忆是逐时间步传递的长依赖要靠隐状态“一路扛过去”一旦中间出现波动率跃升前面的状态就被冲击稀释了。Transformer的自注意力机制则不同任意两个历史时间步之间可以直接建立依赖attention weight会自己找到“上一次出现类似曲面形态时后面怎么演变”的那几天。这种跨步直接建模的能力在波动率持续期跨越多个期限时特别值钱。这就是为什么近两年越来越多的transformer时序预测工作会把注意力放在Patch或分段上而不是逐点建模。但对于曲面参数序列我反而建议先用最朴素的Encoder-only架构输入过去20天的SVI参数输出下一天的SVI参数。不要一上来就上解码器、大步长、分层金字塔那些结构。曲面参数序列长度短样本量也不大复杂架构带来的方差会把收益吃掉。用Encoder-only还有个现实理由预测目标本身就是未来一段序列的起点我们可以用teacher forcing的方式在训练时把目标拼到输入尾部但回归头在最后一个位置输出就行。Encoder做的是把历史窗口的信息充分混合最后取最后一个位置的隐状态接回归头这个结构对短序列足够稳定也方便调试。3.2 位置编码怎么处理曲面网格坐标位置编码是曲面预测里最容易翻车的一环。如果输入是展平的曲面网格你等于把行权价、到期日、时间三个维度全部摊平成一维序列这时位置编码里必须编码清楚“这个网格点对应哪个行权价、哪个到期日”。但市面上常见的sinusoidal位置编码只表达一维顺序直接用在展平网格上会丢失结构关系模型分不清同一个到期日里k0.9和k1.1谁在左谁在右。有人会把vision transformer那套Patch Embedding搬过来把曲面当成图像做。也不是不行但曲面网格不是自然图像没有平移不变性曲面的行权价方向有sticky strike属性到期日方向有期限结构水平和垂直两个方向的意义完全不同。Swin Transformer的窗口注意力依赖局部性假设在图像上行得通在曲面上反而会把跨期限的全局结构打碎。我现在的做法是绕开这个问题不在Transformer的输入层编码曲面网格坐标而是把曲面先压成SVI参数序列。模型只需要在时间维度上放位置编码空间结构由SVI参数本身携带。这个设计的实际效果比在网格上硬加二维位置编码稳得多而且feature维度从上千降到30样本需求指数下降。位置编码用可学习向量即可窗口就20天sinusoidal和可学习两种差异很小可学习的还更灵活。3.3 校准约束如何进入损失函数只让Transformer学会把参数预测得“数值上接近”市场是不够的。模型不知道曲面必须无套利它可能输出一个让局部曲线凹下去的隐波曲面——数值很接近但它根本不能用来给期权定价。常见的做法是把无套利检查写进损失函数做软约束。具体来说两个约束最重要蝶式套利约束要求同一到期日下variance曲线在行权价方向有足够大的二阶斜率不能凹出坑日历套利约束要求不同到期日的total variance不能随T单调递减。如果模型直接输出网格曲面可以在损失里加# 以网格输出为例对行权价方向求一阶差分再求二阶差分 slope torch.diff(grid, dim-1) # 行权价方向 butterfly_penalty torch.relu(-torch.diff(slope, dim-1)).mean() calendar_penalty torch.relu(-torch.diff(grid, dim1)).mean() loss mse 0.2 * (butterfly_penalty calendar_penalty)这里grid是模型输出的隐含方差曲面logit输出后要额外处理成非负。slope是相邻行权价点位的斜率对slope再求差分得到曲率如果曲率为负说明曲线下凹用ReLU截断后作为惩罚。第二个维度方向的diff用于惩罚日历套利。两行代码就能把无套利先验塞进训练过程。但注意如果模型走的是SVI参数头输出不是网格不能直接算diff。那就需要在训练循环里先把预测出的参数展开成网格SVI公式本身就是多项式很快再执行同样的惩罚。这个展开操作PyTorch里可以直接向量化不会拖慢训练。我建议宁可训练慢10%也要让模型在参数空间和曲面空间之间建立双向意识。3.4 输出层设计参数头还是网格头输出层有两种主流设计。参数头只有5×M个输出M是到期日数量结构紧凑优化容易缺点是所有误差都被压缩到低维空间一个参数预测偏了整条曲线就废了。网格头直接预测整张曲面灵活度高但维度上千需要大量样本和更强的正则化实际落地对数据量要求极高。我的判断是第一版先上参数头同时必须给参数加可解释的边界约束。b和σ必须是正的ρ必须在[-1,1]内这些不能用普通的Linear输出否则训练中期可能出现负波动率整个曲面失效。下面第4章的代码里就用Softplus和Tanh做了这个处理。等到你积累了足够的真实数据和验证结论再考虑网格头也不迟。4. 落地复现以SVI参数序列为输入的Transformer最小实现4.1 数据准备从市场报价构造训练样本先说明数据源这件事。无论你拿的是公开期权链还是商业数据源最终都需要先把原始报价整理成统一格式行权价、到期日、收盘时或固定时点的中间价。然后对每个到期日用标准SVI拟合流程得到五个参数再用这些参数按到期日排序拼成一行样本。不同来源的SVI拟合实现可能有微小差异但参数含义一致后面模型不关心拟合细节。这一步踩坑最多的是“到期日对齐”。市场上1M、3M、6M、1Y这几个标准期限流动性好2Y以上的尾部报价稀疏拟合出的SVI参数不稳定。常见做法是只保留流动性最好的6个到期日每个到期日5个参数最终特征维度为30。样本构造代码如下# 数据准备示例把多到期日的SVI参数序列切成训练窗口 import numpy as np # svi_df: 每列是一个到期日的参数共 n_tenors * 5 列 # 顺序按到期日排列每个到期日内部依次是 [a, b, rho, m, sigma] def make_svi_windows(svi_df, window20): arr svi_df.to_numpy(dtypenp.float32) # [n_days, n_tenors*5] X, y [], [] for i in range(window, len(arr)): X.append(arr[i-window:i]) # 历史窗口 y.append(arr[i]) # 下一日整条曲面参数 return np.array(X), np.array(y) # X: [N, window, feat_dim], y: [N, feat_dim]这段代码的逻辑很直白以过去window天为特征预测下一天的SVI参数。window20意味着覆盖约一个月的交易日对于日频曲面来说是经验起始值。窗口设到60不一定更好因为太久的曲面状态对明天的边际影响很小反而会把训练样本拉短。如果数据量够可以把window做成超参用验证集对比20和40两种配置。4.2 模型定义Encoder-only Transformer与参数边界模型结构我推荐一个最稳的组合输入映射层把30维SVI参数映射到d_model维加上可学习时间位置编码然后接TransformerEncoder最后用最后一个时间步的隐状态接回归头。回归头输出原始值后在forward里按SVI参数顺序拆分并施加约束。# 最小Transformer编码器读历史窗口回归头输出下一天的SVI参数 import torch import torch.nn as nn class SurfaceSviTransformer(nn.Module): def __init__(self, feat_dim30, d_model64, nhead4, num_layers3, dim_feedforward256, dropout0.1, max_window64): super().__init__() self.input_proj nn.Linear(feat_dim, d_model) self.pos nn.Parameter(torch.randn(1, max_window, d_model)) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforwarddim_feedforward, dropoutdropout, batch_firstTrue ) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) # 回归头从最后一个历史日的隐状态直接映射到完整参数向量 self.head nn.Sequential( nn.Linear(d_model, 128), nn.GELU(), nn.Linear(128, feat_dim) ) self.softplus nn.Softplus() self.tanh nn.Tanh() def forward(self, x): # x: [B, T, feat_dim] B, T, _ x.shape emb self.input_proj(x) self.pos[:, :T, :] # 因果mask预测未来时每个时间步只能看到它之前的历史 mask torch.triu(torch.ones(T, T, devicex.device) * float(-inf), diagonal1) out self.encoder(emb, maskmask) # [B, T, d_model] last out[:, -1, :] # 最后一个历史日的隐状态 raw self.head(last) # [B, feat_dim] # 拆分成每个到期日的5个参数并施加数值约束 a raw[:, 0::5] # 水平参数允许负值 b self.softplus(raw[:, 1::5]) # 左翼斜率必须为正 rho self.tanh(raw[:, 2::5]) # 相关性限制在 [-1,1] m raw[:, 3::5] # 最低点位置不做约束 sigma self.softplus(raw[:, 4::5]) # 过渡宽度必须为正 # 重新拼回 [B, feat_dim] return torch.stack([a, b, rho, m, sigma], dim-1).reshape(B, -1)关于因果mask有必要做一句说明我们用过去20天预测明天那么第t步只能看到第t步及之前的信息。如果不加maskTransformerEncoder会让后面的时间步把整个窗口都看一遍预测就泄漏了未来信息训练损失会虚低模型实盘一跑就现原形。mask加上之后attention矩阵的上三角全部被屏蔽最后取第20天的隐状态正好代表“看完所有历史后对明天的准备”。关于参数边界b和σ用Softplus输出后始终大于0ρ用Tanh限制在[-1,1]区间。这两处是硬约束比训练后再clip可靠得多。因为clip发生在后处理梯度早已污染了模型参数而用激活函数做约束梯度会自然穿过这个可导变换模型知道边界在哪。4.3 训练循环损失函数、优化器与梯度裁剪训练函数里加了两样东西一个小的平滑正则项让预测不要偏离最后一天曲面太远一段梯度裁剪防止Transformer在早期训练时因为注意力权重的尖峰产生梯度爆炸。# 训练循环MSE 平滑正则 梯度裁剪 def train_one_epoch(model, loader, optimizer, device, lam0.1): model.train() total 0.0 for xb, yb in loader: xb, yb xb.to(device), yb.to(device) pred model(xb) mse ((pred - yb) ** 2).mean() # 平滑项预测结果不应离最后一天曲面太远避免单日突变 prev xb[:, -1, :] smooth ((pred - prev) ** 2).mean() loss mse lam * smooth optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() total loss.item() * xb.size(0) return total / len(loader.dataset)平滑项的权重lam我一般从0.1起调它本质上是告诉模型“如果你看不准至少别预测得太离谱。”但lam太大也会带来滞后模型会更多复制前一天的曲面而不是真正学习变化。梯度裁剪设为1.0是经验值对nhead4的小模型来说通常不会触发但保留它能让超参搜索时少踩很多坑。优化器用Adam学习率1e-4起不要用1e-3Transformer对学习率很敏感调大之后loss曲线会像心电图一样抖。4.4 关键参数表与调参方向下表是我在类似任务上验证过的一组起点参数可以直接抄。参数推荐取值说明d_model64输入维度才3064足够表达升到128收益有限nhead4与d_model整除训练稳定num_layers3短序列上2-4层差异不大3层是折中dim_feedforward256约为d_model的4倍过大容易过拟合window20一个月交易日数据多可对比40dropout0.2数据量几百到几千时0.2更稳learning_rate1e-4Adam默认先用1e-4建立基准batch_size32样本量小32比64收敛更平顺lam0.1平滑正则从0.1开始调调参不要一上来就追求最优先跑通一条基准再逐个动d_model和window。Transformer的参数计算量在窗口很小时不是瓶颈数据质量才是。5. 避坑与排查预测曲面翻车往往不是模型的问题5.1 训练损失能降但预测曲面出现套利窗口现象训练loss一路向下验证loss也能看但把预测参数展开成variance曲面后居然在某个行权价附近凹出一个坑。按这个曲面给期权定价会直接产生蝶式套利。原因MSE只管数值接近不管曲面形态。模型学到的是“平均值正确”局部曲率完全失控。解决在第3.3节里把无套利惩罚写进损失函数并且要在训练中监控这个惩罚项本身。我习惯每10个epoch打印一次butterfly_penalty和calendar_penalty的数值如果它们持续大于0说明模型还在输出畸形曲面需要加大惩罚系数。5.2 预测曲面比市场更“平”涨跌反应很钝现象预测曲面看起来特别光滑大体形状接近历史平均曲面但市场某天突然大跌时模型预测出来的曲面几乎没变化。原因这是MSE目标下的典型“均值预测病”。训练集里既有高波动日也有低波动日模型在不确定时选择预测一个中间形态因为这样损失最小。对于风险对冲这种钝化的曲面毫无用处。解决改残差学习。让baseline随机过程比如简单的均值回归AR外推去捕捉那个大尺度的波动水平变化Transformer只学“市场实际曲面 − baseline预测”这个残差。残差的幅度远小于曲面本身模型即使学不好也不至于完全钝化。这是第6章两阶段方案的动机之一。5.3 换一段交易日重新训练效果明显缩水现象用上半年数据训练下半年验证表现尚可但把训练窗口切到下半年重新训练后全年验证效果反而下降。原因两个问题叠加。一是regime shift不同波动时期的曲面形态差异大二是训练/验证切分没有严格按时间随机切分导致窗口重叠信息泄漏。解决训练集和验证集之间设一段缓冲期比如验证集从训练集结束两周后开始。更稳的做法是walk-forward验证每次用前200天训练、预测第201天然后滚动往后。这样每次预测都是真实的前视场景模型的表现才可信。5.4 远端尾部外插变形现象对2Y、5Y这些不活跃期限预测出的SVI参数经常让尾部斜率忽高忽低甚至出现“微笑”变“皱眉”的倒挂形态。原因远端样本少训练时占比低模型在这部分几乎没有压力去学对形态。同时SVI对远端尾部参数的拟合本身就不稳定原始标签里就带着噪声。解决训练时对远端到期日的损失加权比如把2Y以上期限的权重乘以2推理时再对远端参数做clip。流自远端的数据本来就少靠边界约束兜底不要指望模型自己学会。5.5 模型回测漂亮但定价团队根本不敢用现象RMSE比传统方法低了几个点但把预测曲面放到定价系统里交易员说它和当前盘口的中间价对不上没法给客户报价。原因模型预测的是统计期望曲面不是当前市场可交易的曲面。统计期望会在“各种可能的市场形态”之间取平均但这个平均值不等于当前时刻的可交易价格曲面也没有被约束在当前盘口的有效区间里。解决两阶段方案在这里体现出最大价值。上线前把最终预测做一次约束校准以预测为初值用最小二乘把曲面拉回当前盘口报价附近再检查套利。如果校准后偏差仍然过大就回退到纯baseline不用残差。这个回退机制本身就能让定价团队安心因为他们知道极端情况下模型输出会退化为经典模型。6. 让Transformer预测真正被交易台用起来残差校准与实践验证6.1 两阶段实际操作baseline 残差 校准把前面所有讨论收拢成交易台能跑的流程实际落地的是这样一个管线每天收盘后拟合当日SVI参数用随机过程baseline外推明天的参数再用训练好的Transformer预测“市场相对baseline的残差”两者相加组成初版预测最后用当前盘口报价做一次约束校准。# 预测流程示意随机过程baseline Transformer残差 最终校准 baseline fit_sabr_ou_on_history(svi_history) # 随机过程外推明天参数 baseline_pred baseline.predict_next() residual surface_transformer.predict(svi_window) # 只预测残差 final_pred baseline_pred residual # 校准以final_pred为初值拟合到当前可交易报价 calibrated calibrate_to_market(final_pred, current_quotes, no_arbitrageTrue)这套流程的真正价值不是Transformer预测得多准而是它让Transformer只负责“修正”。baseline决定了曲面的基本面残差修正让模型有机会学到regime变化校准兜底保证输出曲面和当前报价在同一参照系里。三个环节任何一个出问题系统都能定位到具体模块。6.2 上线前怎么验证建议看这四个指标第一walk-forward的RMSE和MAE这个必须有。第二方向正确率预测的曲面相对今天变陡还是变平方向是否和第二天实际一致。第三套利检查预测曲面和校准后曲面各自出现蝶式、日历套利的次数必须是0才算通过。第四回退率一个月内有多少天因为残差过大触发回退。回退率高说明Transformer还没学到可信规律这时候应该继续收集数据而不是急着加大模型规模。我做这类预测有个习惯每次上线新版本前先把新模型在某一段历史上的回退率打印出来盯两个月。如果回退率超过三成直接把新模型关掉回到上一版。这个习惯帮我避开了好几次“看似提升实则脆弱的版本”。希望帮到你。本文还有配套的精品资源点击获取
返回列表