# Caps y Floors

Hasta ahora sólo hemos visto productos lineales de tasa de interés. Hoy introducimos el primer ejemplo de productos no lineales (u opciones) de tasa de interés.

## Configuración

```python
from finrisk import QC_Financial_3 as Qcf
from IPython.core.display import HTML
from IPython.display import Image
import modules.auxiliary as aux
import pandas as pd
```

Se genera el calendario NY.

```python
bus_cal = aux.get_cal(aux.BusCal.NY)
```

Esta función devuelve una pata flotante con Libor USD 3M.

```python
def get_libor_usd_3m_leg(
        rp: Qcf.RecPay,
        notional: float,
        fecha_inicio: Qcf.QCDate,
        maturity: Qcf.Tenor,
        spread: float = 0.0) -> Qcf.Leg:

    # Se da de alta los parámetros requeridos
    periodicidad_pago = Qcf.Tenor('3M')
    periodo_irregular_pago = Qcf.StubPeriod.SHORTFRONT
    lag_pago = 0
    periodicidad_fijacion = Qcf.Tenor('3M')
    periodo_irregular_fijacion = Qcf.StubPeriod.SHORTFRONT
    bus_adj_rule = Qcf.BusyAdjRules.MODFOLLOW
    amort_es_flujo = True
    gearing = 1.0  # intereses -> gearing * Libor + spread

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

    # Definición del índice
    codigo = 'LIBORUSD3M'
    lin_act360 = Qcf.QCInterestRate(0.0, Qcf.QCAct360(), Qcf.QCLinearWf())
    fixing_lag = Qcf.Tenor('2d')
    tenor = Qcf.Tenor('3m')
    usd = Qcf.QCUSD()
    libor_usd_3m = Qcf.InterestRateIndex(codigo,
                                         lin_act360,
                                         fixing_lag,
                                         tenor,
                                         bus_cal,
                                         bus_cal,
                                         usd)
    # Fin índice

    meses = maturity.get_years() * 12 + maturity.get_months()
    fecha_final = fecha_inicio.add_months(meses)
    ibor_leg = Qcf.LegFactory.build_bullet_ibor_leg(
        rp,
        fecha_inicio,
        fecha_final,
        bus_adj_rule,
        periodicidad_pago,
        periodo_irregular_pago,
        bus_cal,
        lag_pago,
        periodicidad_fijacion,
        periodo_irregular_fijacion,
        bus_cal,
        lag_de_fijacion,
        libor_usd_3m,
        notional,
        amort_es_flujo,
        usd,
        spread,
        gearing)

    return ibor_leg
```

Formato para la visualización de los `DataFrame`.

```python
frmt = {
    'nominal': '{:,.2f}',
    'amortizacion': '{:,.2f}',
    'interes': '{:,.2f}',
    'flujo': '{:,.2f}',
    'interes_cap': '{:,.2f}',
    'interes_total': '{:,.2f}',
    'valor_tasa': '{:.6%}',
    'tasa': '{:.6%}'
}
```

## Black-Scholes-Merton Revisited

Recordemos la fórmula para una *call* sobre una acción que no paga dividendos.

$$
\begin{equation}
C\left(S,t\right)=e^{-rT}\left[Se^{rT}\cdot N\left(d_1\right)-K\cdot N\left(d_2\right)\right]
\end{equation}
$$

$$
\begin{equation}
d_1=\frac{\ln\frac{Se^{rT}}{K}+\frac{1}{2}\sigma^2T}{\sigma\sqrt{T}}
\end{equation}
$$

$$
\begin{equation}
d_2=d_1-\sigma\sqrt{T}
\end{equation}
$$

En la medida ajustada por riesgo tenemos que $Se^{rT}=\mathbb{E}_t^Q\left(S_T\right)$ y por lo tanto:

$$
\begin{equation}
C\left(S,t\right)=df\left(r,T\right)\left[\mathbb{E}_t^Q\left(S_T\right)\cdot N\left(d_1\right)-K\cdot N\left(d_2\right)\right]
\end{equation}
$$

$$
\begin{equation}
d_1=\frac{\ln\frac{\mathbb{E}_t^Q\left(S_T\right)}{K}+\frac{1}{2}\sigma^2T}{\sigma\sqrt{T}}
\end{equation}
$$

$$
\begin{equation}
d_2=d_1-\sigma\sqrt{T}
\end{equation}
$$

## Caps

- Un Cap es una opción que ofrece protección contra subidas de alguna tasa de interés.
- Por ejemplo, una empresa puede tener un financiamiento a tasa variable y estar interesada en tener protección contra incrementos de la tasa de referencia por sobre un cierto umbral.
- Veamos el ejemplo de un Cap al 0.35% para proteger un crédito en USD a 2Y a tasa flotante Libor 3M.

### Crédito a Libor USD 3M

Se construye un crédito a Libor USD 3M a 2Y.

```python
credito = get_libor_usd_3m_leg(Qcf.RecPay.PAY, 10000000, Qcf.QCDate(26, 10, 2020), Qcf.Tenor('2Y'))
```

Visualizamos y vemos como ninguno de los cupones tiene su *fixing*, es decir el valor de la Libor a la fecha de fixing.

```python
aux.show_leg(credito, 'IborCashflow', '').style.format(frmt)
```

        	        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
                        2020-10-26
                        2021-01-26
                        2020-10-22
                        2021-01-26
                        -10,000,000.00
                        0.00
                        -0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        1
                        2021-01-26
                        2021-04-26
                        2021-01-22
                        2021-04-26
                        -10,000,000.00
                        0.00
                        -0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        2
                        2021-04-26
                        2021-07-26
                        2021-04-22
                        2021-07-26
                        -10,000,000.00
                        0.00
                        -0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        3
                        2021-07-26
                        2021-10-26
                        2021-07-22
                        2021-10-26
                        -10,000,000.00
                        0.00
                        -0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        4
                        2021-10-26
                        2022-01-26
                        2021-10-22
                        2022-01-26
                        -10,000,000.00
                        0.00
                        -0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        5
                        2022-01-26
                        2022-04-26
                        2022-01-24
                        2022-04-26
                        -10,000,000.00
                        0.00
                        -0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        6
                        2022-04-26
                        2022-07-26
                        2022-04-22
                        2022-07-26
                        -10,000,000.00
                        0.00
                        -0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        7
                        2022-07-26
                        2022-10-26
                        2022-07-22
                        2022-10-26
                        -10,000,000.00
                        -10,000,000.00
                        -0.00
                        True
                        -10,000,000.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

[www.global-rates.com (valores Libor USD)](https://www.global-rates.com/en/interest-rates/libor/american-dollar/american-dollar.aspx)

Vamos a considerar un **escenario arbitrario** en que los *fixings* de la Libor aumentan 0.04% en cada nuevo cupón. El *fixing* del primer cupón corresponde al valor de la Libor USD 3M del 22-10-2020.

```python
first_fixing = .0021475
increment = .0004 # Suponemos que los fixings sucesivos van aumentando en este monto.
for i in range(credito.size()):
    cshflw = credito.get_cashflow_at(i)
    cshflw.set_interest_rate_value(first_fixing + i * increment)
```

Vemos como, en este escenario, los intereses a pagar aumentan hasta más del doble del monto de intereses del primer cupón.

```python
df_cred = aux.show_leg(credito, 'IborCashflow', '')
df_cred.style.format(frmt)
```

        	        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
                        2020-10-26
                        2021-01-26
                        2020-10-22
                        2021-01-26
                        -10,000,000.00
                        0.00
                        -5,488.06
                        True
                        -5,488.06
                        USD
                        LIBORUSD3M
                        0.214750%
                        0.000000
                        1.000000
                        LinAct360

                        1
                        2021-01-26
                        2021-04-26
                        2021-01-22
                        2021-04-26
                        -10,000,000.00
                        0.00
                        -6,368.75
                        True
                        -6,368.75
                        USD
                        LIBORUSD3M
                        0.254750%
                        0.000000
                        1.000000
                        LinAct360

                        2
                        2021-04-26
                        2021-07-26
                        2021-04-22
                        2021-07-26
                        -10,000,000.00
                        0.00
                        -7,450.63
                        True
                        -7,450.63
                        USD
                        LIBORUSD3M
                        0.294750%
                        0.000000
                        1.000000
                        LinAct360

                        3
                        2021-07-26
                        2021-10-26
                        2021-07-22
                        2021-10-26
                        -10,000,000.00
                        0.00
                        -8,554.72
                        True
                        -8,554.72
                        USD
                        LIBORUSD3M
                        0.334750%
                        0.000000
                        1.000000
                        LinAct360

                        4
                        2021-10-26
                        2022-01-26
                        2021-10-22
                        2022-01-26
                        -10,000,000.00
                        0.00
                        -9,576.94
                        True
                        -9,576.94
                        USD
                        LIBORUSD3M
                        0.374750%
                        0.000000
                        1.000000
                        LinAct360

                        5
                        2022-01-26
                        2022-04-26
                        2022-01-24
                        2022-04-26
                        -10,000,000.00
                        0.00
                        -10,368.75
                        True
                        -10,368.75
                        USD
                        LIBORUSD3M
                        0.414750%
                        0.000000
                        1.000000
                        LinAct360

                        6
                        2022-04-26
                        2022-07-26
                        2022-04-22
                        2022-07-26
                        -10,000,000.00
                        0.00
                        -11,495.07
                        True
                        -11,495.07
                        USD
                        LIBORUSD3M
                        0.454750%
                        0.000000
                        1.000000
                        LinAct360

                        7
                        2022-07-26
                        2022-10-26
                        2022-07-22
                        2022-10-26
                        -10,000,000.00
                        -10,000,000.00
                        -12,643.61
                        True
                        -10,012,643.61
                        USD
                        LIBORUSD3M
                        0.494750%
                        0.000000
                        1.000000
                        LinAct360

### Crédito a Libor USD 3M + Cap

En un escenario como el anterior, quien ha suscrito el crédito, podría tener interés en contratar protección contra las subidas de la Libor. En particular, podría contratar un Cap de Libor a 2Y.

El payoff de un Cap, está dado por la siguiente función `caplet` asociada a cada uno de los cupones del crédito.

**NB:** la función se llama `caplet` por que corresponde a una componente del Cap total.

```python
def caplet(notional: float,
        fixing: float,
        strike: float,
        fecha_inicio: Qcf.QCDate,
        fecha_final: Qcf.QCDate) -> float:

    valor_tasa = max(fixing - strike, 0)
    lin_act360 = Qcf.QCInterestRate(valor_tasa, Qcf.QCAct360(), Qcf.QCLinearWf())
    wf = lin_act360.wf(fecha_inicio, fecha_final)

    # Los interes que se devengan con valor_tasa entre la fecha_inicio y la fecha_final
    return notional * (wf - 1)
```

Apliquemos `caplet` a cada uno de los cupones y *fixings* del escenario, considerando una tasa Cap (o strike) de 0.35%.

```python
strike = .0035
df_cred['interes_cap'] = df_cred.apply(
    lambda row: -caplet(
        row['nominal'],
        row['valor_tasa'],
        strike,
        Qcf.build_qcdate_from_string(row['fecha_inicial']),
        Qcf.build_qcdate_from_string(row['fecha_final'])
    ),
    axis=1
)

df_cred['interes_total'] = df_cred['interes'] + df_cred['interes_cap']
```

Vemos como cada `caplet` provee un flujo de caja positivo, cada vez que el *fixing* de la Libor supera el valor de la tasa Cap o strike del Cap.

Los últimos pagos de intereses (total) están a una tasa efectiva igual al *strike* (0.35%).

```python
df_cred.style.format(frmt)
```

        	        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	        interes_cap	        interes_total

                        0
                        2020-10-26
                        2021-01-26
                        2020-10-22
                        2021-01-26
                        -10,000,000.00
                        0.00
                        -5,488.06
                        True
                        -5,488.06
                        USD
                        LIBORUSD3M
                        0.214750%
                        0.000000
                        1.000000
                        LinAct360
                        0.00
                        -5,488.06

                        1
                        2021-01-26
                        2021-04-26
                        2021-01-22
                        2021-04-26
                        -10,000,000.00
                        0.00
                        -6,368.75
                        True
                        -6,368.75
                        USD
                        LIBORUSD3M
                        0.254750%
                        0.000000
                        1.000000
                        LinAct360
                        0.00
                        -6,368.75

                        2
                        2021-04-26
                        2021-07-26
                        2021-04-22
                        2021-07-26
                        -10,000,000.00
                        0.00
                        -7,450.63
                        True
                        -7,450.63
                        USD
                        LIBORUSD3M
                        0.294750%
                        0.000000
                        1.000000
                        LinAct360
                        0.00
                        -7,450.63

                        3
                        2021-07-26
                        2021-10-26
                        2021-07-22
                        2021-10-26
                        -10,000,000.00
                        0.00
                        -8,554.72
                        True
                        -8,554.72
                        USD
                        LIBORUSD3M
                        0.334750%
                        0.000000
                        1.000000
                        LinAct360
                        0.00
                        -8,554.72

                        4
                        2021-10-26
                        2022-01-26
                        2021-10-22
                        2022-01-26
                        -10,000,000.00
                        0.00
                        -9,576.94
                        True
                        -9,576.94
                        USD
                        LIBORUSD3M
                        0.374750%
                        0.000000
                        1.000000
                        LinAct360
                        632.50
                        -8,944.44

                        5
                        2022-01-26
                        2022-04-26
                        2022-01-24
                        2022-04-26
                        -10,000,000.00
                        0.00
                        -10,368.75
                        True
                        -10,368.75
                        USD
                        LIBORUSD3M
                        0.414750%
                        0.000000
                        1.000000
                        LinAct360
                        1,618.75
                        -8,750.00

                        6
                        2022-04-26
                        2022-07-26
                        2022-04-22
                        2022-07-26
                        -10,000,000.00
                        0.00
                        -11,495.07
                        True
                        -11,495.07
                        USD
                        LIBORUSD3M
                        0.454750%
                        0.000000
                        1.000000
                        LinAct360
                        2,647.85
                        -8,847.22

                        7
                        2022-07-26
                        2022-10-26
                        2022-07-22
                        2022-10-26
                        -10,000,000.00
                        -10,000,000.00
                        -12,643.61
                        True
                        -10,012,643.61
                        USD
                        LIBORUSD3M
                        0.494750%
                        0.000000
                        1.000000
                        LinAct360
                        3,699.17
                        -8,944.44

### Fórmula de Valorización

- Cada componente individual de un Cap se dice caplet
- Evaluemos un caplet: su pago está dado por

$$
\begin{equation}
yf\cdot N\cdot max\left(L_{T_f}-L_K,0\right)
\end{equation}
$$

- Donde $yf$ es la fracción de año que se usa para la tasa de referencia (lo más usual es Act/360), $N$ es el nocional del contrato, $L_K$ es la tasa Cap y $T_f$ es la fecha en la que se produce el *fixing*.
- Notar que, aunque el pago queda determinado al tiempo $T_f$, éste se realiza sólo cuando ha concluido el período de devengo dado por $yf$.

- Vemos, por lo tanto, que el pago de cada caplet está dado por una fórmula de Call sobre $L_{T_f}$ con strike $L_K$.
- Queremos entonces poder aplicar una fórmula de BSM para el valor del caplet.
- El mercado ha adoptado como estándar el uso de la fórmula de BSM para este producto (en el formato fácil de recordar que es utilizando el valor esperado del activo subyacente).

De esta forma se obtiene que:

$$
\begin{equation}
caplet=yf\cdot N\cdot e^{-r_{OIS}\cdot T_v}\left[\mathbb{E}_t^Q\left(L_{T_f}\right)\cdot N\left(d_1\right)-L_K\cdot N\left(d_2\right)\right]
\end{equation}
$$

$$
\begin{equation}
d_1=\frac{\ln\frac{\mathbb{E}_t^Q\left(L_{T_f}\right)}{L_K}+\frac{1}{2}\sigma^2T_f}{\sigma\sqrt{T_f}}
\end{equation}
$$

$$
\begin{equation}
d_2=d_1-\sigma\sqrt{T_f}
\end{equation}
$$

Donde $T_f$ es el plazo hasta el fixing de la opción, $T_v$ es el plazo hasta el pago de la opción y la tasa de descuento es la que proviene de la curva *OIS*.

## Floors

- Un Floor es una opción que ofrece protección contra bajadas de alguna tasa de interés.
- Por ejemplo, un inversionista puede tener activos a tasa variable y estar interesado en protección contra disminuciones de la tasa de referencia por debajo un cierto umbral.
- Veamos el ejemplo de un Floor al 0.01% para proteger un activo en USD a 2Y a tasa flotante Libor 3M.

### Activo a Libor USD 3M

Se construye un activo al Libor USD 3M. Podría ser un bono o el mismo crédito anterior desde el punto de vista del banco que lo otorga.

```python
activo = get_libor_usd_3m_leg(Qcf.RecPay.RECEIVE,
                              10000000, Qcf.QCDate(26, 10, 2020), Qcf.Tenor('2Y'))
```

Visualizamos y vemos como ninguno de los cupones tiene su *fixing*, es decir el valor de la Libor a la fecha de fixing.

```python
aux.show_leg(activo, 'IborCashflow', '').style.format(frmt)
```

        	        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
                        2020-10-26
                        2021-01-26
                        2020-10-22
                        2021-01-26
                        10,000,000.00
                        0.00
                        0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        1
                        2021-01-26
                        2021-04-26
                        2021-01-22
                        2021-04-26
                        10,000,000.00
                        0.00
                        0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        2
                        2021-04-26
                        2021-07-26
                        2021-04-22
                        2021-07-26
                        10,000,000.00
                        0.00
                        0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        3
                        2021-07-26
                        2021-10-26
                        2021-07-22
                        2021-10-26
                        10,000,000.00
                        0.00
                        0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        4
                        2021-10-26
                        2022-01-26
                        2021-10-22
                        2022-01-26
                        10,000,000.00
                        0.00
                        0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        5
                        2022-01-26
                        2022-04-26
                        2022-01-24
                        2022-04-26
                        10,000,000.00
                        0.00
                        0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        6
                        2022-04-26
                        2022-07-26
                        2022-04-22
                        2022-07-26
                        10,000,000.00
                        0.00
                        0.00
                        True
                        0.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

                        7
                        2022-07-26
                        2022-10-26
                        2022-07-22
                        2022-10-26
                        10,000,000.00
                        10,000,000.00
                        0.00
                        True
                        10,000,000.00
                        USD
                        LIBORUSD3M
                        0.000000%
                        0.000000
                        1.000000
                        LinAct360

Vamos a considerar un **escenario arbitrario** en que los *fixings* de la Libor disminuyen 0.03% en cada nuevo cupón. El *fixing* del primer cupón corresponde al valor de la Libor USD 3M del 22-10-2020.

```python
increment = -.0003 # Suponemos que los fixings sucesivos van cambiando en este monto.
for i in range(activo.size()):
    cshflw = activo.get_cashflow_at(i)
    cshflw.set_interest_rate_value(first_fixing + i * increment)
```

Vemos como, en este escenario, los intereses por recibir llegan casi a 0 en el último cupón.

```python
df_activo = aux.show_leg(activo, 'IborCashflow', '')
df_activo.style.format(frmt)
```

        	        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
                        2020-10-26
                        2021-01-26
                        2020-10-22
                        2021-01-26
                        10,000,000.00
                        0.00
                        5,488.06
                        True
                        5,488.06
                        USD
                        LIBORUSD3M
                        0.214750%
                        0.000000
                        1.000000
                        LinAct360

                        1
                        2021-01-26
                        2021-04-26
                        2021-01-22
                        2021-04-26
                        10,000,000.00
                        0.00
                        4,618.75
                        True
                        4,618.75
                        USD
                        LIBORUSD3M
                        0.184750%
                        0.000000
                        1.000000
                        LinAct360

                        2
                        2021-04-26
                        2021-07-26
                        2021-04-22
                        2021-07-26
                        10,000,000.00
                        0.00
                        3,911.74
                        True
                        3,911.74
                        USD
                        LIBORUSD3M
                        0.154750%
                        0.000000
                        1.000000
                        LinAct360

                        3
                        2021-07-26
                        2021-10-26
                        2021-07-22
                        2021-10-26
                        10,000,000.00
                        0.00
                        3,188.06
                        True
                        3,188.06
                        USD
                        LIBORUSD3M
                        0.124750%
                        0.000000
                        1.000000
                        LinAct360

                        4
                        2021-10-26
                        2022-01-26
                        2021-10-22
                        2022-01-26
                        10,000,000.00
                        0.00
                        2,421.39
                        True
                        2,421.39
                        USD
                        LIBORUSD3M
                        0.094750%
                        0.000000
                        1.000000
                        LinAct360

                        5
                        2022-01-26
                        2022-04-26
                        2022-01-24
                        2022-04-26
                        10,000,000.00
                        0.00
                        1,618.75
                        True
                        1,618.75
                        USD
                        LIBORUSD3M
                        0.064750%
                        0.000000
                        1.000000
                        LinAct360

                        6
                        2022-04-26
                        2022-07-26
                        2022-04-22
                        2022-07-26
                        10,000,000.00
                        0.00
                        878.40
                        True
                        878.40
                        USD
                        LIBORUSD3M
                        0.034750%
                        0.000000
                        1.000000
                        LinAct360

                        7
                        2022-07-26
                        2022-10-26
                        2022-07-22
                        2022-10-26
                        10,000,000.00
                        10,000,000.00
                        121.39
                        True
                        10,000,121.39
                        USD
                        LIBORUSD3M
                        0.004750%
                        0.000000
                        1.000000
                        LinAct360

### Activo a Libor USD 3M + Floor

En un escenario como el anterior, el tenedor del bono o crédito, podría tener interés en contratar protección contra las bajadas de la Libor. En particular, podría contratar un Floor de Libor a 2Y.

El payoff de un Floor, está dado por la siguiente función `floorlet` asociada a cada uno de los cupones del activo.

**NB:** la función se llama `floorlet` por que corresponde a una componente del Floor total.

```python
def floorlet(notional: float,
             fixing: float,
             strike: float,
             fecha_inicio: Qcf.QCDate,
             fecha_final: Qcf.QCDate) -> float:

    valor_tasa = max(strike - fixing, 0)
    lin_act360 = Qcf.QCInterestRate(valor_tasa, Qcf.QCAct360(), Qcf.QCLinearWf())
    wf = lin_act360.wf(fecha_inicio, fecha_final)
    return notional * (wf - 1)
```

Apliquemos `floorlet` a cada uno de los cupones y *fixings* del escenario, considerando una tasa Floor (o strike) de 0.15%.

```python
strike = .0015
df_activo['interes_floor'] = df_activo.apply(
    lambda row: floorlet(
        row['nominal'],
        row['valor_tasa'],
        strike,
        Qcf.build_qcdate_from_string(row['fecha_inicial']),
        Qcf.build_qcdate_from_string(row['fecha_final'])
    ),
    axis=1
)

df_activo['interes_total'] = df_activo['interes'] + df_activo['interes_floor']
```

Vemos como cada `floorlet` provee un flujo de caja positivo, cada vez que el *fixing* de la Libor es inferior al valor de la tasa Floor o strike del Floor.

Los últimos pagos de intereses (total) están a una tasa efectiva igual al *strike* (0.15%).

```python
df_activo.style.format(frmt)
```

        	        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	        interes_floor	        interes_total

                        0
                        2020-10-26
                        2021-01-26
                        2020-10-22
                        2021-01-26
                        10,000,000.00
                        0.00
                        5,488.06
                        True
                        5,488.06
                        USD
                        LIBORUSD3M
                        0.214750%
                        0.000000
                        1.000000
                        LinAct360
                        0.000000
                        5,488.06

                        1
                        2021-01-26
                        2021-04-26
                        2021-01-22
                        2021-04-26
                        10,000,000.00
                        0.00
                        4,618.75
                        True
                        4,618.75
                        USD
                        LIBORUSD3M
                        0.184750%
                        0.000000
                        1.000000
                        LinAct360
                        0.000000
                        4,618.75

                        2
                        2021-04-26
                        2021-07-26
                        2021-04-22
                        2021-07-26
                        10,000,000.00
                        0.00
                        3,911.74
                        True
                        3,911.74
                        USD
                        LIBORUSD3M
                        0.154750%
                        0.000000
                        1.000000
                        LinAct360
                        0.000000
                        3,911.74

                        3
                        2021-07-26
                        2021-10-26
                        2021-07-22
                        2021-10-26
                        10,000,000.00
                        0.00
                        3,188.06
                        True
                        3,188.06
                        USD
                        LIBORUSD3M
                        0.124750%
                        0.000000
                        1.000000
                        LinAct360
                        645.277778
                        3,833.33

                        4
                        2021-10-26
                        2022-01-26
                        2021-10-22
                        2022-01-26
                        10,000,000.00
                        0.00
                        2,421.39
                        True
                        2,421.39
                        USD
                        LIBORUSD3M
                        0.094750%
                        0.000000
                        1.000000
                        LinAct360
                        1411.944444
                        3,833.33

                        5
                        2022-01-26
                        2022-04-26
                        2022-01-24
                        2022-04-26
                        10,000,000.00
                        0.00
                        1,618.75
                        True
                        1,618.75
                        USD
                        LIBORUSD3M
                        0.064750%
                        0.000000
                        1.000000
                        LinAct360
                        2131.250000
                        3,750.00

                        6
                        2022-04-26
                        2022-07-26
                        2022-04-22
                        2022-07-26
                        10,000,000.00
                        0.00
                        878.40
                        True
                        878.40
                        USD
                        LIBORUSD3M
                        0.034750%
                        0.000000
                        1.000000
                        LinAct360
                        2913.263889
                        3,791.67

                        7
                        2022-07-26
                        2022-10-26
                        2022-07-22
                        2022-10-26
                        10,000,000.00
                        10,000,000.00
                        121.39
                        True
                        10,000,121.39
                        USD
                        LIBORUSD3M
                        0.004750%
                        0.000000
                        1.000000
                        LinAct360
                        3711.944444
                        3,833.33

### Fórmula de Valorización

- Cada componente individual de un floor se dice floorlet.
- Evaluemos un floorlet: su pago está dado por

$$
\begin{equation}
yf\cdot N\cdot max\left(L_K-L_{T_f},0\right)
\end{equation}
$$

- Donde $yf$ es la fracción de año que se usa para la tasa de referencia (lo más usual es Act/360), $N$ es el nocional del contrato y $L_K$ es la tasa floor.
- Notar que, aunque el pago queda determinado al tiempo $T_f$, éste se realiza sólo cuando ha concluido el período de devengo dado por $yf$.

- Vemos, por lo tanto, que el pago de cada floorlet está dado por una fórmula de Put sobre $L_{T_f}$ con strike $L_K$
- Queremos entonces poder aplicar una fórmula de BSM para el valor del floorlet.
- El mercado ha adoptado como estándar el uso de la fórmula de BSM para este producto (en el formato fácil de recordar que es utilizando el valor esperado del activo subyacente).

De esta forma se obtiene que:

$$
\begin{equation}
floorlet=yf\cdot N\cdot e^{-r_{OIS}\cdot T_v}\left[L_K\cdot N\left(-d_2\right)-\mathbb{E}_t^Q\left(L_{T_f}\right)\cdot N\left(-d_1\right)\right]
\end{equation}
$$

$$
\begin{equation}
d_1=\frac{\ln\frac{\mathbb{E}_t^Q\left(L_{T_f}\right)}{L_K}+\frac{1}{2}\sigma^2T_f}{\sigma\sqrt{T_f}}
\end{equation}
$$

$$
\begin{equation}
d_2=d_1-\sigma\sqrt{T_f}
\end{equation}
$$

Donde $T_f$ es el plazo hasta el fixing de la opción, $T_v$ es el plazo hasta el pago de la opción y la tasa de descuento es la que proviene de la curva *OIS*.

## Ejercicio

Recordemos la Put – Call parity, ésta nos dice que:

$$
\begin{equation}
Call-Put=Forward
\end{equation}
$$

- ¿Qué forma asume esta relación para el caso de Caps y Floors?
- Notar que si compro un Cap y un Floor con la misma estructura temporal y el mismo strike sucede que:
  - Si la tasa flotante está por arriba del strike recibo este exceso
  - Si la tasa flotante está por debajo del strike pago este déficit

$$
\begin{equation}
Cap-Floor=Swap
\end{equation}
$$

## Matriz de Volatilidad

En esta pantalla para cotizar Caps, se puede ver el valor del parámetro de volatilidad utilizado para calcular el valor del Cap (campo NPV).

```python
Image(url="img/20201027_cap.gif", width=900, height=720)
```

Dicha volatilidad se deduce de la siguiente matriz de volatilidad, la cual, a su vez, proviene de cotizaciones de bancos y otras instituciones activas en este mercado.

```python
Image(url="img/20201027_cap_vol.gif", width=900, height=720)
```

```python
curvas = pd.read_csv('data/20201027_curvas.csv')
curvas.columns = ['fecha', 'plazo', 'tasa', 'codigo']
```

```python
curvas.style.format(frmt)
```

        	        fecha	        plazo	        tasa	        codigo

                        0
                        2020-10-27
                        1
                        -0.473373%
                        EURIBOR6M

                        1
                        2020-10-27
                        4
                        -0.509682%
                        EURIBOR6M

                        2
                        2020-10-27
                        7
                        -0.545975%
                        EURIBOR6M

                        3
                        2020-10-27
                        14
                        -0.545947%
                        EURIBOR6M

                        4
                        2020-10-27
                        32
                        -0.549275%
                        EURIBOR6M

                        5
                        2020-10-27
                        61
                        -0.533269%
                        EURIBOR6M

                        6
                        2020-10-27
                        92
                        -0.516071%
                        EURIBOR6M

                        7
                        2020-10-27
                        120
                        -0.515312%
                        EURIBOR6M

                        8
                        2020-10-27
                        151
                        -0.514439%
                        EURIBOR6M

                        9
                        2020-10-27
                        182
                        -0.513428%
                        EURIBOR6M

                        10
                        2020-10-27
                        214
                        -0.512320%
                        EURIBOR6M

                        11
                        2020-10-27
                        243
                        -0.513078%
                        EURIBOR6M

                        12
                        2020-10-27
                        273
                        -0.513167%
                        EURIBOR6M

                        13
                        2020-10-27
                        305
                        -0.512171%
                        EURIBOR6M

                        14
                        2020-10-27
                        335
                        -0.513040%
                        EURIBOR6M

                        15
                        2020-10-27
                        365
                        -0.513895%
                        EURIBOR6M

                        16
                        2020-10-27
                        547
                        -0.522153%
                        EURIBOR6M

                        17
                        2020-10-27
                        732
                        -0.525765%
                        EURIBOR6M

                        18
                        2020-10-27
                        1096
                        -0.523011%
                        EURIBOR6M

                        19
                        2020-10-27
                        1461
                        -0.504153%
                        EURIBOR6M

                        20
                        2020-10-27
                        1826
                        -0.478194%
                        EURIBOR6M

                        21
                        2020-10-27
                        2556
                        -0.407062%
                        EURIBOR6M

                        22
                        2020-10-27
                        3652
                        -0.278615%
                        EURIBOR6M

                        23
                        2020-10-27
                        4383
                        -0.194625%
                        EURIBOR6M

                        24
                        2020-10-27
                        5478
                        -0.094742%
                        EURIBOR6M

                        25
                        2020-10-27
                        7305
                        -0.020922%
                        EURIBOR6M

                        26
                        2020-10-27
                        9132
                        -0.023148%
                        EURIBOR6M

                        27
                        2020-10-27
                        10959
                        -0.053014%
                        EURIBOR6M

                        28
                        2020-10-27
                        1
                        0.073027%
                        LIBORUSD3MBBG

                        29
                        2020-10-27
                        4
                        0.091292%
                        LIBORUSD3MBBG

                        30
                        2020-10-27
                        7
                        0.093902%
                        LIBORUSD3MBBG

                        31
                        2020-10-27
                        14
                        0.109562%
                        LIBORUSD3MBBG

                        32
                        2020-10-27
                        32
                        0.146116%
                        LIBORUSD3MBBG

                        33
                        2020-10-27
                        61
                        0.172501%
                        LIBORUSD3MBBG

                        34
                        2020-10-27
                        92
                        0.216516%
                        LIBORUSD3MBBG

                        35
                        2020-10-27
                        120
                        0.230912%
                        LIBORUSD3MBBG

                        36
                        2020-10-27
                        151
                        0.235829%
                        LIBORUSD3MBBG

                        37
                        2020-10-27
                        182
                        0.232641%
                        LIBORUSD3MBBG

                        38
                        2020-10-27
                        211
                        0.230663%
                        LIBORUSD3MBBG

                        39
                        2020-10-27
                        243
                        0.227994%
                        LIBORUSD3MBBG

                        40
                        2020-10-27
                        273
                        0.225191%
                        LIBORUSD3MBBG

                        41
                        2020-10-27
                        306
                        0.222675%
                        LIBORUSD3MBBG

                        42
                        2020-10-27
                        335
                        0.220774%
                        LIBORUSD3MBBG

                        43
                        2020-10-27
                        365
                        0.219179%
                        LIBORUSD3MBBG

                        44
                        2020-10-27
                        547
                        0.223157%
                        LIBORUSD3MBBG

                        45
                        2020-10-27
                        732
                        0.232801%
                        LIBORUSD3MBBG

                        46
                        2020-10-27
                        1096
                        0.266422%
                        LIBORUSD3MBBG

                        47
                        2020-10-27
                        1461
                        0.325791%
                        LIBORUSD3MBBG

                        48
                        2020-10-27
                        1826
                        0.405632%
                        LIBORUSD3MBBG

                        49
                        2020-10-27
                        2556
                        0.583247%
                        LIBORUSD3MBBG

                        50
                        2020-10-27
                        3652
                        0.818369%
                        LIBORUSD3MBBG

                        51
                        2020-10-27
                        4383
                        0.940271%
                        LIBORUSD3MBBG

                        52
                        2020-10-27
                        5478
                        1.065859%
                        LIBORUSD3MBBG

                        53
                        2020-10-27
                        7305
                        1.191864%
                        LIBORUSD3MBBG

                        54
                        2020-10-27
                        9132
                        1.243035%
                        LIBORUSD3MBBG

                        55
                        2020-10-27
                        10959
                        1.265036%
                        LIBORUSD3MBBG

                        56
                        2020-10-27
                        1
                        0.219241%
                        LIBOR6MBBG

                        57
                        2020-10-27
                        4
                        0.228388%
                        LIBOR6MBBG

                        58
                        2020-10-27
                        7
                        0.234924%
                        LIBOR6MBBG

                        59
                        2020-10-27
                        14
                        0.232315%
                        LIBOR6MBBG

                        60
                        2020-10-27
                        32
                        0.234126%
                        LIBOR6MBBG

                        61
                        2020-10-27
                        61
                        0.233679%
                        LIBOR6MBBG

                        62
                        2020-10-27
                        92
                        0.233624%
                        LIBOR6MBBG

                        63
                        2020-10-27
                        120
                        0.248305%
                        LIBOR6MBBG

                        64
                        2020-10-27
                        151
                        0.253051%
                        LIBOR6MBBG

                        65
                        2020-10-27
                        182
                        0.250152%
                        LIBOR6MBBG

                        66
                        2020-10-27
                        211
                        0.253671%
                        LIBOR6MBBG

                        67
                        2020-10-27
                        243
                        0.255819%
                        LIBOR6MBBG

                        68
                        2020-10-27
                        273
                        0.256340%
                        LIBOR6MBBG

                        69
                        2020-10-27
                        306
                        0.256761%
                        LIBOR6MBBG

                        70
                        2020-10-27
                        335
                        0.257058%
                        LIBOR6MBBG

                        71
                        2020-10-27
                        365
                        0.257260%
                        LIBOR6MBBG

                        72
                        2020-10-27
                        547
                        0.262732%
                        LIBOR6MBBG

                        73
                        2020-10-27
                        732
                        0.300910%
                        LIBOR6MBBG

                        74
                        2020-10-27
                        1096
                        0.364513%
                        LIBOR6MBBG

                        75
                        2020-10-27
                        1461
                        0.438432%
                        LIBOR6MBBG

                        76
                        2020-10-27
                        1826
                        0.527113%
                        LIBOR6MBBG

                        77
                        2020-10-27
                        2556
                        0.717528%
                        LIBOR6MBBG

                        78
                        2020-10-27
                        3652
                        0.961599%
                        LIBOR6MBBG

                        79
                        2020-10-27
                        4383
                        1.087461%
                        LIBOR6MBBG

                        80
                        2020-10-27
                        5478
                        1.217227%
                        LIBOR6MBBG

                        81
                        2020-10-27
                        7305
                        1.346548%
                        LIBOR6MBBG

                        82
                        2020-10-27
                        9132
                        1.396844%
                        LIBOR6MBBG

                        83
                        2020-10-27
                        10959
                        1.420908%
                        LIBOR6MBBG

                        84
                        2020-10-27
                        1
                        0.329042%
                        LIBORUSD12MBBG

                        85
                        2020-10-27
                        4
                        0.338202%
                        LIBORUSD12MBBG

                        86
                        2020-10-27
                        7
                        0.334282%
                        LIBORUSD12MBBG

                        87
                        2020-10-27
                        14
                        0.336909%
                        LIBORUSD12MBBG

                        88
                        2020-10-27
                        32
                        0.335956%
                        LIBORUSD12MBBG

                        89
                        2020-10-27
                        61
                        0.336339%
                        LIBORUSD12MBBG

                        90
                        2020-10-27
                        92
                        0.336348%
                        LIBORUSD12MBBG

                        91
                        2020-10-27
                        120
                        0.336245%
                        LIBORUSD12MBBG

                        92
                        2020-10-27
                        151
                        0.336307%
                        LIBORUSD12MBBG

                        93
                        2020-10-27
                        182
                        0.336365%
                        LIBORUSD12MBBG

                        94
                        2020-10-27
                        211
                        0.336309%
                        LIBORUSD12MBBG

                        95
                        2020-10-27
                        243
                        0.336349%
                        LIBORUSD12MBBG

                        96
                        2020-10-27
                        273
                        0.336304%
                        LIBORUSD12MBBG

                        97
                        2020-10-27
                        306
                        0.336336%
                        LIBORUSD12MBBG

                        98
                        2020-10-27
                        335
                        0.336336%
                        LIBORUSD12MBBG

                        99
                        2020-10-27
                        365
                        0.336327%
                        LIBORUSD12MBBG

                        100
                        2020-10-27
                        547
                        0.390821%
                        LIBORUSD12MBBG

                        101
                        2020-10-27
                        732
                        0.418427%
                        LIBORUSD12MBBG

                        102
                        2020-10-27
                        1096
                        0.535385%
                        LIBORUSD12MBBG

                        103
                        2020-10-27
                        1461
                        0.636915%
                        LIBORUSD12MBBG

                        104
                        2020-10-27
                        1826
                        0.737168%
                        LIBORUSD12MBBG

                        105
                        2020-10-27
                        2556
                        0.946149%
                        LIBORUSD12MBBG

                        106
                        2020-10-27
                        3652
                        1.206352%
                        LIBORUSD12MBBG

                        107
                        2020-10-27
                        4383
                        1.328154%
                        LIBORUSD12MBBG

                        108
                        2020-10-27
                        5478
                        1.461553%
                        LIBORUSD12MBBG

                        109
                        2020-10-27
                        7305
                        1.593587%
                        LIBORUSD12MBBG

                        110
                        2020-10-27
                        9132
                        1.647409%
                        LIBORUSD12MBBG

                        111
                        2020-10-27
                        10959
                        1.671933%
                        LIBORUSD12MBBG

                        112
                        2020-10-27
                        1
                        0.073027%
                        USDSOFR

                        113
                        2020-10-27
                        4
                        0.082159%
                        USDSOFR

                        114
                        2020-10-27
                        7
                        0.083464%
                        USDSOFR

                        115
                        2020-10-27
                        14
                        0.084769%
                        USDSOFR

                        116
                        2020-10-27
                        32
                        0.085587%
                        USDSOFR

                        117
                        2020-10-27
                        61
                        0.082913%
                        USDSOFR

                        118
                        2020-10-27
                        92
                        0.083954%
                        USDSOFR

                        119
                        2020-10-27
                        120
                        0.081865%
                        USDSOFR

                        120
                        2020-10-27
                        151
                        0.079934%
                        USDSOFR

                        121
                        2020-10-27
                        182
                        0.078561%
                        USDSOFR

                        122
                        2020-10-27
                        211
                        0.075467%
                        USDSOFR

                        123
                        2020-10-27
                        243
                        0.073496%
                        USDSOFR

                        124
                        2020-10-27
                        273
                        0.072511%
                        USDSOFR

                        125
                        2020-10-27
                        306
                        0.072512%
                        USDSOFR

                        126
                        2020-10-27
                        335
                        0.071469%
                        USDSOFR

                        127
                        2020-10-27
                        365
                        0.070800%
                        USDSOFR

                        128
                        2020-10-27
                        547
                        0.061336%
                        USDSOFR

                        129
                        2020-10-27
                        732
                        0.054770%
                        USDSOFR

                        130
                        2020-10-27
                        1096
                        0.066411%
                        USDSOFR

                        131
                        2020-10-27
                        1461
                        0.115014%
                        USDSOFR

                        132
                        2020-10-27
                        1826
                        0.185709%
                        USDSOFR

                        133
                        2020-10-27
                        2556
                        0.352623%
                        USDSOFR

                        134
                        2020-10-27
                        3652
                        0.580231%
                        USDSOFR

                        135
                        2020-10-27
                        4383
                        0.698839%
                        USDSOFR

                        136
                        2020-10-27
                        5478
                        0.819661%
                        USDSOFR

                        137
                        2020-10-27
                        7305
                        0.939151%
                        USDSOFR

                        138
                        2020-10-27
                        9132
                        0.978000%
                        USDSOFR

                        139
                        2020-10-27
                        10959
                        1.002141%
                        USDSOFR

```python
df_libor3m = curvas[curvas.codigo == 'LIBORUSD3MBBG']
df_sofr = curvas[curvas.codigo == 'USDSOFR']
```

```python
df_sofr.style.format(frmt)
```

        	        fecha	        plazo	        tasa	        codigo

                        112
                        2020-10-27
                        1
                        0.073027%
                        USDSOFR

                        113
                        2020-10-27
                        4
                        0.082159%
                        USDSOFR

                        114
                        2020-10-27
                        7
                        0.083464%
                        USDSOFR

                        115
                        2020-10-27
                        14
                        0.084769%
                        USDSOFR

                        116
                        2020-10-27
                        32
                        0.085587%
                        USDSOFR

                        117
                        2020-10-27
                        61
                        0.082913%
                        USDSOFR

                        118
                        2020-10-27
                        92
                        0.083954%
                        USDSOFR

                        119
                        2020-10-27
                        120
                        0.081865%
                        USDSOFR

                        120
                        2020-10-27
                        151
                        0.079934%
                        USDSOFR

                        121
                        2020-10-27
                        182
                        0.078561%
                        USDSOFR

                        122
                        2020-10-27
                        211
                        0.075467%
                        USDSOFR

                        123
                        2020-10-27
                        243
                        0.073496%
                        USDSOFR

                        124
                        2020-10-27
                        273
                        0.072511%
                        USDSOFR

                        125
                        2020-10-27
                        306
                        0.072512%
                        USDSOFR

                        126
                        2020-10-27
                        335
                        0.071469%
                        USDSOFR

                        127
                        2020-10-27
                        365
                        0.070800%
                        USDSOFR

                        128
                        2020-10-27
                        547
                        0.061336%
                        USDSOFR

                        129
                        2020-10-27
                        732
                        0.054770%
                        USDSOFR

                        130
                        2020-10-27
                        1096
                        0.066411%
                        USDSOFR

                        131
                        2020-10-27
                        1461
                        0.115014%
                        USDSOFR

                        132
                        2020-10-27
                        1826
                        0.185709%
                        USDSOFR

                        133
                        2020-10-27
                        2556
                        0.352623%
                        USDSOFR

                        134
                        2020-10-27
                        3652
                        0.580231%
                        USDSOFR

                        135
                        2020-10-27
                        4383
                        0.698839%
                        USDSOFR

                        136
                        2020-10-27
                        5478
                        0.819661%
                        USDSOFR

                        137
                        2020-10-27
                        7305
                        0.939151%
                        USDSOFR

                        138
                        2020-10-27
                        9132
                        0.978000%
                        USDSOFR

                        139
                        2020-10-27
                        10959
                        1.002141%
                        USDSOFR

## Ejercicio

Utilizando las curvas *Libor* y *SOFR* de más arriba, valorice el Cap a 5Y con strike ATM. Las convenciones de tasas de ambas curvas es Com Act/365.

**NOTA:** El valor esperado de la Libor a una cierta fecha usando la curva **Libor de BBG** se calcula como:

$$
\begin{equation}
\mathbb{E}_t^Q\left(L_{T_f}\right)=\left(\frac{P_L\left(t,T_f\right)}{P_L\left(t,T_{f+3M}\right)}-1\right)\cdot\frac{1}{yf\left(T_f,T_{f+3M}\right)}
\end{equation}
$$

Donde $P_L\left(t,T\right)$ es el factor de descuento obtenido desde la curva Libor entre $t=hoy$ y $T$ y $f$ es la fecha de inicio de la Libor.

Idealmente, utilicen funciones de la librería.

```python
plazos = [90, 180, 270, 360]
tasas = [.01, .012, .014, .016]
```

```python
df_90 = 1/(1+tasas[0])**(plazos[0]/365)
df_90
```

    0.9975495011486121

```python
df_180 = 1/(1+tasas[1])**(plazos[1]/365)
df_180
```

    0.9941346856205336

```python
ValorEsperado_L3M_en_90D = (df_90 / df_180 - 1) * 360 / (plazos[1] - plazos[0])
ValorEsperado_L3M_en_90D
```

    0.013739850655937857


```python
import numpy as np
import itertools
```

```python
num_pulleys=4
```

```python
from google.colab import drive
drive.mount('/content/drive')
```

    Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdocs.test%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.photos.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fpeopleapi.readonly&response_type=code

    Enter your authorization code:
    ··········
    Mounted at /content/drive

### Representation used for this code development ###

#### `get_li` ####

For getting inital $L_i$, we assume that the bot is at the `top-center`. With this assumption, we only need to consider `B & D` metrics for calculating $L_i$

```python
def get_Li(B, D):
    """
    The inital length of strings based on B and D
    """
    return 0.5*np.sqrt(B**2 + D**2)
```

### TODO
 - [ ] We need to account for 360 degree rotation here
    - Are you talking about the decrease in radius each time the pulley rotates completely?
 - [x] We need to have a reverse calculation done, i.e. given a $x_i,y_i,z_i$ & $x_f,y_f,z_f$ we need to find out what the inital and final thetas. But more on that later.

#### `get_final_string_length` ####
Considering that we have an inital length available, `delta_thetas` given, this function would provide what would be the final length if the pulleys are moved by `delta_theta`

$$ L_f = L_i + r_i\Delta\theta_i $$

#### `get_theta_from_string_length` ####
If we have final length $L_f$ and inital string length $L_i$ and the radius of the pulleys, then we can find the `delta_thetas` moved by the pulleys

NOTE

Remember, $L_i$ and & $L_f$ are array/list, so the output would also be array/list each corresponding to individual pulleys.

```python
def get_final_string_length(Li, r, delta_thetas=np.zeros((1,num_pulleys))):
    """
    The final length of strings which need to be achieved for
    reaching a specific location (based on thetas).
    """
    return Li + r*delta_thetas

def get_theta_from_string_length(Lf, Li, r):
    """
    Given some string lengths Lf and initial lengths Li, return
    the rotation of the pulleys
    """
    return (Lf - Li)/r
```

#### `get_coordinate` ####
Assumption:
We start from the top of the board, therefore the $L_i$ is same for all strings

1. We take the initial $L_i$ for all strings
2. We are given the `thetas` that the pulley moved
3. We use initial $L_i$ and `thetas` to get final lengths

Then we use following formulas for getting coordinates of the final place the bot moved to

\begin{align}
y &= \frac{l_1^2 - l_2^2 + D^2}{2D} \\
x &= \frac{l_2^2 - l_3^2 + B^2}{2B}
\end{align}

\begin{aligned}
l_1^2 &= (0 - x)^2 + (0 - y)^2 + (H - z)^2 \\ &= x^2 + y^2 + (H - z)^2
\end{aligned}

\begin{aligned}
(H - z)^2 = l_1^2 - x^2 - y^2\\
\end{aligned}

$(H - z)^2$ = Hz2 (Python variable)

```python
def get_coordinate(thetas, B, D, H, r):
    """
    thetas: numpy array of rotations, shape: (num_arrays, )
    B, D, H, r: scalar constants
    """
    Li = get_Li(B, D)
    l = get_final_string_length(Li, r, thetas)
    x = (l[1]**2 - l[2]**2 + B**2)/(2*B)
    y = (l[0]**2 - l[1]**2 + D**2)/(2*D)
    Hz2 = l[0]**2 - x**2 - y**2 # (H-z)^2, eqn 1
    # Is it taut?
    # print(l[0]**2 - l[3]**2, l[1]**2 - l[2]**2)
    z = H - np.sqrt(Hz2)
    return np.array([x, y, z])
```

### `get_thetas` ###

We assume that we always start from the `top` center of the construct that you see on the top.

With the above assumption, the inital length of strings would be taken from `get_Li(B,D)` function.
If we are given the final co-ordinates (x,y,z), we can fund the $Lf$ using following formulas

\begin{align}
\begin{aligned}
l_1^2 &= x^2 + y^2 + (H - z)^2
\end{aligned} \\
\begin{aligned}
l_2^2 &= x^2 + (D - y)^2 + (H - z)^2
\end{aligned} \\
\begin{aligned}
l_3^2 &= (B - x)^2 + (D - y)^2 + (H - z)^2
\end{aligned} \\
\begin{aligned}
l_4^2 &= (B - x)^2 + y^2 + (H - z)^2
\end{aligned} \\
\end{align}

python variable $Hz2 = (H - Z)^2$

```python
def get_thetas(coords, B, D, H, r, back_home=False):
    """
    coords: (x, y, z) coordinates
    B, D, H, r: scalar constants
    """
    l = np.zeros((4,))
    x, y, z = coords
    Hz2 = (H - z)**2 # common term
    l[0] = np.sqrt(x**2 + y**2 + Hz2)
    l[1] = np.sqrt(x**2 + (D - y)**2 + Hz2)
    l[2] = np.sqrt((B - x)**2 + (D - y)**2 + Hz2)
    l[3] = np.sqrt((B - x)**2 + y**2 + Hz2)
    if back_home:
        return get_theta_from_string_length(get_Li(B, D),l, r)
    else:
        return get_theta_from_string_length(l, get_Li(B, D), r)
```

```python
# Box dimensions
B, D, H = 100, 200, 300
# Pulley radius
r = 0.1
# Pulley locations
Pulleys = [(0, 0, H), (0, D, H), (B, D, H), (B, 0, H)]
```

Taking some random values of $\Theta$, we can get the corresponding coordinates from `get_coordinates()`.
If $\Theta$ is such that the ropes are taught, we will get back the original $\Theta$ from `get_thetas()`.
If they aren't, the values will be different. Both are demonstrated in the following example. In the first case, $\theta_4$ is clearly different from the value we set, but in the second case, all angles are exactly the same.

### Below is just a cross-check function. ###
1. Each pulley rotated `[10,20,30,40]` rotations respectively, (i.e 1st pulley, 2nd pulley etc.)
2. With these rotations we get the final co-ordinates, remember we assume we start from top of the structure area.
3. While using the final_cordinates we use `get_thetas` function, i.e how much theta is supposed to be moved, this should come back to thetas we decided earlier.

```python
def display(rotations, coords, theta):
    print(f'For rotation = {rotations/(2*np.pi)}')
    x, y, z = coords
    print(f'\tx = {x}, y = {y}, z = {z}')
    print('\tΘ = ', theta)
    print()

# Pulleys rotated 10 revolutions, 20, 30, and 40, respectively
rotations = np.array([10, 20, 30, 40])*2*np.pi
coords = get_coordinate(rotations, B, D, H, r)
theta = get_thetas(coords, B, D, H, r)/(2*np.pi)
display(rotations, coords, theta)

# Pulleys rotated 10 revolutions, 20, 20, and 10, respectively
rotations = np.array([10, 20, 20, 10])*2*np.pi
coords = get_coordinate(rotations, B, D, H, r)
theta = get_thetas(coords, B, D, H, r)/(2*np.pi)
display(rotations, coords, theta)
```

    For rotation = [10. 20. 30. 40.]
    	x = 41.98822482885034, y = 96.19150450244695, z = 245.88322994588324
    	Θ =  [10.        20.        30.        20.5045589]

    For rotation = [10. 20. 20. 10.]
    	x = 50.0, y = 96.19150450244695, z = 253.18508971098808
    	Θ =  [10. 20. 20. 10.]

As we know that all points are calculated from Top, therefore, we need to 2 sets to data.
1. Move from base $(B/2,D/2,H)$ to the target location
2. Move from some location back to $(B/2,D/2,H)$

In this case, we are looking at a dataset which looks like this

|$X_i$|$Y_i$|$Z_i$|$X_f$|$Y_f$|$Z_f$| B | D | H | r |theta_0|theta_1|theta_2|theta_3|
|-----|-----|-----|-----|-----|-----|---|---|---|---|-------|-------|-------|-------|

```python
B = D = H = np.arange(1,7,0.5)
r = [0.008]

data = np.zeros((1,11))
first=True
element = 0
for b,d,h,r in itertools.product(B,D,H,r):
    x_range = np.arange(0,b,1)
    y_range = np.arange(0,d,1)
    z_range = np.arange(0,h,1)
    for x,y,z in itertools.product(x_range,y_range,z_range):
        thetas_from_home = get_thetas([x,y,z],B=b,D=d,H=h,r=r,back_home=False)
        if first:
            data = np.array([x,y,z,b,d,h,r,thetas_from_home[0],thetas_from_home[1],thetas_from_home[2],thetas_from_home[3]])
            first=False
        else:
            data = np.vstack((data,np.array([x,y,z,b,d,h,r,thetas_from_home[0],thetas_from_home[1],thetas_from_home[2],thetas_from_home[3]])))
            first = False
    if (element+1) % 100 == 0:
        print("Shape : ",data.shape)
    if (element+1) % 1000 == 0:
        print("Completed with %d / %d" %(element+1, 5000))
    element += 1
```

    Shape :  (1192, 11)
    Shape :  (3216, 11)
    Shape :  (7008, 11)
    Shape :  (9792, 11)
    Shape :  (13392, 11)
    Shape :  (18864, 11)
    Shape :  (23616, 11)
    Shape :  (28608, 11)
    Shape :  (35520, 11)
    Shape :  (42656, 11)
    Completed with 1000 / 5000
    Shape :  (48936, 11)
    Shape :  (57216, 11)
    Shape :  (66864, 11)
    Shape :  (74448, 11)
    Shape :  (83808, 11)
    Shape :  (94912, 11)
    Shape :  (105216, 11)

```python
data.shape
```

    (110592, 11)

```python
import pandas as pd
data_df = pd.DataFrame(data,columns=["x_f","y_f","z_f","b","d","h","r","theta0","theta1","theta2","theta3"])
```

```python
data_df.apply(lambda x : x.unique().shape)
```

    x_f          (7,)
    y_f          (7,)
    z_f          (7,)
    b           (12,)
    d           (12,)
    h           (12,)
    r            (1,)
    theta0    (6678,)
    theta1    (9539,)
    theta2    (6681,)
    theta3    (9539,)
    dtype: object

```python
data_df.head(2)
```

      x_f
      y_f
      z_f
      b
      d
      h
      r
      theta0
      theta1
      theta2
      theta3

      0
      0.0
      0.0
      0.0
      1.0
      1.0
      1.0
      0.008
      36.611652
      88.388348
      128.118003
      88.388348

      1
      0.0
      0.0
      0.0
      1.0
      1.0
      1.5
      0.008
      99.111652
      136.958607
      169.305754
      136.958607

Lets verify whether the thetas calculated are correct, by reverse calculated the $x_f,y_f,z_f$ using the theta values

```python
idx = 10
print(get_coordinate(thetas = data_df.iloc[idx,[-4,-3,-2,-1]],
                     B = data_df.iloc[idx,]["b"],
                     D = data_df.iloc[idx,]["d"],
                     H = data_df.iloc[idx,]["h"],
                     r=0.008))
print(data_df.iloc[idx,[0,1,2]].values)
```

    [ 4.44089210e-16 -2.22044605e-16  2.00000000e+00]
    [0. 0. 2.]

```python
data_df.to_csv("suspended_robot_data_r_0.008_B_D_H_1_6_20190826.csv",header=True,index=False)
```

```python
data_df_model = data_df.copy()
```

```python
import matplotlib.pyplot as plt
```

```python
fig, ax = plt.subplots(nrows=1,ncols=4,sharex=False,sharey=False,figsize=(20,5))
ax[0].hist(data_df_model.theta0,bins=100)
ax[1].hist(data_df_model.theta1,bins=100)
ax[2].hist(data_df_model.theta2,bins=100)
ax[3].hist(data_df_model.theta3,bins=100)
plt.show()
```

```python
from sklearn.preprocessing import StandardScaler
```

```python
obj = StandardScaler()
obj.fit(data_df_model[["theta0","theta1","theta2","theta3"]])
```

    StandardScaler(copy=True, with_mean=True, with_std=True)

```python
import pickle
with open("Y_theta_value_scalar_1X4_format.pkl","wb") as f:
    pickle.dump(obj,f)
```

```python
data_df_model_2 = pd.concat([data_df_model.drop(columns=["theta0","theta1","theta2","theta3"],inplace=False),
                             pd.DataFrame(obj.transform(data_df_model[["theta0","theta1","theta2","theta3"]]),columns=["scaled_theta0","scaled_theta1","scaled_theta2","scaled_theta3"])],axis=1)
```

```python
data_df_model_2.head()
```

      x_f
      y_f
      z_f
      b
      d
      h
      r
      scaled_theta0
      scaled_theta1
      scaled_theta2
      scaled_theta3

      0
      0.0
      0.0
      0.0
      1.0
      1.0
      1.0
      0.008
      -0.487743
      -0.452308
      -0.461088
      -0.452308

      1
      0.0
      0.0
      0.0
      1.0
      1.0
      1.5
      0.008
      -0.176528
      -0.208896
      -0.253446
      -0.208896

      2
      0.0
      0.0
      1.0
      1.0
      1.0
      1.5
      0.008
      -0.798957
      -0.637847
      -0.607318
      -0.637847

      3
      0.0
      0.0
      0.0
      1.0
      1.0
      2.0
      0.008
      0.134686
      0.062536
      -0.008981
      0.062536

      4
      0.0
      0.0
      1.0
      1.0
      1.0
      2.0
      0.008
      -0.487743
      -0.452308
      -0.461088
      -0.452308

```python
data_df_model_2.to_csv("suspended_robot_data_all_scaled_r_0.008_B_D_H_1_6_20190826.csv",header=True,index=False)
```

```python
X_df = data_df_model_2.drop(columns=["r","scaled_theta0","scaled_theta1","scaled_theta2","scaled_theta3"],inplace=False)
Y_df = data_df_model_2[["scaled_theta0","scaled_theta1","scaled_theta2","scaled_theta3"]]
```

```python
X_df.to_csv("suspended_robot_X_data_df.csv",header=True,index=False)
Y_df.to_csv("suspended_robot_Y_data_df.csv",header=True,index=False)
```

```python
X = data_df_model_2.drop(columns=["r","scaled_theta0","scaled_theta1","scaled_theta2","scaled_theta3"],inplace=False).values
Y = data_df_model_2[["scaled_theta0","scaled_theta1","scaled_theta2","scaled_theta3"]].values
```

```python
X.shape,Y.shape
```

    ((110592, 6), (110592, 4))

```python
X.dump("suspended_robot_X_data.npy")
Y.dump("suspended_robot_Y_data.npy")
```

```python

```


# DFT Plot of notable neighborhoods
#### Author: Duke Yang @ NYU CUSP
###### TODO: FFT computation result and DFT matrix multiplication different. Why?

```python
import pandas as pd
import geopandas as gpd
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
import numpy as np
from scipy import signal

from numpy import array as arr
import matplotlib
from sklearn.preprocessing import scale
import matplotlib.pyplot as plt
%matplotlib inline
import gc
from pprint import pprint
import sympy

sympy.init_printing(pretty_print=True, use_latex='mathjax')
```

```python
# __DATA_FILE_PATH__ = __YOUR_PATH_HERE__
__DATA_FILE_PATH__ = '../data/DEP_NTANM_NZONLY_311_Service_Requests_from_2010_to_Present.csv'
__COMPLAINT_TYPES__ = [
'Noise - Vehicle', 'Noise - Street/Sidewalk', 'Noise - Commercial',
       'Noise - Residential', 'Noise', 'Noise - Park',
       'Noise - Helicopter', 'Noise - House of Worship',
       'Collection Truck Noise'
#     , 'Noise Survey'
]
```

```python
def _convert_dt(dt):
    try:
        return pd.to_datetime(dt, format='%Y-%m-%d %H:%M:%S')
    except:
        return np.nan

__RAW_DF__ = pd.read_csv(
    __DATA_FILE_PATH__
    , converters={k: _convert_dt for k in ['Created Date', 'Closed Date', 'Due Date' , 'Resolution Action Updated Date']}
)
```

### Group by each day

```python
grp_dt = __RAW_DF__.copy(deep=True)
grp_dt.set_index('Created Date', inplace=True)

grp_dt = grp_dt.groupby(['ntaname', pd.Grouper(freq='D')]).agg(
    {
        'Agency': 'count'
    }
)
grp_dt.rename(
    mapper={'Agency': 'count'}
    , axis=1
    , inplace=True
)
grp_dt.head()
```

      count

      ntaname
      Created Date

      Airport
      2010-06-10
      1

      2012-06-14
      1

      2016-07-11
      1

      2017-01-07
      1

      2017-04-14
      1

## Compute DFT of each neighborhood's daily noise complaint counts as input signal (T=365)

```python
def get_dft(ntanm):
    """
    Returns freq, dft
    """
    _dt = pd.DataFrame(pd.date_range(start='2010-01-01', end='2019-12-31'))
    _dt['count'] = 0
    _dt.set_index(0, inplace=True)
    _dt = _dt.loc[grp_dt.loc[ntanm].index[0]:grp_dt.loc[ntanm].index[-1]]
    _df = _dt.merge(grp_dt.loc[ntanm], how='outer', left_index=True, right_index=True)
    _df.fillna(0, inplace=True)
    _df.drop(labels=['count_x'], axis=1, inplace=True)

    # calc. DFT
    _l = _df.shape[0]
    dfts = scipy.linalg.dft(_l) @ _df
    freq = np.fft.fftshift(np.fft.fftfreq(_l, d=1/365.25))
    dft = np.fft.fftshift(np.real(dfts)[:])
    return freq, dft

```

### Hotspot

```python
from datetime import datetime

hotspot_nbs = [
    'park-cemetery-etc-Bronx',
    'Marble Hill-Inwood',
    'Central Harlem North-Polo Grounds',
    'Hamilton Heights',
    'Manhattanville',
    'Morningside Heights',
    'Central Harlem South',
    'Upper West Side',
    'Hudson Yards-Chelsea-Flatiron-Union Square',
    'Lincoln Square',
    'Clinton',
    'Midtown-Midtown South',
    'Turtle Bay-East Midtown',
    'Murray Hill-Kips Bay',
    'Gramercy',
    'East Village',
    'West Village',
    'SoHo-TriBeCa-Civic Center-Little Italy',
    'Battery Park City-Lower Manhattan',
    'Chinatown',
    'Lower East Side',
    'Lenox Hill-Roosevelt Island',
    'Yorkville',
    'East Harlem South',
    'East Harlem North',
    'Washington Heights North',
    'Washington Heights South',
    'Upper East Side-Carnegie Hill',
    'Stuyvesant Town-Cooper Village',
    'park-cemetery-etc-Manhattan'
]

plt.close('all')
ntanms = sorted(grp_dt.index.levels[0])
fig, axs = plt.subplots(len(hotspot_nbs), 2, figsize=(14, 48)
                       )
plt.tight_layout()

for i, ntanm in enumerate(hotspot_nbs):
    # Time Series
    axs[i][0].plot(
        grp_dt.loc[ntanm].index
        , grp_dt.loc[ntanm]
        ,'r+'
    )
    axs[i][0].set_title('%s' % ntanm)

    # DFT
    freq, dft = zip(*sorted(list(zip(*get_dft(ntanm))), key=lambda t: t[0]))
    axs[i][1].plot(freq, arr(dft), 'r-')
    axs[i][1].set_title('DFT: %s, n:%d' % (ntanm, grp_dt.loc[ntanm].index.size))

plt.show()
```

### Daily Count by Neighborhood SVD result: $u_0 \in U, \, M_{count}=USV^{T}$

```python
from datetime import datetime

u1_nbs = ['Prospect Heights',
 'Madison',
 'Old Astoria',
 'Park Slope-Gowanus',
 'Kingsbridge Heights',
 'Schuylerville-Throgs Neck-Edgewater Park',
 'Chinatown',
 'park-cemetery-etc-Bronx']

plt.close('all')
ntanms = sorted(grp_dt.index.levels[0])
fig, axs = plt.subplots(len(u1_nbs), 2, figsize=(14, 14)
                       )
plt.tight_layout()

for i, ntanm in enumerate(u1_nbs):
    # Time Series
    axs[i][0].plot(
        grp_dt.loc[ntanm].index
        , grp_dt.loc[ntanm]
        ,'r+'
    )
    axs[i][0].set_title('%s' % ntanm)

    # DFT
    freq, dft = zip(*sorted(list(zip(*get_dft(ntanm))), key=lambda t: t[0]))
    axs[i][1].plot(freq, arr(dft), 'r-')
    axs[i][1].set_title('DFT: %s, n:%d' % (ntanm, grp_dt.loc[ntanm].index.size))

plt.show()
```


# Algorithms Exercise 2

## Imports

```python
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns
import numpy as np
from IPython.display import display
```

## Peak finding

Write a function `find_peaks` that finds and returns the indices of the local maxima in a sequence. Your function should:

* Properly handle local maxima at the endpoints of the input array.
* Return a Numpy array of integer indices.
* Handle any Python iterable as input.

```python
def find_peaks(a):
    """Find the indices of the local maxima in a sequence."""
    a = np.array()
    np.argmax(a)
    np.argmax(a)
    return()

```

```python
p1 = find_peaks([2,0,1,0,2,0,1])
assert np.allclose(p1, np.array([0,2,4,6]))
p2 = find_peaks(np.array([0,1,2,3]))
assert np.allclose(p2, np.array([3]))
p3 = find_peaks([3,2,1,0])
assert np.allclose(p3, np.array([0]))
```

Here is a string with the first 10000 digits of $\pi$ (after the decimal). Write code to perform the following:

* Convert that string to a Numpy array of integers.
* Find the indices of the local maxima in the digits of $\pi$.
* Use `np.diff` to find the distances between consequtive local maxima.
* Visualize that distribution using an appropriately customized histogram.

```python
from sympy import pi, N
pi_digits_str = str(N(pi, 10001))[2:]
```

```python
n= np.fromstring(pi_digits_str, np.dtype(int))
n
```

    array([3834307354039497777, 3617297835054609715, 3689633584008935475, ...,
           3474022643680228657, 3617856382531809584, 4050765992030320181])

```python
np.argmax(n)
```

    132

```python
np.diff(n)
```

    array([-217009518984888062,   72335748954325760, -215887987194527744, ...,
           -577024840473510148,  143833738851580927,  432909609498510597])

```python
plt.hist(np.histogram(n))
plt.title('Distribution of local max. in $\pi$');
```

```python
assert True # use this for grading the pi digits histogram
```