Estoy empezando a utilizar Quantlib, y quiero intentar replicar la funcionalidad de SWPM en Bloomberg, y fijar el precio de un OIS EUR a 5 años. A continuación se muestran los datos generales del swap utilizados en BBG:
Ajustes generales
- Fecha de la curva: 2021-09-13
- Fecha de valoración (liquidación): 2021-09-20
- CSA Collateral Crncy: EUR
- Utiliza el OIS DC Stripping: Sí
Curvas
- EUR OIS ESTR (descuento), Mid, Piecewise Linear Interpolation
- EUR vs. 6m (proyectando), Mid, Piecewise Linear Interpolation
Intercambiar
- Vainilla EUR 5y receptor IRS
- 10mm EUR,
- Fecha de entrada en vigor : 2021-09-20 (5d)
- Vencimiento: 2026-09-20 (5 años)
BBG resuelve los dos tramos con un VAN = 70.789,04 y un cupón fijo de -0,280922
¡Ahora, mi código se encuentra a continuación (ejecutable) - pero, obviamente, me da un error, o de lo contrario no pediría su ayuda!
Código:
import QuantLib as ql
""" General settings """
calendar = ql.TARGET()
todaysDate = ql.Date(13, ql.September, 2021)
ql.Settings.instance().evaluationDate = todaysDate
fixingDays = 5
settlementDate = calendar.advance(todaysDate, fixingDays, ql.Days)
# must be a business day
settlementDate = calendar.adjust(settlementDate)
depositDayCounter = ql.Actual360()
swFixedLegFrequency = ql.Annual
termStructureDayCounter = ql.Actual365Fixed()
print("Today: %s " % todaysDate)
print("Settlement date: %s " % settlementDate)
""" Quotes """
estr_rates = """
1D -0.571
1W -0.571
2W -0.5707
1M -0.571
2M -0.57075
3M -0.571
4M -0.57105
5M -0.5705
6M -0.5703
7M -0.56895
8M -0.56795
9M -0.56695
10M -0.567
11M -0.56525
12M -0.56455
18M -0.55815
2Y -0.55213
3Y -0.51495
4Y -0.47592
5Y -0.4216
6Y -0.37382
7Y -0.31379
8Y -0.2502
9Y -0.18776
10Y -0.12588
11Y -0.06616
12Y -0.00469
15Y 0.13036
20Y 0.24826
25Y 0.27148
30Y 0.25117
40Y 0.19365
50Y 0.14543
"""
euribor_6m_rates = """
6M -0.52
7M -0.512
8M -0.507
9M -0.5
10M -0.492
11M -0.486
12M -0.474
13M -0.472
14M -0.467
15M -0.462
16M -0.455
17M -0.448
18M -0.44
2Y -0.4598
3Y -0.4032
4Y -0.3485
5Y -0.2825
6Y -0.2249
7Y -0.1605
8Y -0.094
9Y -0.029
10Y 0.0328
11Y 0.0918
12Y 0.1468
15Y 0.2778
20Y 0.384
25Y 0.3998
30Y 0.3743
40Y 0.3053
50Y 0.245
"""
euribor_data = {line.split('\t')[0] : float(line.split('\t')[-1]) for line in euribor_6m_rates.splitlines() if line.strip()}
estr_data = {line.split('\t')[0] : float(line.split('\t')[-1]) for line in estr_rates.splitlines() if line.strip()}
# /*********************
# *** RATE HELPERS ***
# *********************/
eonia = ql.Eonia()
helpers = []
for tenor, rate in estr_data.items():
if tenor == '1D':
helpers.append(ql.DepositRateHelper(rate / 100, eonia))
else:
helpers.append( ql.OISRateHelper(2, ql.Period(tenor), ql.QuoteHandle(ql.SimpleQuote(rate/100)), eonia) )
# /*********************
# ** CURVE BUILDING **
# *********************/
# /*********************
# ** ESTR CURVE **
# *********************/
estrTermStructure = ql.PiecewiseLogCubicDiscount(todaysDate, helpers, termStructureDayCounter)
estrTermStructure.enableExtrapolation()
# // the one used for discounting cash flows
discountingTermStructure = ql.RelinkableYieldTermStructureHandle()
# /*********************
# ** EURIBOR 6M **
# *********************/
euribor6M = ql.Euribor6M()
helpers = []
for tenor, rate in euribor_data.items():
if tenor == '6M':
helpers.append( ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(rate/100)),ql.Period(tenor), 3, calendar, ql.Following, False, depositDayCounter) )
elif 'M' in tenor:
helpers.append( ql.FraRateHelper(ql.QuoteHandle(ql.SimpleQuote(rate/100)),int(tenor[0:tenor.find('M')]), euribor6M) )
else:
helpers.append( ql.SwapRateHelper(ql.QuoteHandle(ql.SimpleQuote(rate/100)),ql.Period(tenor), calendar, swFixedLegFrequency, ql.Unadjusted, ql.Thirty360(ql.Thirty360.BondBasis),euribor6M, ql.QuoteHandle(), ql.Period(0, ql.Days), discountingTermStructure) )
euribor6MTermStructure = ql.PiecewiseLogCubicDiscount(settlementDate, helpers, termStructureDayCounter)
# // the one used for forward rate forecasting
forecastingTermStructure = ql.RelinkableYieldTermStructureHandle()
# /*********************
# ** Swap **
# *********************/
nominal = 1000000.0
#fixed leg
fixedLegFrequency = ql.Annual
fixedLegConvention = ql.ModifiedFollowing
fixedLegDayCounter = ql.Thirty360(ql.Thirty360.BondBasis)
fixedRate = 0.007
firstFixDate = ql.Date(20,9,2022)
#floating leg
floatingLegDayCounter = ql.Actual360()
floatingLegFrequency = ql.Semiannual
floatingLegConvention = ql.ModifiedFollowing
euriborIndex = ql.Euribor6M(forecastingTermStructure)
spread = 0.0
lengthInYears = 5
swapType = ql.VanillaSwap.Receiver
maturity = ql.Date(20, ql.September, 2026) #settlementDate + lengthInYears*12
fixedSchedule = ql.Schedule(settlementDate, maturity,
ql.Period(fixedLegFrequency),
calendar, fixedLegConvention,
fixedLegConvention,
ql.DateGeneration.Backward, False, firstFixDate)
"""
list(fixedSchedule)
[Date(20,9,2021), << is this the start of the period?
Date(20,9,2022), << this one should be first payment date
Date(20,9,2023),
Date(20,9,2024),
Date(22,9,2025),
Date(21,9,2026)]
"""
floatSchedule = ql.Schedule(settlementDate, maturity,
ql.Period(floatingLegFrequency),
calendar, floatingLegConvention,
floatingLegConvention,
ql.DateGeneration.Backward, False)
"""
list(floatSchedule)
[Date(20,9,2021), << is this the start of the period?
Date(21,3,2022),
Date(20,9,2022),
Date(20,3,2023),
Date(20,9,2023),
Date(20,3,2024),
Date(20,9,2024),
Date(20,3,2025),
Date(22,9,2025),
Date(20,3,2026),
Date(21,9,2026)]
"""
forecastingTermStructure.linkTo(euribor6MTermStructure)
discountingTermStructure.linkTo(estrTermStructure)
spot5YearSwap = ql.VanillaSwap(swapType, nominal,
fixedSchedule, fixedRate, fixedLegDayCounter,
floatSchedule, euriborIndex, spread,
floatingLegDayCounter)
# and then the discount curve for the engine:
swapEngine = ql.DiscountingSwapEngine(discountingTermStructure)
spot5YearSwap.setPricingEngine(swapEngine)
NPV = spot5YearSwap.NPV()
fairSpread = spot5YearSwap.fairSpread()
fairRate = spot5YearSwap.fairRate()
Ahora, al ejecutar esto, obtengo el siguiente error:
RuntimeError: 2nd leg: more than one instrument with pillar September 15th, 2023
Que he encontrado en alguna parte que puede tener que ver con la construcción de las curvas (usando el mismo tenor dos veces), pero no puedo averiguar dónde.
Las preguntas a las que me gustaría obtener una respuesta son:
- ¿Es legítimo utilizar Eonia() y los respectivos ayudantes para construir mi curva "ESTR"/OIS? ¿O cómo debería seguir adelante con esto? Mirando al BCE, la metodología Eonia de octubre de 2019 parece ajustarse a la futura transición ESTR, pero ¿se refleja esto en Quantlib? (Véase aquí: Eonia/ESTR-transición )
- ¿Por qué recibo el error anterior?
- ¿Estoy construyendo mis horarios para mi intercambio correctamente - las fechas de BBG y Python están alineadas, sin embargo mis horarios muestran la primera y la última fecha?
¿Cuánto me falta para que esto funcione?
Como soy nuevo en esto, y realmente no puedo encontrar ningún ejemplo "fresco" de esto en cualquier lugar, pensé que podría ser una buena pregunta en este foro - especialmente teniendo en cuenta EONIA está fuera, y ESTR será el nuevo estándar en el futuro.
Lo mejor,
/N
0 votos
Así que, después de algunas comprobaciones, parece que el error está en la construcción del manipulador que utiliza FRAs en la curva Euribor6M. Alguien más familiarizado que yo con esto que pueda detectar dónde falla?
0 votos
¿Será por el hecho de que el último FRA comienza en el mes 18 con un periodo de 6 meses (24) y por lo tanto colisiona con el 2Y-swap?
0 votos
Hola, he estado tratando de replicar su código con el arreglo FRA, pero sigue fallando con el error
RuntimeError: 2nd leg: 1st iteration: failed at 1st alive instrument, pillar March 16th, 2022, maturity March 16th, 2022, reference date September 20th, 2021: negative time (-0.0109589) given
. ¿Has visto algo así?