
简介面向衍生品定价与量化研究领域的27页技术文档适合金融工程、量化交易与机器学习交叉方向的研究人员、开发者及高年级学生。文档以随机过程Transformer为主线系统讲解期权波动率曲面预测的完整路径从BS模型局限、波动率微笑与曲面构建到数据清洗与特征工程、模型训练与超参数优化并给出MSE/MAE/RMSE评估及风险管理、投资组合、交易策略三类实际案例。资源包为单个PDF文件大小约2.05MB内置目录并可大纲跳转便于按章节检索目前已有71人学习使用。读者可将其作为衍生品定价模型入门到进阶的参考资料既能理解随机过程与注意力机制结合的原理也能借鉴金融场景下的建模与评估思路并可用于课程设计、技术调研或算法方向参考。1. 波动率曲面预测为什么需要Transformer和随机过程来共同作答做期权做市或衍生品风险的人每天睁开眼盯的第一样东西大概率不是Greeks而是整张波动率曲面。曲面一旦被模型拟合好所有期权的价格、风险敞口、对冲比例都从这上面来。但曲面并不是静止的它随市场情绪、到期时间、执行价结构不断扭曲和滑动传统做法是用SVI、Heston或SABR这类参数化模型去贴可它们表达力有限曲面形态一复杂就容易翻车。于是最近几年开始有人把Transformer搬进这个领域用它直接学习曲面随时间的演化。这个标题里“随机过程Transformer”并不是一个噱头它说的是用随机过程刻画波动率自身的时间动力学用Transformer去拟合随机过程描述不了的异质残差。这套思路不是让你把黑匣子套上就完事而是要你理解哪些部分交给随机过程建模哪些部分交给Transformer去学习边界划清楚才能真正落到交易系统里。2. 从随机过程到Transformer这个组合在解决什么问题2.1 波动率曲面的固有动态随机过程是物理常识不是玄学先回到曲面本身。一个给定到期日和执行价的隐含波动率与其说是一个独立的数值不如说是一条随时间和市场状态演变的随机曲线。经验上ATM波动率有聚集性大波动后大波动偏度skew在下跌市里变陡期限结构会随时翻转——这些特征和Heston模型、rough Bergomi模型中的均值回复、分数布朗运动设定高度一致。所以你直接上Transformer让它从头预测每个点的波动率相当于让模型在没有任何先验的情况下重新发现时间序列的自相关性样本量不够时预测很容易崩塌。反过来如果你只用随机过程模型参数少、拟合快但曲面与执行价的关系往往被压缩成几条简单参数曲线遇到奇异期权或跨资产联动就无能为力。我的经验是把随机过程嵌入Transformer的输入或损失约束里是性价比最高的姿势。常见做法有两种一种是把随机过程的路径特征比如历史波动率的均值回复速率、长期均值作为额外特征拼进Transformer的输入另一种更高级一点用随机过程作为预测结果的平滑先验在损失函数里对相邻时间点的预测结果施加“差分遵循某种随机过程分布”的正则。无论哪种目的都是让Transformer不在时间维度上胡来——它负责空间和状态依赖随机过程负责时间演化的骨架。2.2 Transformer能补充随机过程模型的哪些盲区随机过程模型本质上是用一组参数描述整个曲面变换比如Heston的kappa、theta、sigmaSVI的a、b、rho、m、sigma它们对微笑形状的描述高度参数化但参数之间的联动关系很复杂而且这些参数随时间的动态也是隐式的。Transformer最擅长什么长序列依赖捕捉和特征交互。你可以把过去60个交易日的曲面快照按时间排成序列每个时间步上是一个展平的曲面向量比如10个执行价格档乘以5个到期期限得到50维输入。Transformer在每个时间步内部做自注意力学到执行价和到期期限之间的相互作用跨时间步又能学到曲面如何随之就市变化。这种双维度的表达能力是传统参数化模型不具备的。另外Transformer的编码器输出可以被设计成预测下一时刻的“曲面参数增量”而不是原始波动率。比如先用SVI拟合每个交易日的曲面得到参数序列然后让Transformer去预测下一日的SVI参数变化量。这样模型输出维度从几百个网格点降到5到6个参数既保留了SVI曲面本身的无套利特性又给Transformer提供了足够灵活的学习空间。我在实际项目中就采用过这种混合方式预测的曲面在美式和欧式期权的回测中都hold得住。2.3 一个落地选型用随机过程做“骨架”用Transformer拟合残差这里给出一个我认为最稳妥的落地路径两阶段建模。第一阶段对历史每个交易日的曲面做SVI参数拟合得到参数序列 ( \theta_t {a_t, b_t, rho_t, m_t, sigma_t} )。第二阶段对参数序列先拟合一个Ornstein-Uhlenbeck过程得到每个参数的长期均值、均值回复速度、噪声标准差把这些动态特征作为条件向量。第三阶段把这些条件向量连同原始市场状态标的价格、历史波动率、VIX水平拼接成Transformer的输入让Transformer输出下一期参数增量的分布。这样做的好处是如果你训练数据里遇到异常市场状态OU过程已经把“均值回复”的物理约束施加进去Transformer即使预测跑偏也会被拉回合理范围。# 两阶段建模的输入构造示意伪代码 import numpy as np def build_input(features, ou_params): features: (batch, seq_len, feature_dim) 每步的曲面特征市场状态 ou_params: (batch, param_dim) 从历史拟合的OU动态特征均值、回复强度等 返回: (batch, seq_len, feature_dim param_dim) param_broadcast np.tile(ou_params[:, np.newaxis, :], (1, features.shape[1], 1)) x np.concatenate([features, param_broadcast], axis-1) return x这段代码把OU过程提炼出的参数拼到每个时间步的输入上让Transformer在每一步都知道当前参数均值回复的“锚点”。参数dim通常5到10维不会增加太多计算量。如果你不想手动拟合OU也可以把原始曲面序列直接输入Transformer但训练时要在损失里加正则我们后面会讲。3. 数据构造把期权链和隐含波动率做成Transformer能消化的时序面板3.1 原始数据和清洗从期权链到标准化曲面预测波动率曲面的第一步是构建历史每个交易日的曲面快照。你需要的数据其实不复杂标的日终价格、每条期权链的到期日、执行价、看涨看跌标志、权利金、无风险利率、股息率。用Black-Scholes公式反解出每个合约的隐含波动率然后用插值构建标准网格。注意这里的坑特别多最典型的是深度虚值合约流动性差报价隐含波动率会乱跳必须用交易量和持仓量过滤。我一般会要求当日每个合约的成交量至少50张或持仓量200张以上否则直接用相邻档中位数替代。标准化曲面网格时不建议直接用执行价而应该用货币化度 ( \text{moneyness} K / F )其中F是远期价格。到期日则按交易日天数归一化。这样构建出来的网格与标的价格无关跨时间段可比性更强。网格通常取执行价档位0.8到1.2每隔0.02共21档到期期限档位20到180天取5个典型期限。这样每张快照就是一个21×5105维的矩阵展平后作为Transformer的一个时间步。from scipy.stats import norm import pandas as pd def compute_iv(row): # row包含: S, K, tau, r, q, market_price_flag # 使用二分法求解BS隐含波动率 # 省略完整二分代码这里是示意 pass def build_curve_snapshot(chain_df): 从原始期权链构建标准化曲面网格 返回: (n_moneyness, n_tenor) 的波动率矩阵 # 过滤深度虚值、低流动性合约 chain_df chain_df[(chain_df[volume] 50) (chain_df[open_interest] 200)] # 计算远期价格 F S*exp((r-q)*tau) 但更准确用Put-Call parity # 此处用近似 chain_df[moneyness] chain_df[strike] / chain_df[forward] chain_df[tau] np.maximum((chain_df[expiry_date] - chain_df[date]).dt.days / 365, 1e-6) # 按固定网格插值这里用pivot真实项目需用2D插值 pivot chain_df.pivot_table(indexmoneyness, columnstau, valuesiv, aggfuncmean) # 对缺失值做插值边界外填充 pivot pivot.interpolate(methodlinear, axis0).bfill().ffill() return pivot # shape: (n_moneyness, n_tenor)这里有个小提示实际构建时需要额外的“交易日”索引最好把同一个交易日的所有期权链数据打包成一个快照。不要用自然日因为周末和节假日的曲面变化模式完全不同使用交易日序数更贴近交易逻辑。3.2 时间窗口切分与样本标签用滑窗构造监督学习样本曲面预测的自然形式是用过去 (T) 个交易日的曲面快照预测未来 (H) 个交易日的曲面快照。比如用60天预测未来5天。滑窗构造样本时要注意重叠问题。如果每天滑动一天相邻样本的输入有59天重叠这会导致训练集和验证集高度相关验证性能虚高。更稳妥的做法是按季度或半年做不重叠切分。def create_samples(surface_data, T60, H5, stride21): surface_data: shape (n_days, feature_dim) stride21 表示每月抽样一个样本避免重叠 samples [] labels [] for start in range(0, len(surface_data) - T - H, stride): end start T x surface_data[start:end] # (T, feature_dim) y surface_data[end:endH] # (H, feature_dim) samples.append(x) labels.append(y) return np.array(samples), np.array(labels)滑动步长设为21约一个月这样训练集和验证集之间输入重叠减到最低。如果数据集很小步长可以减小到5但要配合更严格的早停否则容易过拟合。标签 (y) 是未来5天的曲面损失函数可以比较预测和真实曲面。如果你想要的是“下一条曲面”(H1) 也可以但预测多日能让模型学到更平滑的动态。3.3 数据增强与避免前视偏差波动率曲面数据不像图像可以翻转裁剪但可以做“时间扰动”增强。常见做法是给输入特征加一点高斯噪声噪声标准差设为原始序列标准差的1%到3%。更有效的是用bootstrap从历史数据中随机抽取连续60天的区间并随机对市场状态特征如标的价格水平做小幅平移。这类增强能提升Transformer对轻微状态偏移的鲁棒性但不要过度否则会破坏曲面内部的绝对水平。前视偏差最危险的来源有两个。一个是全局归一化如果你用全数据集的均值和标准差去做z-score训练时已经看到了未来的统计量验证集的曲线会变得异常漂亮。正确做法是只对历史区间拟合归一化参数然后套用到验证集。另一个是标签构造时的未来信息比如用未来一天的真实波动率来调整训练权重这是明显的作弊。我的习惯是做一个向后兼容测试把最后一个交易日的输入切掉再训练一次看损失是否显著变大如果基本不变说明没有过度依赖未来边界信息。4. 实现随机过程Transformer最小可复现的PyTorch代码4.1 模型结构输入Embedding 时序编码 Transformer Encoder 输出头这里给出一个可以直接跑起来的最小实现。模型的输入是时间序列 ((batch, seq_len, feature_dim))feature_dim是曲面快照展平后的维度比如105。首先进入一个线性Embedding把维度映射到模型的d_model比如128。然后加上两类位置编码可学习的位置编码用来标记第几个交易日跟着一个交易日历编码输入是日期特征比如星期几、月份、距离上次财报的天数这些额外编码对期权波动率很重要事件驱动效应。编码完之后进入标准的Transformer Encoder层。最后输出头根据任务不同可以输出未来5天曲面的所有值或者输出SVI参数变化量。import torch import torch.nn as nn import math class StochasticTransformer(nn.Module): def __init__(self, feature_dim, d_model128, nhead8, num_layers4, output_horizon5): super().__init__() self.input_proj nn.Linear(feature_dim, d_model) self.pos_emb nn.Parameter(torch.randn(1, 500, d_model) * 0.02) self.calendar_emb nn.Linear(4, d_model) # 星期几、月、日、季度信息 encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforward256, dropout0.1, activationgelu, batch_firstTrue ) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) self.output_head nn.Linear(d_model, output_horizon * feature_dim) def forward(self, x, cal_feature): # x: (batch, seq_len, feature_dim) batch, seq_len x.shape[:2] x self.input_proj(x) self.pos_emb[:, :seq_len, :] cal_emb self.calendar_emb(cal_feature) # (batch, seq_len, d_model) x x cal_emb x self.encoder(x) # (batch, seq_len, d_model) x x[:, -1, :] # 取最后时间步 out self.output_head(x) # (batch, horizon*feature_dim) out out.view(batch, self.output_horizon, -1) return out这里有两个容易忽略的设计点。第一pos_emb用的是可学习位置编码而不是正弦编码。因为交易日序列长度固定且语义随时间衰减可学习编码更能适配。第二calendar_emb把日期信息通过一个线性层映射到d_model并加到每个时间步上。这样模型能感知“周五的曲面”和“周一的曲面”在演化模式上的差异。如果你的业务里没有明显的事件效应可以去掉这个模块但我在期权数据上测试加入后回测年化误差能减少5%到8%。4.2 损失函数MSE 随机过程一致性正则单纯的MSE会鼓励模型输出均值回归的平滑结果这会让预测曲面丢失波动率微笑的尖峰。更关键的是我们需要迫使预测序列在时间维度上符合随机过程的特性。以Ornstein-Uhlenbeck为例它的离散化形式是 ( \Delta y_t \theta (\mu - y_{t-1}) \epsilon )其中 (\epsilon) 的方差稳定。我们可以在损失函数里加一个正则项约束模型预测的未来5条曲面之间的差分方差不要偏离训练集上观测到的差分方差太多。def loss_function(pred_curves, target_curves, train_diff_std, lambda_reg0.1): pred_curves: (batch, H, feature_dim) target_curves: (batch, H, feature_dim) train_diff_std: 从训练集计算的相邻日曲面差分的全局标准差 (feature_dim,) mse nn.functional.mse_loss(pred_curves, target_curves) # 预测曲面在时间维度的一阶差分 pred_diff pred_curves[:, 1:, :] - pred_curves[:, :-1, :] # (batch, H-1, feature_dim) pred_diff_std pred_diff.std(dim1) # (batch, feature_dim) # 约束差分标准差接近训练集的统计量 reg torch.mean((pred_diff_std - train_diff_std.view(1, -1)) ** 2) return mse lambda_reg * reg这里的lambda_reg很重要太小起不到随机过程约束作用太大会让模型变得保守预测趋近于平均波动。我一般先用0.01开始看验证集MSE是否改善再逐步调到0.05~0.2。train_diff_std是从训练样本的标签序列中计算的要防止它本身包含未来信息——计算时只使用训练集并在验证/测试阶段保持固定。注意不要把这个统计量放在模型里当作训练参数否则又变成黑匣子。4.3 训练与超参数batch size、学习率、warmup、early stoppingTransformer对超参数比线性模型敏感得多。我推荐的起手参数batch size 64学习率 1e-4峰值用AdamW权重衰减0.01warmup 1000步然后按余弦退火下降。训练轮数20轮左右配合early stoppingpatience设为5轮监控验证集MSE。from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts optimizer AdamW(model.parameters(), lr1e-4, weight_decay0.01) scheduler torch.optim.lr_scheduler.OneCycleLR( optimizer, max_lr1e-4, steps_per_epochlen(train_loader), epochs20 ) best_val_mse float(inf) patience 0 for epoch in range(20): model.train() for x, cal, y in train_loader: optimizer.zero_grad() pred model(x, cal) loss loss_function(pred, y, train_diff_std) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() # 验证部分省略这里的clip_grad_norm几乎是必需品。Transformer的训练经常出现某个batch里的离群点导致梯度爆炸尤其数据里有极端市场行情时。梯度裁剪到1.0看似保守但实际上能让训练更稳定。学习率调度用OneCycle比固定小步长收敛更快但要注意steps_per_epoch必须与实际batch数量一致否则调度器会算错。watch早停如果连续5个验证轮次MSE没有下降就恢复最优状态并停止。5. 避坑波动率曲面预测最常见的五个翻车点5.1 现象模型预测出负波动率或蝶式套利漏洞这是初学者最容易踩的坑。预测结果是以曲面网格点为单位输出的神经网络输出层没有约束完全可能算出负数。而且即使全部为正也可能违反蝶式套利无套利条件即波动率随执行价变化的二阶导过大。原因很简单损失函数只惩罚与真实值的偏差并未惩罚曲面形状不合理。解决第一层防线输出层改为预测log(IV)再取指数。第二层防线更有效不直接预测网格点而是预测SVI参数变化量并用SVI公式重新生成曲面。SVI本身能保证截面无套利在参数满足范围时输出参数时再对每个参数设置归一化范围。比如用tanh输出后再乘以预设上下界确保参数落在合法区间。5.2 现象回测绩效很好实盘一上就崩这基本就是数据泄漏。最常见泄漏源是z-score归一化用了全样本统计量还有滑窗重叠导致训练集和验证集共享大量历史区间。我曾经见过一个团队用日频滑窗构造样本步长设为1验证集的输入与训练集的输入只差一个交易日模型几乎是闭眼抄回测误差比基准低一个数量级实盘却比基准还差。原因就是验证集和训练集几乎没有区分度。解决严格按时间切分。训练集最早60%验证集中间20%测试集最后20%。滑窗步长至少设为5个交易日最好20个以上。所有标准化参数均值、标准差、差分统计量只从训练集拟合然后套用到验证和测试。另外做一次“灌口测试”把验证集的时间段提前一年再看如果误差显著变大说明模型可能过拟合到了特定市场风格。5.3 现象Transformer训练不收敛loss剧烈震荡Transformer不是标准MLP它自带层归一化和残差连接但仍然可能输出尺度问题导致训练不稳定。我遇到最典型的原因是输入没有标准化曲面波动率数值在0.1到0.8之间市场状态特征标的价格5000VIX 20混合后数值范围相差几十倍Attention的softmax会被大数值主导梯度自然爆炸。解决所有连续特征做z-score确保每个维度均值为0方差1。然后学习率从3e-5起步确认能收敛后再调到1e-4。另外如果你的序列长度超过100建议用Pre-LN结构即先对每层输入做LayerNorm再进Attention层而不是输出后做。Pre-LN在长序列上更稳定代码里只需把TransformerEncoderLayer的norm_first设为True。5.4 现象预测曲面太平滑丢失了波动率微笑的细节MSE损失会自动惩罚大误差对数值较小的浅虚值点这些点波动率可能低但绝对值误差小不敏感模型于是倾向于输出一个近似平均曲面把微笑压平。特别是接近到期日时ATM和OTM的波动率差异可以很大如果损失对每个网格点一视同仁模型会优先拟合绝对值变化大的ATM部分。解决损失按网格点的定价敏感度加权。对每个网格点先用当前波动率计算对应期权的vegavega大的点权重高。vega本质上是该点对价格的影响所以加权MSE更加贴近交易盈利。另外也可以用 ( \log(\text{IV}) ) 作为预测目标这样惩罚的是相对误差而不是绝对误差可以保住低波动率点的形状。5.5 现象到期日临近的合约预测偏差很大临近到期时期权的时间价值快速衰减波动率曲面的形态变得极不稳定尤其是执行价偏离ATM的合约可能出现价格跳动导致的隐含波动率巨幅震荡。模型很难从历史中学到这种非线性行为预测误差集中爆发。这不是Transformer的锅而是标签本身信噪比太低。解决训练阶段去除到期日不足10天的样本。如果要预测短期限曲面单独训练一个短期限模型输入特征中加入距到期天数让模型显式感知时间衰减。我常用做法是为短期限模型设置更小的时间窗口过去30天因为越靠近到期历史信息对当前的影响衰减越快。长窗口反而会引入无关噪声。6. 验证一套预测曲面是否可用从无套利检查到Backtesting多数人训练完模型只看MSE和MAE但做衍生品定价真正该关心的是预测曲面能不能被用来给复杂产品无套利定价。我习惯在模型上线前做三个验证步骤。第一步是静态无套利检查。对每个预测日的曲面遍历执行价计算蝶式价差组合的回报。假设相邻执行价 (K_1 K_2 K_3)如果波动率曲面导致组合 (C(K_1) - 2C(K_2) C(K_3) 0)那就存在蝶式套利。检查代码很简单def check_butterfly(curve, K, r, tau, S): # curve: 波动率网格, K: 对应执行价, S: 标的价格 from scipy.stats import norm d1 lambda K, iv: (np.log(S/K) (r 0.5*iv**2)*tau) / (iv*np.sqrt(tau)) d2 lambda K, iv: d1(K, iv) - iv*np.sqrt(tau) call lambda K, iv: S*norm.cdf(d1(K, iv)) - K*np.exp(-r*tau)*norm.cdf(d2(K, iv)) c1, c2, c3 call(K[0], curve[0]), call(K[1], curve[1]), call(K[2], curve[2]) butterfly c1 - 2*c2 c3 return butterfly -1e-6如果这一条不过说明模型输出的曲面在截面上存在明显不现实的地方宁可暂时回退到上一版模型也不要硬上。实际中很多深度模型预测出的曲面表面精细内部已存在大量凹坑只有这种检查能拦住。第二步是动态backtest。用预测出的曲面验证一个简单策略每天预测曲面后做市商可以按此前一个交易日的收盘价卖出所有被高估的期权根据预测曲面重定价判断买入被低估的持有到次日再平仓。但注意回测时必须扣除真实市场买卖价差和最小跳价否则纯理论曲线会让你误以为模型是印钞机。第三步是做期限结构单调性检查。同一执行价档位下预测出的波动率随着到期日增大应大致递增或递减绝不能出现锯齿状。我会计算相邻期限的差值若 5个百分点的双向波动连续出现则标记模型出现异常。这个检查虽然粗糙但能快速发现位置编码失效或月份效应被模型错误吸收。最后说一个我自己的血泪经验不要只盯着MSE。MSE改进10%也许只让曲面平均移动0.2个Vol点但极有可能在某个深虚值档位制造一个0.8Vol点的凹坑这个凹坑足以让一个带收益增强结构的产品定价出现几十个基点的偏差。与其盲目调Transformer结构不如多做随机过程正则和网格点敏感性验证它们才是把模型从“拟合趋势”推向“可交易曲面”的那一层栅栏。希望帮到你。本文还有配套的精品资源点击获取