2 votos

QuantLib: Fijación de precios del swap de cupón cero BRL utilizando atributos relevantes en QuantLib

Estoy tratando de calcular el precio del zero coupon swap en BRL. Como sabemos que los ZC swaps de pago fijo realizan un único pago al vencimiento y el pagador flotante realiza los pagos intermedios hasta el vencimiento. Entonces, en este caso, ¿dónde está la frecuencia de pago o el atributo de calendario para calcular el precio de este swap? Si sigo la forma mencionada abajo, obtengo una variación enorme en el NPV del swap en comparación con el NPV dado. Por favor, guíame si estoy siguiendo el enfoque correcto.

maturity = []
discountfactor = []
day_count = ql.Actual360()
calendar = ql.JointCalendar(ql.UnitedStates(), ql.UnitedKingdom())
yield_curve = ql.DiscountCurve(maturity, discountfactor, day_count, calendar)
yield_curve.enableExtrapolation()
curve_handle = ql.YieldTermStructureHandle(yield_curve)

# Índice BRL
BRL_index = ql.OvernightIndex('cdi', 0, ql.BRLCurrency(), ql.Brazil(), ql.Business252())
notional = 20000000
notional currency = 'BRL'
fixed_rate = 0.05
fixed_leg_daycount = ql.Business252()
float_spread = 0.0
float_leg_daycount = ql.Business252()
payment frequency = ql.Once

# Aplicando la función de zero coupon swap
swap = ql.ZeroCouponSwap(ql.Swap.Receiver, notional, start_date, end_date,
                    fixed_rate, fixed_leg_daycount, BRL_index, ql.Brazil())

engine = ql.DiscountingSwapEngine(yield_curve)
swap.setPricingEngine(engine)
npv = swap.NPV

0 votos

0 votos

@DimitriVulis Gracias por responder. Pero mi publicación es específica para la configuración y ajustes del swap sin cupón BRL. Déjame saber tus pensamientos sobre mi publicación.

1voto

savageguy Puntos 570

"La respuesta corta es que la frecuencia de los pagos flotantes está determinada por la Programación de un IborIndex (o OvernightIndex). Por lo tanto, si definimos lo siguiente

ibor_index= ql.IborIndex(
    "MyIndex",
    ql.Period("1Y"),
    0,
    ql.SEKCurrency(),
    ql.Sweden(),
    ql.ModifiedFollowing,
    False,
    ql.Actual360(),
    ts_handle,
)

Se puede interpretar que los cupones del ZeroCouponSwap tendrán una frecuencia de 1Y, es decir, cada día de pago de cupón ocurrirá 1Y entre ellos.


Si consideramos una respuesta más detallada. Entonces encontré la definición de ZeroCouponSwap muy útil al investigar esta clase en particular. Además, supongamos que queremos replicar la siguiente cantidad de este particular ZeroCouponSwap para comprender completamente cómo se valora la pierna flotante de ZeroCouponSwap:

import QuantLib as ql
import pandas as pd
# Establecer la fecha de referencia
ref_date = ql.Date(1, 1, 2022)
ql.Settings.instance().evaluationDate = ql.Date(1, 1, 2022)

dfs = [1, 0.965, 0.94]  # factores de descuento
fechas = [
    ql.Date(1, 1, 2022),
    ql.Date(1, 1, 2023),
    ql.Date(1, 1, 2025),
]  # fechas de vencimiento de los factores de descuento

day_counter = ql.Actual360()

# Crear la curva de descuento
curva = ql.DiscountCurve(fechas, dfs, day_counter)

# La curva no estará vinculada en caso de que deseemos actualizar las comillas más adelante
ts_handle = ql.YieldTermStructureHandle(curva)

custom_index= ql.IborIndex(
    "MyIndex",
    ql.Period("1Y"),
    0,
    ql.SEKCurrency(),
    ql.Sweden(),
    ql.ModifiedFollowing,
    False,
    ql.Actual360(),
    ts_handle,
)

start_date= ql.Date(1,1,2022)
end_date = ql.Date(1,1,2024)
nominal= 10e6
swap = ql.ZeroCouponSwap(
    ql.Swap.Payer,
    nominal,
    start_date,
    end_date,
    0.02,
    day_counter,
    custom_index,
    ql.Sweden(),
)

engine = ql.DiscountingSwapEngine(ts_handle)
swap.setPricingEngine(engine)

pd.DataFrame([{
    'fechaFijacion': cf.fixingDate().ISO(),
    'inicioAcumulación': cf.accrualStartDate().ISO(),
    'finAcumulación': cf.accrualEndDate().ISO(),
    'periodoAcumulación': cf.accrualPeriod(),
    "fechaPago": cf.date().ISO(),
    'forward': cf.indexFixing(),
    'tasa': cf.rate(),
    "cantidad": cf.amount(),
} for cf in map(ql.as_floating_rate_coupon, swap.leg(1))])

El código anterior producirá los siguientes valores:

fechaFijacion

inicioAcumulación

finAcumulación

periodoAcumulación

fechaPago

forward

tasa

cantidad

2023-06-05

2022-02-03

2024-06-03

2.363888889

2024-06-03

0.0130119

0.021911

517947.6

Luego, en referencia al enlace mencionado anteriormente, la tasa flotante de los cupones para ZeroCouponSwap se calcula mediante:

$$R^{FLT} = \left[ \prod_{k=0}^{K-1} (1+\alpha(T_{k},T_{k+1}) L(T_{k},T_{k+1})) -1 \right]$$

Por lo tanto, nuestro objetivo es identificar los atributos que QuantLib utiliza para calcular el monto de la tasa flotante. Sin embargo, al inspeccionar el código fuente de QuantLib, se observará que la pierna flotante de ZeroCouponSwap consta simplemente de SubPeriodsCoupon. Para simplificar este proceso, podemos usar el siguiente código para determinar los atributos exactos que se emplean (ya que entendí que los pagos de cupones flotantes no se encuentran en la clase original ZeroCouponSwap):

import QuantLib as ql
import pandas as pd
# Establecer la fecha de referencia
ql.Settings.instance().evaluationDate = ql.Date(1, 1, 2022)

dfs = [1, 0.965, 0.94]  # factores de descuento
fechas = [
    ql.Date(1, 1, 2022),
    ql.Date(1, 1, 2023),
    ql.Date(1, 1, 2025),
]  # fechas de vencimiento de los factores de descuento

day_counter = ql.Actual360()

# Crear la curva de descuento
curva = ql.DiscountCurve(fechas, dfs, day_counter)

# La curva no estará vinculada en caso de que deseemos actualizar las comillas más adelante
ts_handle = ql.YieldTermStructureHandle(curva)

custom_index= ql.IborIndex(
    "MyIndex",
    ql.Period("1Y"),
    0,
    ql.SEKCurrency(),
    ql.Sweden(),
    ql.ModifiedFollowing,
    False,
    ql.Actual360(),
    ts_handle,
)

start_date= ql.Date(1,1,2022)
end_date = ql.Date(1,1,2024)

cupon = ql.SubPeriodsCoupon(ql.Date(3,6,2024), 1e7, start_date, end_date, 0, custom_index)

cupon.setPricer(ql.CompoundingRatePricer())
print(f'{cupon.amount() = }')

Esto nos dará el mismo monto que ZeroCouponSwap. Por lo tanto, dado la ecuación que se encontró en la documentación, llegamos a

fechas_fijacion_cupon = cupon.fixingDates()
fijaciones_sub_periodos = []
for fecha_fijacion in fechas_fijacion_cupon:
    fijaciones_sub_periodos.append(custom_index.fixing(fecha_fijacion))
fracciones_sub_periodos = cupon.dt()
factor_compuesto = 1 
for fijacion, frac in zip(fijaciones_sub_periodos, fracciones_sub_periodos):
    factor_compuesto *= 1+fijacion * frac 
tasa = ((factor_compuesto-1) / cupon.accrualPeriod()) 

Lo cual produce el número 0.021910825388354933. Este es el número que se utiliza para obtener el monto de la pierna flotante. Al multiplicar este valor por $10^6$ y accrualPeriod llegamos al monto exacto.


Espero que la forma más eficiente de ver los días de pago de cupones sea usando coupon.valueDates() que se crea con ql.MakeSchedule utilizando start_date, end_date y ql.Period para ver los flujos de efectivo de la pierna flotante.

0 votos

Xiapedia Gracias por el esfuerzo y el tiempo. Según tu respuesta, las conclusiones clave son: 1) Mi enfoque para el precio del swap de cupón cero BRL parece correcto. 2) La frecuencia de pago es irrelevante si asumo BRL como índice overnight. Sin embargo, siempre puedo ajustar la frecuencia a ql.Once en el iborIndex

0 votos

3) El BRL se puede definir como ql.OvernightIndex('brlcdi', 0, ql.BRLCurrency, ql.Brazil(), ql.Business252()) o ql.IborIndex("brlcdi", ql.Period("0D"), 0, ql.BRLCurrency(), ql.Brazil(), ql.ModifiedFollowing, False, ql.Business252(), ts_handle) 4) Los atributos de la clase Quantlib zerocouponswap tienen la configuración requerida para el precio y no es necesario calcular las patas de Flujo o Fijo por separado.

0 votos

Me alegra que hayas encontrado útil mi respuesta. Por favor, considera marcarla como la solución para futuros usuarios.

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