I want to make sure that I can properly use SABR model on 1-period interest rate options, i.e. caplets, therefore I attempted to get lognormal volatilities for 4%, 6%, ATM, 8%, 10% strikes for 3Mx6M caps on RUB MOSKP3 index with IRS ICVS179 RUB as both discounting and projecting curve and fit SABR model to corresponding volatility smile. I use pysabr library for SABR model implementation in python.
The Bloomberg data is as follows:
capStrikes = [0.04, 0.06, 0.0745136, 0.08, 0.10]
capVols = [23.52/100.00, 16.24/100.00, 18.35/100.00, 20.17/100.00, 26.19/100.00]
IRS curve = [1WK 0.0713, 1MO 0.0722, 2MO 0.0732, 3MO 0.0747, 6MO 0.0753, 1YR 0.0789]
I think this is all that I need to more or less fit the model to Bloomberg quotes. This is an example of the interface that I'm trying to use:
from pysabr import Hagan2002LognormalSABR
# Forward = 2.5%, Shift = 3%, ATM Normal Vol = 40bps
# Beta = 0.5, Rho = -20%, Volvol = 0.30
sabr = Hagan2002LognormalSABR(f=0.025, shift=0.03, t=1., v_atm_n=0.0040,
beta=0.5, rho=-0.2, volvol=0.30)
k = 0.025
sabr.lognormal_vol(k) * 100
# returns 7.27
sabr.normal_vol(k) *1e4
# returns 40
However I can't properly fit the smile. Please see my code below:
import pysabr
from pysabr import Hagan2002LognormalSABR as LNsabr
import numpy as np
testStrikes = np.array([0.04, 0.06, 0.0745136, 0.08, 0.10])
testVols = np.array([23.52/100.00, 16.24/100.00, 18.35/100.00, 20.17/100.00, 26.19/100.00])
forward_3m_6m = (1/0.25) * (-1 + (1+0.0753*0.5) / (1+0.0747*0.25))
calibration = LNsabr(f = forward_3m_6m, shift = 0, t = 0.5, beta = 0.5).fit(testStrikes, testVols)
smile = []
for strike in testStrikes:
smile.append(LNsabr(f = forward_3m_6m, shift = 0, t = 0.5, v_atm_n = 136.75/10000.00, beta = 0.5, rho = calibration[1], volvol = calibration[2]).lognormal_vol(strike) * 100.00)
print(smile)
The SABR implied lognormal results are pretty far away from the initial volatilities.
[21.331968636265458, 19.365310362267866, 18.36618998284387, 18.04637126862935, 17.06699480954601]
What am I doing wrong? Why my results are so far from the quotes? I will be very grateful if someone would show me a proper way to fit the Bloomberg smile using pysabr.
EDIT: I'm looking for either a fix to my code which will do a better SABR fit or a qualitative explanation why the obtained fit is that bad. Alternatively one can show me an example of fitting a pysabr to any other Bloomberg SWPM cap/floor smile given 4-5 strikes and their volatilities.