折现因子与即期利率:从市场报价中提取折现因子,构建即期利率曲线
好,咱们今天聊点实在的。折现因子和即期利率,这俩东西是利率衍生品定价的根基。你想想看,不管你是做IRS(利率互换)还是做债券估值,最后都得回到一个问题上:未来的1块钱,今天值多少钱?
我个人习惯把折现因子叫做「时间的价格」。市场不会直接告诉你这个价格,它只会给你一堆乱七八糟的报价——存款利率、期货价格、互换利率。我们的工作,就是从这些报价里把折现因子「挖」出来。
2.1 折现因子:最朴素的金融直觉
先别急着上公式。折现因子的逻辑其实特别简单:
- 如果今天给你100块,你肯定要。但如果一年后才给你100块,你就不太乐意了——因为这一年里你本来可以拿这100块去赚利息。
- 所以,未来的100块,在今天看来应该打个折。这个折扣率,就是折现因子。
数学上,我们记 \( D(t) \) 为从今天到未来某个时间点 \( t \) 的折现因子。那么:
D(t) = 1 / (1 + r * t) // 单利
D(t) = 1 / (1 + r)^t // 复利
D(t) = exp(-r * t) // 连续复利
嗯,这里要注意:不同的市场产品用不同的计息方式。货币市场通常用单利(ACT/360),债券市场用复利,而我们在做衍生品定价时,更习惯用连续复利——因为数学上方便,求导求积分都顺手。
2.2 从市场报价中提取折现因子
市场不会直接给你 D(t),它只会给你各种利率产品的价格。我们需要反推。我把它分成三步走:
- 收集市场数据:存款利率、FRA(远期利率协议)价格、利率期货、互换利率。
- 确定每个产品的现金流:每个产品在未来哪些时间点有现金流?
- 用「无套利」原则反推:产品的市场价格 = 所有未来现金流的折现值之和。
举个例子。假设市场上有一个3个月的存款,年化利率是2.5%(单利,ACT/360)。那么:
存款本金:100万
3个月后本息和:100万 * (1 + 0.025 * 90/360) = 100.625万
折现因子 D(90天) = 100 / 100.625 = 0.993788
你看,就这么简单。一个存款报价,就能算出一个折现因子。
2.3 即期利率:折现因子的另一种表达
折现因子和即期利率,其实是同一个东西的两种说法。即期利率 \( r(t) \) 定义为:
D(t) = exp(-r(t) * t)
=> r(t) = -ln(D(t)) / t
为什么要有即期利率?因为折现因子太小了,比如 D(30年) 可能只有0.2左右,看起来不直观。而即期利率告诉你:如果你从现在开始投资到 t 时刻,年化收益率是多少。这个数字更符合人的直觉。
我个人习惯在构建曲线时,先用折现因子做中间计算,最后再转换成即期利率展示给交易员看。因为交易员更习惯看利率,而不是看0.9几的小数。
2.4 构建即期利率曲线:从离散到连续
市场只能给你几个离散时间点的折现因子,比如3个月、6个月、1年、2年、5年、10年。但定价时需要任意时间点的折现因子,怎么办?
这就涉及到插值和拟合了。常用的方法有:
- 线性插值:在折现因子或即期利率上做线性插值。简单,但曲线不够光滑。
- 三次样条插值:保证曲线二阶连续可导。我个人比较喜欢这种方法,曲线看起来舒服。
- Nelson-Siegel模型:用一个参数化函数拟合整条曲线。适合做预测,但拟合精度一般。
- Bootstrap方法:从短端到长端,逐段推导。这是最经典的方法,尤其适合处理互换曲线。
下面我用Python演示一个最简单的Bootstrap过程:
import numpy as np
# 假设市场报价:存款利率和互换利率
maturities = [0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 10.0] # 年
rates = [0.025, 0.028, 0.030, 0.032, 0.033, 0.035, 0.036] # 连续复利
# 计算折现因子
discount_factors = np.exp(-np.array(rates) * np.array(maturities))
# 线性插值函数(在折现因子上插值)
def discount_factor(t):
if t <= maturities[0]:
return discount_factors[0]
if t >= maturities[-1]:
return discount_factors[-1]
# 找到插值区间
idx = np.searchsorted(maturities, t) - 1
t0, t1 = maturities[idx], maturities[idx+1]
d0, d1 = discount_factors[idx], discount_factors[idx+1]
# 线性插值
return d0 + (d1 - d0) * (t - t0) / (t1 - t0)
# 测试:计算2.5年的折现因子
print(f"D(2.5) = {discount_factor(2.5):.6f}")
# 转换成即期利率
r_2_5 = -np.log(discount_factor(2.5)) / 2.5
print(f"r(2.5) = {r_2_5:.4%}")
2.5 知识体系结构图
下面这张图总结了从市场报价到即期利率曲线的完整流程:
2.6 实战中的几个坑
做了这么多年曲线构建,我踩过的坑可以写本书了。挑几个最典型的说说:
- 期限错配:存款是3个月,互换是3个月,但存款的计息基准是ACT/360,互换是30/360。如果不调整,3个月的实际天数不一样,折现因子就对不上。
- 流动性问题:有些期限的市场报价很少,比如7年期互换。这时候强行插值会引入误差。我一般会参考相邻期限的报价,或者用模型平滑处理。
- 负利率环境:2015年以后,欧洲和日本出现了负利率。这时候折现因子 D(t) > 1,即期利率 r(t) < 0。很多老代码直接崩溃了,因为 log(负数) 会报错。嗯,这个坑我帮你们踩过了。