user402078
user402078

Reputation: 63

Using QuantLib to compute cash flows for floored FloatingRateBonds

I'm encountering an issue generating cash flows from bonds with a floor.

I initially had an issue because I neglected to set a pricer. I've since set a pricer as below.

ql_bond = QuantLib.FloatingRateBond(settlement_days, #settlementDays
                                face_amount, # faceAmount
                                ql_schedule,
                                ql_index,
                                QuantLib.Thirty360(),
                                gearings = [],
                                spreads = [libor_spread],
                                caps = [],
                                floors = [libor_floor]
    )

    volatility = 0
    vol = QuantLib.ConstantOptionletVolatility(settlement_days,
                                           QuantLib.UnitedKingdom(),
                                           QuantLib.Unadjusted,
                                           volatility,
                                           QuantLib.Thirty360())

    pricer = QuantLib.BlackIborCouponPricer(QuantLib.OptionletVolatilityStructureHandle(vol))
    QuantLib.setCouponPricer(ql_bond.cashflows(), pricer)

On certain cash flows, I am able to generate a reasonable amount for the cashflow. Other times however I encounter an error. The value given for the strike (-.0225) equals libor_floor - libor_spread. I'm pretty sure I'm making an obvious mistake here but not sure where to start. If anyone more familiar with QuantLib has any suggestions they would be greatly appreciated.

Traceback (most recent call last):
  File "C:\Users\Ryan\git\optimizer\src\calcs\cashflow_calcs.py", line 161, in generate_cashflow
    cashflows.append(utils.cashflow.InterestCashflow(cf_date, cf.amount(), cf_fixing_date, c.indexFixing(), c.accrualDays()))
  File "C:\Users\Ryan\Anaconda3\lib\site-packages\QuantLib\QuantLib.py", line 8844, in amount
    return _QuantLib.CashFlow_amount(self)
RuntimeError: strike + displacement (-0.0225 + 0) must be non-negative

This is related to an earlier post I made Using QuantLib to compute cash flows for FloatingRateBond with Floor

Upvotes: 3

Views: 1110

Answers (2)

Luigi Ballabio
Luigi Ballabio

Reputation: 4333

The problem is not QuantLib per se. The Black model is a lognormal one, and doesn't work for negative values (since you can't take their logarithm). As you can guess, that turned out to be a problem when rates started going negative. It can be solved in two different ways: the first is to change the model and use a normal one, and the second is to introduce a fixed displacement D and model log(R+D) instead of log(R) so that the argument of the logarithm is positive.

In both cases, the volatility has to change (and in fact, quoted volatilities will also tell you what model and displacement was used). In QuantLib, this means that you'll have to pass the relevant information when you build the volatility term structure—and this is where you're currently in trouble. The C++ library has been providing the functionality for a while, but the Python module doesn't export the corresponding ConstantOptionletVolatility yet, so you're getting the default values, i.e., lognormal model and null displacement.

If you're somewhat comfortable with SWIG, you can modify the corresponding interface file QuantLib-SWIG/SWIG/volatilities.i (you'll have to add a couple of arguments to the ConstantOptionletVolatility constructors, in the same way it's done for the ConstantSwaptionVolatility class in the same file), regenerate the wrappers and compile them. Otherwise, open an issue at https://github.com/lballabio/QuantLib-SWIG/issues and we'll try to add the feature in the next release.


Update: in the latest release, ConstantOptionletVolatility can take an optional displacement.

Upvotes: 1

dsugasa
dsugasa

Reputation: 683

Instead of using ConstantOptionLetVolatility you can build a vol surface and then use OptionletStripper1 to add the optionlet volatility to the pricer. This is an adaptation from http://gouthamanbalaraman.com/blog/interest-rate-cap-floor-valuation-quantlib-python.html

pricer = ql.BlackIborCouponPricer()

strikes = [-0.01, 0.0, 0.01] 
expiries = [ql.Period(i, ql.Years) for i in range(1,6)] #or tenors of your choice
vols = ql.Matrix(len(expiries), len(strikes))
data = [[65, 65, 65, 65, 65], 
        [65, 65, 65, 65, 65], #vols of your choice
        [65, 65, 65, 65, 65]  
         ]

for i in range(vols.rows()):
    for j in range(vols.columns()):
        vols[i][j] = data[j][i]/100.0

bdc = ql.Unadjusted
settle_days =0
daycount = ql.Actual360()       

capfloor_vol = ql.CapFloorTermVolSurface(settle_days, calendar, bdc, expiries, 
strikes, vols, daycount)

optionlet_surf = ql.OptionletStripper1(parVolSurface = capfloor_vol,
                                   index = float_index,
                                   displacement = 0.01) #this make 'k + d' non-neg 

pricer.setCapletVolatility(ql.OptionletVolatilityStructureHandle
                         (ql.StrippedOptionletAdapter(optionlet_surf)))
ql.setCouponPricer(bond.cashflows(), pricer)

Upvotes: 0

Related Questions