|
From: Dan V. <dan...@gm...> - 2022-12-14 22:52:44
|
Hi Klaus,
Following up on this example, it turns out AH actually produces a very poor
calibration quality when fed many data points (like an SPX vol surface).
Alternatively we have tried to build a classic localvol surface from our
blackvariancesurface which works. However, when generating paths for Monte
Carlo, it appears that many paths have extremely weird jumps (from 2700 to
0.1 for example see below) which makes all valuations completely wrong
clearly.
I was wondering if this is a behavior you have observed before and could
point us in the right direction on something we may be doing wrong ?
We are feeding a SPX volatility surface with many points that would be too
long to reproduce here. Getting it to not break has taken us a lot of
effort as it appears the QL checks are extremely extremely sensitive (in
fact it is almost impossible to actually build a local vol from a
blackvariancesurface that doesn't break).
In the end we managed to get a path generation that works (very similar to
the way you generate the paths below except with a classic local vol
derived from our standard surface instead of the AH one in your code) but
we get these very strange paths which couldn't be explained even with an
insanely broken surface.
I guess my question is dual:
1. Do you know of an obvious mistake people make that leads to this kind of
behavior ?
2. If not, has anyone been able to implement real life pricing with local
vol in QL ? (by real life I mean beyond the 4x4 vol surfaces that are used
in examples that are usually well behaved enough due to the low number of
points)
Thank you so much for your help !
Attached are examples of such paths as described above. All of them have
one of these weird jumps at some point. The ones below have these jumps at
the very beginning.
[image: image.png]
On Thu, Nov 17, 2022 at 4:46 AM Wojciech Slusarski <
woj...@gm...> wrote:
> Klaus,
>
> I agree with Dan, it is very usefull, it deserves posting in
> Python/examples directory.
>
> Regards,
> Wojtek
>
> czw., 17 lis 2022, 02:29 użytkownik Dan VolPM <dan...@gm...>
> napisał:
>
>> 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))}))
>>>
>>>
>>>
>>>
>>>
>> _______________________________________________
>> QuantLib-users mailing list
>> Qua...@li...
>> https://lists.sourceforge.net/lists/listinfo/quantlib-users
>>
>
|