Please solve this problem and provide the python c...

创建于:2025年5月2日

使用 O4 Mini High 回答 Chat01

问题

image.pngimage.png

Please solve this problem and provide the python code as the solutions. You may refer to the helper functions for SABR model in matlab:
function x=call(F,K,T,sig)
if Tsig == 0
x=max(F-K,0);
return;
end
d1=log(F/K)+0.5
(sig^2)T;
d1=d1/(sig
sqrt(T));
d2=d1-sigsqrt(T);
nd1=0.5
(1+erf(d1/sqrt(2)));
nd2=0.5*(1+erf(d2/sqrt(2)));
x=Fnd1-Knd2;
return;

function sig_B=ImpVolSABR(F,K,T,alpha,beta,rho,nu)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% This program calculates the value of an interest rate cap by the SABR%
% model. %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

clear;

% Input cap parameters

text
T=1:10; % Cap maturity K=[1:8]/100; % Strike rates f=0.04*ones(120,1); %0.02:0.00025:0.04975; % LIBOR rates Nf=length(f);

% Calculate the discount factor

text
dt=0.25; % caplet tenor P=zeros(Nf,1); P(1)=1/(1+dt*f(1)); for i=2:Nf P(i)=P(i-1)/(1+dt*f(i)); end

% Calculate cap value

text
beta = 0.5; alpha = 0.03; rho = -0.5; nu = 0.25; NT=length(T); NK=length(K); sig_B=zeros(4*NT,NK); cap=zeros(NT,NK); Tm1=0; for i=1:NT for j=1:NK for k=(4*Tm1+1):4*T(i) sig_B(k,j)=ImpVolSABR(f(k),K(j),(k-1)*dt,alpha,beta,rho,nu); cap(i,j)=cap(i,j)+P(k)*dt*call(f(k),K(j),(k-1)*dt,sig_B(k,j)); end if i < NT cap(i+1,j)=cap(i,j); end end Tm1=T(i); end TT=1:4*T(NT); figure(1), surf(K,TT(2:4*NT),sig_B(2:4*NT,:)); xlabel('Strike rate'); ylabel('Cap maturity'); zlabel('Implied Black volatility'); title('Implied Black volatility'); figure(2), surf(K,T,cap); xlabel('Strike rate'); ylabel('Cap maturity'); zlabel('Cap value'); title('Cap value for $1 notional');

bs=1-beta;
NK=length(K);
sig_B=zeros(NK,1);
for i=1:NK
if T == 0
sig_B(i)=alpha;
continue
end
sig_B(i)=bs^2/24alpha^2/(FK(i))^bs+0.25rhobetanualpha/(FK(i))^(bs/2)+(2-3rho^2)/24nu^2;
sig_B(i)=1+sig_B(i)T;
if F == K(i)
coef = alpha/K(i)^(bs);
else
z=nu/alpha
(F^bs-K(i)^bs)/bs;
if bs == 0
z=nu/alpha
log(F/K(i));
end
xz=(sqrt(1-2rhoz+z^2)+z-rho)/(1-rho);
xz=log(xz);
coef = nulog(F/K(i))/xz;
end
sig_B(i)=coef
sig_B(i);
end
return

text
function x=put(F,K,T,sig) if T*sig == 0 x=max(K-F,0); return; end d1=log(F/K)+0.5*(sig^2)*T; d1=d1/(sig*sqrt(T)) d2=d1-sig*sqrt(T) nmd1=0.5*(1+erf(-d1/sqrt(2))) nmd2=0.5*(1+erf(-d2/sqrt(2))) x=K*nmd2-F*nmd1; return;

思考

Considering SABR implementation

I’m working through a formula where I'm calculating implied volatility using Hagan’s SABR model. The key is to compute the “caplet” implied vol surface, focusing on how these caplets are defined over time. Since we're dealing with a 30-year period divided into 120 quarters, each period corresponds to a maturity and strike. I’ll need to calculate the vol for each caplet at each time, and then plot the resulting surfaces for volatility and cap values based on strikes and maturities. It's quite a detailed process, but I think I have it figured out.

Implementing Hagan SABR formula

I need to implement Hagan's SABR formula for implied volatility in Python. The formula involves terms for α, F, K, and β, and I’ll compute the volatility for each strike and maturity. I start by defining a grid for strikes, parameters, and discount factors, then use the Black-76 formula for call options to calculate the caplet values. I’m planning to loop through all the caplets and compute their value using the relevant forward rates and implied volatilities. I'm also considering recalculating caps directly instead of filling them in.

Clarifying caplet computation

I’m working on the computation of caplets, where I need to use the Black formula to calculate the value. For each maturity and strike, I define the caplet indices, then calculate the discount factors and call the Black formula. There’s some ambiguity in how the code handles maturity time, especially with the use of forward rates and volatility. It seems like the code recursively calls the SABR formula, but that seems less ideal. I’ll try to match the existing approach, including using specific time points (T) for settlement.

Thought for 21 seconds

回答

Below is a step‐by‐step solution in Python. We first build the 3‐month SOFR curve, discount factors and helper routines (Black‐76 caplet pricer and Hagan’s SABR implied‐vol formula), then loop to compute:

  1. Implied Black volatilities for each caplet (for maturities Tk=kΔtT_k = k\Delta t, k=1,,40k=1,\dots,40, and strikes K=1%,2%,,9%K=1\%,2\%,\dots,9\%).
  2. Cap values for cap maturities Ti=1,,10T_i=1,\dots,10 years across the same strikes.

Finally we plot, for each parameter set:

  • a 3D surface of σBlack(Tk,K)\sigma_{\rm Black}(T_k,K) (“vol‐surface”), and
  • a 3D surface of CapValue(Ti,K)\mathrm{CapValue}(T_i,K).
python
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.special import erf # 1. Problem data dt = 0.25 N = 120 # quarters in our SOFR curve f = np.linspace(0.0525, 0.0475, N) # f_j(0) from 5.25% down to 4.75% # build discount factors P[k] = P(0,t_k), t_k = k*dt P = np.empty(N) P[0] = 1.0 / (1.0 + dt * f[0]) for k in range(1, N): P[k] = P[k-1] / (1.0 + dt * f[k]) # strikes and maturities K_grid = np.arange(0.01, 0.10, 0.01) # 1% to 9% T_caps = np.arange(1, 11) # cap maturities in years # we will need caplet times only up to 10y = 40 quarters K_periods = int(T_caps[-1] / dt) # = 40 # 2. Helper functions def black_call(F, K, T, sigma): """Black-76 call price (undiscounted) with forward F, strike K, time to expiry T.""" if T * sigma == 0: return np.maximum(F - K, 0.0) d1 = (np.log(F/K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T)) d2 = d1 - sigma * np.sqrt(T) N1 = 0.5 * (1 + erf(d1/np.sqrt(2))) N2 = 0.5 * (1 + erf(d2/np.sqrt(2))) return F * N1 - K * N2 def sabr_implied_vol(F, K, T, alpha, beta, rho, nu): """Hagan SABR implied volatility approximation.""" # edge‐cases if T == 0: return alpha if F <= 0 or K <= 0: return np.nan bs = 1.0 - beta FKb = (F * K)**(bs/2) # small‐time expansion factor A = (bs**2 / 24) * (alpha**2) / ((F * K)**bs) \ + 0.25 * rho * beta * nu * alpha / ((F * K)**(bs/2)) \ + (2 - 3*rho**2) * nu**2 / 24 E = 1 + A * T # z and x(z) if abs(F - K) < 1e-12: # ATM formula vol = alpha / (F**bs) * E else: if bs != 0: z = (nu/alpha) * (F**bs - K**bs) / bs else: z = (nu/alpha) * np.log(F/K) # x(z) numer = np.sqrt(1 - 2*rho*z + z**2) + z - rho denom = 1 - rho xz = np.log(numer / denom) coef = (nu * np.log(F/K)) / xz vol = coef * E return vol # 3. Compute surfaces for both parameter sets param_sets = { 'a (β=0.5, α=0.05, ν=0.25, ρ=-0.5)': dict(beta=0.5, alpha=0.05, nu=0.25, rho=-0.5), 'b (β=0.4, α=0.0372, ν=0.25, ρ=-0.5)': dict(beta=0.4, alpha=0.0372, nu=0.25, rho=-0.5) } surfaces_vol = {} surfaces_caps = {} for label, pars in param_sets.items(): β, α, ρ, ν = pars['beta'], pars['alpha'], pars['rho'], pars['nu'] # 3.1 Caplet vol surface σ_B[k, j] for k=1..K_periods, j over strikes sigB = np.zeros((K_periods, len(K_grid))) times = np.arange(1, K_periods+1) * dt # T_k = k*dt for k in range(K_periods): T_k = k * dt # note: the Hagan formula uses time-to-expiry = (k)*dt Fk = f[k] for j, Kj in enumerate(K_grid): sigB[k, j] = sabr_implied_vol(Fk, Kj, T_k, α, β, ρ, ν) # 3.2 Cap price surface Cap(T_i, K_j) caps = np.zeros((len(T_caps), len(K_grid))) for i, Ti in enumerate(T_caps): # caplet indices for this cap: these run from periods (i*4)−4 to i*4−1 (zero‐based) idx_start = 4*(i) - 4 idx_end = 4*(i) - 1 for j, Kj in enumerate(K_grid): price = 0.0 for k in range(idx_start, idx_end+1): # maturity for call() = (k)*dt price += P[k] * dt * black_call(f[k], Kj, k*dt, sigB[k, j]) caps[i, j] = price surfaces_vol[label] = (times, K_grid, sigB) surfaces_caps[label] = (T_caps, K_grid, caps) # 4. Plot everything for label in param_sets: times, Ks, sigB = surfaces_vol[label] Tc, Ks2, cap_vals = surfaces_caps[label] fig = plt.figure(figsize=(12,5)) ax = fig.add_subplot(121, projection='3d') Tm, Km = np.meshgrid(times, Ks) ax.plot_surface(Km, Tm, sigB.T, cmap='viridis', edgecolor='none') ax.set_title(f'Implied Black Vol Surface ({label})') ax.set_xlabel('Strike K') ax.set_ylabel('Caplet Maturity (y)') ax.set_zlabel('σ_Black') ax2 = fig.add_subplot(122, projection='3d') Ti, Kj = np.meshgrid(Tc, Ks2) ax2.plot_surface(Kj, Ti, cap_vals.T, cmap='plasma', edgecolor='none') ax2.set_title(f'Cap Value Surface ({label})') ax2.set_xlabel('Strike K') ax2.set_ylabel('Cap Maturity (y)') ax2.set_zlabel('Cap Price') plt.tight_layout() plt.show()

Explanation of key steps

  1. Term‐rate curve {fj}\{f_j\}
    We place 120 quarterly forward rates fjf_j evenly from 5.25% down to 4.75%, and build discount factors

    P(0,tk)=m=1k11+Δtfm,Δt=0.25. P(0,t_k)=\prod_{m=1}^k\frac1{1+\Delta t\,f_m}\,,\quad \Delta t=0.25.
  2. Black‐76 caplet pricer
    The function black_call(F,K,T,σ) implements

    CBlack(F,K,T)  =  FN(d1)KN(d2),d1,2=ln(F/K)±12σ2TσT. C_{\rm Black}(F,K,T) \;=\; F\,N(d_1)-K\,N(d_2), \quad d_{1,2}=\frac{\ln(F/K)\pm\frac12\sigma^2T}{\sigma\sqrt T}.
  3. Hagan SABR formula
    In sabr_implied_vol we code the standard small‐time expansion plus the z/x(z)z/x(z) correction:

    σSABR(F,K)=νln(F/K)x(z)[1+AT],z=ναF1βK1β1β, \sigma_{\rm SABR}(F,K) = \frac{\nu\ln(F/K)}{x(z)}\,[1 + A\,T],\quad z=\frac{\nu}{\alpha}\frac{F^{1-\beta}-K^{1-\beta}}{1-\beta},

    with

    A=(1β)224α2(FK)1β+ρβνα4(FK)(1β)/2+23ρ224ν2. A = \frac{(1-\beta)^2}{24}\frac{\alpha^2}{(FK)^{1-\beta}} + \frac{\rho\beta\nu\alpha}{4\,(F K)^{(1-\beta)/2}} + \frac{2-3\rho^2}{24}\nu^2.

    The ATM special case F ⁣ ⁣KF\!\approx\!K is handled separately.

  4. Building the surfaces

    • Volatility surface: for each caplet expiry Tk=kΔtT_k=k\Delta t up to 10 years (40 quarters) and each strike.
    • Caplet pricing: each cap of maturity TiT_i is the sum of its four quarter‐caplets, k=4(i1)4i1PkΔt  ×\displaystyle \sum_{k=4(i-1)}^{4i-1}P_k\,\Delta t\;\times Black call price.
  5. Visualization
    We use Matplotlib’s 3D plotting to show, side by side for each parameter set,

    • the implied‐vol surface σBlack(Tk,K)\sigma_{\rm Black}(T_k,K), and
    • the cap‐value surface CapValue(Ti,K)\mathrm{CapValue}(T_i,K).

Feel free to tweak the plotting styles, color maps or grid‐resolution (e.g. a finer strike mesh) as needed.

问题

我不太确定你的代码是否正确,我用另一个生成了下面的答案。请你重新分析你的代码和这个代码,判断你的代码或者他的代码是否正确。请重新生成你认为是这道题的正确解法的最终代码。import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erf
from mpl_toolkits.mplot3d import Axes3D

Black-Scholes call price

def call(F, K, T, sig):
if T * sig == 0:
return max(F - K, 0.0)
d1 = (np.log(F/K) + 0.5sig**2T) / (signp.sqrt(T))
d2 = d1 - sig
np.sqrt(T)
return F * 0.5*(1 + erf(d1/np.sqrt(2))) - K * 0.5*(1 + erf(d2/np.sqrt(2)))

SABR Implied Volatility (Hagan et al. formula)

def sabr_vol(F, K, T, alpha, beta, rho, nu):
if F == K:
# ATM case
z = 1e-8 # avoid division by zero
else:
if beta == 1:
z = (nu/alpha) * np.log(F/K)
else:
z = (nu/(alpha*(1-beta))) * (F**(1-beta) - K**(1-beta))

text
xz = np.log((np.sqrt(1 - 2*rho*z + z**2) + z - rho)/(1 - rho)) if z == 0: return alpha * F**(beta-1) # Compute the implied volatility sigma = alpha * z / xz sigma /= (F*K)**((1-beta)/2) * (1 + ((1-beta)**2*alpha**2/(24*(F*K)**(1-beta)) + 0.25*rho*beta*nu*alpha/((F*K)**((1-beta)/2)) + (2-3*rho**2)*nu**2/24)*T) return sigma

生成SOFR利率曲线

j = np.arange(1, 121)
f = 0.0525 + (j - 1)/119 * (0.0475 - 0.0525)

计算贴现因子

dt = 0.25
P = np.zeros(120)
P[0] = 1 / (1 + dtf[0])
for i in range(1, 120):
P[i] = P[i-1] / (1 + dt
f[i])

参数组

params = [
{'beta':0.5, 'alpha':0.05, 'nu':0.25, 'rho':-0.5},
{'beta':0.4, 'alpha':0.0372, 'nu':0.25, 'rho':-0.5}
]

主计算循环

for p_idx, param in enumerate(params):
beta = param['beta']
alpha = param['alpha']
nu = param['nu']
rho = param['rho']

text
# 创建网格存储结果 strikes = np.arange(1, 10)/100 # 1%到9% maturities = np.arange(1, 11) # 1到10年 # 隐含波动率矩阵 (caplet维度, strike维度) impl_vols = np.zeros((40, 9)) # 40个季度caplet(10年) # 计算每个caplet的隐含波动率 for qtr in range(40): T = (qtr+1)*0.25 # caplet期限 F = f[qtr] # 对应远期利率 for k_idx, K in enumerate(strikes): impl_vols[qtr, k_idx] = sabr_vol(F, K, T, alpha, beta, rho, nu) # 计算cap价值 cap_values = np.zeros((10, 9)) for T_idx, T in enumerate(maturities): num_caplets = T*4 # cap包含的caplet数量 for k_idx, K in enumerate(strikes): value = 0.0 for qtr in range(num_caplets): vol = impl_vols[qtr, k_idx] df = P[qtr] payoff = call(f[qtr], K, qtr*dt, vol) value += df * dt * payoff cap_values[T_idx, k_idx] = value # 绘制图形 fig = plt.figure(figsize=(14, 6)) # 隐含波动率曲面 ax1 = fig.add_subplot(1, 2, 1, projection='3d') X, Y = np.meshgrid(strikes*100, np.arange(0.25, 10.25, 0.25)) ax1.plot_surface(X, Y, impl_vols, cmap='viridis') ax1.set_title(f'Implied Volatility (Param Set {p_idx+1})') ax1.set_xlabel('Strike (%)') ax1.set_ylabel('Maturity (years)') ax1.set_zlabel('Volatility') # Cap价值曲面 ax2 = fig.add_subplot(1, 2, 2, projection='3d') X, Y = np.meshgrid(strikes*100, maturities) ax2.plot_surface(X, Y, cap_values, cmap='plasma') ax2.set_title(f'Cap Value (Param Set {p_idx+1})') ax2.set_xlabel('Strike (%)') ax2.set_ylabel('Maturity (years)') ax2.set_zlabel('Value') plt.tight_layout()

plt.show()

思考

Evaluating the code and SABR implementation

The user provided alternative code and wants me to compare it with my own. I should analyze the differences in how we implemented the SABR model, particularly in how my formula for the implied vol differs from the standard Hagan formula.

As I examine the SABR implied volatility formula, I need to focus on the factors like alpha, beta, rho, nu, and the formulas used to compute constant values like A. I'll also double-check standard references for potential discrepancies.

Analyzing SABR implementation

It seems like I identified an issue with how my code calculates the caplet maturity and the corresponding forward rate. I initially set T_k = k*dt, but for the correct first caplet, I should be using T_k = (k+1)*dt. This off-by-one issue affects the volatility surface calculation, including the application of the SABR model on the correct forward rate and maturity. I need to adjust the loop so that the correct time-to-maturity (T_k) aligns with the intended caplet expiry.

Analyzing SABR volatility formula

It looks like there are inconsistencies in the user-provided sabr_vol code for the ATM case. Specifically, they return alpha * F**(beta-1), which misses the "E" factor that should be included in the formula for implied volatility at ATM. The correct version involves multiplying by the expansion term (1 + AT) which is missing in their code. Additionally, their handling of z and xz in the denominator seems off compared to the Hagan formula. I'll need to adjust this part to match the correct approach.

Evaluating SABR formula implementation

Okay, I'm looking at the SABR implied volatility formula. It seems my current code is missing a correction factor, particularly the α\alpha term and the prefactor involving (FK)(1β)/2(F K)^{(1 - \beta) / 2}.

I set some elements, such as z=να(FβKβ)/βz = \frac{\nu}{\alpha} (F^{\beta} - K^{\beta}) / \beta, and some related calculations, but I forgot to incorporate that essential component in the final volume formula. My existing implementation isn’t quite matching Hagan's formula. Looks like I'll need to code the missing elements correctly.

Thought for 48 seconds

回答

下面我对两份代码做了对比和检查,结论是:

  • 我最初给出的代码

    • 在计算 SABR 隐含波动率时,完全漏掉了前缀 α(FK)(1β)/2\displaystyle\frac{\alpha}{(FK)^{(1-\beta)/2}} 项。
    • 在构造波动率曲面时,用错了到期时间:第 jj 个 caplet 的到期应是 (j+1)Δt(j+1)\Delta t,而我用了 jΔtj\Delta t
    • 在合约定价里,caplet 索引也偏移错误,导致第一个 cap 反而用到了负索引。
  • 你贴过来的备用代码

    • ATM(F=KF=K)情形下只返回了 α/F1β\alpha/F^{1-\beta},却漏掉了小-时间展开修正项 [1+AT]\bigl[1+AT\bigr]
    • 非 ATM 情形里又把修正项 [1+AT]\bigl[1+AT\bigr] 放在了分母,而正确的应该是放在分子。

正确解法的完整 Python 实现

python
import numpy as np import matplotlib.pyplot as plt from scipy.special import erf from mpl_toolkits.mplot3d import Axes3D # noqa: F401 # 1. 构造 3 个月 SOFR 曲线 dt = 0.25 N_quarters = 120 j = np.arange(1, N_quarters+1) f = 0.0525 + (j-1)/(119) * (0.0475 - 0.0525) # f_j 从 5.25% 线性下跌到 4.75% # 2. 贴现因子 P(0,t_j) = &prod;_{m=1}^j 1/(1 + dt * f_m) P = np.empty(N_quarters) P[0] = 1.0 / (1.0 + dt * f[0]) for m in range(1, N_quarters): P[m] = P[m-1] / (1.0 + dt * f[m]) # 3. Black-76 caplet 定价函数 def black_call(F, K, T, sigma): if T * sigma == 0: return max(F - K, 0.0) d1 = (np.log(F/K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T)) d2 = d1 - sigma * np.sqrt(T) N1 = 0.5 * (1 + erf(d1/np.sqrt(2))) N2 = 0.5 * (1 + erf(d2/np.sqrt(2))) return F * N1 - K * N2 # 4. Hagan 等式的 SABR 隐含波动率 def sabr_implied_vol(F, K, T, alpha, beta, rho, nu): # T = 0 时直接返回 0,让 black_call 走 T*sigma=0 分支 if T <= 0: return 0.0 bs = 1.0 - beta # ATM 特殊处理 if abs(F - K) < 1e-12: # α / F^{1-β} * [1 + A T] A = (bs**2/24)*(alpha**2/(F**(2*bs))) \ + (rho*beta*nu*alpha)/(4*(F**bs)) \ + ((2-3*rho**2)/24)*nu**2 return alpha / (F**bs) * (1 + A * T) # 非 ATM # A 系数 A = (bs**2/24)*(alpha**2/((F*K)**bs)) \ + (rho*beta*nu*alpha)/(4*((F*K)**(bs/2))) \ + ((2-3*rho**2)/24)*nu**2 E = 1 + A * T # z = ν/α * (F^{1-β} - K^{1-β})/(1-β) if abs(bs) > 1e-16: z = (nu/alpha) * (F**bs - K**bs) / bs else: z = (nu/alpha) * np.log(F/K) # χ(z) = ln[(&radic;(1−2ρz+z^2)+z−ρ)/(1−ρ)] numer = np.sqrt(1 - 2*rho*z + z**2) + z - rho denom = 1 - rho chi = np.log(numer / denom) # 最终公式: α/( (F K)^{(1-β)/2} ) * E * (z/χ) prefactor = alpha / ((F*K)**(bs/2)) return prefactor * E * (z / chi) # 5. 网格设定 strikes = np.arange(1, 10) / 100.0 # 1% 到 9% cap_mats = np.arange(1, 11) # 1y 到 10y max_caplets = int(cap_mats[-1] / dt) # 40 个 caplet # 6. 参数组 param_sets = [ ('(a) β=0.5, α=0.05, ν=0.25, ρ=-0.5', 0.5, 0.05, 0.25, -0.5), ('(b) β=0.4, α=0.0372, ν=0.25, ρ=-0.5', 0.4, 0.0372, 0.25, -0.5), ] # 7. 逐组计算并绘图 for title, beta, alpha, nu, rho in param_sets: # 7.1 先算 caplet 隐含波动率曲面 σ[j,k] sigma_surf = np.zeros((max_caplets, len(strikes))) caplet_times = (np.arange(1, max_caplets+1) * dt) for j_idx, Tj in enumerate(caplet_times): Fj = f[j_idx] # 对应第 j+1 个远期 for k_idx, K in enumerate(strikes): sigma_surf[j_idx, k_idx] = sabr_implied_vol(Fj, K, Tj, alpha, beta, rho, nu) # 7.2 再算各期限 Cap 的价格 Cap(T_i,K) cap_surf = np.zeros((len(cap_mats), len(strikes))) for i_idx, Ti in enumerate(cap_mats): n_caplets = int(Ti / dt) for k_idx, K in enumerate(strikes): price = 0.0 for j_idx in range(n_caplets): Tj = (j_idx+1) * dt Fj = f[j_idx] vol = sigma_surf[j_idx, k_idx] price += P[j_idx] * dt * black_call(Fj, K, Tj, vol) cap_surf[i_idx, k_idx] = price # 8. 可视化 fig = plt.figure(figsize=(12, 5)) # 8.1 Vol 曲面 ax1 = fig.add_subplot(1, 2, 1, projection='3d') K_mesh, T_mesh = np.meshgrid(strikes, caplet_times) ax1.plot_surface(K_mesh, T_mesh, sigma_surf, cmap='viridis', edgecolor='none') ax1.set_title(f'{title} 的隐含波动率曲面') ax1.set_xlabel('Strike K') ax1.set_ylabel('Caplet 到期 T (年)') ax1.set_zlabel('σ_Black') # 8.2 Cap 价值曲面 ax2 = fig.add_subplot(1, 2, 2, projection='3d') K2_mesh, T2_mesh = np.meshgrid(strikes, cap_mats) ax2.plot_surface(K2_mesh, T2_mesh, cap_surf, cmap='plasma', edgecolor='none') ax2.set_title(f'{title} 的 Cap 价值曲面') ax2.set_xlabel('Strike K') ax2.set_ylabel('Cap 到期 T (年)') ax2.set_zlabel('价格 (单位名义)') plt.tight_layout() plt.show()

关键修正点

  1. SABR 公式前缀
    α(FK)1β2\displaystyle\frac{\alpha}{(F\,K)^{\frac{1-\beta}{2}}},以及小时间展开修正项 [1+AT]\bigl[1+A\,T\bigr] 都必须放在“分子”上。

  2. 到期时间
    jj 个 caplet 的实际到期是 (j+1)Δt(j+1)\Delta t,因此波动率和 Black 定价里都要用 T=(j+1)dtT=(j+1)\,dt

  3. Caplet 索引
    cap of maturity TiT_i 包含前 4Ti4\,T_i 个 caplet(即从 j=1j=1j=4Tij=4T_i),对应 Python 里索引从 0 到 4Ti14T_i-1

这样即可正确绘出隐含波动率曲面和 Cap 价值曲面。

分享这个问答