债券总回报

债券总回报 · ISDA(国际通用) 到期总回报(BondEquiv / EffectiveRate)
固定息票债券持有至到期的总回报,票息按再投资率复利。
提交格式 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_bndtotalreturn(
    price_var: float,
    coupon_var: float,
    settle_var: str,
    maturity_var: str,
    reinvest_var: float,
    period_var: int = 2,
    basis_var: int = 0,
):
    try:
        if price_var <= 0:
            return {"ok": False, "msg": "净价必须为正"}
        if coupon_var < 0:
            return {"ok": False, "msg": "息票率不能为负"}
        if reinvest_var < 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": "结算日必须早于到期日"}

        coupon_period, freq = _map_period(period_var)
        day_counter = _map_basis(basis_var)

        # ---------- 构造付息日序列(与 bndprice 同口径) ----------
        if period_var == 0:
            coupon_dates = [maturity_date]
            coupon_amount = 0.0
        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,
            )
            coupon_dates = [d for d in schedule if settle_date < d <= maturity_date]
            coupon_amount = 100.0 * coupon_var / period_var

        # ---------- 票息再投资终值 ----------
        future_value_coupons = 0.0
        for d in coupon_dates:
            if d == maturity_date:
                # 到期日那一期票息不再投资(再投资期数为 0)
                future_value_coupons += coupon_amount
            else:
                T = day_counter.yearFraction(d, maturity_date)
                future_value_coupons += coupon_amount * ((1.0 + reinvest_var) ** T)

        total_fv = 100.0 + future_value_coupons   # 到期本金 + 票息终值

        # ---------- 持有期 ----------
        holding_period = day_counter.yearFraction(settle_date, maturity_date)
        if holding_period <= 0:
            return {"ok": False, "msg": "持有期必须为正"}

        # MATLAB: 投入 = 净价 Price(不含应计利息)
        compound_factor = total_fv / price_var

        # ---------- BondEquiv:按 Period 频率复利 ----------
        if period_var == 0 or freq == ql.NoFrequency:
            bond_equiv = compound_factor ** (1.0 / holding_period) - 1.0
        else:
            rate = ql.InterestRate(0.0, day_counter, ql.Compounded, freq)
            bond_equiv = rate.impliedRate(
                compound_factor, day_counter, ql.Compounded, freq,
                settle_date, maturity_date
            ).rate()

        # ---------- EffectiveRate:年复利 ----------
        rate_eff = ql.InterestRate(0.0, day_counter, ql.Compounded, ql.Annual)
        effective_rate = rate_eff.impliedRate(
            compound_factor, day_counter, ql.Compounded, ql.Annual,
            settle_date, maturity_date
        ).rate()

        return {
            "ok": True,
            "bond_equiv": round(bond_equiv, 4),
            "effective_rate": round(effective_rate, 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_bndtotalreturn(
        price_var=101.0,
        coupon_var=0.05,
        settle_var="2011-11-15",
        maturity_var="2031-11-15",
        reinvest_var=0.04,
        period_var=2,
        basis_var=0,
    )
    print(r1)

    print()
    print("=" * 60)
    print("示例 2:附息债(年付息,30/360 USA)")
    print("=" * 60)
    r2 = qjquant_calu_bndtotalreturn(
        price_var=102.0,
        coupon_var=0.045,
        settle_var="2024-03-15",
        maturity_var="2029-06-20",
        reinvest_var=0.035,
        period_var=1,
        basis_var=1,
    )
    print(r2)

    print()
    print("=" * 60)
    print("示例 3:零息债(period=0)")
    print("=" * 60)
    r3 = qjquant_calu_bndtotalreturn(
        price_var=92.0,
        coupon_var=0.0,
        settle_var="2024-03-15",
        maturity_var="2026-06-20",
        reinvest_var=0.0,
        period_var=0,
        basis_var=0,
    )
    print(r3)

    print()
    print("=" * 60)
    print("示例 4:附息债(季付,实际/365 Fixed)")
    print("=" * 60)
    r4 = qjquant_calu_bndtotalreturn(
        price_var=98.5,
        coupon_var=0.04,
        settle_var="2024-03-15",
        maturity_var="2029-06-20",
        reinvest_var=0.03,
        period_var=4,
        basis_var=3,
    )
    print(r4)