|
From: Klaus S. <kl...@sp...> - 2022-11-15 22:44:38
|
Hi Dan,
please find below an example of a Andreasen-Huge local volatility calibration, a Monte-Carlo pricing of one of the calibration options using this local volatility surface and the comparison with the expected result.
W.r.t. point 1, the GeneralizedBlackScholesProcess should be constructed directly using the local volatility surface from the Andreasen-Huge algorithm (see code below), not the implied volatility surface from the Andreasen-Huge interpolation algorithm. In the latter case the process would use Dupire's equation to get back the local volatility, which is notourisly instable and might lead to arbitrage violations.
I hope the example code below helps wr.t. 2 and 3.
best regards
Klaus
import math
import QuantLib as ql
if __name__ == "__main__":
today = ql.Date.todaysDate()
ql.Settings.instance().evaluationDate = today
spot = ql.QuoteHandle(ql.SimpleQuote(100))
dc = ql.Actual365Fixed()
qTS = ql.YieldTermStructureHandle(ql.FlatForward(today, 0.025, dc))
rTS = ql.YieldTermStructureHandle(ql.FlatForward(today, 0.05, dc))
vol_data = [
# maturity in days, strike, volatility
(30, 75, 0.13),
(30, 100, 0.26),
(30, 125, 0.3),
(180, 80, 0.4),
(180, 150, 0.6),
(365, 110, 0.5)]
calibration_set = ql.CalibrationSet(
[(
ql.VanillaOption(
ql.PlainVanillaPayoff(ql.Option.Call, strike),
ql.EuropeanExercise(today + ql.Period(maturity_in_days, ql.Days))
),
ql.SimpleQuote(volatility)
) for maturity_in_days, strike, volatility in vol_data]
)
local_vol = ql.LocalVolTermStructureHandle(
ql.AndreasenHugeLocalVolAdapter(
ql.AndreasenHugeVolatilityInterpl(calibration_set, spot, rTS, qTS)
)
)
option = calibration_set[-2][0] # maturity in days: 180, strike: 150, vol: 0.6
dummy_vol = ql.BlackVolTermStructureHandle()
local_vol_process = ql.GeneralizedBlackScholesProcess(spot, qTS, rTS, dummy_vol, local_vol)
option.setPricingEngine(ql.MCEuropeanEngine(
local_vol_process, "lowdiscrepancy",
timeSteps=100, brownianBridge=True, requiredSamples=32000, seed=42)
)
T = dc.yearFraction(today, option.exercise().lastDate())
fwd = spot.value() * qTS.discount(T) / rTS.discount(T)
vol = calibration_set[-2][1].value()
expected = ql.BlackCalculator(
ql.as_plain_vanilla_payoff(option.payoff()), fwd, vol * math.sqrt(T), rTS.discount(T)).value()
print("Expected : %.3f" % expected)
print("Local Vol Monte-Carlo: %.3f" % option.NPV())
time_steps = 100
rsg = ql.GaussianRandomSequenceGenerator(
ql.UniformRandomSequenceGenerator(time_steps, ql.UniformRandomGenerator(42))
)
path_generator = ql.GaussianPathGenerator(local_vol_process, 1.0, time_steps, rsg, False)
next_path = path_generator.next().value()
print("\nExample path: " + str({next_path.time(i): next_path.value(i) for i in range(len(next_path))}))
|