<jupyter_start><jupyter_text>Renta Fija Local En este notebook valorizaremos renta fija local utilizando una curva cupón cero, a partir de eso calcularemos un spread y veremos como calcular la $\delta$ del bono respecto a la curva.  Librerías https://github.com/HIPS/autograd<jupyter_code>from datetime import date
import datetime
import bisect
import pandas as pd
import numpy as np

import autograd.numpy as agnp
from autograd import grad

import my_functions as mf<jupyter_output><empty_output><jupyter_text>Data de Bonos Vamos a utilizar la misma base de datos de la sesión anterior.<jupyter_code>bonos = pd.read_excel('data/bonos_empresa_carga_inicial.xlsx')<jupyter_output><empty_output><jupyter_text>Al hacer el append, se regenera el índice. Con el valor por default `ignore_index=True` se matienen los índices originales de ambos `DataFrame`.<jupyter_code>bonos = bonos.append(pd.read_excel('data/bonos_estado_carga_inicial.xlsx'), ignore_index=True)
bonos.head()
bonos.tail()
tablas_desarrollo = pd.read_csv('data/tablas_desarrollo.csv')
tablas_desarrollo.head()<jupyter_output><empty_output><jupyter_text>Data de Curva Cero Cupón Se utilizará la curva de gobierno en CLP para el 2021-08-20 construida por RiskAmerica. La tasa está en convención Comp Act/365.<jupyter_code>curva_clp = pd.read_excel('data/20210820_curva_gob_ra.xlsx', sheet_name='curva_clp')
curva_clp.style.format({'tasa': '{:.4%}'})<jupyter_output><empty_output><jupyter_text>Cálculo de Valor Presente con Curva Cero Cupón Vamos a elegir bonos en CLP emitidos recientemente.<jupyter_code>bonos[(bonos.unidad_monetaria=='CLP') & (bonos.fecha_emision >= '2020-01-01')]<jupyter_output><empty_output><jupyter_text>De esta lista hay varios que no están colocados. Vamos a volver a utilizar el *BWATT-Q*.<jupyter_code>tabla = tablas_desarrollo[tablas_desarrollo.nemotecnico=='BWATT-Q'].copy()
tabla.head()<jupyter_output><empty_output><jupyter_text>Para usar la curva cupón cero, tenemos que escribir una función que pueda calcular el valor presente a partir de ella.Además, vamos a escribir una función que interpole linealmente.<jupyter_code>def lin_interpol(plazo, plazos, tasas):
    """
    Implementa interpolación lineal.
    
    Parameters
    ----------
    
    plazo: float
        Plazo en días al que se quiere interpolar.
    
    plazos: array (numpy o autograd.numpy)
        Plazos en días de las tasas de la curva.
        
    tasas: array ((numpy o autograd.numpy))
        Tasas en en convención Comp Act/365 de la curva. Si se usa autograd, se puede derivar respecto
        a este parámetro.
        
    Returns
    -------
    
    Un `float` que corresponde al valor presente de los flujos.

    """
    i = bisect.bisect(plazos, plazo)
    if i == 0:
        return tasas[i]
    elif i == len(tasas):
        return tasas[i - 1]
    else:
        m = (tasas[i] - tasas[i - 1]) / (plazos[i] - plazos[i - 1])
        return tasas[i - 1] + m * (plazo - plazos[i - 1])
plazos = agnp.array(curva_clp[['plazo']])
tasas = agnp.array(curva_clp[['tasa']])
lin_interpol(100, plazos, tasas)
glin_interpol = grad(lin_interpol, 2)
temp = glin_interpol(180, plazos, tasas)
temp
temp[4] * .0001
def valor_presente_curva(fecha_valor, fechas, flujos, plazos, tasas):
    """
    Calcula el valor presente de un conjunto de flujos utilizando una curva cero cupón. Para encontrar
    una tasa a un plazo específico se utiliza interpolación lineal.
    
    Parameters
    ----------
    
    fecha_valor: datetime.date
        Fecha a la cual se quiere obtener el valor presente.
        
    fechas: List[datetime.date]
        Fechas de pago de los flujos.
        
    flujos: List[float]
        Flujos a traer a valor presente. Deben corresponder a las fechas en el parámetro `fechas`.
        Los flujos cuyas fechas sean iguales o previas a `fecha_valor` no serán incluidos en el cálculo.
        
    plazos: numpy.array
        Plazos en días de las tasas de la curva.
        
    tasas: jnp.array
        Tasas en en convención Comp Act/365 de la curva.
        
    Returns
    -------
    
    Un `float` que corresponde al valor presente de los flujos.
    """
    result = 0.0
    for fec, fl in zip(fechas, flujos):
        p = (fec - fecha_valor).days
        if p > 0:
            tasa = lin_interpol(p, plazos, tasas)
            result += fl * agnp.power((1 + tasa), -p / 365.0)
    return result<jupyter_output><empty_output><jupyter_text>Probemos ...<jupyter_code>tabla['fecha_vcto_cupon'].values
fechas = np.asarray(
    [datetime.datetime.strptime(f, "%Y-%m-%d").date() for f in tabla['fecha_vcto_cupon'].values])
fechas
fecha_valor = date(2021, 8, 13)
resultado = valor_presente_curva(
    fecha_valor,
    fechas,
    tabla['interes'] + tabla['amortizacion'],
    curva_clp['plazo'],
    agnp.asarray(curva_clp['tasa'])
)
print(f'El valor presente es: {resultado:,.8f}')<jupyter_output>El valor presente es: 96.12432231<jupyter_text>¿Cómo podemos verificar el resultado? Hagamos un cálculo estilo Excel utilizando el `DataFrame` que contiene la tabla de desarrollo del bono. Agregamos una columna `plazo`.<jupyter_code>tabla['plazo'] = [(f - fecha_valor).days for f in fechas]<jupyter_output><empty_output><jupyter_text>Agregamos una columna `tasa`.<jupyter_code>tabla['tasa'] = agnp.interp(
    agnp.asarray(tabla['plazo']),
    curva_clp['plazo'],
    agnp.asarray(curva_clp['tasa']),
)<jupyter_output><empty_output><jupyter_text>Agregamos la columna `df` que representa el factor de descuento asociado a la tasa.<jupyter_code>tabla['df'] = agnp.power((1 + tabla['tasa']), -tabla['plazo'] / 365.0)<jupyter_output><empty_output><jupyter_text>Agregamos la columna `vp` que es el valor presente del flujo.<jupyter_code>tabla['vp'] = (tabla['interes'] + tabla['amortizacion']) * tabla['df']<jupyter_output><empty_output><jupyter_text>Finalmente, se suman los valores presente de los flujos con `plazo` mayor a cero.<jupyter_code>check = tabla[tabla.plazo > 0]['vp'].sum()
print(f'Check: {check:,.8f}')<jupyter_output>Check: 96.12432231<jupyter_text>Spread Sobre Curva Cero Cupón Notar que hemos valorizado un bono corporativo con una curva de bonos de gobierno. Esperaríamos que el valor presente así obtenido sea más alto que el que se obtendría descontando el mismo bono a su tir de mercado. Podemos calcular cuál es el spread implícito en la valorización con curva de gobierno. Para eso, se debe calcular cuál es la tir equivalente al descuento con curva. Dicho de otra forma, qué tir permite obtener el mismo valor presente que el calculado con la curva.Para esto, vamos a reciclar y modificar una función que ya vimos al estudiar la instrucción `while`.<jupyter_code>def encuentra_tir_2(fecha_valor, fechas, flujos, vp_obj, tasa):
    """
    Calcula la tasa de descuento o TIR que hace que el valor presente del bono (en base 100)
    sea igual a un valor dado.

    Parameters
    ----------

    fecha_valor: datetime.date
        Fecha a la cual se realiza el cálculo.

    plazos: List[float]
            Contiene las fechas de los flujos.

    flujos: List[float]
            Contiene los flujos del bono.

    vp_obj: float
        Valor presente del bono para el cual se quiere encontrar la tir.

    tasa: float
          Estimación inicial del valor del resultado.

    Returns
    -------

    float
         Tasa de descuento buscada.
    """
    epsilon = .000001
    diff = 1000
    der = grad(mf.valor_presente, 3)
    while diff > epsilon:
        q = ((mf.valor_presente(fecha_valor, fechas, flujos, tasa) - vp_obj) /
             der(fecha_valor, fechas, flujos, tasa)
             )
        nueva_tasa = tasa - q
        diff = abs(nueva_tasa - tasa)
        tasa = nueva_tasa
    return tasa<jupyter_output><empty_output><jupyter_text>Notar que en esta versión de la función, se ha reemplazado el cálculo numérico de la derivada del bono respecto a su tir por el cálculo automático utilizando `autograd`.<jupyter_code>tir_eq = encuentra_tir_2(
    fecha_valor,
    fechas,
    tabla['interes'] + tabla['amortizacion'],
    resultado,
    .02,
)
print(f'La TIR equivalente es: {tir_eq: .4%}')<jupyter_output>La TIR equivalente es:  4.7593%<jupyter_text>Podemos verificar el resultado:<jupyter_code>check_tir_eq = mf.valor_presente(
    fecha_valor, fechas, tabla['interes'] + tabla['amortizacion'], tir_eq)
print(f'El valor presente con tir equivalente es: {check_tir_eq:,.13f}')<jupyter_output>El valor presente con tir equivalente es: 96.1243223118909<jupyter_text>De acuerdo a RiskAmerica, el spread de este bono debiera ser 2.00%, o que la TIR base es 4.76%, que, a dos decimales es el mismo resultado que acabamos de obtener.![RA](assets/20210820_bwatt-q_val_ra.png)  Sensibilidad a Curva Cero Cupón Se puede extender la idea de duración del bono (pensada como $\Delta$ del bono respecto a su tir de mercado) si consideramos el vector de $\delta$ del bono respecto a cada una de las tasas cupón cero. Usando `autograd` esto se calcula fácilmente de la siguiente forma:<jupyter_code># 4 corresponde a la posición de las tasas de la curva en la función valor_presente_curva.
gcurva = grad(valor_presente_curva, 4)
delta = gcurva(
    fecha_valor,
    fechas,
    tabla['interes'] + tabla['amortizacion'],
    agnp.asarray(curva_clp['plazo']),
    agnp.asarray(curva_clp['tasa'])
)
delta<jupyter_output><empty_output><jupyter_text>Para calcular la sensibilidad a 1 punto básico en cada vértice basta con lo siguiente (para ver cifras significativas vamos a asumir un nominal de 100,000,000 CLP):<jupyter_code>nominal = 100000000
sens = [d * .0001 * nominal / 100.0 for d in delta]
for i, s in enumerate(sens):
    print(f'Sensibilidad vértice {i} al plazo {curva_clp.iloc[i, 0]}: {s:,.6f}')<jupyter_output>Sensibilidad vértice 0 al plazo 1: -3.778323
Sensibilidad vértice 1 al plazo 30: -6.182710
Sensibilidad vértice 2 al plazo 60: 0.000000
Sensibilidad vértice 3 al plazo 90: 0.000000
Sensibilidad vértice 4 al plazo 180: -80.307037
Sensibilidad vértice 5 al plazo 270: -22.944868
Sensibilidad vértice 6 al plazo 365: -310.775758
Sensibilidad vértice 7 al plazo 730: -703.290515
Sensibilidad vértice 8 al plazo 1095: -1,002.651608
Sensibilidad vértice 9 al plazo 1460: -1,267.202976
Sensibilidad vértice 10 al plazo 1825: -1,497.495813
Sensibilidad vértice 11 al plazo 2190: -1,694.933365
Sensibilidad vértice 12 al plazo 2555: -1,866.782566
Sensibilidad vértice 13 al plazo 2920: -50,950.326656
Sensibilidad vértice 14 al plazo 3285: -3,074.010953
Sensibilidad vértice 15 al plazo 3650: 0.000000
Sensibilidad vértice 16 al plazo 4380: 0.000000
Sensibilidad vértice 17 al plazo 5475: 0.000000
Sensibilidad vértice 18 al plazo 7300: 0.000000<jupyter_text>Ejercicio Verificar el resultado anterior de forma numérica, al menos para algunos vértices. Verifiquemos el vértice 13.<jupyter_code>curva_clp.at[13, 'tasa'] += .0001
vp_mas = valor_presente_curva(fecha_valor,
    fechas,
    tabla['interes'] + tabla['amortizacion'],
    curva_clp['plazo'],
    agnp.asarray(curva_clp['tasa'])
)
curva_clp.at[13, 'tasa'] -= .0002
vp_menos = valor_presente_curva(fecha_valor,
    fechas,
    tabla['interes'] + tabla['amortizacion'],
    curva_clp['plazo'],
    agnp.asarray(curva_clp['tasa'])
)
sens_diff = nominal * (vp_mas - vp_menos) / 200
print(f'Sens. en vértice 13: {sens_diff: ,.6f}')
curva_clp.at[13, 'tasa'] += .0001<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09949898918871407, 'fraction_numerical': 0.04034455480355102, 'mean_word_length': 4.010127697049758, 'pattern_counts': {'":': 0, '<': 72, '<?xml version=': 0, '>': 76, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '633628', 'license': 'MIT', 'n_tokens_mistral': 4699, 'n_tokens_neox': 4298, 'n_words': 1197, 'path': '03_02_a_renta fija.ipynb', 'repo_name': 'adiaz-efaa/intro-fin-python', 'size': '11377'}