Un bono es un flujo de promesas
Un bono es un préstamo dividido en piezas negociables. El emisor promete un cupón fijo cada periodo y el valor nominal (par) al vencimiento. Un bono a 10 años con valor nominal 1000 y un cupón del 5 % pagado dos veces al año promete 25 cada seis meses y 1000 más en el último pago. Su precio es lo que valen hoy esas promesas: el valor actual de los flujos, descontados al rendimiento que el mercado exige ahora para ese riesgo y ese vencimiento.
import numpy as np
def bond_cashflows(face, coupon_rate, years, freq):
n = int(round(years * freq)) # number of payments
times = np.arange(1, n + 1) / freq # in years: 0.5, 1.0, 1.5, ...
flows = np.full(n, face * coupon_rate / freq) # a coupon each time
flows[-1] += face # and the face value with the last one
return times, flows
times, flows = bond_cashflows(1000, 0.05, 2, 2) # a 2-year bond, to keep the output short
print(times.tolist()) # [0.5, 1.0, 1.5, 2.0]
print(flows.tolist()) # [25.0, 25.0, 25.0, 1025.0]Precio y rendimiento se mueven en sentido contrario
Descuenta cada flujo a (1 + ytm / freq) ** (freq * time) y suma. Cuando el rendimiento iguala la tasa del cupón, el bono vale exactamente su valor nominal (cotiza a la par). Si los rendimientos de mercado suben por encima del cupón, los bonos nuevos pagan más que este, así que su precio cae por debajo de la par (un descuento); si bajan, cotiza con una prima. Es el mismo mecanismo del tema anterior, ahora con fechas de pago reales. El rendimiento corriente, cupón anual entre precio, es una medida rápida pero incompleta porque ignora la ganancia o pérdida al vencimiento.
def bond_price(face, coupon_rate, ytm, years, freq=2):
times, flows = bond_cashflows(face, coupon_rate, years, freq)
return (flows / (1 + ytm / freq) ** (freq * times)).sum()
for ytm in (0.03, 0.05, 0.06, 0.08):
print(ytm, round(bond_price(1000, 0.05, ytm, 10), 2))
# 0.03 1171.69 yield below the coupon: premium
# 0.05 1000.0 yield = coupon: par
# 0.06 925.61 yield above the coupon: discount
# 0.08 796.15
print(round(50 / bond_price(1000, 0.05, 0.06, 10), 5)) # 0.05402 current yield: 5.40%, not the 6% yield to maturityLos bonos largos reaccionan más
El cambio relativo del precio para un movimiento dado del rendimiento depende del vencimiento. Los pagos lejanos se descuentan con una potencia alta, así que son muy sensibles a la tasa. Un bono a 1 año pierde cerca del 1 % cuando los rendimientos suben un punto, uno a 10 años cerca del 7 % y uno a 30 años casi el 14 %. Por eso los inversores en bonos hablan de riesgo de tipos de interés y por eso los siguientes temas construyen medidas (rendimiento al vencimiento, duración) para cuantificarlo.
for years in (1, 5, 10, 30):
change = bond_price(1000, 0.05, 0.06, years) / 1000 - 1 # the coupon is 5%, the yield rises to 6%
print(years, round(change, 4))
# 1 -0.0096
# 5 -0.0427
# 10 -0.0744
# 30 -0.1384# The same 6% yield with annual, semiannual and monthly coupons: the quoting convention matters a little
for freq in (1, 2, 12):
print(freq, round(bond_price(1000, 0.05, 0.06, 10, freq), 2))
# 1 926.4
# 2 925.61
# 12 924.94freq) junto a un rendimiento. Los bonos reales también tienen cupón corrido entre fechas de cupón y convenciones de cómputo de días, que este lab ignora al valorar en fechas de cupón.- Precio = suma de
flujo / (1 + ytm / freq) ** (freq * tiempo); cupones cada periodo, el nominal con el último. - Rendimiento = cupón: precio = par; rendimiento por encima del cupón: descuento; por debajo: prima. Precio y rendimiento se mueven en sentido contrario.
- A mayor vencimiento, mayor cambio de precio para el mismo movimiento de rendimiento: ese riesgo de tipos es lo que mide la duración después.
A continuación: el rendimiento al vencimiento, en el sentido contrario: del precio de mercado al rendimiento.
Lab: qué se califica
- Listar los pagos de un bono con
bond_cashflows25 pts - Descontar los pagos hasta un precio con
bond_price25 pts - Comparar el cupón con el precio con
current_yield25 pts - Ver el efecto de un movimiento de tipos con
price_change25 pts