Another Example of an Exogenous Variable (SABR’s Beta)#
The development of exogenous variables in rateslib came with attempting to capture sensitivity to recovery risk for a credit name with CDSs. But, their scope is completely general and they are easily used.
Here we will give a second example of capturing sentivity to the beta parameter in a SABR model for FXOptions. The beta parameter is usually characterised as representing how normal or log-normal the underlying price process is; zero for fully normal and one for fully log-normal with a ranged value representing a blend.
We can plot the variation in a SabrSmile below for differing values.
First we create the FX Forwards market consistent with some of the other FX Option cookbooks.
[1]:
from rateslib import *
from pandas import DataFrame
eur = Curve({dt(2009, 5, 3): 1.0, dt(2011, 5, 10): 1.0})
usd = Curve({dt(2009, 5, 3): 1.0, dt(2011, 5, 10): 1.0})
fxf = FXForwards(
fx_rates=FXRates({"eurusd": 1.34664}, settlement=dt(2009, 5, 5)),
fx_curves={"eureur": eur, "usdusd": usd, "eurusd": eur},
)
fx_solver = Solver(
curves=[eur, usd],
instruments=[
Value(dt(2009, 5, 4), curves=eur, metric="cc_zero_rate"),
Value(dt(2009, 5, 4), curves=usd, metric="cc_zero_rate")
],
s=[1.00, 0.4759550366220911],
fx=fxf,
)
SUCCESS: `func_tol` reached after 4 iterations (levenberg_marquardt), `f_val`: 5.204923799977582e-16, `time`: 0.0029s
Then build and calibrate an FXSabrSmile.
[2]:
fxs = FXSabrSmile(
nodes={"alpha": 0.04, "beta": Variable(0.9, ["beta"]), "rho": 0.00, "nu": 0.01},
eval_date=dt(2009, 5, 3), expiry=dt(2010, 5, 3), pair="eurusd",
)
[3]:
solver = Solver(
pre_solvers=[fx_solver],
curves=[fxs],
instruments=[
FXCall(expiry=dt(2010, 5, 3), pair="eurusd", strike="25d", curves=[None, eur, None, usd], vol=fxs, metric="vol"),
FXCall(expiry=dt(2010, 5, 3), pair="eurusd", strike="atm_delta", curves=[None, eur, None, usd], vol=fxs, metric="vol"),
FXCall(expiry=dt(2010, 5, 3), pair="eurusd", strike="75d", curves=[None, eur, None, usd], vol=fxs, metric="vol"),
],
s=[10.0, 9.0, 9.9],
fx=fxf,
id="options",
)
SUCCESS: `func_tol` reached after 11 iterations (levenberg_marquardt), `f_val`: 2.6994473697e-16, `time`: 0.0323s
[4]:
fxs.plot(f=fxf)
[4]:
(<Figure size 640x480 with 1 Axes>,
<Axes: >,
[<matplotlib.lines.Line2D at 0x1155c07d0>])

Beta sensitivity#
What is the sensitivity if beta changes on this Smile? We can evaluate it numerically for a specific Option. This numeric method involves a finite difference approach, shifting it up and down and revaluing the Option.
Numerically: Finite Difference#
[5]:
fxc = FXCall(expiry=dt(2010, 5, 3), pair="eurusd", strike=1.40, curves=[None, eur, None, usd], vol=fxs, premium=26710)
base_npv = fxc.npv(solver=solver)
base_npv
[5]:
<Dual: 8.139989, (9eedf_0, 9eedf_1, 9eedf_2, ...), [493131.5, 11879.0, 11093.2, ...]>
[6]:
fxs.update_node("beta", 0.91)
solver.iterate()
(fxc.npv(solver=solver) - base_npv) * 100.0
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 4.634801672310094e-13, `time`: 0.0168s
[6]:
<Dual: -77.691655, (9eedf_0, 9eedf_1, 9eedf_2, ...), [151647.0, 2429.1, -944.9, ...]>
[7]:
fxs.update_node("beta", 0.90) # reset
solver.iterate()
fxs.update_node("beta", 0.89)
solver.iterate()
(fxc.npv(solver=solver) - base_npv) * 100.0
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 4.4170014689484205e-13, `time`: 0.0168s
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 4.543107428764549e-13, `time`: 0.0186s
[7]:
<Dual: 77.658269, (9eedf_0, 9eedf_1, 9eedf_2, ...), [-151211.9, -2427.7, 941.7, ...]>
Rateslib AD: exo_delta
#
The result of the finite difference approach is that if beta is increased by one unit the Option will lose in value by c.77.6USD, whilst if it is decreased by one unit the Option will gain in value by c.77.6USD.
This sensitivity can be expressly calculated using exo_delta
. We have injected Variable sensitivity into the calculation process and all of the internal calculations are configured to recognise this.
[8]:
fxs.update_node("beta", Variable(0.9, ["beta"]))
solver.iterate()
fxc.exo_delta(solver=solver, vars=["beta"])
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 4.4838432739613067e-13, `time`: 0.0181s
[8]:
local_ccy | usd | ||
---|---|---|---|
display_ccy | usd | ||
type | solver | label | |
exogenous | options | beta | -77.794966 |