2 votos

Quantlib: convertir las tasas de intercambio de par en tasas cero de ida y vuelta

Construí una curva de cupón cero a partir de una curva genérica de tipo de cambio de par ( Paso 1 ) y estoy tratando de recuperar la curva de intercambio de la curva de cupón cero ( Paso 2 ).

El paso 1 funciona pero no el 2. Tengo citas cercanas pero no coinciden exactamente. ¿Alguien tiene alguna idea de lo que está mal en mi Paso 2?

Mi conjetura es que no proviene de cuestiones de calendario, ya que estoy usando el calendario teórico, el contador de días y el índice, sin ningún ajuste.

Aquí está mi código:

PASO 1:

# define constants
face_amount = 100
settlementDays = 0
calendar = ql.NullCalendar()
fixedLegAdjustment = ql.Unadjusted
floatingLegAdjustment = ql.Unadjusted
fixedLegDayCounter = ql.SimpleDayCounter()
floatingLegDayCounter = ql.SimpleDayCounter()
fixedLegFrequency = ql.Semiannual
end_of_month = False
floating_rate = ql.IborIndex("MyIndex", ql.Period(3, ql.Months), settlementDays, ql.USDCurrency(), calendar, floatingLegAdjustment, end_of_month, floatingLegDayCounter)

# irs is a DataFrame with one line and the column as maturities (from 3M to 120M)
deposits = [irs.columns[0]]
swaps = irs.columns[1:]

# curve dates
zero_rates = {}
curve_date = ql.DateParser.parseFormatted(str("2017-01-01"), "%Y-%m-%d")
ql.Settings.instance().evaluationDate = curve_date
spot_date = calendar.advance(curve_date, settlementDays, ql.Days)

# deposit helper
deposit_helpers_mat = []
for tenor in deposits:
    deposit_helpers_mat.append([ql.Period(int(tenor), ql.Months), ql.QuoteHandle(ql.SimpleQuote(irs[int(tenor)] / 100))])

deposit_helper = [ql.DepositRateHelper(tenors_deposit, settlementDays, calendar, fixedLegAdjustment, end_of_month, fixedLegDayCounter) for tenors_deposit, deposit_rates in deposit_helpers_mat]

# swap helper
swap_helpers_mat = []
for tenor in swaps:
    swap_helpers_mat.append([ql.Period(int(tenor), ql.Months), ql.QuoteHandle(ql.SimpleQuote(irs[int(tenor)] / 100))])

swap_helper = [ql.SwapRateHelper(swap_rates, tenors_swap, calendar, fixedLegFrequency, fixedLegAdjustment, fixedLegDayCounter, floating_rate) for tenors_swap, swap_rates in swap_helpers_mat]

# aggregate helpers
helper = deposit_helper + swap_helper

# build curve
zc_curve = ql.PiecewiseCubicZero(curve_date, helper, ql.SimpleDayCounter())
zero_rate = []
tenors = []
# loop over maturities
for tenor in np.arange(3, 120 + 1, 3):
    maturity_date = calendar.advance(spot_date, ql.Period(int(tenor), ql.Months))
    zero_rate_curve = (zc_curve.zeroRate(maturity_date, ql.SimpleDayCounter(), ql.Compounded, ql.Annual).rate()* 100)
    zero_rate.append(zero_rate_curve)
    tenors.append(tenor)

# build the zero curve representation into a DataFrame
zero_rates = pd.DataFrame(np.transpose(list(zip(zero_rate))), columns=list(tenors))

PASO 2:

# constant
fixedRate = 0.02
spread =0
TENORS = np.arange(3, 120 + 1, 3)

# pre-allocate
irs_rates = {}
# calculate dates
curve_date = ql.DateParser.parseFormatted(str("2017-01-01"), "%Y-%m-%d")
ql.Settings.instance().evaluationDate = curve_date
spot_date = calendar.advance(curve_date, settlementDays, ql.Days)

# zero curve
irs_rate = []
tenors = []
maturity_dates = []
zc_rates = []
# loop over maturities
for tenor in TENORS:
    # maturity date
    maturity_date = calendar.advance(spot_date, ql.Period(int(tenor), ql.Months))
    # gather maturity dates
    maturity_dates.append(maturity_date)
    # gather zc rates
    zc_rates.append(zero_rates[int(tenor)] / 100)

# build zero coupon curve object
zero_curve = ql.YieldTermStructureHandle(ql.CubicZeroCurve(maturity_dates, zc_rates, fixedLegDayCounter, calendar))
# libor curve
libor_curve = ql.YieldTermStructureHandle(ql.CubicZeroCurve(maturity_dates, zc_rates, floatingLegDayCounter, calendar))
# floating rate
floating_rate = ql.IborIndex("MyIndex", ql.Period(3, ql.Months), settlementDays, ql.USDCurrency(), calendar, floatingLegAdjustment, end_of_month, floatingLegDayCounter, libor_curve)

# build swap curve
# loop over maturities
j = 0
for maturity in maturity_dates:
    # fixed leg tenor
    fixedLegTenor = ql.Period(3, ql.Months)
    # fixed leg coupon schedule
    fixedLegSchedule = ql.Schedule(spot_date, maturity, fixedLegTenor, calendar, fixedLegAdjustment, fixedLegAdjustment, ql.DateGeneration.Forward, end_of_month)

    # floating leg tenor
    floatingLegTenor = ql.Period(3, ql.Months)
    # floating leg coupon schedule
    floatingLegSchedule = ql.Schedule(spot_date, maturity, floatingLegTenor, calendar, floatingLegAdjustment, floatingLegAdjustment, ql.DateGeneration.Forward, end_of_month)

    # build swap pricer
    swap_rate = ql.VanillaSwap(ql.VanillaSwap.Payer, face_amount, fixedLegSchedule, fixedRate, fixedLegDayCounter, floatingLegSchedule, floating_rate, spread, floatingLegDayCounter)

    # build swap curve
    swap_curve = ql.DiscountingSwapEngine(zero_curve)
    # get swap rate
    swap_rate.setPricingEngine(swap_curve)

    # gather par irs rate
    irs_rate.append(swap_rate.fairRate() * 100)
    # gather irs tenor
    tenor = int(TENORS[j])
    j = j + 1
    tenors.append(tenor)
    # build the swap curve representation into a DataFrame
    irs_rates = pd.DataFrame(np.transpose(list(zip(irs_rate))), columns=list(tenors))

¡Muchas gracias de antemano por su ayuda!

3voto

Chris Mc Puntos 31

Tal vez deberías empezar con un ejemplo sencillo, porque tienes tantas partes móviles que es difícil averiguar dónde está la diferencia. Lo más probable es que haya alguna convención diferente entre tus ayudantes y los instrumentos que estás tratando de tasar.

import QuantLib as ql

today = ql.Date().todaysDate()
calendar = ql.TARGET()
spot = calendar.advance(today, 2, ql.Days)

helpers = ql.RateHelperVector()
helpers.append( ql.DepositRateHelper(0.01, ql.Euribor6M()) )

swaps = [
    ('1Y', 0.015),
    ('2Y', 0.02),
    ('3Y', 0.025)
]
for tenor, rate in swaps:
    swapIndex = ql.EurLiborSwapIsdaFixA(ql.Period(tenor))
    helpers.append( ql.SwapRateHelper(rate, swapIndex) )

curve = ql.PiecewiseLogLinearDiscount(spot, helpers, ql.Actual360())
yts = ql.YieldTermStructureHandle(curve)
engine = ql.DiscountingSwapEngine(yts)

index = ql.Euribor6M(yts)

print("maturity, market, model")
for tenor, rate in swaps:
    swap = ql.MakeVanillaSwap(ql.Period(tenor), index, 0.01, ql.Period('0D'), pricingEngine=engine)    
    print(f"{tenor}, {rate:.6f}, {swap.fairRate():.6f}")

madurez, mercado, modelo
1Y, 0.015000, 0.015000
2Y, 0.020000, 0.020000
3Y, 0.025000, 0.025000

0 votos

Gracias por tu ejemplo David. Un par de preguntas: - ¿Es normal (por definición) que la conversión del tipo cero de 3M en un swap de 3M me dé valores nanos? - En la primera conversión (de swaps a tasas cero), no uso ninguna curva para especificar mi tasa_flotante. Sin embargo, en la segunda conversión (de tasas cero a swaps), sí lo hago (y tú también). ¿Es eso un problema? - Por último, parece que el uso de "ql.DateGeneration.Zero" hace que mi código se bloquee en el segundo paso, y sólo funciona Forward. ¿Dónde puedo especificar que es el SwapHelper para estar seguro de que se utiliza la misma convención?

0voto

Chris Mc Puntos 31

Para responder a las preguntas de su comentario:

  1. ¿Qué es un tipo de cambio 3M? O bien es un tipo fijo frente a un plazo más corto (ej: 1m) o un tipo fijo frente al mismo plazo pero a plazo (en este caso un FRA), o si es al contado de partida entonces es lo mismo que un tipo cero porque tiene que intermediar los pagos.

  2. Las convenciones de tipo flotante están en la definición del índice flotante. Además, hay plantillas que puede utilizar: ql.USDLibor(ql.Period('3M'))

  3. Para especificar su ayudante, puede utilizar una plantilla donde las convenciones ya están definidas ( por ejemplo: ql.UsdLiborSwapIsdaFixAm ) o utilizar uno de los varios constructores. Consulte aquí para obtener más información: https://quantlib-python-docs.readthedocs.io/en/latest/thelpers.html#swapratehelper

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