|
From: Peter C. <pca...@gm...> - 2022-08-12 12:22:49
|
Hi Aditya 2 the error shouldn't show up unless you pass a volatilityType to the pricer. Might have to do with how SWIG calls the constructor, not sure at the moment. 3 what I meant was: the cms price depends on the volatilities outside the quoted part ATM-200 / ATM+200 as well, so maybe BBG uses different extrapolated vols than you do. And as I said, the model itself (and the mean reversion value) have a big impact, so you might first want check what BBG is doing Thanks Peter On Fri, 12 Aug 2022 at 10:43, Aditya Gupta <adi...@co...> wrote: > Hi Peter, > > > > For point 2) the LinearTsrPricer can take the input as > ql.ConstantSwaptionVolatilityStructure, but the Lognormal pricer complains > by saying: " if only an atm surface is given, the volatility type must be > inherited" So I guess it is not possible. To create a ‘constant volatility > cube’, I instantiated an atmMatrix with a constant value, but all the OTM > and ITM swaptions had a volatility of 0. This way, for every triplet > (strike, expiry, tenor), QL would return a constant volatility. It is a > workaround, but it sufficed for my purpose. > > > > For 3) The code and data for the extrapolation is below. Some notes: > > > > volHandle at the end is passed into the pricer. The data is not complete > but should be enough for debugging purposes. The BBG/QL volatility cubes > seem to match to a decent degree (i.e. I don’t believe it is the source of > the error). The volatilities are within 10 bps when testing random data > points. However, the atm strike does not change (even if the volatility is > constant) and does not agree with Bloomberg. > > > > normalVolCube = > > Expiry > > 1Yr > > 2Yr > > 3Yr > > 4Yr > > 5Yr > > 7Yr > > 10Yr > > 12Yr > > 15Yr > > 20Yr > > 25Yr > > 30Yr > > 1Mo > > 153.65 > > 160.51 > > 151.81 > > 143.8 > > 141.03 > > 129.88 > > 123.16 > > 118.88 > > 110.61 > > 108.9 > > 104.21 > > 101.91 > > 3Mo > > 150.06 > > 152.12 > > 149.09 > > 140.08 > > 134.79 > > 125.74 > > 118.65 > > 115.21 > > 112.26 > > 107.13 > > 103.41 > > 99.43 > > 6Mo > > 154.30 > > 150.74 > > 143.9 > > 137.09 > > 132.84 > > 123.31 > > 115.03 > > 111.31 > > 107.88 > > 103.41 > > 100.5 > > 96.31 > > 9Mo > > 155.29 > > 149.86 > > 142.87 > > 135.17 > > 130.97 > > 121.33 > > 112.06 > > 108.57 > > 105.04 > > 99.95 > > 97.12 > > 93.19 > > 1Yr > > 155.98 > > 149.11 > > 140.78 > > 133.39 > > 127.82 > > 118.13 > > 109.42 > > 106.32 > > 102.06 > > 97.15 > > 94.05 > > 90.8 > > 2Yr > > 142.61 > > 135.71 > > 129.64 > > 123.34 > > 118.42 > > 110 > > 102.05 > > 99.13 > > 95.18 > > 89.95 > > 86.93 > > 84.38 > > 3Yr > > 130.05 > > 124.03 > > 117.71 > > 113.72 > > 109.87 > > 102.87 > > 96.37 > > 93.46 > > 89.5 > > 84.21 > > 81.15 > > 79.07 > > 4Yr > > 118.85 > > 113.22 > > 108.13 > > 104.87 > > 102.41 > > 97.49 > > 91.33 > > 88.78 > > 85.31 > > 79.42 > > 77.09 > > 74.93 > > 5Yr > > 109.73 > > 104.94 > > 102.2 > > 99.09 > > 96.85 > > 92.53 > > 87.38 > > 84.84 > > 81.37 > > 76.05 > > 73.36 > > 72.24 > > 6Yr > > 102.35 > > 99.42 > > 97.38 > > 94.42 > > 92.18 > > 88.36 > > 83.64 > > 81.19 > > 77.88 > > 72.88 > > 70.54 > > 69.33 > > 7Yr > > 96.52 > > 93.55 > > 91.44 > > 89.36 > > 87.41 > > 84.15 > > 79.99 > > 77.68 > > 74.58 > > 70.13 > > 68.25 > > 66.85 > > 8Yr > > 91.55 > > 89.03 > > 87.38 > > 85.58 > > 83.78 > > 80.83 > > 77.08 > > 74.82 > > 71.81 > > 67.42 > > 65.22 > > 64.37 > > 9Yr > > 86.91 > > 85.11 > > 83.57 > > 81.89 > > 80.19 > > 77.48 > > 74.07 > > 71.89 > > 68.99 > > 64.76 > > 62.82 > > 62.05 > > 10Yr > > 83.39 > > 81.29 > > 79.55 > > 77.84 > > 76.4 > > 74.03 > > 71.19 > > 69.2 > > 66.54 > > 62.56 > > 60.74 > > 60.1 > > 12Yr > > 77.24 > > 75.44 > > 74 > > 72.38 > > 70.97 > > 69.1 > > 66.47 > > 64.68 > > 62.27 > > 58.88 > > 57.3 > > 56.85 > > 15Yr > > 68.34 > > 67.24 > > 65.61 > > 64.58 > > 64.34 > > 62.11 > > 60.25 > > 58.73 > > 56.7 > > 53.89 > > 52.72 > > 52.28 > > 20Yr > > 60.48 > > 60.35 > > 59.15 > > 58.46 > > 57.75 > > 56.64 > > 55.07 > > 53.74 > > 51.97 > > 49.87 > > 48.96 > > 48.63 > > 25Yr > > 58.04 > > 56.62 > > 55.74 > > 55.16 > > 54.62 > > 53.59 > > 52.25 > > 51.07 > > 49.5 > > 47.49 > > 46.72 > > 46.08 > > 30Yr > > 54.83 > > 53.31 > > 52.73 > > 52.34 > > 51.96 > > 51.06 > > 49.7 > > 48.76 > > 47.52 > > 45.21 > > 44.11 > > 44.1 > > > > > > strikeSpreadData Snippet: > > Term x Tenor > > -200bps > > -100bps > > -50bps > > -25bps > > ATM > > 25bps > > 50bps > > 100bps > > 200bps > > 3Mo X 1Yr > > 163.06 > > 147.61 > > 145.65 > > 147.02 > > 150.06 > > 154.64 > > 160.47 > > 174.74 > > 208.13 > > 3Mo X 2Yr > > 168.32 > > 152.63 > > 149.59 > > 150.1 > > 152.12 > > 155.6 > > 160.36 > > 172.72 > > 203.32 > > 3Mo X 3Yr > > 165.94 > > 150.14 > > 146.87 > > 147.22 > > 149.09 > > 152.42 > > 157.05 > > 169.18 > > 199.38 > > 3Mo X 5Yr > > 154.44 > > 137.68 > > 133.49 > > 133.36 > > 134.79 > > 137.76 > > 142.09 > > 153.73 > > 182.89 > > 3Mo X 7Yr > > 145.98 > > 128.92 > > 124.5 > > 124.31 > > 125.74 > > 128.76 > > 133.16 > > 144.93 > > 173.96 > > 3Mo X 10Yr > > 141.64 > > 123.6 > > 118.33 > > 117.66 > > 118.65 > > 121.3 > > 125.42 > > 136.78 > > 165.14 > > 3Mo X 12Yr > > 124.87 > > 115.74 > > 113.96 > > 114.17 > > 115.21 > > 117.06 > > 119.67 > > 126.74 > > 145.33 > > 3Mo X 15Yr > > 137.53 > > 118.56 > > 112.58 > > 111.56 > > 112.26 > > 114.72 > > 118.73 > > 130 > > 158.17 > > 3Mo X 20Yr > > 133.96 > > 114.4 > > 107.91 > > 106.63 > > 107.13 > > 109.46 > > 113.42 > > 124.65 > > 152.72 > > 3Mo X 25Yr > > 130.47 > > 110.79 > > 104.19 > > 102.88 > > 103.41 > > 105.83 > > 109.9 > > 121.34 > > 149.56 > > 3Mo X 30Yr > > 126.66 > > 106.9 > > 100.19 > > 98.87 > > 99.43 > > 101.94 > > 106.13 > > 117.77 > > 146.12 > > 6Mo X 1Yr > > 153.08 > > 147.26 > > 148.88 > > 151.13 > > 154.3 > > 158.3 > > 163.02 > > 174.1 > > 199.93 > > 6Mo X 2Yr > > 155.61 > > 147.49 > > 147.3 > > 148.56 > > 150.74 > > 153.82 > > 157.69 > > 167.34 > > 191.18 > > 6Mo X 3Yr > > 151.41 > > 141.74 > > 140.85 > > 141.85 > > 143.9 > > 146.93 > > 150.84 > > 160.73 > > 185.27 > > 6Mo X 5Yr > > 144.96 > > 132.89 > > 130.7 > > 131.19 > > 132.84 > > 135.64 > > 139.46 > > 149.43 > > 174.44 > > 6Mo X 7Yr > > 137.51 > > 124.48 > > 121.67 > > 121.87 > > 123.31 > > 125.95 > > 129.67 > > 139.51 > > 164.24 > > 6Mo X 10Yr > > 131.51 > > 117.49 > > 113.98 > > 113.86 > > 115.03 > > 117.47 > > 121.05 > > 130.73 > > 155.14 > > 6Mo X 12Yr > > 119.63 > > 111.38 > > 109.93 > > 110.24 > > 111.31 > > 113.15 > > 115.68 > > 122.45 > > 140.14 > > 6Mo X 15Yr > > 126.39 > > 111.49 > > 107.36 > > 106.95 > > 107.88 > > 110.16 > > 113.64 > > 123.22 > > 147.39 > > 6Mo X 20Yr > > 123.32 > > 107.85 > > 103.29 > > 102.66 > > 103.41 > > 105.58 > > 109 > > 118.53 > > 142.61 > > 6Mo X 25Yr > > 120.86 > > 105.2 > > 100.48 > > 99.78 > > 100.5 > > 102.67 > > 106.1 > > 115.7 > > 139.82 > > > > > > # ATM Volatility matrix > > volType = ql.Normal > > flatExtrapolation = False > > > > atmSwapTenors = [ql.Period(tenor[:-1]) for tenor in > normalVolCube.columns[1:]] > > atmOptionTenors = [ql.Period(tenor[:-1]) for tenor in > normalVolCube['Expiry']] > > > > normalVols = normalVolCube[normalVolCube.columns[1:]].apply( > > lambda x: x * 1e-4).values.tolist() > > > > swaptionVolMatrix = ql.SwaptionVolatilityMatrix(calendar, convention, > > atmOptionTenors, > atmSwapTenors, > > ql.Matrix(normalVols), > > dayCounter, > flatExtrapolation, > > volType) > > > > swaptionVolMatrixHandle = ql.SwaptionVolatilityStructureHandle( > > swaptionVolMatrix) > > > > strikeSpreads = [-200, -100, -50, -25, 0, 25, 50, 100, 200] > > strikeSpreads = [x * 1e-4 for x in strikeSpreads] > > > > optionTenors = [ > > ql.Period(3, ql.Months), > > ql.Period(6, ql.Months), > > ql.Period(9, ql.Months), > > ql.Period(1, ql.Years), > > ql.Period(3, ql.Years), > > ql.Period(5, ql.Years), > > ql.Period(7, ql.Years), > > ql.Period(10, ql.Years), > > ql.Period(15, ql.Years), > > ql.Period(20, ql.Years), > > ql.Period(30, ql.Years) > > ] > > > > swapTenors = [ > > ql.Period(1, ql.Years), > > ql.Period(2, ql.Years), > > ql.Period(3, ql.Years), > > ql.Period(5, ql.Years), > > ql.Period(7, ql.Years), > > ql.Period(10, ql.Years), > > ql.Period(12, ql.Years), > > ql.Period(15, ql.Years), > > ql.Period(20, ql.Years), > > ql.Period(25, ql.Years), > > ql.Period(30, ql.Years) > > ] > > > > nRows = len(optionTenors) * len(swapTenors) > > nCols = len(strikeSpreads) > > > > volSpreadsMatrix = strikeSpreadData[strikeSpreadData.columns[1:]].apply( > > lambda x: x).values.tolist() > > > > volSpreads = [] > > > > for i in range(nRows): > > volSpreadsRow = [] > > for j in range(nCols): > > volSpreadsRow.append( > > ql.QuoteHandle(ql.SimpleQuote(volSpreadsMatrix[i][j]))) > > > > volSpreads.append(volSpreadsRow) > > > > vegaWeightedSmileFit = False > > > > volCube = ql.SwaptionVolCube2(swaptionVolMatrixHandle, optionTenors, > > swapTenors, strikeSpreads, volSpreads, > > swapIndex1, swapIndex2, vegaWeightedSmileFit) > > > > volHandle = ql.SwaptionVolatilityStructureHandle(volCube) > > volHandle.enableExtrapolation() > > > > Thank you! > > > > -----Original Message----- > From: Peter Caspers <pca...@gm...> > Sent: Friday, August 12, 2022 4:20 PM > To: Aditya Gupta <adi...@co...> > Cc: qua...@li... > Subject: Re: [Quantlib-users] CMS Spread Cap Pricing > > > > [You don't often get email from pca...@gm.... Learn why this is > important at https://aka.ms/LearnAboutSenderIdentification ] > > > > CAUTION: EXTERNAL Sender > > > > > > Hi Aditya > > > > 1. correct > > 2. the pricer takes any SwaptionVolatilityStructure, i.e. a > ConstantSwaptionVolatilityStructure should work. Is that what you mean? > > 3. this might be related to smile extrapolation, which strike range do you > cover in your swaption cube and how do you set up the extrapolation? The > pricer uses an integration over [-200%, +200%] by default. > > 4. I agree 500% is much to high, let's discuss further once 3 is resolved, > i.e. QL / BBG matches > > > > In addition, the linear TSR model might not work well when the time to > expiry is large. Do you have more info on which model BBG uses? > > > > Thank you > > Peter > > > > On Fri, 12 Aug 2022 at 09:36, Aditya Gupta <adi...@co...> > wrote: > > > > > > Hi, > > > > > > > > > > > > I am trying to price a CMS Spread Cap using Quantlib. I am using data > from Bloomberg, and I am using the following classes: > > > > > > > > > > > > yts is a bootstrapped Sofr curve > > > > > > Instrument = ql.CappedFlooredCmsSpreadCoupon() > > > > > > volCube = ql.SwaptionVolCube2() with the correct data and volType = > > > ql.Normal > > > > > > cmsPricer = ql.LinearTSRPricer(volHandle, meanReversion, yts) > > > > > > pricer = ql.LognormalCmsSpreadPricer(cmsPricer, correlation, yts, 16) > > > > > > > > > > > > I have a few questions. > > > > > > > > > > > > It seems to me that to price a ql. CappedFlooredCmsSpreadCoupon, the > only option is to use a ql.LognormalCmsSpreadPricer, and hence if we use > Normal volatility, the input cmsPricer must be the LinearTSRPricer (Hagan > cannot be used). Is my assessment correct? > > > In order to check the pricing, I used a ‘constant’ volatility cube (set > the atm matrix to a constant, rest to 0) and tested the pricing. The > instrument was priced correctly (within the error from the discount curve). > Can the pricer accept a constant volatility otherwise? > > > The volCube seems to be returning the correct volatilities (within > reason) when compared to Bloomberg, and is being passed to the pricer > without error. But for some reason, the price is completely different > (about half of what it should be). What could be the cause for the error, > or how could I go about debugging this? > > > If I use scipy.optimize to ‘artificially’ optimize the price as a > function of the mean reversion, for a value of around 5 (which is way too > high for the mean correlation), I was able to bring the error down to less > than 1 dollar. Clearly this approach is not sensible. So I used the > HullWhite model on the swaption matrix, and calibrated the mean reversion. > But the error for this value of the mean reversion is much too high. How > can I determine a good value for the mean reversion? > > > > > > > > > > > > Right now I am content with there being some error compared to > Bloomberg, but I am unable to understand the blackbox behind the pricing > engine and the mean reversion calibration, and if there is any way to solve > these issues. Any and all help would be greatly appreciated. Thank you. > > > > > > > > > > > > Some of the code and data is as follows: > > > > > > > > > > > > today = ql.Date(25, 7, 2022) > > > > > > ql.Settings.instance().evaluationDate = today > > > > > > > > > > > > #Global variables > > > > > > calendar = ql.UnitedStates(ql.UnitedStates.FederalReserve) > > > > > > convention = ql.ModifiedFollowing > > > > > > endOfMonth = False > > > > > > dayCounter = ql.Actual360() > > > > > > fixingDays = 0 > > > > > > compounding = ql.Compounded > > > > > > compoundingFrequency = ql.Annual > > > > > > > > > > > > yts = ql.RelinkableYieldTermStructureHandle() > > > > > > index = ql.Sofr(yts) > > > > > > > > > > > > helper = [] > > > > > > > > > > > > # sofrData are just the rates from a dataframe > > > > > > > > > > > > helper += [ > > > > > > ql.DepositRateHelper( > > > > > > ql.QuoteHandle(ql.SimpleQuote(sofrData['Shifted Rate'][0] / > > > 100.0)), > > > > > > index) > > > > > > ] > > > > > > > > > > > > helper += [ > > > > > > ql.OISRateHelper(0, > > > > > > ql.Period(expiration), > > > > > > ql.QuoteHandle(ql.SimpleQuote(rate / 100.0)), > > > > > > index, > > > > > > paymentLag=0, > > > > > > paymentConvention=convention, > > > > > > paymentFrequency=ql.Annual, > > > > > > paymentCalendar=calendar, > > > > > > averagingMethod=ql.RateAveraging.Compound) for > > > rate, > > > > > > expiration in zip(sofrData['Shifted Rate'][1:], > > > sofrData["Term"][1:]) > > > > > > ] > > > > > > > > > > > > curve = ql.PiecewiseSplineCubicDiscount(today, helper, ql.Actual360()) > > > > > > curve.enableExtrapolation() > > > > > > yts.linkTo(curve) > > > > > > > > > > > > meanReversion = ql.QuoteHandle(ql.SimpleQuote(5.037702498779181)) > > > #this is the ‘optimised’ value but I need further help > > > > > > correlation = ql.QuoteHandle(ql.SimpleQuote(36.6 * 1e-4)) > > > > > > > > > > > > nominal = 1e9 > > > > > > gearing = 1.0 > > > > > > spread = 0.0 > > > > > > cap = -8 * 1e-4 > > > > > > floor = ql.nullDouble() #infinite floor > > > > > > > > > > > > startDate = ql.Date(26, 9, 2022) > > > > > > endDate = ql.Date(26, 9, 2023) > > > > > > paymentDate = ql.Date(26, 9, 2023) > > > > > > > > > > > > isInArrears = False > > > > > > exCouponDate = ql.Date() > > > > > > > > > > > > fixingDays = 0 > > > > > > > > > > > > swapIndex1 = ql.UsdLiborSwapIsdaFixAm(ql.Period('30y'), yts, yts) > > > > > > ql.IndexManager.instance().clearHistory(swapIndex1.name()) > > > > > > > > > > > > swapIndex2 = ql.UsdLiborSwapIsdaFixAm(ql.Period('10y'), yts, yts) > > > > > > ql.IndexManager.instance().clearHistory(swapIndex2.name()) > > > > > > > > > > > > spreadIndex = ql.SwapSpreadIndex("Joint Index", swapIndex1, > > > swapIndex2) > > > > > > > > > > > > instrument = ql.CappedFlooredCmsSpreadCoupon(paymentDate, nominal, > > > startDate, > > > > > > endDate, fixingDays, > > > spreadIndex, > > > > > > gearing, spread, cap, > > > floor, > > > > > > ql.Date(), ql.Date(), > > > dayCounter, > > > > > > isInArrears, > > > exCouponDate) > > > > > > > > > > > > cmsPricer = ql.LinearTsrPricer(volHandle, meanReversion, yts) > > > > > > > > > > > > # cmsPricer = ql.NumericHaganPricer(volHandle, > > > ql.GFunctionFactory.ParallelShifts , meanReversion)#, Rate > > > lowerLimit=0.0, Rate upperLimit=1.0, Real precision=1.0e-6, Real > > > hardUpperLimit=QL_MAX_REAL) > > > > > > > > > > > > integrationPoints = 16 > > > > > > > > > > > > pricer = ql.LognormalCmsSpreadPricer(cmsPricer, correlation, yts, > > > > > > integrationPoints) > > > > > > > > > > > > instrument.setPricer(pricer) > > > > > > > > > > > > Regards, > > > > > > Aditya Gupta. > > > > > > > > > > > > ________________________________ > > > Complus Asset Management Limited ("CAML") is licensed by the Securities > and Futures Commission of Hong Kong (“SFC”) for the Regulated Activities of > Advising on Securities and Asset Management with CE number AWX256. CAML is > subject to certain SFC codes and regulations, details of which can be found > on the SFC's website at http://www.sfc.hk. CAML is a company > incorporated in Hong Kong with its registered office and principal place of > business as indicated above. > > > > > > Unless specifically indicated, this message should not be construed as > an offer to sell or solicitation to purchase any securities, financial > products or services or the giving of investment advice within or outside > Hong Kong. This message is intended solely for the person(s) to whom it is > addressed. If you receive this message in error, please immediately delete > it and all copies of it from your computer network or hard copies, and > notify the sender. > > > _______________________________________________ > > > QuantLib-users mailing list > > > Qua...@li... > > > https://lists.sourceforge.net/lists/listinfo/quantlib-users > |