|
From: Amine I. <ami...@gm...> - 2021-04-03 09:40:09
|
Hi Philippe, To be able to fully reconcile market and MC price for your swaption, your model needs to be calibrated to market prices. Also, the scenarios generated need to be consistent with the numéraire you use for discounting. If you are simulating under risk neutral measure, then your scenarios need to be consistent with: E[D(0,T1)D(T1,T2)] = B(0,T2) for all 0<=T1<= T2 where E[] is the average over your scenarios. Maybe you could enforce this condition as part of your minimisation of model/market value ? That’s my 2c on the subject. Hope it helps. Amine Ifri > On 3 Apr 2021, at 04:12, Philippe Hatstadt <phi...@ex...> wrote: > > > I was able to build a Jupyter Notebook that generates one factor Hull-White paths. I checked that the libor setting does change on each path and it does. > I build a swaption (swaption object below) using the standard method from the Cookbook, as a 1y maturity into a 3y swap based on US 30/360 and 3m USDLibor index. I use sigma and mean rev both equal to 0.10, and a flat forward rate at 5%. > The closed-form Jamshidian pricing comes out at 0.91mm on notional of 10mm. My problem is that the MC value is too high by about 25%, at 1.2mm. I price the swaption with the code below, with 10,000 paths and 10 day timestep. > I checked manually that for all paths, swap.NPV() is correct, which returns the PV as seen as the trade date of a forward starting swap on each path-dependent curve. I was wondering if I am supposed to apply some additional numeraire adjustment? Since swap.NPV() computes the NPV of the swap with full path-wise discounting and forecasting curve, I consider that the Annuity Measure (At) (which makes the forward swap rate a martingale under the At measure), is embedded in the swap.NPV() and therefore I do not need to do anything, or do I? > > MC Python Code for MC Swaption > ---------------------------------------------------------------------------------------------------------- > sum_pv = 0 > ql.Settings.instance().evaluationDate = trade_date > swap = swaption.underlyingSwap() > exercise_date = swaption.exercise().date(0) > for i in range(num_paths): > curve = path_discount_curve(i) > forecastHandle.linkTo(curve) > engine = ql.DiscountingSwapEngine(forecastHandle) > swap.setPricingEngine(engine) > swap_npv = swap.NPV() > sum_pv += max(0, swap_npv) > return sum_pv / num_paths > > > Philippe Hatstadt > > >> On Fri, Apr 2, 2021 at 12:03 PM Luigi Ballabio <lui...@gm...> wrote: >> Hello, >> I would have guessed that passing the forecastHandle to the index (as you did) and relinking it would change the fixings as you expect. I understand your code is probably proprietary, but is there any chance you can post a simplified or abridged version we can run to reproduce the issue? If not, try returning the index as well from the function that builds the swaption, so after relinking the curve you can check if its fixings change or not, or if the curve it contains is actually the one you linked. >> >> Hope this helps, >> Luigi >> >> >>> On Fri, Apr 2, 2021 at 1:05 PM Philippe Hatstadt <phi...@ex...> wrote: >>> Thank you. I’m not sure about the exact steps to clone the libor index? I assume I would create an index inside the MC loop and link its curve to my stochastic curve, but how do I “assign” such index to a swap that already exists? >>> >>> Regards >>> >>> Philippe Hatstadt >>> +1-203-252-0408 >>> https://www.linkedin.com/in/philippe-hatstadt >>> >>> >>>>> On Apr 2, 2021, at 3:23 AM, Amine Ifri <ami...@gm...> wrote: >>>>> >>>> Hi Philippe, >>>> >>>> I believe your forecastHandle variable - which is set to the MC curve generated for scénario i - is actually used for discounting only. discountingSwapEngine only affects the discounting and not the curves upon which the floating index is dependent. >>>> >>>> You need to “clone” the libor 3m index for your underlying swap and relink its curve to a MC curve as well. >>>> >>>> Amine Ifri >>>> >>>>>> On 2 Apr 2021, at 04:11, Philippe Hatstadt <phi...@ex...> wrote: >>>>>> >>>>> >>>>> I have built a Hull-White sequence via standard method, by following precisely the method in the QuantLib Cookbook, with sigma = 10% and mean_rev = 10%. >>>>> My goal is to ultimately build an OAS model for some Agency CMOs, for which I am building a Monte-carlo engine. >>>>> I also built a Jamshidian engine to compute a closed-form value of a european swaption as a test, to make sure that my MC valuation converges to the theoretical value. >>>>> The convergence doesn't work, and I think it has to do with the floating index of the swaption, so let me explain. >>>>> >>>>> I first build a curve name forecastHandle of type ql.RelinkableYieldTermStructureHandle >>>>> I then build a swaption object via my own build_swaption() function, along the lines of the swaption helpers from the Cookbook approach, which returns a ql.Swaption() object. The curve handle is passed to the build_swaption() call along with tenor, maturity and strike. >>>>> I build the floating index as follows inside the function: >>>>> libor_3m = ql.USDLibor(ql.Period('3M'), forecastHandle) >>>>> >>>>> Now switching to the MC calculation. I loop on all the sequences of short-term rates generated by my HW sequence. I verified that the expected value and the variance of the short-rate are the same as in the Cookbook. >>>>> Lastly, in order to calculate the value of the swaption via MC integration, I do the following, which takes place inside a function called swaption_MC(forecastHandle, my_swaption). Importantly, forecastHandle is the same handle that was used to build the swaption, including its libor_3m index. >>>>> for i in range(num_paths): >>>>> curve = hw_discount_curve(i) >>>>> forecastHandle.linkTo(curve) >>>>> engine = ql.DiscountingSwapEngine(forecastHandle) >>>>> swap = my_swaption.underlyingSwap() >>>>> swap.setPricingEngine(engine) >>>>> swap_npv = swap.NPV() >>>>> sum_pv += max(0, swap_npv) >>>>> return sum_pv / num_paths >>>>> I was therefore hoping that by linking the forecastHandle to each path-wise stochastic curve, the libor 3m index would also be path dependent. but somehow, that doesn't appear to be the case, as the floating rates for each reset do not change with each stochastic curve. So I am wondering what I am doing wrong? >>>>> >>>>> Help appreciated. >>>>> >>>>> Philippe Hatstadt >>>>> >>>>> >>>>> >>>>> Broker-Dealer services offered through Exos Securities LLC, member of SIPC / FINRA / BrokerCheck / 2021 Exos, inc. For important disclosures, click here. >>>>> >>>>> _______________________________________________ >>>>> QuantLib-users mailing list >>>>> Qua...@li... >>>>> https://lists.sourceforge.net/lists/listinfo/quantlib-users >>>> _______________________________________________ >>>> QuantLib-users mailing list >>>> Qua...@li... >>>> https://lists.sourceforge.net/lists/listinfo/quantlib-users >>> >>> >>> >>> Broker-Dealer services offered through Exos Securities LLC, member of SIPC / FINRA / BrokerCheck / 2021 Exos, inc. For important disclosures, click here. >>> >>> _______________________________________________ >>> QuantLib-users mailing list >>> Qua...@li... >>> https://lists.sourceforge.net/lists/listinfo/quantlib-users > > > > Broker-Dealer services offered through Exos Securities LLC, member of SIPC / FINRA / BrokerCheck / 2021 Exos, inc. For important disclosures, click here. > |