Saltar a contenido

Valorización y Sensibilidad

Configuración

Para ejecutar todos los ejemplos se debe importar la librería. Se sugiere utilizar siempre el alias qcf.

import qcfinancial as qcf
qcf.id()
'version: 0.10.0, build: 2024-06-09 10:20'

Librerías adicionales.

import aux_functions as aux # Aquí se guardó la funcion leg_as_dataframe del notebook 3
from math import exp, log
import pandas as pd
import numpy as np
pd.options.display.max_columns=300

Para formateo de pandas.DataFrames.

format_dict = {
    'nominal': '{:,.2f}',
    'amort': '{:,.2f}',
    'interes': '{:,.2f}',
    'flujo': '{:,.2f}',
    'nocional': '{:,.2f}',
    'amortizacion': '{:,.2f}',
    'icp_inicial': '{:,.2f}',
    'icp_final': '{:,.2f}',
    'uf_inicial': '{:,.2f}',
    'uf_final': '{:,.2f}',
    'plazo': '{:,.0f}',
    'tasa': '{:,.4%}',
    'valor_tasa': '{:,.4%}',
    'valor_tasa_equivalente': '{:,.4%}',
    'spread': '{:,.4%}',
    'gearing': '{:,.2f}',
    'amort_moneda_pago': '{:,.2f}',
    'interes_moneda_pago': '{:,.2f}',
    'valor_indice_inicial': '{:,.2f}',
    'valor_indice_final': '{:,.2f}',
    'valor_indice_fx': '{:,.2f}',
    'flujo_en_clp': '{:,.2f}',
}

Construcción de la Curva

La construcción de una curva se hace en varios pasos.

Vectores de Float e Int

Este es un vector de números enteros (grandes, de ahí la l (long))

lvec = qcf.long_vec()

Agregar un elemento.

lvec.append(1000)

Este es un vector de números float.

vec = qcf.double_vec()

Agregar un elemento.

vec.append(.025)

Obtener ese elemento.

print(f"Plazo: {lvec[0]:,.0f}")
print(f"Tasa: {vec[0]:,.2%}")
Plazo: 1,000
Tasa: 2.50%

Objeto Curva

Es simplemente un long_vec que representa las abscisas de la curva y un double_vec que representa las ordenadas. Ambos vectores deben tener el mismo largo.

curva = qcf.QCCurve(lvec, vec)

Un elemento de una curva se representa como un par abscisa, ordenada.

curva.get_values_at(0)
(1000, 0.025)

Se obtiene el plazo en una posición de la curva.

curva.get_values_at(0)[0]
1000

Se obtiene la tasa en una posición de la curva.

curva.get_values_at(0)[1]
0.025

Se agrega un par (plazo, valor) a la curva.

curva.set_pair(100, .026)

Se verifica. Notar que se debe usar el índice 0 ya que la curva se ordena automáticamente por plazos ascendentes.

curva.get_values_at(0)[0]
100
curva.get_values_at(0)[1]
0.026

Se agrega un par más.

curva.set_pair(370, .03)

Se itera sobre la curva mostrando sus valores

for i in range(curva.get_length()):
    pair = curva.get_values_at(i)
    print("Tenor: {0:,.0f} Valor: {1:.4%}".format(pair[0], pair[1]))
Tenor: 100 Valor: 2.6000%
Tenor: 370 Valor: 3.0000%
Tenor: 1,000 Valor: 2.5000%

Interpolador

Se agrega un interpolador. En este caso, un interpolador lineal.

lin = qcf.QCLinearInterpolator(curva)

Se puede ahora obtener una tasa a un plazo cualquiera.

plazo = 120
print(f"Tasa a {plazo:,.0f} días es igual a {lin.interpolate_at(plazo):.4%}")
Tasa a 120 días es igual a 2.6296%

Curva Cero Cupón

Para completar el proceso se define un objeto de tipo QCInterestRate. Con este objeto, que representa la convención de las tasas de interés asociadas a la curva, se termina de dar de alta una curva cero cupón.

yf = qcf.QCAct365()
wf = qcf.QCContinousWf()
tasa = qcf.QCInterestRate(.0, yf, wf)
zcc = qcf.ZeroCouponCurve(lin, tasa)

El interpolador permite obtener una tasa a cualquier plazo.

plazo = 300
print(f"Tasa en {plazo:,.0f} es igual a {zcc.get_rate_at(plazo):.4%}")
Tasa en 300 es igual a 2.8963%

Otros métodos:

Discount factor.

print(f"Discount factor at {plazo}: {zcc.get_discount_factor_at(plazo):.6%}")
print(f"Check: {exp(-zcc.get_rate_at(plazo) * plazo / 365):.6%}")
Discount factor at 300: 97.647593%
Check: 97.647593%

Derivadas del factor de descuento respecto a los vértices de la curva.

[zcc.wf_derivative_at(i) for i in range(zcc.get_length())]
[0.21822330167566764, 0.6234951476447647, 0.0]

Tasa Forward

d1 = 30
d2 = 90
print(f"Tasa forward entre los días {d1:,.0f} y {d2:,.0f}: {zcc.get_forward_rate(d1, d2):.4%}")
Tasa forward entre los días 30 y 90: 2.6000%

Se verifica el cálculo.

df1 = zcc.get_discount_factor_at(d1)
df2 = zcc.get_discount_factor_at(d2)
df12 = df1 / df2
print(f"Check: {log(df12) * 365 / (d2 - d1):.4%}")
Check: 2.6000%

Derivadas del factor de capitalización de la tasa forward.

[zcc.fwd_wf_derivative_at(i) for i in range(zcc.get_length())]
[0.16508763600814624, 0.0, 0.0]

Valorizar

Para valorizar es necesario dar de alta un objeto de tipo PresentValue.

pv = qcf.PresentValue()

Depósito a Plazo

Se utilizará como instrumento un depósito a plazo en CLP o USD. Este instrumento se modela como un SimpleCashflow. Este, a su vez se construye con un monto, una fecha y una moneda.

fecha_vcto = qcf.QCDate(13, 1, 2025)
monto = 10_000_000.0
clp = qcf.QCCLP()

# Se construye el depósito
depo = qcf.SimpleCashflow(fecha_vcto, monto, clp)
print(f"Monto del depósito: {depo.amount():,.0f}")
Monto del depósito: 10,000,000

Se define una fecha de valorización y se calcula el valor presente del depo.

fecha_hoy = qcf.QCDate(31, 1, 2024)
print(f"Valor presente depo: {pv.pv(fecha_hoy, depo, zcc):,.2f}")
Valor presente depo: 9,721,044.77

Se verifica a mano el resultado.

plazo = fecha_hoy.day_diff(fecha_vcto)
print("Plazo:", plazo)
Plazo: 348
tasa_int = zcc.get_rate_at(plazo)
print(f"Tasa: {tasa_int:,.4%}")
Tasa: 2.9674%
valor_presente = monto * exp(-tasa_int * plazo / 365)
print(f"Valor presente a mano: {valor_presente:,.2f}")
Valor presente a mano: 9,721,044.77

Renta Fija de Chile

Se muestra el ejemplo de valorización de un bono bullet a tasa fija con las convenciones de la Bolsa de Comercio de Santiago. Para el ejemplo usamos las características del BTU0150326.

Se dan de alta los parámetros requeridos para instanciar un objeto de tipo FixedRateLeg.

rp = qcf.RecPay.RECEIVE
fecha_inicio = qcf.QCDate(1, 3, 2015)
fecha_final = qcf.QCDate(1, 3, 2026)
bus_adj_rule = qcf.BusyAdjRules.NO
periodicidad = qcf.Tenor('6M')
periodo_irregular = qcf.StubPeriod.NO
calendario = qcf.BusinessCalendar(fecha_inicio, 20)
lag_pago = 0
nominal = 100.0
amort_es_flujo = True
valor_tasa_fija = .015
tasa_cupon = qcf.QCInterestRate(
    valor_tasa_fija, 
    qcf.QC30360(),
    qcf.QCLinearWf()
)
moneda = qcf.QCCLP()
es_bono = True

Se da de alta el objeto.

pata_bono = qcf.LegFactory.build_bullet_fixed_rate_leg(
    rp,
    fecha_inicio,
    fecha_final,
    bus_adj_rule,
    periodicidad,
    periodo_irregular,
    calendario,
    lag_pago,
    nominal,
    amort_es_flujo,
    tasa_cupon,
    moneda,
    es_bono
)

Se da de alta el valor de la TERA y luego se construye un objeto de tipo ChileanFixedRateBond.

tera = qcf.QCInterestRate(.015044, qcf.QCAct365(), qcf.QCCompoundWf())
bono_chileno = qcf.ChileanFixedRateBond(pata_bono, tera)

Se valoriza al 2021-09-28 a una TIR de mercado del 1.61%.

fecha_valor = qcf.QCDate(28, 9, 2021)
tir = qcf.QCInterestRate(.0161, qcf.QCAct365(), qcf.QCCompoundWf())

valor_presente = bono_chileno.present_value(fecha_valor, tir)
precio = bono_chileno.precio(fecha_valor, tir)
valor_par = bono_chileno.valor_par(fecha_valor)

print(f'Valor presente: {valor_presente:,.8f}')
print(f'Precio: {precio:,.2%}')
print(f'Valor par: {valor_par:,.18f}')
Valor presente: 99.67188455
Precio: 99.56%
Valor par: 100.110516628864033351

Con esto el valor a pagar es:

valor_uf = 30_080.37
valor_pago = precio * valor_par * valor_uf
print(f'Valor a pagar: {valor_pago:,.0f}')
Valor a pagar: 2,998,111

Con 4 decimales en el precio (4 decimales porcentuales, 6 decimales en el número):

precio2 = bono_chileno.precio2(fecha_valor, tir, 6)
print(f'Precio a 4 decmales: {precio2:.4%}')
Precio a 4 decmales: 99.5619%

La función precio2 entrega el mismo resultado que la función precio cuando se utiliza con 2 decimales porcentuales.

precio22 = bono_chileno.precio2(fecha_valor, tir, 4)
print(f'Precio a 4 decmales: {precio22:.4%}')
Precio a 4 decmales: 99.5600%

Se muestran las diferencias con la convención de precio usual en mercados desarrollados.

bono = qcf.FixedRateBond(pata_bono)
print(f'Valor presente: {bono.present_value(fecha_valor, tir):,.8f}')
print(f'Precio: {bono.price(fecha_valor, tir):,.8f}')
Valor presente: 99.67188455
Precio: 99.55938455

Curvas Reales

Construyamos dos curvas a partir de data real. Primero una curva en CLP.

curva_clp = pd.read_excel("./input/curva_clp.xlsx")
curva_clp.style.format(format_dict)
  plazo tasa
0 1 1.7500%
1 4 1.7501%
2 96 1.4867%
3 188 1.3049%
4 279 1.2870%
5 369 1.3002%
6 553 1.3035%
7 734 1.2951%
8 1,099 1.4440%
9 1,465 1.6736%
10 1,830 1.9860%
11 2,195 2.2766%
12 2,560 2.5812%
13 2,926 2.8216%
14 3,291 3.0373%
15 3,656 3.2154%
16 4,387 3.4525%
17 5,482 3.7636%
18 7,309 4.1641%

Se da de alta un vector con los plazos (variable de tipo long) y un vector con las tasas (variable de tipo double).

lvec1 = qcf.long_vec()
vec1 = qcf.double_vec()
for t in curva_clp.itertuples():
    lvec1.append(int(t.plazo))
    vec1.append(t.tasa)

Luego, con una curva, un interpolador y un objeto QCInterestRate(que indica la convención de las tasas de la curva) se construye una curva cupón cero.

curva1 = qcf.QCCurve(lvec1, vec1)
lin1 = qcf.QCLinearInterpolator(curva1)
zcc_clp = qcf.ZeroCouponCurve(lin1, tasa)

Luego, una curva en USD.

curva_usd = pd.read_excel("./input/curva_usd.xlsx")
curva_usd.style.format(format_dict)
  plazo tasa
0 3 1.5362%
1 4 1.1521%
2 7 1.5536%
3 14 1.5850%
4 31 1.6595%
5 60 1.7698%
6 91 1.8010%
7 123 1.7711%
8 152 1.7542%
9 182 1.7394%
10 213 1.7288%
11 244 1.7183%
12 276 1.7027%
13 305 1.6917%
14 335 1.6820%
15 367 1.6722%
16 549 1.6207%
17 731 1.5947%
18 1,096 1.5788%
19 1,461 1.5906%
20 1,827 1.6190%
21 2,558 1.7028%
22 3,653 1.8533%
23 4,385 1.9547%
24 5,479 2.0893%
25 7,305 2.2831%
26 9,132 2.4306%
27 10,958 2.5576%

Se encapasulará todo el procedimiento anterior en una función que dado un DataFrame construya un objeto ZeroCouponCurve.

def zcc_from_df(df: pd.DataFrame, tasa: qcf.QCInterestRate) -> qcf.ZeroCouponCurve:
    lvec = qcf.long_vec()
    vec = qcf.double_vec()
    for t in df.itertuples():
        lvec.append(int(t.plazo))
        vec.append(t.tasa)
    curva = qcf.QCCurve(lvec, vec)
    lin = qcf.QCLinearInterpolator(curva)
    return qcf.ZeroCouponCurve(lin, tasa)
zcc_usd = zcc_from_df(curva_usd, tasa)

Finalmente, una curva en CLF.

curva_clf = pd.read_excel("./input/curva_clf.xlsx")
curva_clf.style.format(format_dict)
  plazo tasa
0 1 -5.6780%
1 4 -5.6744%
2 35 -0.9340%
3 64 -2.1183%
4 96 -2.0079%
5 126 -2.0762%
6 155 -1.9197%
7 188 -1.9347%
8 218 -1.7626%
9 249 -1.7987%
10 279 -1.9335%
11 309 -1.8159%
12 341 -1.5940%
13 369 -1.5994%
14 400 -1.5068%
15 428 -1.6115%
16 461 -1.5923%
17 491 -1.5780%
18 522 -1.5186%
19 553 -1.5533%
20 582 -1.5649%
21 734 -1.6594%
22 1,099 -1.4881%
23 1,465 -1.2740%
24 1,830 -1.0201%
25 2,195 -0.8009%
26 2,560 -0.5868%
27 2,926 -0.4145%
28 3,291 -0.3047%
29 3,656 -0.2242%
30 4,387 -0.1871%
31 5,482 -0.1056%
32 7,309 -0.0639%
zcc_clf = zcc_from_df(curva_clf, tasa)

Curvas para Sensibilidad

Para calcular sensibilidad a la curva cero cupón, se define qué vértice de la curva se quiere desplazar y el monto en puntos básicos del desplazamiento.

vertice = 15
bp = 1

Se construyen las curvas con ese vértice 1 punto básico más arriba y 1 punto básico más abajo. Para esto se define una función auxiliar.

def curvas_sens(
    df: pd.DataFrame, 
    tasa: qcf.QCInterestRate, 
    vertice: int, 
    bp: float
) -> tuple[qcf.ZeroCouponCurve, qcf.ZeroCouponCurve]:
    bp /= 10_000
    lvec = qcf.long_vec()
    vec_sens_up = qcf.double_vec()
    vec_sens_down = qcf.double_vec()
    for t in df.itertuples():
        lvec.append(int(t.plazo))
        if t.Index == vertice:
            vec_sens_up.append(t.tasa + bp)
            vec_sens_down.append(t.tasa - bp)
        else:
            vec_sens_up.append(t.tasa)
            vec_sens_down.append(t.tasa)

    zcc_sens_up = qcf.QCCurve(lvec, vec_sens_up)
    lin_sens_up = qcf.QCLinearInterpolator(zcc_sens_up)
    zz_sens_up = qcf.ZeroCouponCurve(lin_sens_up, tasa)

    zcc_sens_down = qcf.QCCurve(lvec, vec_sens_down)
    lin_sens_down = qcf.QCLinearInterpolator(zcc_sens_down)
    zz_sens_down = qcf.ZeroCouponCurve(lin_sens_down, tasa)

    return zz_sens_up, zz_sens_down
zcc_clp_up, zcc_clp_down = curvas_sens(curva_clp, tasa, vertice, bp)
zcc_usd_up, zcc_usd_down = curvas_sens(curva_usd, tasa, vertice, bp)
zcc_clf_up, zcc_clf_down = curvas_sens(curva_clf, tasa, vertice, bp)

FixedRateCashflow Leg

Se da de alta una pata fija:

rp = qcf.RecPay.RECEIVE
fecha_inicio = qcf.QCDate(31, 1, 2024)
fecha_final = qcf.QCDate(31, 1, 2025)
bus_adj_rule = qcf.BusyAdjRules.MODFOLLOW
periodicidad = qcf.Tenor('6M')
periodo_irregular = qcf.StubPeriod.SHORTFRONT
calendario = qcf.BusinessCalendar(fecha_inicio, 20)
lag_pago = 0
nominal = 20_000_000.0
amort_es_flujo = True
valor_tasa_fija = .01774
tasa_cupon = qcf.QCInterestRate(
    valor_tasa_fija, 
    qcf.QC30360(), 
    qcf.QCLinearWf()
)
moneda = qcf.QCUSD()
es_bono = False

fixed_rate_leg = qcf.LegFactory.build_bullet_fixed_rate_leg(
    rp,
    fecha_inicio,
    fecha_final,
    bus_adj_rule,
    periodicidad,
    periodo_irregular,
    calendario,
    lag_pago,
    nominal,
    amort_es_flujo,
    tasa_cupon,
    moneda,
    es_bono
)
aux.leg_as_dataframe(fixed_rate_leg).style.format(format_dict)
  fecha_inicial fecha_final fecha_pago nominal amortizacion interes amort_es_flujo flujo moneda valor_tasa tipo_tasa
0 2024-01-31 2024-07-31 2024-07-31 20,000,000.00 0.00 177,400.00 True 177,400.00 USD 1.7740% Lin30360
1 2024-07-31 2025-01-31 2025-01-31 20,000,000.00 20,000,000.00 177,400.00 True 20,177,400.00 USD 1.7740% Lin30360

Se calcula ahora el valor presente:

vp_fija = pv.pv(fecha_hoy, fixed_rate_leg, zcc_usd)
print(f"Valor presente de la pata fija es: {vp_fija:,.0f}")
Valor presente de la pata fija es: 20,017,705

Al calcular el valor presente, también se calculan las derivadas del valor presente respecto a cada uno de los vértices de la curva.

der = pv.get_derivatives()

Con esas derivadas, se puede calcular la sensibilidad a la curva cupón cero a un movimiento de 1 punto básico.

i = 0
total = 0
for d in der:
    total += d * bp / 10_000
    print(f"Sensibilidad en {i}: {d * bp / 10_000:0,.0f}")
    i += 1
print(f"Sensibilidad total: {total:,.0f}")
Sensibilidad en 0: 0
Sensibilidad en 1: 0
Sensibilidad en 2: 0
Sensibilidad en 3: 0
Sensibilidad en 4: 0
Sensibilidad en 5: 0
Sensibilidad en 6: 0
Sensibilidad en 7: 0
Sensibilidad en 8: 0
Sensibilidad en 9: -9
Sensibilidad en 10: 0
Sensibilidad en 11: 0
Sensibilidad en 12: 0
Sensibilidad en 13: 0
Sensibilidad en 14: -62
Sensibilidad en 15: -1,927
Sensibilidad en 16: 0
Sensibilidad en 17: 0
Sensibilidad en 18: 0
Sensibilidad en 19: 0
Sensibilidad en 20: 0
Sensibilidad en 21: 0
Sensibilidad en 22: 0
Sensibilidad en 23: 0
Sensibilidad en 24: 0
Sensibilidad en 25: 0
Sensibilidad en 26: 0
Sensibilidad en 27: 0
Sensibilidad total: -1,998

Se puede verificar la sensibilidad por diferencias finitas.

Se calcula el valor presente con las curvas desplazadas.

vp_fija_sens_up = pv.pv(fecha_hoy, fixed_rate_leg, zcc_usd_up)
vp_fija_sens_down = pv.pv(fecha_hoy, fixed_rate_leg, zcc_usd_down)
print(f"Valor presente up de la pata fija es: {vp_fija_sens_up:,.4f}")
print(f"Valor presente down de la pata fija es: {vp_fija_sens_down:,.4f}")
Valor presente up de la pata fija es: 20,015,777.5553
Valor presente down de la pata fija es: 20,019,632.4437

Finalmente, se calcula la sensibilidad (usando la aproximación central por diferencias finitas).

print(f"Sensibilidad por diferencias finitas: {(vp_fija_sens_up - vp_fija_sens_down) / 2:,.0f}")
Sensibilidad por diferencias finitas: -1,927

OvernightIndex Leg

Se da de alta una pata OvernightIndex.

rp = qcf.RecPay.RECEIVE
fecha_inicio = qcf.QCDate(10,1,2019)
fecha_final = qcf.QCDate(10,7,2029)
bus_adj_rule = qcf.BusyAdjRules.FOLLOW
fix_adj_rule = qcf.BusyAdjRules.PREVIOUS
dates_for_eq_rate = qcf.DatesForEquivalentRate.ACCRUAL
periodicidad_pago = qcf.Tenor('2Y')
periodo_irregular_pago = qcf.StubPeriod.SHORTFRONT
calendario = qcf.BusinessCalendar(fecha_inicio, 20)
lag_pago = 0
nominal = 38_000_000_000.0
amort_es_flujo = True
spread = .0
gearing = 1.0

overnight_index_leg = qcf.LegFactory.build_bullet_overnight_index_leg(
    rec_pay=rp,
    start_date=fecha_inicio,
    end_date=fecha_final,
    bus_adj_rule=bus_adj_rule,
    fix_adj_rule=fix_adj_rule,
    settlement_periodicity=periodicidad_pago,
    stub_period=periodo_irregular_pago,
    settlement_calendar=calendario,
    fixing_calendar=calendario,
    settlement_lag=lag_pago,
    initial_notional=nominal,
    amort_is_cashflow=amort_es_flujo,
    spread=spread,
    gearing=gearing,
    interest_rate=qcf.QCInterestRate(0.0, qcf.QCAct360(), qcf.QCLinearWf()),
    index_name='ICPCLP',
    eq_rate_decimal_places=2,
    notional_currency=qcf.QCCLP(),
    dates_for_eq_rate=dates_for_eq_rate,
)
aux.leg_as_dataframe(overnight_index_leg).style.format(format_dict)
  fecha_inicial_devengo fecha_final_devengo fecha_inicial_indice fecha_final_indice fecha_pago nocional amortizacion amort_es_flujo moneda_nocional nombre_indice valor_indice_inicial valor_indice_final valor_tasa_equivalente tipo_tasa interes flujo spread gearing
0 2019-01-10 2019-07-10 2019-01-10 2019-07-10 2019-07-10 38,000,000,000.00 0.00 True CLP ICPCLP 1.00 1.00 0.0000% LinAct360 0.00 0.00 0.0000% 1.00
1 2019-07-10 2021-07-12 2019-07-10 2021-07-12 2021-07-12 38,000,000,000.00 0.00 True CLP ICPCLP 1.00 1.00 0.0000% LinAct360 0.00 0.00 0.0000% 1.00
2 2021-07-12 2023-07-10 2021-07-12 2023-07-10 2023-07-10 38,000,000,000.00 0.00 True CLP ICPCLP 1.00 1.00 0.0000% LinAct360 0.00 0.00 0.0000% 1.00
3 2023-07-10 2025-07-10 2023-07-10 2025-07-10 2025-07-10 38,000,000,000.00 0.00 True CLP ICPCLP 1.00 1.00 0.0000% LinAct360 0.00 0.00 0.0000% 1.00
4 2025-07-10 2027-07-12 2025-07-10 2027-07-12 2027-07-12 38,000,000,000.00 0.00 True CLP ICPCLP 1.00 1.00 0.0000% LinAct360 0.00 0.00 0.0000% 1.00
5 2027-07-12 2029-07-10 2027-07-12 2029-07-10 2029-07-10 38,000,000,000.00 38,000,000,000.00 True CLP ICPCLP 1.00 1.00 0.0000% LinAct360 0.00 38,000,000,000.00 0.0000% 1.00

Notar que al dar de alta un Leg con OvernightIndexCashflow, los valores futuros de los índeces son los default (=1.0). Por lo tanto, el primer paso para valorizar estos cashflows, es calcular los valores forward de los índices.

Se da de alta un objeto de tipo ForwardRates.

fwd_rates = qcf.ForwardRates()

Se calculan los índices forward.

icp_val = 18_890.34 # icp a la fecha de proceso
fecha_hoy = qcf.QCDate(5, 3, 2020)

for i in range(overnight_index_leg.size()):
    cashflow = overnight_index_leg.get_cashflow_at(i)
    if cashflow.get_start_date() <= fecha_hoy <= cashflow.get_end_date():
        index = i

icp_fecha_inicio_cupon_vigente = 18_376.69
overnight_index_leg.get_cashflow_at(index).set_start_date_index(icp_fecha_inicio_cupon_vigente)

fwd_rates.set_rates_overnight_index_leg(fecha_hoy, icp_val, overnight_index_leg, zcc_clp)
aux.leg_as_dataframe(overnight_index_leg).style.format(format_dict)
  fecha_inicial_devengo fecha_final_devengo fecha_inicial_indice fecha_final_indice fecha_pago nocional amortizacion amort_es_flujo moneda_nocional nombre_indice valor_indice_inicial valor_indice_final valor_tasa_equivalente tipo_tasa interes flujo spread gearing
0 2019-01-10 2019-07-10 2019-01-10 2019-07-10 2019-07-10 38,000,000,000.00 0.00 True CLP ICPCLP 1.00 1.00 0.0000% LinAct360 0.00 0.00 0.0000% 1.00
1 2019-07-10 2021-07-12 2019-07-10 2021-07-12 2021-07-12 38,000,000,000.00 0.00 True CLP ICPCLP 18,376.69 19,226.28 2.0000% LinAct360 1,547,444,444.44 1,547,444,444.44 0.0000% 1.00
2 2021-07-12 2023-07-10 2021-07-12 2023-07-10 2023-07-10 38,000,000,000.00 0.00 True CLP ICPCLP 19,226.28 19,877.30 2.0000% LinAct360 1,536,888,888.89 1,536,888,888.89 0.0000% 1.00
3 2023-07-10 2025-07-10 2023-07-10 2025-07-10 2025-07-10 38,000,000,000.00 0.00 True CLP ICPCLP 19,877.30 21,118.61 3.0000% LinAct360 2,314,833,333.33 2,314,833,333.33 0.0000% 1.00
4 2025-07-10 2027-07-12 2025-07-10 2027-07-12 2027-07-12 38,000,000,000.00 0.00 True CLP ICPCLP 21,118.61 22,978.68 4.0000% LinAct360 3,090,666,666.67 3,090,666,666.67 0.0000% 1.00
5 2027-07-12 2029-07-10 2027-07-12 2029-07-10 2029-07-10 38,000,000,000.00 38,000,000,000.00 True CLP ICPCLP 22,978.68 25,238.13 5.0000% LinAct360 3,847,500,000.00 41,847,500,000.00 0.0000% 1.00

Con esto, podemos calcular el valor presente.

vp_on_index_leg = pv.pv(fecha_hoy, overnight_index_leg, zcc_clp)
print(f"Valor presente pata ON Index Leg: {vp_on_index_leg:,.0f}")
Valor presente pata ON Index Leg: 39,062,144,488

También en este caso es posible calcular la sensibilidad a la curva de descuento.

der = pv.get_derivatives()
i = 0
for d in der:
    print(f"Sensibilidad en {i:}: {d * bp / 10_000:0,.0f}")
    i += 1
sens_disc = [d * bp / 10_000 for d in der]
print()
print("Sensibilidad de descuento: {0:,.0f} CLP".format(sum(sens_disc)))
Sensibilidad en 0: 0
Sensibilidad en 1: 0
Sensibilidad en 2: 0
Sensibilidad en 3: 0
Sensibilidad en 4: 0
Sensibilidad en 5: -74,910
Sensibilidad en 6: -158,708
Sensibilidad en 7: 0
Sensibilidad en 8: -271,813
Sensibilidad en 9: -137,584
Sensibilidad en 10: -753,029
Sensibilidad en 11: -382,738
Sensibilidad en 12: -1,332,749
Sensibilidad en 13: -691,260
Sensibilidad en 14: -19,372,761
Sensibilidad en 15: -9,846,486
Sensibilidad en 16: 0
Sensibilidad en 17: 0
Sensibilidad en 18: 0

Sensibilidad de descuento: -33,022,037 CLP

La estructura es la misma que para una pata fija, lo que indica que se debe también incluir la sensibilidad a la curva de proyección.

result = []
for i in range(overnight_index_leg.size()):
    cshflw = overnight_index_leg.get_cashflow_at(i)
    amt_der = cshflw.get_amount_derivatives()
    df = zcc_clp.get_discount_factor_at(fecha_hoy.day_diff(cshflw.get_settlement_date()))
    amt_der = [a * bp / 10_000  * df for a in amt_der]
    if len(amt_der) > 0:
        result.append(np.array(amt_der))
total = result[0] * 0

for r in result:
    total += r

for i in range(len(total)):
    print("Sensibilidad en {0:}: {1:0,.0f}".format(i, total[i]))

print()
print("Sensibilidad de proyección: {0:,.0f} CLP".format(sum(total)))
Sensibilidad en 0: 0
Sensibilidad en 1: 0
Sensibilidad en 2: 0
Sensibilidad en 3: 0
Sensibilidad en 4: 0
Sensibilidad en 5: 74,910
Sensibilidad en 6: 158,708
Sensibilidad en 7: 0
Sensibilidad en 8: 271,813
Sensibilidad en 9: 137,584
Sensibilidad en 10: 753,029
Sensibilidad en 11: 382,738
Sensibilidad en 12: 1,332,749
Sensibilidad en 13: 691,260
Sensibilidad en 14: 19,372,761
Sensibilidad en 15: 9,846,486
Sensibilidad en 16: 0
Sensibilidad en 17: 0
Sensibilidad en 18: 0

Sensibilidad de proyección: 33,022,037 CLP

Como se espera de una pata OvernightIndex (con lag de pago igual a 0 y spread igual a 0), ambas sensibilidades se cancelan.

Se verifica la sensibilidad de proyección por diferencias finitas:

fwd_rates.set_rates_overnight_index_leg(fecha_hoy, icp_val, overnight_index_leg, zcc_clp_up)
vp_on_index_leg_up = pv.pv(fecha_hoy, overnight_index_leg, zcc_clp)

fwd_rates.set_rates_overnight_index_leg(fecha_hoy, icp_val, overnight_index_leg, zcc_clp_down)
vp_on_index_leg_down = pv.pv(fecha_hoy, overnight_index_leg, zcc_clp)

print(f"Sensibilidad en vértice {vertice}: {(vp_on_index_leg_up - vp_on_index_leg_down) / 2:,.0f} CLP")
Sensibilidad en vértice 15: 9,846,486 CLP

IborCashflow Leg

Se da de alta una pata de tipo IborCashflow.

rp = qcf.RecPay.RECEIVE
fecha_inicio = qcf.QCDate(12, 11, 2019)
fecha_final = qcf.QCDate(12, 5, 2021)
bus_adj_rule = qcf.BusyAdjRules.MODFOLLOW
periodicidad_pago = qcf.Tenor('6M')
periodo_irregular_pago = qcf.StubPeriod.NO
calendario = qcf.BusinessCalendar(fecha_inicio, 20)
lag_pago = 0
periodicidad_fijacion = qcf.Tenor('6M')
periodo_irregular_fijacion = qcf.StubPeriod.NO

# vamos a usar el mismo calendario para pago y fijaciones
lag_de_fijacion = 2

# Definición del índice
codigo = 'TERSOFR6M'
lin_act360 = qcf.QCInterestRate(.0, qcf.QCAct360(), qcf.QCLinearWf())
fixing_lag = qcf.Tenor('2d')
tenor = qcf.Tenor('6m')
fixing_calendar = calendario
settlement_calendar = calendario
usd = qcf.QCUSD()
termsofr_6m = qcf.InterestRateIndex(
    codigo,
    lin_act360,
    fixing_lag,
    tenor,
    fixing_calendar,
    settlement_calendar,
    usd
)
# Fin índice

nominal = 20_000_000.0
amort_es_flujo = True
moneda = usd
spread = .0
gearing = 1.0

ibor_leg = qcf.LegFactory.build_bullet_ibor_leg(
    rp, 
    fecha_inicio, 
    fecha_final, 
    bus_adj_rule, 
    periodicidad_pago,
    periodo_irregular_pago, 
    calendario, 
    lag_pago,
    periodicidad_fijacion, 
    periodo_irregular_fijacion,
    calendario, 
    lag_de_fijacion, 
    termsofr_6m,
    nominal, 
    amort_es_flujo, 
    moneda, 
    spread, 
    gearing
)
aux.leg_as_dataframe(ibor_leg).style.format(format_dict)
  fecha_inicial fecha_final fecha_fixing fecha_pago nominal amortizacion interes amort_es_flujo flujo moneda codigo_indice_tasa valor_tasa spread gearing tipo_tasa
0 2019-11-12 2020-05-12 2019-11-08 2020-05-12 20,000,000.00 0.00 0.00 True 0.00 USD TERSOFR6M 0.0000% 0.0000% 1.00 LinAct360
1 2020-05-12 2020-11-12 2020-05-08 2020-11-12 20,000,000.00 0.00 0.00 True 0.00 USD TERSOFR6M 0.0000% 0.0000% 1.00 LinAct360
2 2020-11-12 2021-05-12 2020-11-10 2021-05-12 20,000,000.00 20,000,000.00 0.00 True 20,000,000.00 USD TERSOFR6M 0.0000% 0.0000% 1.00 LinAct360
valor_termsofr_6m = 0.02
fecha_hoy = qcf.QCDate(25, 2, 2020)
ibor_leg.get_cashflow_at(0).set_interest_rate_value(valor_termsofr_6m)
fwd_rates.set_rates_ibor_leg1(fecha_hoy, ibor_leg, zcc_usd)
aux.leg_as_dataframe(ibor_leg).style.format(format_dict)
  fecha_inicial fecha_final fecha_fixing fecha_pago nominal amortizacion interes amort_es_flujo flujo moneda codigo_indice_tasa valor_tasa spread gearing tipo_tasa
0 2019-11-12 2020-05-12 2019-11-08 2020-05-12 20,000,000.00 0.00 202,222.22 True 202,222.22 USD TERSOFR6M 2.0000% 0.0000% 1.00 LinAct360
1 2020-05-12 2020-11-12 2020-05-08 2020-11-12 20,000,000.00 0.00 169,878.64 True 169,878.64 USD TERSOFR6M 1.6619% 0.0000% 1.00 LinAct360
2 2020-11-12 2021-05-12 2020-11-10 2021-05-12 20,000,000.00 20,000,000.00 155,899.59 True 20,155,899.59 USD TERSOFR6M 1.5504% 0.0000% 1.00 LinAct360

Se verifica la tasa forward del segundo cashflow.

which_cashflow = 1
d1 = fecha_hoy.day_diff(ibor_leg.get_cashflow_at(which_cashflow).get_start_date())
d2 = fecha_hoy.day_diff(ibor_leg.get_cashflow_at(which_cashflow).get_end_date())
print(f"d1: {d1:,.0f}")
print(f"d2: {d2:,.0f}")
crv = zcc_usd
w1 = 1 / crv.get_discount_factor_at(d1)
w2 = 1 / crv.get_discount_factor_at(d2)
print(f"Factor forward: {w2 / w1:.4%}")
print(f"Tasa forward: {(w2 / w1 - 1) * 360 / (d2 - d1):.4%}")
print(f"Curve method {crv.get_forward_rate_with_rate(termsofr_6m.get_rate(), d1, d2):.4%}")
d1: 77
d2: 261
Factor forward: 100.8494%
Tasa forward: 1.6619%
Curve method 1.6619%

Cálculo de valor presente.

vp_ibor = pv.pv(fecha_hoy, ibor_leg, zcc_usd)
print(f"Valor presente pata IBOR: {vp_ibor:,.0f}")
Valor presente pata IBOR: 20,126,209

Derivadas del valor presente.

der = pv.get_derivatives()
i = 0
for d in der:
    print(f"Sensibilidad en {i}: {d * bp / 10_000:0,.0f}")
    i += 1
print()
print(f"Sensibilidad de descuento: {sum(der) * bp / 10_000:,.0f} USD")
Sensibilidad en 0: 0
Sensibilidad en 1: 0
Sensibilidad en 2: 0
Sensibilidad en 3: 0
Sensibilidad en 4: 0
Sensibilidad en 5: -2
Sensibilidad en 6: -2
Sensibilidad en 7: 0
Sensibilidad en 8: 0
Sensibilidad en 9: 0
Sensibilidad en 10: 0
Sensibilidad en 11: -6
Sensibilidad en 12: -6
Sensibilidad en 13: 0
Sensibilidad en 14: 0
Sensibilidad en 15: -1,407
Sensibilidad en 16: -986
Sensibilidad en 17: 0
Sensibilidad en 18: 0
Sensibilidad en 19: 0
Sensibilidad en 20: 0
Sensibilidad en 21: 0
Sensibilidad en 22: 0
Sensibilidad en 23: 0
Sensibilidad en 24: 0
Sensibilidad en 25: 0
Sensibilidad en 26: 0
Sensibilidad en 27: 0

Sensibilidad de descuento: -2,409 USD

Se verifica la sensibilidad de descuento por diferencias finitas.

vp_ibor_up = pv.pv(fecha_hoy, ibor_leg, zcc_usd_up)
print(f"Valor presente up pata IBOR: {vp_ibor_up:,.0f}")

vp_ibor_down = pv.pv(fecha_hoy, ibor_leg, zcc_usd_down)
print(f"Valor presente down pata IBOR: {vp_ibor_down:,.0f}")

print(f"Sensibilidad de descuento en el vértice {vertice}: {(vp_ibor_up - vp_ibor_down) / 2:,.0f}")
Valor presente up pata IBOR: 20,124,803
Valor presente down pata IBOR: 20,127,616
Sensibilidad de descuento en el vértice 15: -1,407

Se calcula también la sensibilidad a la curva de proyección.

result = []

for i in range(ibor_leg.size()):
    cshflw = ibor_leg.get_cashflow_at(i)
    df = zcc_usd.get_discount_factor_at(fecha_hoy.day_diff(cshflw.get_settlement_date()))
    amt_der = cshflw.get_amount_derivatives()
    if len(amt_der) > 0:
        amt_der = [a * bp / 10_000 * df for a in amt_der]
        result.append(np.array(amt_der))

total = result[0] * 0
for r in result:
    total += r

for i in range(len(total)):
    print(f"Sensibilidad en {i}: {total[i]:0,.0f}")
print()
print(f"Sensibilidad de proyección: {sum(total):,.0f} USD")
Sensibilidad en 0: 0
Sensibilidad en 1: 0
Sensibilidad en 2: 0
Sensibilidad en 3: 0
Sensibilidad en 4: 0
Sensibilidad en 5: -190
Sensibilidad en 6: -231
Sensibilidad en 7: 0
Sensibilidad en 8: 0
Sensibilidad en 9: 0
Sensibilidad en 10: 0
Sensibilidad en 11: 6
Sensibilidad en 12: 6
Sensibilidad en 13: 0
Sensibilidad en 14: 0
Sensibilidad en 15: 1,407
Sensibilidad en 16: 986
Sensibilidad en 17: 0
Sensibilidad en 18: 0
Sensibilidad en 19: 0
Sensibilidad en 20: 0
Sensibilidad en 21: 0
Sensibilidad en 22: 0
Sensibilidad en 23: 0
Sensibilidad en 24: 0
Sensibilidad en 25: 0
Sensibilidad en 26: 0
Sensibilidad en 27: 0

Sensibilidad de proyección: 1,984 USD

Se verifica la sensibilidad de proyección por diferencias finitas.

fwd_rates.set_rates_ibor_leg1(fecha_hoy, ibor_leg, zcc_usd_up)
vp_ibor_up = pv.pv(fecha_hoy, ibor_leg, zcc_usd)
print(f"Valor presente up pata IBOR: {vp_ibor_up:,.0f}")

fwd_rates.set_rates_ibor_leg1(fecha_hoy, ibor_leg, zcc_usd_down)
vp_ibor_down = pv.pv(fecha_hoy, ibor_leg, zcc_usd)
print(f"Valor presente down pata IBOR: {vp_ibor_down:,.0f}")

print(f"Sensibilidad de proyección en el vértice {vertice}: {(vp_ibor_up - vp_ibor_down) / 2:,.0f}")
Valor presente up pata IBOR: 20,127,616
Valor presente down pata IBOR: 20,124,803
Sensibilidad de proyección en el vértice 15: 1,407

IcpClfCashflow Leg

Se da de alta una pata de tipo IcpClfCashflow.

rp = qcf.RecPay.RECEIVE
fecha_inicio = qcf.QCDate(31, 5, 2018)
fecha_final = qcf.QCDate(31, 3, 2021) 
bus_adj_rule = qcf.BusyAdjRules.FOLLOW
periodicidad_pago = qcf.Tenor('6M')
periodo_irregular_pago = qcf.StubPeriod.SHORTFRONT
calendario = qcf.BusinessCalendar(fecha_inicio, 20)
lag_pago = 0
nominal = 300_000.0
amort_es_flujo = True 
spread = .0
gearing = 1.0

icp_clf_leg = qcf.LegFactory.build_bullet_icp_clf_leg(
    rp, 
    fecha_inicio, 
    fecha_final, 
    bus_adj_rule, 
    periodicidad_pago,
    periodo_irregular_pago, 
    calendario, 
    lag_pago,
    nominal, 
    amort_es_flujo, 
    spread, 
    gearing
)
aux.leg_as_dataframe(icp_clf_leg).style.format(format_dict)
  fecha_inicial fecha_final fecha_pago nominal amortizacion amort_es_flujo flujo moneda icp_inicial icp_final uf_inicial uf_final valor_tasa interes spread gearing tipo_tasa flujo_en_clp
0 2018-05-31 2018-10-31 2018-10-31 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
1 2018-10-31 2019-04-30 2019-04-30 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
2 2019-04-30 2019-10-31 2019-10-31 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
3 2019-10-31 2020-04-30 2020-04-30 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
4 2020-04-30 2020-11-02 2020-11-02 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
5 2020-11-02 2021-04-30 2021-04-30 300,000.00 300,000.00 True 300,000.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 10,500,000,000.00
icp_hoy = 18_882.07
uf_hoy = 28_440.19
fwd_rates.set_rates_icp_clf_leg(fecha_hoy, icp_hoy, uf_hoy, icp_clf_leg, zcc_clp, zcc_clp, zcc_clf)
cshflw = icp_clf_leg.get_cashflow_at(3)
cshflw.set_start_date_uf(28_080.26)
cshflw.set_start_date_icp(18_786.13)
aux.leg_as_dataframe(icp_clf_leg).style.format(format_dict)
  fecha_inicial fecha_final fecha_pago nominal amortizacion amort_es_flujo flujo moneda icp_inicial icp_final uf_inicial uf_final valor_tasa interes spread gearing tipo_tasa flujo_en_clp
0 2018-05-31 2018-10-31 2018-10-31 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
1 2018-10-31 2019-04-30 2019-04-30 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
2 2019-04-30 2019-10-31 2019-10-31 300,000.00 0.00 True 0.00 CLF 10,000.00 10,000.00 35,000.00 35,000.00 0.0000% 0.00 0.0000% 1.00 LinAct360 0.00
3 2019-10-31 2020-04-30 2020-04-30 300,000.00 0.00 True -3,401.28 CLF 18,786.13 18,935.12 28,080.26 28,627.71 -2.2426% -3,401.28 0.0000% 1.00 LinAct360 -97,370,764.00
4 2020-04-30 2020-11-02 2020-11-02 300,000.00 0.00 True -2,589.43 CLF 18,935.12 19,050.65 28,627.71 29,053.02 -1.6706% -2,589.43 0.0000% 1.00 LinAct360 -75,230,762.00
5 2020-11-02 2021-04-30 2021-04-30 300,000.00 300,000.00 True 298,044.72 CLF 19,050.65 19,173.77 29,053.02 29,432.64 -1.3108% -1,955.28 0.0000% 1.00 LinAct360 8,772,243,383.00
vp_icp_clf = pv.pv(fecha_hoy, icp_clf_leg, zcc_clf)
print(f"Valor presente en UF: {vp_icp_clf:,.2f}")
print(f"Valor presente en CLP: {vp_icp_clf * uf_hoy:,.0f}")
Valor presente en UF: 297,715.99
Valor presente en CLP: 8,467,099,423

Sensibilidad de Descuento

der = pv.get_derivatives()
i = 0
for d in der:
    print(f"Sensibilidad en {i}: {d * bp / 10_000:0,.2f}")
    i += 1
print()
print(f"Sensibilidad de descuento: {sum(der) * bp / 10_000:,.2f} CLF")
Sensibilidad en 0: 0.00
Sensibilidad en 1: 0.00
Sensibilidad en 2: 0.00
Sensibilidad en 3: 0.06
Sensibilidad en 4: 0.00
Sensibilidad en 5: 0.00
Sensibilidad en 6: 0.00
Sensibilidad en 7: 0.00
Sensibilidad en 8: 0.00
Sensibilidad en 9: 0.17
Sensibilidad en 10: 0.01
Sensibilidad en 11: 0.00
Sensibilidad en 12: 0.00
Sensibilidad en 13: 0.00
Sensibilidad en 14: 0.00
Sensibilidad en 15: -33.62
Sensibilidad en 16: -2.17
Sensibilidad en 17: 0.00
Sensibilidad en 18: 0.00
Sensibilidad en 19: 0.00
Sensibilidad en 20: 0.00
Sensibilidad en 21: 0.00
Sensibilidad en 22: 0.00
Sensibilidad en 23: 0.00
Sensibilidad en 24: 0.00
Sensibilidad en 25: 0.00
Sensibilidad en 26: 0.00
Sensibilidad en 27: 0.00
Sensibilidad en 28: 0.00
Sensibilidad en 29: 0.00
Sensibilidad en 30: 0.00
Sensibilidad en 31: 0.00
Sensibilidad en 32: 0.00

Sensibilidad de descuento: -35.54 CLF

Sensibilidad de Proyección

result = []
for i in range(icp_clf_leg.size()):
    cshflw = icp_clf_leg.get_cashflow_at(i)
    df = zcc_clf.get_discount_factor_at(fecha_hoy.day_diff(cshflw.date()))
    amt_der = cshflw.get_amount_ufclf_derivatives()
    if len(amt_der) > 0:
        amt_der = [a * bp / 10_000 * df for a in amt_der]
        result.append(np.array(amt_der))

total = result[0] * 0
for r in result:
    total += r

for i in range(len(total)):
    print(f"Sensibilidad en {i}: {total[i]:0,.2f}")
Sensibilidad en 0: 0.00
Sensibilidad en 1: 0.00
Sensibilidad en 2: 0.00
Sensibilidad en 3: -0.06
Sensibilidad en 4: -0.00
Sensibilidad en 5: 0.00
Sensibilidad en 6: 0.00
Sensibilidad en 7: 0.00
Sensibilidad en 8: 0.00
Sensibilidad en 9: -0.17
Sensibilidad en 10: -0.01
Sensibilidad en 11: 0.00
Sensibilidad en 12: 0.00
Sensibilidad en 13: 0.00
Sensibilidad en 14: 0.00
Sensibilidad en 15: 33.62
Sensibilidad en 16: 2.17
Sensibilidad en 17: 0.00
Sensibilidad en 18: 0.00
Sensibilidad en 19: 0.00
Sensibilidad en 20: 0.00
Sensibilidad en 21: 0.00
Sensibilidad en 22: 0.00
Sensibilidad en 23: 0.00
Sensibilidad en 24: 0.00
Sensibilidad en 25: 0.00
Sensibilidad en 26: 0.00
Sensibilidad en 27: 0.00
Sensibilidad en 28: 0.00
Sensibilidad en 29: 0.00
Sensibilidad en 30: 0.00
Sensibilidad en 31: 0.00
Sensibilidad en 32: 0.00

CompoundedOvernightRate Leg

Se da de alta una pata de tipo CompoundedOvernightRate.

rp = qcf.RecPay.PAY
fecha_inicio = qcf.QCDate(31, 3, 2020)
fecha_final = qcf.QCDate(31, 1, 2023)
bus_adj_rule = qcf.BusyAdjRules.MODFOLLOW
periodicidad_pago = qcf.Tenor('12M')
periodo_irregular_pago = qcf.StubPeriod.SHORTFRONT
calendario = qcf.BusinessCalendar(fecha_inicio, 20)
lag_pago = 0
lookback = 2

######################################################################
# Definición del índice

codigo = 'OISTEST'
lin_act360 = qcf.QCInterestRate(.0, qcf.QCAct360(), qcf.QCLinearWf())
fixing_lag = qcf.Tenor('0d')
tenor = qcf.Tenor('1d')
fixing_calendar = calendario
settlement_calendar = calendario
usd = qcf.QCUSD()
oistest = qcf.InterestRateIndex(
    codigo,
    lin_act360,
    fixing_lag,
    tenor,
    fixing_calendar,
    settlement_calendar,
    usd)

# Fin índice
######################################################################

nominal = 5_500_000.0
amort_es_flujo = True
moneda = usd
spread = .0
gearing = 1.0

cor_leg = qcf.LegFactory.build_bullet_compounded_overnight_rate_leg_2(
    rp,
    fecha_inicio,
    fecha_final,
    bus_adj_rule,
    periodicidad_pago,
    periodo_irregular_pago,
    calendario,
    lag_pago,
    calendario,
    oistest,
    nominal,
    amort_es_flujo,
    usd,
    spread,
    gearing,
    qcf.QCInterestRate(0.0, qcf.QCAct360(), qcf.QCLinearWf()),
    10,
    lookback,
    0
)

Valor Presente

ts = qcf.time_series()
fwd_rates.set_rates_compounded_overnight_leg2(
    fecha_hoy,
    cor_leg,
    zcc_usd,
    ts
)
aux.leg_as_dataframe(cor_leg).style.format(format_dict)
  fecha_inicial fecha_final fecha_pago nominal amortizacion interes amort_es_flujo flujo moneda codigo_indice_tasa tipo_tasa valor_tasa spread gearing
0 2020-03-31 2021-01-29 2021-01-29 -5,500,000.00 0.00 -0.00 True -77,568.07 USD OISTEST LinAct360 1.6701% 0.0000% 1.00
1 2021-01-29 2022-01-31 2022-01-31 -5,500,000.00 0.00 -0.00 True -84,818.39 USD OISTEST LinAct360 1.5127% 0.0000% 1.00
2 2022-01-31 2023-01-31 2023-01-31 -5,500,000.00 -5,500,000.00 -0.00 True -5,585,601.26 USD OISTEST LinAct360 1.5351% 0.0000% 1.00
print(f'Valor presente: {pv.pv(fecha_hoy, cor_leg, zcc_usd):,.0f}')
Valor presente: -5,491,175

Sensibilidad de Proyección

proj_sens_by_cashflow = np.array([np.array(
    np.array(cor_leg.get_cashflow_at(i).get_amount_derivatives()) *
    zcc_usd.get_discount_factor_at(fecha_hoy.day_diff(cor_leg.get_cashflow_at(i).get_settlement_date())) * bp / 10_000)
                             for i in range(cor_leg.size())])
proj_sens = np.sum(proj_sens_by_cashflow, axis=0)
for i, s in enumerate(proj_sens):
     print(f"Sensibilidad en {i}: {s:0,.2f}")
Sensibilidad en 0: 0.00
Sensibilidad en 1: 0.00
Sensibilidad en 2: 0.00
Sensibilidad en 3: 0.00
Sensibilidad en 4: 45.39
Sensibilidad en 5: 7.26
Sensibilidad en 6: 0.00
Sensibilidad en 7: 0.00
Sensibilidad en 8: 0.00
Sensibilidad en 9: 0.00
Sensibilidad en 10: 0.00
Sensibilidad en 11: 0.00
Sensibilidad en 12: 0.00
Sensibilidad en 13: 0.00
Sensibilidad en 14: -6.21
Sensibilidad en 15: -0.89
Sensibilidad en 16: -2.18
Sensibilidad en 17: -120.89
Sensibilidad en 18: -1,457.54
Sensibilidad en 19: 0.00
Sensibilidad en 20: 0.00
Sensibilidad en 21: 0.00
Sensibilidad en 22: 0.00
Sensibilidad en 23: 0.00
Sensibilidad en 24: 0.00
Sensibilidad en 25: 0.00
Sensibilidad en 26: 0.00
Sensibilidad en 27: 0.00

Verifica sensibilidad de proyección.

fwd_rates.set_rates_compounded_overnight_leg2(fecha_hoy, cor_leg, zcc_usd_up, ts)
vp_cor_up = pv.pv(fecha_hoy, cor_leg, zcc_usd)
print(f"Valor presente up pata CompoundedOvernightRate: {vp_cor_up:,.0f}")

fwd_rates.set_rates_compounded_overnight_leg2(fecha_hoy, cor_leg, zcc_usd_down, ts)
vp_cor_down = pv.pv(fecha_hoy, cor_leg, zcc_usd)
print(f"Valor presente down pata CompoundedOvernightRate: {vp_cor_down:,.0f}")

print(f"Sensibilidad de proyección en el vértice {vertice}: {(vp_cor_up - vp_cor_down) / 2:,.2f}")
Valor presente up pata CompoundedOvernightRate: -5,491,175
Valor presente down pata CompoundedOvernightRate: -5,491,174
Sensibilidad de proyección en el vértice 15: -0.89

Sensibilidad de Descuento

disc_der = np.array(pv.get_derivatives()) * bp / 10_000
for i, s in enumerate(disc_der):
    print(f"Sensibilidad en {i}: {s:0,.2f}")
Sensibilidad en 0: 0.00
Sensibilidad en 1: 0.00
Sensibilidad en 2: 0.00
Sensibilidad en 3: 0.00
Sensibilidad en 4: 0.00
Sensibilidad en 5: 0.00
Sensibilidad en 6: 0.00
Sensibilidad en 7: 0.00
Sensibilidad en 8: 0.00
Sensibilidad en 9: 0.00
Sensibilidad en 10: 0.00
Sensibilidad en 11: 0.00
Sensibilidad en 12: 0.00
Sensibilidad en 13: 0.00
Sensibilidad en 14: 6.20
Sensibilidad en 15: 0.89
Sensibilidad en 16: 2.19
Sensibilidad en 17: 120.90
Sensibilidad en 18: 1,457.54
Sensibilidad en 19: 0.00
Sensibilidad en 20: 0.00
Sensibilidad en 21: 0.00
Sensibilidad en 22: 0.00
Sensibilidad en 23: 0.00
Sensibilidad en 24: 0.00
Sensibilidad en 25: 0.00
Sensibilidad en 26: 0.00
Sensibilidad en 27: 0.00

Verifica la sensibilidad de descuento.

fwd_rates.set_rates_compounded_overnight_leg2(fecha_hoy, cor_leg, zcc_usd, ts)
vp_cor_up = pv.pv(fecha_hoy, cor_leg, zcc_usd_up)
print(f"Valor presente up pata CompoundedOvernightRate: {vp_cor_up:,.2f}")

fwd_rates.set_rates_compounded_overnight_leg2(fecha_hoy, cor_leg, zcc_usd, ts)
vp_cor_down = pv.pv(fecha_hoy, cor_leg, zcc_usd_down)
print(f"Valor presente down pata CompoundedOvernightRate: {vp_cor_down:,.2f}")

print(f"Sensibilidad de descuento en el vértice {vertice}: {(vp_cor_up - vp_cor_down) / 2:,.2f}")
Valor presente up pata CompoundedOvernightRate: -5,491,174
Valor presente down pata CompoundedOvernightRate: -5,491,175
Sensibilidad de descuento en el vértice 15: 0.89