¿Cuánto dura realmente un bono?
El vencimiento dice cuándo llega el último pago, pero un bono que paga cupones grandes devuelve gran parte de tu dinero antes. La duración de Macaulay es el tiempo medio hasta recibir los flujos, ponderando cada fecha de pago por su parte del precio: sum(t * PV(t)) / precio. Un bono cupón cero tiene una duración igual a su vencimiento; los cupones la reducen. Nuestro bono a 10 años del 5 % con rendimiento del 6 % tiene una duración de 7,9 años. La duración es la longitud real del bono en el tiempo.
import numpy as np
def bond_cashflows(face, coupon_rate, years, freq):
n = int(round(years * freq))
times = np.arange(1, n + 1) / freq
flows = np.full(n, face * coupon_rate / freq)
flows[-1] += face
return times, flows
times, flows = bond_cashflows(1000, 0.05, 10, 2)
pv = flows / (1 + 0.06 / 2) ** (2 * times) # present value of each payment
price = pv.sum()
macaulay = (times * pv).sum() / price # the average time, weighted by present value
print(round(price, 4), round(macaulay, 4)) # 925.6126 7.895 shorter than the 10-year maturityDe la duración al riesgo de precio
Dividir entre 1 + ytm / freq da la duración modificada, que tiene un significado directo: es la caída porcentual del precio para una subida de una unidad (100 puntos) del rendimiento; una duración modificada de 7,67 significa alrededor de -7,67 % por +1 punto porcentual, en primera aproximación: dP / P ≈ -D_mod * dy. Es la cifra que las mesas de riesgo citan como sensibilidad a tipos del bono. También explica por qué el bono largo del tema anterior cayó mucho más que el corto: su duración es mayor.
modified = macaulay / (1 + 0.06 / 2)
print(round(modified, 4)) # 7.6654 about -7.67% of price for +1 percentage point of yield
dy = 0.01
print(round(price * (1 - modified * dy), 2)) # 854.66 the first-order estimate after a rise to 7%
# the exact price at 7% is 857.88: duration alone underestimates itConvexidad: la curvatura
La relación precio-rendimiento es una curva, no una recta, y la duración es solo su tangente. La tangente subestima el precio para movimientos en ambos sentidos, porque la curva se dobla hacia arriba: un bono gana más cuando los rendimientos bajan de lo que pierde cuando suben la misma cantidad. La convexidad mide esa curvatura, a partir de la segunda derivada: sum(flujo * t * (t + 1/freq) / (1 + y/freq) ** (freq * t + 2)) / precio. Añadir el término 0.5 * convexidad * dy² a la estimación por duración da una previsión del precio mucho mejor, y importa más cuanto mayor es el movimiento.
convexity = (flows * times * (times + 1 / 2) / (1 + 0.06 / 2) ** (2 * times + 2)).sum() / price
print(round(convexity, 3)) # 71.785
for dy in (0.01, -0.01, 0.03):
exact = bond_price(1000, 0.05, 0.06 + dy, 10)
duration_only = price * (1 - modified * dy)
with_convexity = price * (1 - modified * dy + 0.5 * convexity * dy ** 2)
print(dy, round(exact, 2), round(duration_only, 2), round(with_convexity, 2))
# 0.01 857.88 854.66 857.99
# -0.01 1000.0 996.56 999.88
# 0.03 739.84 712.77 742.67 for a big move convexity matters most# Duration by maturity for a 5% bond at a 6% yield: it grows with maturity, but slower than one for one
for years in (1, 5, 10, 30):
t_, f_ = bond_cashflows(1000, 0.05, years, 2)
p_ = f_ / (1 + 0.06 / 2) ** (2 * t_)
print(years, round((t_ * p_).sum() / p_.sum(), 3))
# 1 0.988
# 5 4.472
# 10 7.895
# 30 14.77bond_price.- Duración de Macaulay =
sum(t * PV) / precio, el tiempo medio hasta el dinero; la de un cupón cero es su vencimiento. - Duración modificada = Macaulay /
(1 + ytm / freq): la caída relativa del precio por +1 de rendimiento,dP / P ≈ -D_mod * dy. - Estimación del precio =
precio * (1 - D_mod * dy + 0.5 * convexidad * dy²); el término de convexidad corrige la tangente, sobre todo en grandes movimientos.
A continuación: las curvas de tipos y la interpolación, porque distintos vencimientos tienen distintos tipos.
Lab: qué se califica
- Hallar el tiempo medio hasta recibir tu dinero con
macaulay_duration25 pts - Convertirla en sensibilidad del precio con
modified_duration25 pts - Capturar la curvatura con
convexity25 pts - Prever el nuevo precio sin revalorar con
price_estimate25 pts