7 votos

Utilice QuantLib Python para calcular el roll-down de un swap

Quisiera usar QuantLib Python para calcular el roll-down de 6 meses de un swap de 5 años.

Creo que el cálculo que debo hacer es el siguiente:

$Rolldown=r_{0,5Y}-r_{0,4.5Y}$

Donde $r_{0,5Y}$ es la tasa spot de 5 años y $r_{0,4.5Y}$ es la tasa spot de 4 años y medio.

Puede encontrar la definición de roll-down en los siguientes enlaces:

https://e-markets.nordea.com/api/research/attachment/2796

http://www.gioa.us/presentations/2012/4-Rajappa.pdf

Por favor, avíseme si he formulado correctamente mi ecuación.

Suponiendo que mi ecuación es correcta, así es como intenté calcular el roll-down del swap utilizando QuantLib Python:

from QuantLib import *

# datos globales
calendario = TARGET()
fechaHoy = Date(6,Noviembre,2001);
Settings.instance().evaluationDate = fechaHoy
fechaLiquidacion = Date(8,Noviembre,2001);

# comillas de mercado
deposits = { (1,Semanas): 0.0382,
             (1,Meses): 0.0372,
             (3,Meses): 0.0363,
             (6,Meses): 0.0353,
             (9,Meses): 0.0348,
             (1,Años): 0.0345 }

swaps = { (2,Años): 0.037125,
          (3,Años): 0.0398,
          (5,Años): 0.0443,
          (10,Años): 0.05165,
          (15,Años): 0.055175 }

# convertirlos en objetos Quote
for n,unit in deposits.keys():
    deposits[(n,unit)] = SimpleQuote(deposits[(n,unit)])
for n,unit in swaps.keys():
    swaps[(n,unit)] = SimpleQuote(swaps[(n,unit)])

# construir ayudantes de tasas

dayCounter = Actual360()
diasLiquidacion = 2
depositHelpers = [ DepositRateHelper(QuoteHandle(deposits[(n,unit)]),
                                     Period(n,unit), diasLiquidacion,
                                     calendario, ModifiedFollowing,
                                     False, dayCounter)
                   for n, unit in [(1,Semanas),(1,Meses),(3,Meses),
                                   (6,Meses),(9,Meses),(1,Años)] ]

frecuenciaPataFija = Anual
tenorPataFija = Period(1,Años)
ajustePataFija = Unadjusted
dayCounterPataFija = Thirty360()
frecuenciaPataFlotante = Semiannual
tenorPataFlotante = Period(6,Meses)
ajustePataFlotante = ModifiedFollowing
swapHelpers = [ SwapRateHelper(QuoteHandle(swaps[(n,unit)]),
                               Period(n,unit), calendario,
                               frecuenciaPataFija, ajustePataFija,
                               dayCounterPataFija, Euribor6M())
                for n, unit in swaps.keys() ]

# manejadores de estructuras temporales

descuentoTermStructure = RelinkableYieldTermStructureHandle()
forecastTermStructure = RelinkableYieldTermStructureHandle()

# construcción de la estructura temporal

ayudantes = depositHelpers + swapHelpers
curvaDepoSwap = PiecewiseFlatForward(fechaLiquidacion, ayudantes, Actual360())

motorSwap = DiscountingSwapEngine(descuentoTermStructure)

# Swap de 5 años

nominal = 1000000
vencimiento1 = calendario.advance(fechaLiquidacion,5,Años)

frecuenciaPataFija = Anual
ajustePataFija = Unadjusted
dayCounterPataFija = Thirty360()
tasaFija = 0.04

frecuenciaPataFlotante = Semiannual
spread = 0.0
diasFijacion = 2
indice = Euribor6M(forecastTermStructure)
ajustePataFlotante = ModifiedFollowing
dayCounterPataFlotante = index.dayCounter()

horarioFijo1 = Schedule(fechaLiquidacion, vencimiento1,
                         tenorPataFija, calendario,
                         ajustePataFija, ajustePataFija,
                         DateGeneration.Forward, False)
horarioFlotante1 = Schedule(fechaLiquidacion, vencimiento1,
                            tenorPataFlotante, calendario,
                            ajustePataFlotante, ajustePataFlotante,
                            DateGeneration.Forward, False)

spot1 = VanillaSwap(VanillaSwap.Receiver, nominal,
                   horarioFijo1, tasaFija, dayCounterPataFija,
                   horarioFlotante1, indice, spread,
                   dayCounterPataFlotante)
spot1.setPricingEngine(swapEngine)

# Swap de 4.5 años

periodoRolldown = Period(6, Meses)

vencimiento2 = calendario.advance(vencimiento1, -periodoRolldown)

horarioFijo2 = Schedule(fechaLiquidacion, vencimiento2,
                         tenorPataFija, calendario,
                         ajustePataFija, ajustePataFija,
                         DateGeneration.Forward, False)
horarioFlotante2 = Schedule(fechaLiquidacion, vencimiento2,
                            tenorPataFlotante, calendario,
                            ajustePataFlotante, ajustePataFlotante,
                            DateGeneration.Forward, False)

spot2 = VanillaSwap(VanillaSwap.Receiver, nominal,
                   horarioFijo2, tasaFija, dayCounterPataFija,
                   horarioFlotante2, indice, spread,
                   dayCounterPataFlotante)
spot2.setPricingEngine(swapEngine)

# precio en dos fechas de evaluación diferentes

descuentoTermStructure.linkTo(curvaDepoSwap)
forecastTermStructure.linkTo(curvaDepoSwap)

tasaSpot5Y = spot1.fairRate()
tasaSpot4Y6M = spot2.fairRate()

print('Tasa spot de 5 años')
print(tasaSpot5Y)
print('Tasa spot de 4.5 años')
print(tasaSpot4Y6M)
print('Roll-down de 6 meses')
print(tasaSpot5Y - tasaSpot4Y6M)

¿Es correcta mi forma de usar QuantLib Python para calcular el roll-down de 6 meses de un swap de 5 años? ¿Es necesario crear 2 objetos de swap como he hecho para calcular el roll-down del swap?

5voto

Brad Tutterow Puntos 5628

No, creo que estás obteniendo una cifra incorrecta. La idea general podría funcionar, pero la construcción del segundo intercambio debe modificarse. (O puedes usar solo un intercambio; hablaré de eso más adelante.)

Según la documentación que vinculaste, la idea del segundo intercambio sería modelar el primer intercambio después de que hayan pasado 6 meses. Sin embargo, veamos los dos intercambios. Definiré una función auxiliar para ver sus cupones:

def show_cashflows(leg):
    for c in leg:
        print '%20s | %s | %.4f%%' % (c.date(), c.amount(),
                                      as_coupon(c).rate()*100)

Esto es lo que paga la pierna fija del intercambio de 5 años...

show_cashflows(spot1.fixedLeg())

 November 8th, 2002 | 40000.0 | 4.0000%
November 10th, 2003 | 40000.0 | 4.0000%
 November 8th, 2004 | 40000.0 | 4.0000%
 November 8th, 2005 | 40000.0 | 4.0000%
 November 8th, 2006 | 40000.0 | 4.0000%

...y aquí está la pierna flotante:

show_cashflows(spot1.floatingLeg())

      May 8th, 2002 | 17748.0555555 | 3.5300%
 November 8th, 2002 | 16930.6254304 | 3.3125%
      May 8th, 2003 | 19219.7194265 | 3.8227%
November 10th, 2003 | 19755.8616671 | 3.8237%
     May 10th, 2004 | 22498.6599297 | 4.4503%
 November 8th, 2004 | 22498.6599297 | 4.4503%
      May 9th, 2005 | 25550.9745581 | 5.0540%
 November 8th, 2005 | 25693.1528497 | 5.0544%
      May 8th, 2006 | 25408.8159749 | 5.0537%
 November 8th, 2006 | 25835.3508522 | 5.0547%

El segundo intercambio debería darte los mismos cupones, como se ve desde una fecha de evaluación 6 meses después. La pierna flotante se ve igual, excepto que un cupón ha desaparecido:

show_cashflows(spot2.floatingLeg())

      May 8th, 2002 | 17748.0555555 | 3.5300%
 November 8th, 2002 | 16930.6254304 | 3.3125%
      May 8th, 2003 | 19219.7194265 | 3.8227%
November 10th, 2003 | 19755.8616671 | 3.8237%
     May 10th, 2004 | 22498.6599297 | 4.4503%
 November 8th, 2004 | 22498.6599297 | 4.4503%
      May 9th, 2005 | 25550.9745581 | 5.0540%
 November 8th, 2005 | 25693.1528497 | 5.0544%
      May 8th, 2006 | 25408.8159749 | 5.0537%

(Parece que el último cupón ha desaparecido, no el primero; pero eso es porque estás asumiendo que la curva permanece igual y cambia con el tiempo, por lo tanto, las fijaciones del segundo cupón en seis meses igualarán la fijación del primer cupón ahora.)

Sin embargo, así es como se ve la pierna fija:

show_cashflows(spot2.fixedLeg())

 November 8th, 2002 | 40000.0 | 4.0000%
November 10th, 2003 | 40000.0 | 4.0000%
 November 8th, 2004 | 40000.0 | 4.0000%
 November 8th, 2005 | 40000.0 | 4.0000%
      May 8th, 2006 | 20000.0 | 4.0000%

Dado que creaste un cronograma de 4.5 años, el último cupón es más corto y falta la mitad del monto. Esto, por supuesto, hace que la tasa justa esté desactivada.

print spot2.fairRate()

0.0434536470094

Tienes un par de formas de corregir esto.

Primero, en lugar de crear un intercambio de 4.5 años, puedes crear un intercambio de 5 años comenzando 6 meses en el pasado:

issue2 = calendar.advance(settlementDate, -rolldown_period)
maturity2 = calendar.advance(issue2, 5, Years)

fixedSchedule2 = Schedule(issue2, maturity2,
                         fixedLegTenor, calendar,
                         fixedLegAdjustment, fixedLegAdjustment,
                         DateGeneration.Forward, False)
floatingSchedule2 = Schedule(issue2, maturity2,
                            floatingLegTenor, calendar,
                            floatingLegAdjustment, floatingLegAdjustment,
                            DateGeneration.Forward, False)

swap3 = VanillaSwap(VanillaSwap.Receiver, nominal,
                    fixedSchedule2, fixedRate, fixedLegDayCounter,
                    floatingSchedule2, index, spread,
                    floatingLegDayCounter)
swap3.setPricingEngine(swapEngine)

Ahora la pierna fija paga los montos correctos...

show_cashflows(swap3.fixedLeg())

     May 8th, 2002 | 40000.0 | 4.0000%
     May 8th, 2003 | 40000.0 | 4.0000%
    May 10th, 2004 | 40000.0 | 4.0000%
     May 9th, 2005 | 40000.0 | 4.0000%
     May 8th, 2006 | 40000.0 | 4.0000%

... y la tasa justa cambia en consecuencia.

print swap3.fairRate()

0.0387677146311

La segunda forma de corregirlo es, como sugeriste, usar un intercambio único.

Puedes construir la estructura a plazo para que se mueva con la fecha de evaluación; en lugar de pasar la fecha de referencia explícitamente, dile a la curva que la fecha de referencia debe ser dos días hábiles después de la fecha de evaluación.

depoSwapCurve = PiecewiseFlatForward(2, TARGET(), helpers, Actual360())

El resto de la inicialización se mantiene igual. Con esta configuración, simplemente puedes preguntar por la tasa justa del intercambio spot1 en dos fechas de evaluación diferentes:

Settings.instance().evaluationDate = todaysDate
print spot1.fairRate()

0.0443

Settings.instance().evaluationDate = calendar.advance(todaysDate, 6, Months)
print spot1.fairRate()

0.038847797035

(La tasa justa después de 6 meses es similar, pero no es exactamente la misma que la calculada de la primera manera. Probablemente se deba a que la curva es un poco diferente; los plazos de los depósitos utilizados para el arranque podrían igualar un número diferente de días dependiendo de la fecha actual.)

4voto

dotnetcoder Puntos 1262

No uses la pieza de Nordea, no es correcta. Permíteme explicarte por qué;

Si la suposición del rolldown es que la curva de IRS permanece estática a lo largo del tiempo, entonces para un IRS a 5 años tienes dos componentes; la primera fijación, es decir, la parte de 0.5 años que se conoce, y el IRS fwd de 0.5Y4.5Y, que es flotante y aún no se ha fijado.

Si, a través del rolldown, asumes que el IRS de 0.5Y4.5Y convergerá al IRS de 0Y4.5Y actual, entonces eso es lo que deberías restar para obtener el número de puntos básicos que son aplicables a la porción de riesgo que existe durante la duración del swap de 0.5Y4.5Y.

No sé por qué el carry y el rolldown son tan mal entendidos de manera universal en finanzas, pero muchas piezas de investigación de bancos están mal o, si no están mal, mal concebidas y excesivamente complicadas. Si quieres leer detalles precisos sobre IRS, considera "pricing and trading interest rate derivatives: a practical guide to swaps" de JHM Darbyshire (este es mi libro), y también echa un vistazo a estas otras respuestas...

Cálculo del carry en un swap de tasa de interés

Pregunta sobre el carry puro para dos bonos

pregunta sobre el carry y el roll de un bono

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