Skip to content
All library documents

QuantLib Cap Pricing Loops and Missing Euribor Fixings

Article Quant Q&A · Author: Marc157

Summary

The document describes a workflow for pricing interest-rate caps at multiple valuation dates with QuantLib. It builds yield curves from dated market data, interpolates cap volatilities across maturities, then creates Euribor-linked cap instruments and prices them with a Black cap-floor engine. The reported failure is a missing fixing for some dates; manually adding fixings then triggers an error that a fixing already exists. The author notes that the first valuation date works when the loops stop after one iteration.

The example highlights how evaluation dates, index histories, and fixing calendars interact in repeated valuation runs. It includes code that sets the global evaluation date for each curve and adds both a valuation-relative fixing and a hard-coded fixing, but it gives no confirmed diagnosis or tested correction. The symptom could depend on which coupon fixing dates are historical or already stored in QuantLib’s shared index history, so the snippet is a troubleshooting case rather than a complete pricing recipe.

Key ideas

  • The example separates curve construction, volatility interpolation, and cap valuation into loops.
  • Each valuation date resets QuantLib’s global evaluation date before building its curve.
  • Euribor cap coupons may require historical fixings, while duplicate additions can also raise errors.
  • The first valuation works in isolation, but the document does not identify a verified cause for later failures.
  • Fixing requirements depend on the coupon dates and the index’s available fixing history.

Tags

Full text
# Python Quantlib Loop to calculate cap prices: Setting of evaluationDate


# Python Quantlib Loop to calculate cap prices: Setting of evaluationDate












I am trying to calculate cap prices for different points in time (valuation dates) with Quantlib. I have built three loops for this.

- The first one creates the quantlib yield curve object ts_handle and other yield curve related items (strikes, etc.) for each time point in an excel.

- The second one interpolates the volatility for each cap term from the cap data of another Excel.

- The third controls the setup for quantlib and calculates the cap prices for each term at each valuation time.

Unfortunately, I always get the error that certain fixing times are missing. If I add these with the method 'Euribor_index.addFixing', I get the error that there is already a fixing for this date 'RuntimeError: At least one duplicated fixing provided:'. If I put a break at the end of the first two loops and stop after the first iteration (first valuation point), the code works and returns the correct prices for the caps at the first valuation point. I don`t understand why I get this error, even I didn`t change anything on the setup. Maybe the error lies in the handling of the evaluationDate. Has anyone here already had experience with such an error, or with loops and Quantlib?

My code structure is like this:

Loop 1 - Setup yield curves:

```
# Setup yield curves
shift = 3.0
yield_curves = []
interpolated_spot_curves = []
discount_factor = []
forward_swap_rate = []
initial_forward_rate = []

for index, row in yield_curve_data.iterrows():
   # Extract Date
   curve_date = ql.Date(row['Date'].day, row['Date'].month, row['Date'].year)
   ql.Settings.instance().evaluationDate = curve_date
   # Extract interest
   yields = [row[maturity_name] for maturity_name in swap_maturity_names]
   yields = [(rate + shift) / 100 for rate in yields]  # Convert to decimals
   # Setup dates of term structure
   dates = [curve_date + ql.Period(maturity_name) for maturity_name in
            swap_maturity_names]
   # Setup ZeroCurve
   term_structure = ql.CubicZeroCurve(dates, yields, ql.Actual360(), ql.TARGET(), ql.Cubic(), ql.Continuous, ql.Annual)
   ts_handle = ql.YieldTermStructureHandle(term_structure)
   yield_curves.append((curve_date, ts_handle))

   # Calculate interpolated spot curve
   interpolated_times = np.arange(0.5, 26, 0.5)
   interpolated_spot_rates =...

   # Calculate discount factors
   discount_factors =...

   # Calculate forward swap rate
   forward_swap_rates =...

   # Calculate initial forward rates
   forward_rate_from_zero = ...
   
   break
```

Loop 2 - Setup Volatility Data:

```
# Setup Cap data
interpolated_vols_list = []
periods = [ql.Period('1y'), ql.Period('2y'), ql.Period('3y'), ql.Period('4y'),
           ql.Period('5y'), ql.Period('6y'), ql.Period('7y'), ql.Period('8y'),
           ql.Period('9y'), ql.Period('10y'), ql.Period('12y'), ql.Period('15y'),
           ql.Period('20y'), ql.Period('25y')]
for index, row in cap_vols_data.iterrows():
    # Extract the date
    cap_date = ql.Date(row['Date'].day, row['Date'].month, row['Date'].year)
    # Extract the Cap Volatilities and divide by 100
    cap_vols = {str(period.length()): row[str(period.length()) + 'Y'] / 100 for
                period in periods}

    # Interpolate market Volatilities for this row
    market_vols_keys = [int(key) for key in cap_vols.keys()]
    market_vols_values = np.array(list(cap_vols.values()))
    volatility_interpolation = interp1d(market_vols_keys, market_vols_values, kind='cubic', fill_value="extrapolate")
    payment_dates = np.arange(0.5, max(market_vols_keys)+0.5, 0.5)
    interpolated_vols = volatility_interpolation(payment_dates)
    # Append the interpolated volatilities to the list
    interpolated_vols_list.append(interpolated_vols)
    break
```

Loop 3 - Calculate Cap Prices:

```
all_cap_prices = []
for idx in range(len(yield_curves)):
    # Set initial data
    strikes = np.array(forward_swap_rate[idx][1])
    valuation_date = yield_curves[idx][0]
    ts_handle = yield_curves[idx][1]

    interpolated_vols = interpolated_vols_list[idx]
    maturities = np.arange(0.5, 26, 0.5)
    calendar = ql.TARGET()
    cap_period = ql.Period(6, ql.Months)

    # Set Initial Euribor Fixing
    Euribor_index = ql.Euribor6M(ts_handle)
    fixing_rate = yield_curve_data.iloc[idx, 1]
    fixing_date = calendar.advance(valuation_date, -2, ql.Days)
    Euribor_index.addFixing(fixing_date, (fixing_rate + 3)/100)
    Euribor_index.addFixing(ql.Date(9,1,2024), (fixing_rate + 3) / 100)

    cap_prices = []
    for i in range(len(interpolated_vols)):
        mat_date = valuation_date + ql.Period(int(maturities[i] * 12), ql.Months)
        fwd_dates_schedule = ql.Schedule(valuation_date, mat_date, cap_period, calendar, ql.ModifiedFollowing,
                                      ql.ModifiedFollowing, ql.DateGeneration.Forward, False)
        Euribor_leg = ql.IborLeg([1], fwd_dates_schedule, Euribor_index)

        # Setup Cap
        Euribor_cap = ql.Cap(Euribor_leg, [strikes[i]])

        # Setup cap vol handle
        shifted_vol = interpolated_vols[i]
        cap_vol = ql.QuoteHandle(ql.SimpleQuote(shifted_vol))

        # Set Pricing Engine
        cap_pricer = ql.BlackCapFloorEngine(ts_handle, cap_vol)
        Euribor_cap.setPricingEngine(cap_pricer)

        # Berechne und speichere den Cap-Preis
        cap_prices.append(Euribor_cap.NPV())

    # Append Cap prices to results
    all_cap_prices.append(cap_prices)
```

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.