Reputation: 63
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
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
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