Very new to QuantLib so guessing this is a rookie mistake. Enjoyed getting to know this powerful library so thank you to the authors and contributors!
I'm able to generate amounts for cashflows for a FloatingRateBond without a pricer if there isn't a floor argument, so I don't understand why including a floor argument would necessitate a pricer. I would think the addition of the floor would just provide a min for each of the fixing values.
Wanted to see if anyone has gotten the FloatingRateBond cashflows to work while using a floor. And, if so, if anyone can spot where I'm going astray. Thanks in advance!
I'm using the QuantLib 1.8 on windows installed via the pre-packaged installer (QuantLib-Python-1.8.win-amd64-py3.5.msi).
Here's where the error occurs:
File "C:/src/misc/generate_cashflows.py", line 138, in generate_cashflow
print(cf.amount())
File "C:\lib\site-packages\QuantLib\QuantLib.py", line 8844, in amount
return _QuantLib.CashFlow_amount(self)
RuntimeError: pricer not set
The specific code is below:
ql_first_day, ql_first_month, ql_first_year = first_payment_date.day, first_payment_date.month, first_payment_date.year
ql_first_date = QuantLib.Date(ql_first_day, ql_first_month, ql_first_year)
maturity_month, maturity_day, maturity_year = maturity.month, maturity.day, maturity.year
ql_maturity_date = QuantLib.Date(maturity_day, maturity_month, maturity_year)
ql_issue_day, ql_issue_month, ql_issue_year = issue_date.day, issue_date.month, issue_date.year
q1_settle_date = QuantLib.Date(ql_issue_day, ql_issue_month, ql_issue_year)
fixing_days = 0
calendar = QuantLib.UnitedStates()
ql_settle_date = calendar.adjust(q1_settle_date)
todays_date = calendar.advance(ql_settle_date, -fixing_days, QuantLib.Days)
QuantLib.Settings.instance().evaluationDate = todays_date
ql_schedule = QuantLib.Schedule(ql_settle_date,
ql_maturity_date, QuantLib.Period(ql_frequency_enum),
QuantLib.UnitedStates(),
QuantLib.Following, QuantLib.Following,
QuantLib.DateGeneration.Forward, False, ql_first_date)
ql_forecast_curve = QuantLib.RelinkableYieldTermStructureHandle()
today = datetime.datetime.today()
calc_date = QuantLib.Date(today.day, today.month, today.year)
QuantLib.Settings.instance().evaluationDate = calc_date
day_count = QuantLib.Thirty360()
# setup swaps
calendar = QuantLib.UnitedStates()
swFixedLegFrequency = QuantLib.Annual
swFixedLegConvention = QuantLib.Unadjusted
swFixedLegDayCounter = QuantLib.Thirty360()
swFloatingLegIndex = QuantLib.USDLibor(QuantLib.Period(3, QuantLib.Months))
swap_raw = [
(1, 0.01251),
(2, 0.01505),
(3, 0.01701),
(5, 0.01972),
(7, 0.02158)
]
swap_rates = []
for year, rate in swap_raw:
swap_rates.append(QuantLib.SwapRateHelper(
QuantLib.QuoteHandle(QuantLib.SimpleQuote(rate)),
QuantLib.Period(year, QuantLib.Years),
calendar,
swFixedLegFrequency,
swFixedLegConvention,
swFixedLegDayCounter,
swFloatingLegIndex
))
swap_curve = QuantLib.PiecewiseFlatForward(calc_date, swap_rates, day_count)
ql_forecast_curve.linkTo(swap_curve)
ql_index = QuantLib.USDLibor(period, ql_forecast_curve)
settlement_days = 0
face_amount = 100
ql_bond = QuantLib.FloatingRateBond(settlement_days, #settlementDays
face_amount, # faceAmount
ql_schedule,
ql_index,
QuantLib.Thirty360(),
gearings = [],
spreads = [libor_spread],
caps = [],
floors = [.01]
)
ql_discount_curve = QuantLib.RelinkableYieldTermStructureHandle()
settlement_date = QuantLib.Date(9, 2, 2017)
flatForward = QuantLib.FlatForward(
settlement_date,
.02,
QuantLib.ActualActual(QuantLib.ActualActual.Bond),
QuantLib.Compounded,
QuantLib.Semiannual)
ql_discount_curve.linkTo(flatForward)
bondEngine = QuantLib.DiscountingBondEngine(ql_discount_curve)
ql_bond.setPricingEngine(bondEngine)
for cf in ql_bond.cashflows():
c = QuantLib.as_floating_rate_coupon(cf)
print(cf.amount())
The theory first: when pricing the coupon with a floor, you can't just take the expected LIBOR rate from your forecast curve and take the minimum between that and the floor. Instead, you need to take the expected value of the minimum between the rate and the floor, and unfortunately E[min(R,F)]
is not the same as min(E[R],F)
. So no, the floor doesn't just provide a minimum; you need a different formula to estimate the expected payoff.
The implication for QuantLib is that simple floating-rate coupons can be (and are) set a default pricer that just reads the rate off the forecast curve, but coupons with caps or floors need the user to specify what pricer to use and to provide it with any additional needed data; in your case, this means at least a volatility term structure, although more optional data can be specified; see the constructor of the BlackIborCouponPricer
class for details.
Usually, the volatility is bootstrapped on market quotes for caps and floors, but the procedure to create it is kind of complex (see these tests for an example in C++), I'm not sure that all the needed classes are exported to Python, and you'll be better off asking about it on the QuantLib mailing list.
If you want to verify that the coupons can works, you can use a constant volatility, as in:
volatility = 0.10;
vol = QuantLib.ConstantOptionletVolatility(settlement_days,
calendar,
QuantLib.ModifiedFollowing,
volatility,
day_count)
pricer = QuantLib.BlackIborCouponPricer(
QuantLib.OptionletVolatilityStructureHandle(vol))
QuantLib.setCouponPricer(ql_bond.cashflows(), pricer)
The above should enable you to get a result; but of course I pulled the 15% volatility out of a hat, and it won't give you actual market values...