4. 到期收益率求解:牛顿法与二分法实现
任务说明
给定债券的现金流与当前市场价格,反解到期收益率(YTM)。本案例用两种方法实现:二分法(稳健但慢)与牛顿法(快但依赖导数)。最后比较两者的迭代次数与收敛速度。
环境与依赖
仅需 Python 3,无第三方依赖。债券定价公式:
P = Σ C/(1+y)^t + F/(1+y)^n
核心实现
def bond_price(face, coupon_rate, years, ytm, freq=1):
"""按年付息债券定价。freq=1 表示每年付息一次。"""
n = int(years * freq)
c = face * coupon_rate / freq
r = ytm / freq
pv = sum(c / (1 + r) ** t for t in range(1, n + 1))
pv += face / (1 + r) ** n
return pv
def ytm_bisection(price, face, coupon_rate, years, freq=1,
lo=1e-6, hi=1.0, tol=1e-10, max_iter=200):
"""二分法:价格随 ytm 单调递减,用区间收缩逼近。"""
for i in range(1, max_iter + 1):
mid = (lo + hi) / 2
p = bond_price(face, coupon_rate, years, mid, freq)
if abs(p - price) < tol:
return mid, i
if p > price: # 价格偏高 → ytm 偏低 → 抬高下界
lo = mid
else:
hi = mid
return (lo + hi) / 2, max_iter
def ytm_newton(price, face, coupon_rate, years, freq=1,
y0=0.05, tol=1e-10, max_iter=100):
"""牛顿法:f(y)=P(y)-price,f'(y) 用解析导数。"""
n = int(years * freq)
c = face * coupon_rate / freq
y = y0
for i in range(1, max_iter + 1):
r = y / freq
p = bond_price(face, coupon_rate, years, y, freq)
# dP/dy = Σ -t*C/(1+r)^(t+1)/freq - n*F/(1+r)^(n+1)/freq
dp = sum(-t * c / (1 + r) ** (t + 1) for t in range(1, n + 1))
dp += -n * face / (1 + r) ** (n + 1)
dp /= freq
diff = p - price
if abs(diff) < tol:
return y, i
y = y - diff / dp
return y, max_iter
if __name__ == "__main__":
face, coupon_rate, years = 1000.0, 0.05, 10
true_y = 0.06
market_price = bond_price(face, coupon_rate, years, true_y)
print(f"市场价 = {market_price:.6f}")
yb, ib = ytm_bisection(market_price, face, coupon_rate, years)
yn, in_ = ytm_newton(market_price, face, coupon_rate, years)
print(f"二分法: ytm={yb:.10f}, 迭代={ib}")
print(f"牛顿法: ytm={yn:.10f}, 迭代={in_}")
运行与验证
直接执行 python ytm.py。判定标准:
- 两种方法求出的 ytm 都应接近 0.06(误差 < 1e-8);
- 牛顿法迭代次数应明显少于二分法(通常 4~6 次 vs 30+ 次);
- 若把
true_y改成其他值(如 0.03、0.10),结果应随之改变且两法一致。
收敛过程示意
关键点
- 二分法要求价格关于 ytm 单调递减,因此区间收缩方向由
p > price决定。 - 牛顿法需要解析导数;若导数接近 0 会发散,实际工程中常加步长限制或回退到二分。
- 两种方法结果一致即可交叉验证实现正确性。