4 votos

Curva OIS de arranque

Estoy intentando obtener una curva cero de una serie de tipos OIS basados en EONIA con Quantlib. Al comparar mi resultado con el de Bloomberg, encuentro algunas diferencias (ver al final de la pregunta), y no consigo encontrar lo que estoy haciendo mal (Es la primera vez que hago bootstraping de tasas OIS).

He consultado la documentación de Quantlib y he intentado seguir su método sin suerte. Entonces, si alguien detecta dónde puede estar el problema y me lo hace saber, ¡estaría súper agradecido!

today = ql.Date(22, 9, 2020)
ql.Settings.instance().evaluationDate = today

OIS_rate = [-0.47, -0.472, -0.4755, -0.481, -0.485, -0.489, -0.505, -0.519, -0.54, -0.552, -0.559, -0.5502, -0.5308, -0.5, -0.462, -0.4257, -0.382, -0.337, -0.2435, -0.1385, -0.056, -0.049, -0.078, -0.128, -0.1723]
terms = [1, 2, 3, 4, 5, 6, 9, 12, 18, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 25, 30, 40, 50]

calendar = ql.TARGET()
bussiness_convention = ql.Following
day_count = ql.Actual360()

#Overnigth Rate
depo_facility = -0.5
depo_helper = [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(depo_facility/100)), ql.Period(1,ql.Days), 1, calendar, ql.Unadjusted, False, day_count)]

settlement_days_EONIA = 1

EONIA = ql.OvernightIndex("EONIA", settlement_days_EONIA, ql.EURCurrency(),ql.TARGET(), day_count)

# Build OIS helpers
OIS_helpers = []
for i in range(len(terms)):
    if i < 8:
        coupon_frequency = ql.Once
        tenor = ql.Period(terms[i],ql.Months)
        rate = OIS_rate[i]
        #OIS_helpers.append(ql.SwapRateHelper(ql.QuoteHandle(ql.SimpleQuote(rate/100.0)),tenor, calendar,coupon_frequency, bussiness_convention,day_count,ql.Euribor3M()))
        OIS_helpers.append(ql.OISRateHelper(settlement_days_EONIA, tenor, ql.QuoteHandle(ql.SimpleQuote(rate/100)), EONIA))
    elif i == 8:
        coupon_frequency = ql.Semiannual
        tenor = ql.Period(terms[i],ql.Months)
        rate = OIS_rate[i]
        OIS_helpers.append(ql.OISRateHelper(settlement_days_EONIA, tenor, ql.QuoteHandle(ql.SimpleQuote(rate/100)), EONIA))
    else:
        coupon_frequency = ql.Semiannual
        tenor = ql.Period(terms[i],ql.Years)
        rate = OIS_rate[i]
        OIS_helpers.append(ql.OISRateHelper(settlement_days_EONIA, tenor, ql.QuoteHandle(ql.SimpleQuote(rate/100)), EONIA))

rate_helpers = depo_helper + OIS_helpers
yieldcurve = ql.PiecewiseLogCubicDiscount(today,rate_helpers,day_count)

spots = []
tenors = []
for d in yieldcurve.dates():
    yrs = day_count.yearFraction(today, d)
    compounding = ql.Simple
    freq = ql.Semiannual
    zero_rate = yieldcurve.zeroRate(yrs, compounding, freq)
    tenors.append(yrs)
    eq_rate = zero_rate.equivalentRate(day_count,compounding,freq,today,d).rate()
    spots.append(100*eq_rate)

datatable = {'Dates':yieldcurve.dates(),'Tenors':tenors,'spots':spots}
df = pd.DataFrame.from_dict(datatable)

A continuación, los tipos al contado que obtengo, comparados con los que se encuentran en Bloomberg <SWDF 133 8>

Tenor   Bloomberg   Output
1 MO    -0.469  -0.47102
2 MO    -0.4711 -0.472474
3 MO    -0.4747 -0.475779
4 MO    -0.4802 -0.48116
5 MO    -0.4843 -0.485102
6 MO    -0.4884 -0.489064
9 MO    -0.5047 -0.504981
12 MO   -0.519  -0.518946
18 MO   -0.5397 -0.538899
2 YR    -0.5519 -0.550289
3 YR    -0.5589 -0.555675
4 YR    -0.5503 -0.545571
5 YR    -0.531  -0.525296
6 YR    -0.5006 -0.494237
7 YR    -0.463  -0.456508
8 YR    -0.4271 -0.420624
9 YR    -0.3837 -0.377845
10 YR   -0.3391 -0.333913
12 YR   -0.2459 -0.242628
15 YR   -0.1405 -0.139172
20 YR   -0.057  -0.056707
25 YR   -0.0497 -0.049446
30 YR   -0.0787 -0.077762
40 YR   -0.1278 -0.124637
50 YR   -0.1705 -0.163544

8voto

Chris Mc Puntos 31

Veo varios problemas que podrían explicar esas diferencias:

  1. La frecuencia del tramo fijo de un swap EONIA es anual y no semi
  2. El tipo de la facilidad de depósito no forma parte de la curva EONIA. Utilice el tipo Eonia.
  3. Está calculando los tipos con capitalización simple y no con capitalización anual

He aquí una aplicación alternativa:

tenors = [
    '1D', '1W', '2W', '1M', '2M', '3M', '4M', '5M', '6M', '7M', '8M', '9M', '10M', '11M', '1Y',
     '18M', '2Y', '30M', '3Y', '4Y', '5Y', '6Y', '7Y', '8Y', '9Y', '10Y',  '11Y', '12Y',
     '15Y', '20Y', '25Y', '30Y', '35Y', '40Y', '50Y']

rates = [
    -0.467, -0.472, -0.47, -0.46, -0.471, -0.47, -0.481, -0.487, -0.5, -0.495, -0.5, -0.506,
     -0.51, -0.515, -0.52, -0.541, -0.551, -0.556, -0.56, -0.551, -0.531, -0.5, -0.462, -0.426,
    -0.379, -0.337, -0.293, -0.251, -0.147, -0.068, -0.055, -0.09, -0.099, -0.134, -0.172]

eonia = ql.Eonia()
helpers = []
for tenor, rate in zip(tenors,rates):
    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) )
eonia_curve = ql.PiecewiseLogCubicDiscount(0, ql.TARGET(), helpers, ql.Actual365Fixed()) 
discount_curve = ql.YieldTermStructureHandle(eonia_curve)
swapEngine = ql.DiscountingSwapEngine(discount_curve)

A continuación, puede crear instrumentos OIS Swap para obtener su tipo de interés justo.

overnightIndex = ql.Eonia(discount_curve)
for tenor, rate in zip(tenors, rates):
    if tenor == '1D': continue
    ois_swap = ql.MakeOIS(ql.Period(tenor), overnightIndex, 0.01, pricingEngine=swapEngine)
    print(f"{tenor}\t{ois_swap.fairRate():.4%}\t{rate:.4f}%")

1W -0.4720% -0.4720%
2W -0.4700% -0.4700%
1M -0.4600% -0.4600%
2M -0.4710% -0.4710%
3M -0.4700% -0.4700%
4M -0.4810% -0.4810%
5M -0.4870% -0.4870%
6M -0.5000% -0.5000%
7M -0.4950% -0.4950%
8M -0.5000% -0.5000%
9M -0.5060% -0.5060%
(...)
10Y -0.3370% -0.3370%
11Y -0.2930% -0.2930%
12Y -0.2510% -0.2510%
15Y -0.1470% -0.1470%
20Y -0.0680% -0.0680%
25Y -0.0550% -0.0550%
30Y -0.0900% -0.0900%
35Y -0.0990% -0.0990%
40Y -0.1340% -0.1340%
50Y -0.1720% -0.1720%

Finanhelp.com

FinanHelp es una comunidad para personas con conocimientos de economía y finanzas, o quiere aprender. Puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X