Please solve this problem and provide the python c...
创建于:2025年5月2日
使用 O4 Mini High 回答 Chat01
创建于:2025年5月2日
使用 O4 Mini High 回答 Chat01
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/(sigsqrt(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
textT=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
textdt=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
textbeta = 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/alphalog(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)=coefsig_B(i);
end
return
textfunction 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;
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:
Finally we plot, for each parameter set:
pythonimport 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()
Term‐rate curve
We place 120 quarterly forward rates evenly from 5.25% down to 4.75%, and build discount factors
Black‐76 caplet pricer
The function black_call(F,K,T,σ)
implements
Hagan SABR formula
In sabr_implied_vol
we code the standard small‐time expansion plus the correction:
with
The ATM special case is handled separately.
Building the surfaces
Visualization
We use Matplotlib’s 3D plotting to show, side by side for each parameter set,
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
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 - signp.sqrt(T)
return F * 0.5*(1 + erf(d1/np.sqrt(2))) - K * 0.5*(1 + erf(d2/np.sqrt(2)))
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))
textxz = 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
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 + dtf[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()
下面我对两份代码做了对比和检查,结论是:
我最初给出的代码
你贴过来的备用代码
pythonimport 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) = ∏_{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[(√(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()
SABR 公式前缀
,以及小时间展开修正项 都必须放在“分子”上。
到期时间
第 个 caplet 的实际到期是 ,因此波动率和 Black 定价里都要用 。
Caplet 索引
cap of maturity 包含前 个 caplet(即从 到 ),对应 Python 里索引从 0 到 。
这样即可正确绘出隐含波动率曲面和 Cap 价值曲面。