QuantLib · Conventions audit
QuantLib 结果为什么不一样?金融约定逐项审计
同一只债券、同一组现金流,换一个 DayCounter、换一种日历、把 settlementDays 从 T+2 改成 T+90,价格和收益率都会变。本页把 evaluationDate、Calendar、BusinessDayConvention、DayCounter、Schedule、settlementDays、clean/dirty/accrued 七项约定逐个替换,用 QuantLib 1.43 的实测输出告诉你每一项到底改动了多少。
四项先定、价格后算:估值日决定「算哪一天」,日历决定「现金流落在哪天」,计息约定决定「每期算多少天」,价格口径决定「报出来的是哪个价」。示意图非实盘界面。
净价、全价、应计利息为什么对不上?
债券报价习惯用净价(clean price),但真正付钱的是全价(dirty price)。QuantLib 把三者拆成三个方法,先看它们在同一只债券上的实测值,再看为什么核对时常出错。
| 量 | 本机实测值(面值 100) | 口径说明 | 核对时要注意 |
|---|---|---|---|
| 净价 cleanPrice() | 99.75408467 | 不含应计利息的报价口径 | 交易日习惯报这个价,但它不是结算金额 |
| 应计利息 accruedAmount() | 0.01639344 | 上一付息日到结算日之间应计的部分 | 随结算日每天变化,T+0 时为 0 |
| 全价 dirtyPrice() | 99.77047812 | 净价 + 应计利息,等于结算金额口径 | 现金流折现得到的是这个价 |
| 净价 + 应计 | 99.77047812 | 与全价完全相同(差 < 1e-10) | 对不上时先查 settlementDays 与 accrued 的计息约定 |
| NPV() | 99.75412364 | 引擎口径的净现值 | 与 dirtyPrice 差一个应计利息量级,别混用 |
| 结算日 settlementDate | January 17th, 2024 | T+2 由 settlementDays=2 决定 | 它同时决定应计天数与折现起点 |
evaluationDate 与 settlementDays,再看 accruedAmount() 用的是哪条应计约定,最后才比较价格。本页实测中,改应计约定会让净价在 99.7535 ~ 99.9942 之间移动,收益率在 3.0004% ~ 3.0456% 之间移动——这两组数就是「价格对不上」最常见的来源。DayCounter 计息约定逐项实测:同样 182 天,能算出 0.4959 到 0.5056
同一个区间(2024-01-15 → 2024-07-15,实际 182 天),14 种 DayCounter 给出的年化分数并不相同。下表全部是本机实测输出,名字列使用 QuantLib 自己返回的名称。
| DayCounter(构造方式) | dayCount | yearFraction | QuantLib 返回的名字 | 典型用途 / 注意点 |
|---|---|---|---|---|
| ql.Actual360() | 182 | 0.5055555556 | Actual/360 | 货币市场基准;比 A/365 多算约 1.4% |
| ql.Actual365Fixed() | 182 | 0.4986301370 | Actual/365 (Fixed) | 固收常用近似;闰年不做特殊处理 |
| ql.Actual365Fixed(NoLeap) | 181 | 0.4958904110 | Actual/365 (No Leap) | 跳过 2 月 29 日,天数会比实际少 1 天 |
| ql.Actual366() | 182 | 0.4972677596 | Actual/366 | 闰年口径;与 A/A ISDA 在本区间相同 |
| ql.ActualActual(ISDA) | 182 | 0.4972677596 | Actual/Actual (ISDA) | 跨国债券与互换最常用的口径 |
| ql.ActualActual(Bond) | 182 | 0.5000000000 | Actual/Actual (ISMA) | 按期数平分;半年期正好 0.5 |
| ql.ActualActual(Historical) | 182 | 0.4972677596 | Actual/Actual (ISDA) | 本区间与 ISDA 同值,跨世纪区间才会分叉 |
| ql.ActualActual(AFB) | 182 | 0.4972677596 | Actual/Actual (AFB) | 南非/欧洲旧口径 |
| ql.Thirty360(BondBasis) | 180 | 0.5000000000 | 30/360 (Bond Basis) | 每月按 30 天算,日历年化常用 |
| ql.Thirty360(European) | 180 | 0.5000000000 | 30E/360 (Eurobond Basis) | 欧债口径 |
| ql.Thirty360(ISDA) | 180 | 0.5000000000 | 30E/360 (ISDA) | German 变体与本口径同实现 |
| ql.Thirty360(USA) | 180 | 0.5000000000 | 30/360 (US) | 美国 NASD 口径 |
| ql.SimpleDayCounter() | 180 | 0.5000000000 | Simple | 教学用,实盘少用 |
| ql.Business252() | 125 | 0.4960317460 | Business/252(Brazil) | 按营业日计数(这里是巴西日历) |
| 边界区间 | 实际天数 | Actual/365 (Fixed) | Actual/365 (NoLeap) | Actual/Actual (ISDA) | Actual/Actual (Bond) | 30/360 (Bond Basis) | 30E/360 (ISDA) |
|---|---|---|---|---|---|---|---|
| 2023-08-15 → 2024-08-15 | 366 | 1.00273973 | 1.00000000 | 1.00104050 | 1.00000000 | 1.00000000 | 1.00000000 |
| 2024-02-15 → 2025-02-15 | 366 | 1.00273973 | 1.00000000 | 1.00033685 | 1.00000000 | 1.00000000 | 1.00000000 |
| 2024-01-31 → 2024-02-29 | 29 | 0.07945205 | 0.07671233 | 0.07923497 | 0.08333333 | 0.08055556 | 0.08333333 |
| 2024-01-31 → 2024-03-31 | 60 | 0.16438356 | 0.16164384 | 0.16393443 | 0.16666667 | 0.16666667 | 0.16666667 |
换计息约定,债券价格和收益率各变多少?
把同一只债券(票息 3%、年付、TARGET、平曲线 3%)的应计计息约定依次替换,其余参数完全不动,净价、票息金额、收益率的变化如下——这是「价格对不上」的第二大来源。
| 应计 DayCounter | 应计天数 | 首期票息金额 | 应计利息 | 净价 | 全价 | 由净价反解的收益率 |
|---|---|---|---|---|---|---|
| Actual/Actual (ISDA) | 366 | 3.01643836 | 0.01639344 | 99.75408467 | 99.77047812 | 3.0454534% |
| Actual/365 (Fixed) | 366 | 3.01643836 | 0.01643836 | 99.76879697 | 99.78523533 | 3.0426513% |
| Actual/360 | 366 | 3.05833333 | 0.01666667 | 99.99419065 | 100.01085731 | 3.0003516% |
| 30/360 (Bond Basis) | 360 | 3.01666667 | 0.01666667 | 99.75352162 | 99.77018829 | 3.0455591% |
| 30E/360 (Eurobond) | 360 | 3.01666667 | 0.01666667 | 99.75352162 | 99.77018829 | 3.0455591% |
日历与营业日调整:同一天,四个日历给出四种答案
日历决定两件事:某一天是否可交割,以及落在非营业日的日期往哪调。下表是本机实测的两种检查——同一批日期在不同日历下是否营业,以及非营业日调整后的落点。
| 日期 | 星期 | TARGET | China.SSE | China.IB | US.NYSE | 说明 |
|---|---|---|---|---|---|---|
| 2024-01-01 | 一 | 休 | 休 | 休 | 休 | 四个日历都是元旦 |
| 2024-02-09 | 五 | 营业 | 休 | 营业 | 营业 | 除夕:SSE 休市,银行间(IB)仍营业 |
| 2024-02-12 | 一 | 营业 | 休 | 休 | 营业 | 春节假期内 |
| 2024-04-04 | 四 | 营业 | 休 | 休 | 营业 | 清明 |
| 2024-05-01 | 三 | 休 | 休 | 休 | 营业 | TARGET 也休(劳动节) |
| 2024-06-10 | 一 | 营业 | 休 | 休 | 营业 | 端午 |
| 2024-10-01 | 二 | 营业 | 休 | 休 | 营业 | 国庆:TARGET 是营业日,SSE 休市 |
| 2024-12-25 | 三 | 休 | 营业 | 营业 | 休 | 圣诞:中国市场正常交易 |
| 原始日期 | 星期 | China.SSE + Following | China.SSE + ModifiedFollowing | China.SSE + Preceding | TARGET + Following | 注意点 |
|---|---|---|---|---|---|---|
| 2024-10-01 | 二 | October 8th, 2024 | October 8th, 2024 | September 30th, 2024 | October 1st, 2024 | 国庆连休 7 天,向前/向后调整差 8 天 |
| 2024-10-05 | 六 | October 8th, 2024 | October 8th, 2024 | September 30th, 2024 | October 7th, 2024 | 调休后的工作日不等于自然营业日 |
| 2024-02-11 | 日 | February 19th, 2024 | February 19th, 2024 | February 8th, 2024 | February 12th, 2024 | 春节调整跨度 8~11 天 |
| 2024-09-15 | 日 | September 18th, 2024 | September 18th, 2024 | September 13th, 2024 | September 16th, 2024 | 中秋 |
| 2024-01-31 | 三 | January 31st, 2024 | January 31st, 2024 | January 31st, 2024 | January 31st, 2024 | 本来就是营业日:三种约定结果相同 |
ql.China(ql.China.SSE) 的名字是「Shanghai stock exchange」,ql.China(ql.China.IB) 是「China inter bank market」——两者假期不同,选错会错开日期。Schedule 生成规则实测:起点、调整约定、周期谁先谁后
Schedule 把「起止日期 + 周期 + 日历 + 调整约定」变成一串现金流日期。下面四组是本机实测的日期串,用来判断你的现金流日期为什么和报价表不一样。
| 实验条件 | 实测生成的日期串 | 该关注的约定 |
|---|---|---|
| 起始 2024-10-01(中国国庆,SSE 休市 / TARGET 营业)+ 半年付 + SSE + ModifiedFollowing | Oct 8, 2024 / Apr 1, 2025 / Oct 9, 2025 / Apr 1, 2026 / Oct 8, 2026 | 起点在假期内会被整体顺延,后续付息日跟着平移 |
| 同上,仅把日历换成 TARGET | Oct 1, 2024 / Apr 1, 2025 / Oct 1, 2025 / Apr 1, 2026 / Oct 1, 2026 | TARGET 不知道中国假期,日期看起来「干净」但与市场不符 |
| 付息日落在周六 2024-06-15 + TARGET + Following | Jun 17, 2024 / Dec 16, 2024 / Jun 16, 2025 / Dec 15, 2025 / … | 每个落在周末的付息日都向后顺延 |
| 同上一组,改为 Unadjusted | Jun 15, 2024 / Dec 15, 2024 / Jun 15, 2025 / Dec 15, 2025 / … | 日期保持日历上的名义日,需自行处理非营业日 |
| 春节起点 2024-02-11 + SSE + ModifiedFollowing + 半年付 | Feb 19, 2024 / Aug 12, 2024 / Feb 11, 2025 / Aug 11, 2025 / … | 首期实际长度 ≠ 半年,会影响首期利息计算 |
| 周期 1 年 > 区间 6 个月(Forward 生成) | Jan 15, 2024 / Jun 17, 2024 | QuantLib 不报错,只按端点生成;周期设置错误不会提示你 |
| 2024-01-31 起半年付,EndOfMonth=False | Jan 31, 2024 / Jul 31, 2024 / Jan 31, 2025 / Jul 31, 2025 | 本组日期恰好都落在月末,与 EndOfMonth=True 输出相同 |
| 2024-01-31 起半年付,EndOfMonth=True | Jan 31, 2024 / Jul 31, 2024 / Jan 31, 2025 / Jul 31, 2025 | 月末规则只在月份天数不同时才显出差异(如 8/31 → 2/28) |
settlementDays 与 evaluationDate:最容易被忽略的两个日期
settlementDays 决定结算日,结算日又决定应计利息与折现起点。下面这张表把同一只债券的 settlementDays 从 T+0 拉到 T+90,可以看到价格与风险指标如何被「日期」而不是「利率」推动。
| settlementDays | 结算日 | 应计利息 | 净价 | 全价 | 由净价反解的收益率 | 修正久期 | BPV |
|---|---|---|---|---|---|---|---|
| 0(T+0) | 2024-01-15 | 0.00000000 | 99.75412364 | 99.75412364 | 3.0454534% | 5.414302 | -0.05400972 |
| 1 | 2024-01-16 | 0.00819672 | 99.75410382 | 99.76230054 | 3.0454534% | 5.411651 | -0.05398769 |
| 2(默认) | 2024-01-17 | 0.01639344 | 99.75408467 | 99.77047812 | 3.0454534% | 5.408999 | -0.05396566 |
| 3 | 2024-01-18 | 0.02459016 | 99.75406620 | 99.77865636 | 3.0454534% | 5.406348 | -0.05394363 |
| 5 | 2024-01-22 | 0.05737705 | 99.75399899 | 99.81137604 | 3.0454534% | 5.395742 | -0.05385546 |
| 10 | 2024-01-29 | 0.11475410 | 99.75390719 | 99.86866129 | 3.0454534% | 5.377181 | -0.05370101 |
| 30 | 2024-02-26 | 0.34426230 | 99.75386899 | 100.09813128 | 3.0454534% | 5.302940 | -0.05308126 |
| 90 | 2024-05-23 | 1.05737705 | 99.75711999 | 100.81449704 | 3.0454534% | 5.072260 | -0.05113557 |
拿到一条「对不上」的价格,按这 6 步核对
顺序很重要:先定点(日期),再点口径(日历与计息),最后才怀疑模型。以下每一步都给出本页实测过的判据。
1. 先确认估值日与结算日
打印ql.Settings.instance().evaluationDate 与 bond.settlementDate()。若结算日跨过付息日,应计利息会归零重算,看起来就像价格突然跳了一档。2. 再确认应计利息的计息约定
本页实测:换应计约定会让净价移动 0.24 元、收益率移动 4.5bp。核对bond.dayCounter().name() 与对方报价单是否一致。3. 分清报的是净价还是全价
现金结算用全价,报价习惯用净价。实测中两者差 0.0164(T+2),T+90 时差 1.0574,差额随时间线性增长。4. 换一种方式复算收益率
用ql.BondFunctions.bondYield(...) 与 ql.BondFunctions.cleanPrice(...) 互相反解:本机实测二者在同组参数下互为逆运算,如果对不上,说明有一侧用的不是同一组约定。5. 检查日历是否属于该市场
中国市场用ql.China(ql.China.SSE) 或 ql.China(ql.China.IB),不要用 TARGET 代替:实测同一日期串会错开 1~8 天。6. 最后才怀疑模型与曲线
曲线折现与单一收益率并不等价:本站实测同一只债券在递增零息曲线下净价 97.6912,而「反解出的单一收益率」只有 3.4326%——这个数字在平曲线下无法复现同一价格。自己复现本页全部结论:一条命令 + 固定参数
下面这段脚本就是本站生成上表数值的写法(去掉打印格式后的核心部分),把它存成文件、在装好 QuantLib 的环境里运行,应当得到同样的数字。约定不同(比如换了日历或计息约定)就会得到不同的数字,这正是本页要说明的事。
| 替换项 | 改哪里 | 本页实测的后果 |
|---|---|---|
| 计息约定 | 第 ② 行的 dc | 净价与收益率同时变化(净价区间 99.7535 ~ 99.9942) |
| 日历 | 第 ② 行的 cal(换 SSE/IB) | 现金流日期错开 1~8 天;本组 1/15 日期恰好不变 |
| 结算天数 | 第 ⑤ 行的 2 | 应计利息与全价变化,净价几乎不变 |
| 估值日 | 第 ① 行的 today | 整条现金流的时间坐标平移 |
| 折现曲线 | 第 ⑥ 行的 FlatForward(today, 0.03, dc) | 价格随曲线水平移动;斜率变化时单一收益率无法对应 |
常见问题
为什么同一只债券,我的价格和别人差 0.2 元?
最常见的是应计计息约定不同。本站实测:应计约定从 Actual/Actual (ISDA) 换成 Actual/360,净价从 99.75408467 变成 99.99419065(差 0.24),收益率从 3.0455% 掉到 3.0004%。先对齐 dayCounter,再对齐 settlementDays,最后才怀疑曲线;以官方文档与报价单口径为准。QuantLib 与 EasyClaw 无已证实集成,这类口径核对需要你自己掌握。
clean price 和 dirty price 哪个才是要付的钱?
现金结算对应全价(dirty price = 净价 + 应计利息)。本站实测二者关系为 99.75408467 + 0.01639344 = 99.77047812,完全相等。报价习惯报净价,所以对账时必须先问清「报的是哪个价」。具体会计口径以你所在市场的结算规则为准。
Actual/365 Fixed 和 Actual/Actual (ISDA) 什么时候会分叉?
跨闰年或跨月短区间才明显。实测:2023-08-15 → 2024-08-15 这 366 天,A/365 Fixed 给 1.00273973、A/A ISDA 给 1.00104050;而 2024-01-31 → 2024-02-29 只有 29 天时,两者分别是 0.07945205 与 0.07923497,Actual/Actual (Bond) 则是 0.08333333。区间越短、月末越特殊,差异占比越大。以官方实现与市场惯例为准。
QuantLib 里有没有中国市场的计息约定?
没有。本机实测 ql.ActualActual 的枚举是 ISDA / Bond / Historical / AFB / Euro / Actual365 / ISMA,不含 China 口径;日历有 ql.China(ql.China.SSE) 与 ql.China(ql.China.IB)。也就是说:日历可以直接用,计息约定要你自己按市场规则选定,别默认任意一个。以官方文档为准。
settlementDays 应该填几?
按你所处市场的结算规则来,不要沿用示例里的 2。本站实测 T+0 到 T+90 的应计利息从 0 变到 1.05737705,修正久期从 5.414302 变到 5.072260——日期设定的影响不小于利率变动。QuantLib 不校验这个值是否合理,填错不会报错,只会让价格悄悄偏离。以你的结算制度为准。
为什么日历换掉之后,这次实验的价格没变?
因为本组现金流日期(每年 1/15、7/15)在 TARGET 与 SSE 下都是营业日,所以日期串完全一致,价格自然相同。本机实测的差异只出现在假期边界上(如 10/1、2/11 起算,日期会错开 1~8 天)。这也是一个提醒:没有报错、价格也没变,不代表日历选对了,要针对性构造边界日期去验证。以官方日历数据为准。
evaluationDate 可以和 today 不一样吗?
可以,而且应该显式设置。ql.Date.todaysDate() 在本机返回运行当天(2026-10-09),如果不设置,所有定价都以运行日为基准,回测或对账时就会得到「每天都在变」的结果。但注意边界:把 evaluationDate 设到 2199-12-31 会立即报错(内部还要加结算天数)。以官方文档为准。
本页的数值能不能直接当成市场数据用?
不能。本页全部是受控实验:票息 3%、曲线固定 3%、标的为虚构债券,目的是让口径差异可比较、可复现。真实债券的票息、付息频率、日历与应计规则都要按发行文件取值;结果仅供研究与对账方法参考,不构成投资建议。