|
From: Dan V. <dan...@gm...> - 2022-11-17 01:28:09
|
Thank you so much Klaus, this is extremely useful and thorough and
definitely helps a ton !
On Tue, Nov 15, 2022 at 5:32 PM Klaus Spanderen <kl...@sp...> wrote:
> 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))}))
>
>
>
>
>
|