Edit: I have added a simple SABR calibration routine (employing RSS) I use to illustrate what I mean below.
def sabr_calibration(swvolcube,a,b,calculation_date,alphas,tolerance):
print('for '+str(a.normalized())+str(b.normalized())+' optimizing alpha to fit sabr to atm vols ...')
T=SABR(swvolcube,a,b,calculation_date,'T')
f=SABR(swvolcube,a,b,calculation_date,'f')
atm_sabr_vol=SABR(swvolcube,a,b,calculation_date,'atm_sabr_vol')
atm_vol=SABR(swvolcube,a,b,calculation_date,'atm_vol')
atm_smile=SABR(swvolcube,a,b,calculation_date,'atm_smile')
strike_spreads=SABR(swvolcube,a,b,calculation_date,'strike_spreads')
atm_strike_set=[f+i for i in strike_spreads]
beta=SABR(swvolcube,a,b,calculation_date,'beta')
nu=SABR(swvolcube,a,b,calculation_date,'nu')
rho=SABR(swvolcube,a,b,calculation_date,'rho')
cubic0=-atm_vol*(f**(-beta))
cubic1=(1+((2-3*(rho**2))/24)*(nu**2)*T)
cubic2=(rho*beta*nu*T)/(4*(f**(1-beta)))
cubic3=(beta*(beta-2)*T)/(24*(f**(2-2*beta)))
coeff=[cubic3,cubic2,cubic1,cubic0]
roots=[np.roots(coeff)[i] for i in range(0,len(np.roots(coeff)))]
positive_roots=[i for i in roots if i>0]
positive_roots.sort()
root=positive_roots[0]
dummy_alpha = alphas.loc[str(a.normalized()).lower(), str(b.normalized()).lower()]
dummy_nu = nus.loc[str(a.normalized()).lower(), str(b.normalized()).lower()]
dummy_rho = rhos.loc[str(a.normalized()).lower(), str(b.normalized()).lower()]
if abs(atm_vol-atm_sabr_vol)>tolerance:
dummy_alpha.setValue(root)
sabr_calibration(swvolcube, a, b, calculation_date,alphas,tolerance)
else:
print('atm vol = ' + str(atm_vol))
print('atm sabr vol = '+str(atm_sabr_vol))
print('optimized alpha = '+str(dummy_alpha.value()))
print('error is = ' + str(abs(atm_vol - atm_sabr_vol)))
print('calibrating nu and rho ...')
params = np.array([nu, rho])
def calib(params):
vols = np.array([
ql.sabrVolatility(strike, f, T, dummy_alpha.value(), beta, *params, vol_type)
for strike in atm_strike_set
])
return ((vols - np.array(atm_smile)) ** 2).mean() ** .5
cons = (
{'type': 'ineq', 'fun': lambda x: 0.999 + x[1]},
{'type': 'ineq', 'fun': lambda x: 0.999 - x[1]},
{'type': 'ineq', 'fun': lambda x: x[0] - 1e-15},
)
result = minimize(calib, params,constraints=cons)
new_params = result['x']
nu = new_params[0]
rho = new_params[1]
dummy_nu.setValue(nu)
dummy_rho.setValue(rho)
atm_sabr_vol = SABR(swvolcube, a, b, calculation_date, 'atm_sabr_vol')
atm_vol = SABR(swvolcube, a, b, calculation_date, 'atm_vol')
if abs(atm_vol - atm_sabr_vol) > tolerance:
print('re-calibrating ...')
sabr_calibration(swvolcube, a, b, calculation_date, alphas, tolerance)
return dummy_alpha.value(),nu,rho,beta