QuantLib Cap Pricing Loops and Missing Euribor Fixings
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.