3
$\begingroup$

I I am trying to price a cap/floor using Quantlib in Python. the initial code from from this website: http://gouthamanbalaraman.com/blog/interest-rate-cap-floor-valuation-quantlib-python.html

Here is my code to price a floor on EURIBOR1M.

When I print the results , I get the following error:

RuntimeError: forward + displacement (-0.437337 + 0) must be positive

And when I launch the computation for a floor I get this error:

start_date = ql.Date(4, 2, 2019)
end_date = ql.Date(31, 8 , 2023)
period = ql.Period(1, ql.Months)
calendar = ql.TARGET()
buss_convention = ql.ModifiedFollowing
rule = ql.DateGeneration.Forward
end_of_month = False

schedule = ql.Schedule(start_date, end_date, period,
                       calendar, buss_convention, buss_convention, 
                       rule, end_of_month)

ibor_index = ql.Euribor(ql.Period(1, ql.Months), ts_handle)
ibor_index.addFixing(ql.Date(31,1, 2019), -0.368)
ibor_leg = ql.IborLeg([2074408.03], schedule, ibor_index)

strike = 0.0
floor = ql.Floor(ibor_leg, [strike])

vols = ql.QuoteHandle(ql.SimpleQuote(0.547295))
engine2 = ql.BlackCapFloorEngine(ts_handle, ovs_handle)
floor.setPricingEngine(engine2)
print (floor.NPV())

RuntimeError: forward + displacement (-0.368 + 0) must be positive

 import QuantLib as ql
    import matplotlib.pyplot as plt
    import numpy as np
    %matplotlib inline

calc_date = ql.Date(22, 2, 2019)
ql.Settings.instance().evaluationDate = calc_date

dates = [ql.Date(22,2,2019), ql.Date(22,4,2019), 
         ql.Date(23,5,2019), ql.Date(24,6,2019),
         ql.Date(24,7,2019), ql.Date(22,8,2019),
         ql.Date(23,9,2019), ql.Date(23,10,2019),
         ql.Date(22,11,2019), ql.Date(23,12,2019),
         ql.Date(23,1,2020), ql.Date(24,2,2020),
         ql.Date(22,5,2020), ql.Date(24,8,2020),
         ql.Date(23,11,2020), ql.Date(22,2,2021),
         ql.Date(24,2,2022), ql.Date(23,2,2023),
         ql.Date(22,2,2024), ql.Date(24,2,2025),
         ql.Date(23,2,2026), ql.Date(22,2,2027), 
         ql.Date(24,2,2028), ql.Date(22,2,2029),
         ql.Date(24,2,2031), ql.Date(23,2,2034), 
         ql.Date(24,2,2039), ql.Date(22,2,2044),
         ql.Date(22,2,2049), 
         ]

yields = [-0.36800000,-0.36500000,
          -0.36520000,-0.36430000,
          -0.36350000,-0.36200000,
          -0.36190000,-0.36150000,
          -0.36000000,-0.35890000,
          -0.35680000,-0.35410000,
          -0.34600000,-0.33650000,
          -0.32000000,-0.30490000,
          -0.22600000,-0.13200000,
          -0.02640000,0.08200000,
          0.19150000,0.30100000,
          0.40100000,0.49900000,
          0.68700000,0.89380000,
          1.06480000,1.13200000,
          1.14900000]


day_count = ql.ActualActual()
calendar = ql.TARGET()
interpolation = ql.Linear()
compounding = ql.Compounded
compounding_frequency = ql.Annual

term_structure = ql.ZeroCurve(dates, yields, day_count, calendar, 
                       interpolation, compounding, compounding_frequency)
ts_handle = ql.YieldTermStructureHandle(term_structure)
ts_handle.referenceDate()

strikes = [0.01,0.02, 0.025, 0.03, 0.04, 0.05]
expiries = [ql.Period(i, ql.Years) for i in range(1,6)]
vols = ql.Matrix(len(expiries), len(strikes))
data = [
        [90.635, 96.25, 73.745, 67.215, 61.17],#vector of vols by strike
        [66.665, 70.58, 60.48,  55.18,  49.6],
        [61.225, 64.64, 56.595, 55.18, 49.6],
        [57.25,  60.33, 53.5695, 49.045, 43.695],
        [51.645, 54.29, 49.065,  45.015, 39.915],
        [47.755,50.13,  45.8,    42.04,  37.21]
   ]

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

calendar = ql.UnitedStates()
bdc = ql.ModifiedFollowing
daycount = ql.Actual365Fixed()
settlement_days = 2
capfloor_vol = ql.CapFloorTermVolSurface(settlement_days, calendar, bdc, expiries, strikes, vols, daycount)

optionlet_surf = ql.OptionletStripper1(capfloor_vol, ibor_index)
ovs_handle = ql.OptionletVolatilityStructureHandle(
    ql.StrippedOptionletAdapter(optionlet_surf)
)
tenors = np.arange(0,10,0.25)

tenors = np.arange(0,5,0.25)
strike = 0.015
capfloor_vols = [capfloor_vol.volatility(t, strike) for t in tenors]
opionlet_vols = [ovs_handle.volatility(t, strike) for t in tenors]

plt.plot(tenors, capfloor_vols, "--", label="CapFloor Vols")
plt.plot(tenors, opionlet_vols,"-", label="Optionlet Vols")
plt.legend(bbox_to_anchor=(0.5, 0.25))*
$\endgroup$
2
$\begingroup$

From Github, It looks like forward + displacement being > 0 is a requirement of the pricing engine in Quantlib.

void checkParameters(QuantLib::Real strike,
                     QuantLib::Real forward,
                     QuantLib::Real displacement)
{
    QL_REQUIRE(displacement >= 0.0, "displacement ("
                                        << displacement
                                        << ") must be non-negative");
    QL_REQUIRE(strike + displacement >= 0.0,
               "strike + displacement (" << strike << " + " << displacement
                                         << ") must be non-negative");
    QL_REQUIRE(forward + displacement > 0.0, "forward + displacement ("
                                                 << forward << " + "
                                                 << displacement
                                                 << ") must be positive");
}

In your code 'AddFixing' is called with the exact negative number shown in the error.

ibor_index.addFixing(ql.Date(31,1, 2019), -0.368)

From my understanding, the numeric portion of 'AddFixing' is an inflation/interest rate. The engine interprets this rate as the 'forward' portion the equation. In most cases I can understand that it should always be positive, but recently with the emergence of negative interest rates I can see why you might intend for it to be negative. If my assumptions are correct, it might be worth contributing to or filing a bug on the QuantLib codebase to allow for negative numbers here.

Edit: It appears this is expected behavior and the way to handle negative interest rates is to add displacement. More information can be found on this thread. Essentially a negative number cannot be entered into a logarithm. Displacement can be calculated or perhaps can be obtained from quote provider.

$\endgroup$
  • $\begingroup$ Thank you for your answer. if i remove the addFixing I still get the error: RuntimeError: forward + displacement (-0.368 + 0) must be positive. Is there s way to get a workaround of it? $\endgroup$ – supermastercode Feb 21 at 6:10
  • $\begingroup$ What if you put a positive rate in addFixing as a troubleshooting step - to see if it at least runs through the code. $\endgroup$ – Daniel Sims Feb 21 at 6:11
  • $\begingroup$ I think the key here is going to be in a positive displacement number. There are some discussions around negative interest rates and how to deal with them in quantlib. Here for example: quantlib.10058.n7.nabble.com/… and quantlib.10058.n7.nabble.com/… This will allow your forward rate to remain negative while correcting the lognormal interest rate model. $\endgroup$ – Daniel Sims Feb 21 at 6:32
  • $\begingroup$ Also, as a side-note, I think that judging from your example link that -0.36% rate should be entered into addFixing as -0.0036. $\endgroup$ – Daniel Sims Feb 21 at 6:34
  • $\begingroup$ Any luck here? If you were able to solve this question another way please post an answer and give it the green check mark. $\endgroup$ – Daniel Sims Feb 26 at 17:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.