Troubleshooting Chilean CLP Swap Curve Bootstrapping
Summary
The document describes a QuantLib setup for constructing a Chilean peso overnight swap curve from short rate quotes and longer dated swap quotes. It defines a Chilean holiday calendar, assigns deposit and overnight indexed swap helpers, builds a piecewise cubic zero curve, and compares the resulting discount factors and zero rates with Bloomberg output. The displayed comparison shows a growing difference at longer maturities, reaching roughly ten basis points near the long end.
The author asks which conventions could explain the discrepancy after experimenting with helper types and year fractions. The document supplies the inputs and outputs but contains no answer or diagnosis, so it does not establish which market convention is responsible. It is useful as a curve construction and validation case: calendar holidays, settlement dates, instrument conventions, compounding, and quote mapping all warrant checking when implementations disagree. The comparison is specific to the stated CLP setup and dates; it does not demonstrate that either curve is correct.
Key ideas
- The curve combines short deposit quotes with longer overnight indexed swap quotes.
- A custom local holiday calendar affects instrument dates and curve pillars.
- QuantLib and Bloomberg outputs can diverge at longer maturities even with similar quoted inputs.
- Curve validation should check market conventions and quote-to-helper mappings, but the document leaves the discrepancy unresolved.
Tags
Full text
# Bootstraping CLP Swap with Quantlib
# Bootstraping CLP Swap with Quantlib
After reviewing and fixing my last code (Swap Bootstrapping with quantlib), i managed to get the zero rates for the ICP swap curve (CLP). Now the thing is that there is a considerable difference between the rates shown in the Bloomberg swap bootstrapping an QL's results. Here is the updated code:
```
import QuantLib as ql
import pandas as pd
def create_calendar_chile(start_year,n_years):
Chile = ql.WeekendsOnly()
days = [1,14,15,1,21,26,2,16,15,18,19,9,27,1,19,8,17,25,31]
months = [1,4,4,5,5,6,8,9,9,10,10,11,12,12,12,12]
name = ['Año Nuevo','Viernes Santo','Sabado Santo','Dia del Trabajo','Dia de las Glorias Navales','San Pedro y San Pablo','Elecciones Primarias','Dia de la Virgen del Carmen','Asuncion de la Virgen','Independencia Nacional','Glorias del Ejercito','Encuentro de dos mundos','Día de las Iglesias Evangélicas y Protestantes','Día de todos los Santos','Elecciones Presidenciales y Parlamentarias','Inmaculada Concepción','Segunda vuelta Presidenciales','Navidad','Feriado Bancario']
for i in range(n_years+1):
for x,y in zip(days,months):
date = ql.Date(x,y,start_year+i)
Chile.addHoliday(date)
return Chile
def get_curve(date,swap,currency = 'CLP'):
calendar = create_calendar_chile(2001,50)
dayCounter_Act360 = ql.Actual360()
settlement_days_icp = 2
# OIS quotes up to 20 years
ICP = ql.OvernightIndex("ICP", settlement_days_icp, ql.CLPCurrency(),
calendar, dayCounter_Act360)
fixingDays = 0
if currency == 'CLP':
# setup DepositRateHelper for 0-1 days
TPM = swap[0]
months = [3,6,9,12,18]
swap_month = swap[1:6]
years =[2,3,4,5,6,7,8,9,10,15,20]
swap_years = swap[6:]
helpers = [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(TPM/100)),
ql.Period(1,ql.Days), fixingDays,
calendar, ql.Following, False, ql.Actual360())]
else:
months = [3,6,9,12]
swap_month = swap[0:3]
years =[2,3,4,5,6,7,8,9,10,15,20]
swap_years = swap[4:]
helpers = []
# setup OISRateHelper from 3 months to 20 years
helpers += [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(rate/100)),
ql.Period(months,ql.Months),
settlement_days_icp,
calendar,
ql.Following,
False,
ql.Actual360())
for rate, months in zip(swap_month,months)]
#helpers += [ql.OISRateHelper(settlement_days_icp, ql.Period(months,ql.Months),
# ql.QuoteHandle(ql.SimpleQuote(rate/100)),ICP)
# for rate, months in zip(swap_month,months)]
helpers += [ql.OISRateHelper(settlement_days_icp, ql.Period(years,ql.Years),
ql.QuoteHandle(ql.SimpleQuote(rate/100)),ICP)
for rate, years in zip(swap_years,years)]
icp_curve = ql.PiecewiseCubicZero(date, helpers, ql.Actual360())
icp_curve.enableExtrapolation()
return icp_curve
def print_zero(date,yieldcurve):
day_count = ql.Actual360()
spots = []
dates = []
tenors = []
df = []
for d in yieldcurve.dates():
yrs = day_count.yearFraction(date, d)
df.append(yieldcurve.discount(d))
dates.append(d)
compounding = ql.Simple
freq = ql.Annual
zero_rate = yieldcurve.zeroRate(yrs, compounding, freq)
tenors.append(yrs)
eq_rate = zero_rate.equivalentRate(day_count,compounding,freq,date,d).rate()
zero_rate.equivalentRate(day_count,compounding,freq,date,d).rate()
spots.append(100*eq_rate)
datatable = {'Dates':dates,'Years': tenors,'DF':df,'Zero': spots}
datatable = pd.DataFrame.from_dict(datatable)
print(datatable)
#Eval. Date
date_ql = ql.Date(10,1,2018)
ql.Settings.instance().evaluationDate = date_ql
swap_clp = [2.5, 2.48, 2.47, 2.48, 2.53, 2.64, 2.82, 3.17, 3.43, 3.62, 3.81,
3.96, 4.09, 4.19, 4.29, 4.45, 4.62]
yieldcurve_clp = get_curve(date_ql,swap_clp)
print_zero(date_ql,yieldcurve_clp)
```
Results:
```
DF Dates Years Zero
0 1.000000 January 10th, 2018 0.000000 0.000000
1 0.999931 January 11th, 2018 0.002778 2.500000
2 0.993700 April 12th, 2018 0.255556 2.480773
3 0.987597 July 12th, 2018 0.508333 2.470668
4 0.981404 October 12th, 2018 0.763889 2.480488
5 0.974721 January 14th, 2019 1.025000 2.530187
6 0.961368 July 12th, 2019 1.522222 2.639855
7 0.944897 January 13th, 2020 2.036111 2.864088
8 0.908864 January 12th, 2021 3.050000 3.287675
9 0.871107 January 12th, 2022 4.063889 3.640960
10 0.833387 January 12th, 2023 5.077778 3.937202
11 0.793979 January 12th, 2024 6.091667 4.259577
12 0.755379 January 13th, 2025 7.111111 4.553990
13 0.717674 January 12th, 2026 8.122222 4.843389
14 0.681810 January 12th, 2027 9.136111 5.108135
15 0.646116 January 12th, 2028 10.150000 5.396163
16 0.506455 January 12th, 2033 15.225000 6.400718
17 0.386192 January 12th, 2038 20.297222 7.830561
```
BBG:
```
Start End Days Start Disc.Factor Frequency Zero
10-01-2018 11-01-2018 1 1,000069 ZERO 2,500
12-01-2018 12-04-2018 90 0,993838 ZERO 2,480
12-01-2018 12-07-2018 181 0,987734 ZERO 2,470
12-01-2018 12-10-2018 273 0,981540 ZERO 2,480
12-01-2018 14-01-2019 367 0,974857 ZERO 2,530
12-01-2018 12-07-2019 546 0,961501 ZERO 2,640
12-01-2018 13-01-2020 731 0,944621 SEMIANNUAL 2,887
12-01-2018 12-01-2021 1096 0,908255 SEMIANNUAL 3,318
12-01-2018 12-01-2022 1461 0,870133 SEMIANNUAL 3,678
12-01-2018 12-01-2023 1826 0,832049 SEMIANNUAL 3,980
12-01-2018 12-01-2024 2191 0,792244 SEMIANNUAL 4,309
12-01-2018 13-01-2025 2558 0,753263 SEMIANNUAL 4,610
12-01-2018 12-01-2026 2922 0,715195 SEMIANNUAL 4,906
12-01-2018 12-01-2027 3287 0,679005 SEMIANNUAL 5,178
12-01-2018 12-01-2028 3652 0,642988 SEMIANNUAL 5,473
12-01-2018 12-01-2033 5479 0,502562 SEMIANNUAL 6,504
12-01-2018 12-01-2038 7305 0,382045 SEMIANNUAL 7,971
```
There is a +10 bp difference in the long term part. I tried using the swap, deposit and ois rate helpers, but minimal changes occurred. Also hoped that changing the year fractions would have an impact but it didn't change significaly. Is there anything i could be missing?
Thanks,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.