Skip to content
All library documents

Displacement for Negative Rates in Black Cap and Floor Pricing

Article Quant Q&A · Author: supermastercode

Summary

The document discusses a QuantLib error when pricing a EURIBOR floor in a market with negative interest rates. The Black cap and floor engine checks that the forward rate plus the volatility displacement is positive. In the example, a negative historical fixing is passed into the index, and the error reports that this rate, with zero displacement, violates that condition. The response connects the reported value to the supplied fixing and notes that the Black model uses a logarithm, which cannot accept a nonpositive input.

The suggested modeling approach is to use a nonnegative displacement large enough to make the shifted forward rate positive, with the displacement potentially estimated or sourced from market quotes. This is a model-domain constraint rather than evidence that negative interest rates themselves are invalid. The document provides no calibrated displacement, revised code, or comparison with alternative pricing models, so it does not establish how to choose the shift or whether a shifted Black model is appropriate for a particular market or instrument.

Key ideas

  • The Black cap and floor engine requires the forward rate plus displacement to be positive.
  • A negative index fixing with zero displacement can trigger the reported pricing error.
  • A positive displacement can accommodate negative rates when the shifted forward remains positive.
  • The example does not explain how to calibrate displacement or compare alternative pricing models.

Tags

Full text
# forward + displacement


# forward + displacement












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))*
```

## Answer by MonteCarloSims (score 2)

https://quant.stackexchange.com/a/44177

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.

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.