债券静态利差 · ISDA(国际通用) 给定零息曲线与净价,反推 Z-Spread
输入零息曲线与债券净价,计算静态利差(Z-Spread)。
曲线日期与利率用逗号分隔、数量一致,且曲线最后日期不早于到期日。
曲线日期与利率用逗号分隔、数量一致,且曲线最后日期不早于到期日。
格式 yyyy-mm-dd
格式 yyyy-mm-dd
计算结果
点击「计算」查看输出
code
计算逻辑代码
以下为「ISDA国际通用」QuantLib 代码示例。
# 依赖版本:
# python==3.9
# QuantLib==1.43
from datetime import datetime
import traceback
import QuantLib as ql
# ============================================================
# 工具函数
# ============================================================
def _parse_date(s: str) -> ql.Date:
"""解析 'yyyy-mm-dd' 字符串为 ql.Date"""
d = datetime.strptime(s.strip(), "%Y-%m-%d")
return ql.Date(d.day, d.month, d.year)
def _map_period(period: int):
"""年付息次数 -> (ql.Period, ql.Frequency)"""
mapping = {
0: (None, ql.NoFrequency),
1: (ql.Period(ql.Annual), ql.Annual),
2: (ql.Period(ql.Semiannual), ql.Semiannual),
3: (ql.Period(4, ql.Months), ql.EveryFourthMonth),
4: (ql.Period(ql.Quarterly), ql.Quarterly),
6: (ql.Period(ql.Bimonthly), ql.Bimonthly),
12: (ql.Period(ql.Monthly), ql.Monthly),
}
if period not in mapping:
raise ValueError(f"不支持的 Period: {period}")
return mapping[period]
def _map_basis(basis: int):
"""日计数基准编号 -> ql.DayCounter"""
mapping = {
0: ql.ActualActual(ql.ActualActual.ISDA),
1: ql.Thirty360(ql.Thirty360.USA),
2: ql.Actual360(),
3: ql.Actual365Fixed(),
4: ql.Thirty360(ql.Thirty360.BondBasis),
5: ql.Thirty360(ql.Thirty360.ISDA),
6: ql.Thirty360(ql.Thirty360.European),
7: ql.Actual365Fixed(),
8: ql.ActualActual(ql.ActualActual.ISMA),
9: ql.Actual360(),
10: ql.Actual365Fixed(),
11: ql.Thirty360(ql.Thirty360.EurobondBasis),
12: ql.Actual365Fixed(),
13: ql.Business252(),
}
if basis not in mapping:
raise ValueError(f"暂不支持的 Basis: {basis}")
return mapping[basis]
# ============================================================
# 核心计算函数
# ============================================================
def qjquant_calu_bndspread(
curve_date_var: str, # 逗号分隔 yyyy-mm-dd
zero_rate_var: str, # 逗号分隔小数
price_var: float,
coupon_var: float,
settle_var: str,
maturity_var: str,
period_var: int = 2,
basis_var: int = 0,
):
try:
# ---- 解析曲线 ----
curve_dates_raw = [x.strip() for x in curve_date_var.split(",") if x.strip()]
zero_rates_raw = [float(x.strip()) for x in zero_rate_var.split(",") if x.strip()]
if len(curve_dates_raw) != len(zero_rates_raw):
return {"ok": False, "msg": "CurveDate 与 ZeroRate 数量必须一致"}
if len(curve_dates_raw) == 0:
return {"ok": False, "msg": "曲线至少需要一个点"}
settle_date = _parse_date(settle_var)
maturity_date = _parse_date(maturity_var)
if settle_date >= maturity_date:
return {"ok": False, "msg": "结算日必须早于到期日"}
if len(curve_dates_raw) == 1:
# 单点:视为平坦零息曲线,扩展为两点(覆盖到到期日之后)
d0 = _parse_date(curve_dates_raw[0])
r0 = zero_rates_raw[0]
curve_dates = [d0, maturity_date + ql.Period(1, ql.Days)]
zero_rates = [r0, r0]
else:
curve_dates = [_parse_date(s) for s in curve_dates_raw]
zero_rates = zero_rates_raw
# 曲线最后日期必须覆盖到期日(单点平坦曲线已自动覆盖)
if len(curve_dates_raw) >= 2 and max(curve_dates) < maturity_date:
return {"ok": False, "msg": "曲线最后日期必须不早于债券到期日"}
ql.Settings.instance().evaluationDate = settle_date
coupon_period, freq = _map_period(period_var)
day_counter = _map_basis(basis_var)
# 按日期排序,保证 ZeroCurve 输入严格递增
pairs = sorted(zip(curve_dates, zero_rates), key=lambda x: x[0])
curve_dates = [p[0] for p in pairs]
zero_rates = [p[1] for p in pairs]
# ---- 关键修正:曲线首日不能晚于结算日 ----
if curve_dates[0] > settle_date:
curve_dates.insert(0, settle_date)
zero_rates.insert(0, zero_rates[0])
# ---- 构建零息曲线,并开启外推 ----
curve = ql.ZeroCurve(
curve_dates,
zero_rates,
day_counter,
ql.NullCalendar(),
)
curve.enableExtrapolation()
# ---- 构建债券 ----
if period_var == 0:
bond = ql.ZeroCouponBond(
settlementDays=0,
calendar=ql.NullCalendar(),
faceAmount=100.0,
maturityDate=maturity_date,
paymentConvention=ql.Unadjusted, # 修正:不是 businessDayConvention
redemption=100.0,
issueDate=settle_date,
)
effective_freq = ql.Annual
else:
issue_date = maturity_date
while issue_date >= settle_date:
issue_date = issue_date - coupon_period
schedule = ql.Schedule(
issue_date, maturity_date, coupon_period,
ql.NullCalendar(), ql.Unadjusted, ql.Unadjusted,
ql.DateGeneration.Forward, False,
)
bond = ql.FixedRateBond(
settlementDays=0, faceAmount=100.0,
schedule=schedule, coupons=[coupon_var],
paymentDayCounter=day_counter,
)
effective_freq = freq if freq != ql.NoFrequency else ql.Annual
# ---- 计算 Z-Spread ----
# 修正:第 2 个参数必须是 BondPrice 对象,不是 float
bond_price = ql.BondPrice(float(price_var), ql.BondPrice.Clean)
z_spread = ql.BondFunctions.zSpread(
bond,
bond_price,
curve,
day_counter,
ql.Compounded,
effective_freq,
settle_date,
1.0e-12,
200,
)
return {
"ok": True,
"spread_bp": round(z_spread * 10000.0, 4),
}
except ValueError as ve:
traceback.print_exc()
return {"ok": False, "msg": f"参数错误: {ve}"}
except Exception as e:
traceback.print_exc()
return {"ok": False, "msg": f"未知错误: {e}"}
# ============================================================
# 示例:复制本文件后直接运行
# ============================================================
if __name__ == "__main__":
print("=" * 60)
print("示例 1:附息债(半年付息,实际/实际)")
print("=" * 60)
r1 = qjquant_calu_bndspread(
curve_date_var="2024-01-01,2024-07-01,2025-01-01,2026-01-01,2030-01-01",
zero_rate_var="0.030,0.032,0.034,0.036,0.040",
price_var=98.5,
coupon_var=0.04,
settle_var="2024-03-15",
maturity_var="2029-06-20",
period_var=2,
basis_var=0,
)
print(r1)
print()
print("=" * 60)
print("示例 2:零息债(period=0)")
print("=" * 60)
r2 = qjquant_calu_bndspread(
curve_date_var="2024-01-01,2024-07-01,2025-01-01,2026-01-01,2027-01-01",
zero_rate_var="0.030,0.032,0.034,0.036,0.038",
price_var=92.0,
coupon_var=0.0,
settle_var="2024-03-15",
maturity_var="2026-06-20",
period_var=0,
basis_var=0,
)
print(r2)
print()
print("=" * 60)
print("示例 3:单点平坦曲线(自动扩展并外推)")
print("=" * 60)
r3 = qjquant_calu_bndspread(
curve_date_var="2024-01-01",
zero_rate_var="0.035",
price_var=99.0,
coupon_var=0.04,
settle_var="2024-03-15",
maturity_var="2029-06-20",
period_var=2,
basis_var=0,
)
print(r3)