Repository navigation
Add Perpetual Bond product type and associated yield and risk measures (#7) - #427
Merged
Merged
Conversation
domokane#7) - Implement BondPerpetual class in financepy/products/bonds/bond_perpetual.py: - Clean and dirty pricing from yield to maturity (UK_DMO, US_STREET, US_TREASURY conventions) using infinite geometric annuity valuation. - Robust yield to maturity inversion via Brent's method root-finding. - Current yield calculation. - Accrued interest with ex-dividend and day-count convention support. - Analytical and finite-difference risk measures: DV01, Dollar Duration, Modified Duration, Macaulay Duration, and Convexity. - Discount curve pricing (clean_price_from_discount_curve, dirty_price_from_discount_curve, value) with terminal perpetuity tail adjustment. - Export BondPerpetual in financepy/products/bonds/__init__.py. - Add comprehensive test suite in tests/unit/test_FinBondPerpetual.py covering par/premium/discount pricing, duration and convexity, current yield, discount curve valuation, vectorization, and input validation. Signed-off-by: huuan26 <phanbuihuuan261103@gmail.com>
domokane
approved these changes
Oct 30, 2026
Owner
|
Thanks. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #7.
Implements the$T = \infty$ ), along with associated analytical pricing, yield solving, and risk measures as requested in Issue #7.
BondPerpetualproduct type (financepy/products/bonds/bond_perpetual.py) for undated debt securities / Consols with regular coupon payments and no maturity date (Mathematical Formulation & Dynamics
1. Dirty and Clean Pricing from Yield to Maturity
For a perpetual bond paying an annualized coupon$c$ at compounding frequency $f$ (with periodic coupon $c/f$ per 100 par) and annualized yield to maturity $y > 0$ :
At settlement date$t_s$ , let $\alpha \in [0, 1]$ be the remaining fraction of the period until the next coupon date (NCD), where $\alpha = 1 - \text{elapsed fraction}$ . The remaining cash flows constitute an infinite coupon annuity discounted from NCD:
Including the coupon payable at NCD (conditioned on indicator$I_{\text{pay}} \in {0, 1}$ , where $I_{\text{pay}} = 1$ if $t_s \le \text{ex-dividend date}$ and $0$ otherwise):
Discounting back to settlement date:
On any coupon payment date ($\alpha = 1$ , accrued interest $= 0$ ):
$$P_{\text{dirty}} = P_{\text{clean}} = \frac{c}{y} \times \text{Par}$$ $P = 100$ when $y = c$ .
which recovers the classic Consol par formula
2. Yield to Maturity Inversion
Given clean price$P_{\text{clean}}$ , dirty price is $P_{\text{dirty}} = P_{\text{clean}} + \text{AccruedInterest}$ . The yield to perpetuity is solved via Brent's method:
$$f(y) = P_{\text{dirty}}(y) - P_{\text{target}} = 0$$ $y \in (0, 2.0]$ .
over
3. Duration & Convexity Risk Measures
4. Valuation from Arbitrary Discount Curves
For any$N = 200$ (100 years for semi-annual), combined with an analytical terminal perpetuity tail evaluated at the curve's terminal forward/zero rate $r_\infty$ :
$$\text{TailPV} = \text{DF}(t_N) \times \frac{c}{y_\infty}$$
DiscountCurve, future coupons are discounted period-by-period up to horizonChanges
financepy/products/bonds/bond_perpetual.py:BondPerpetual(Product)class.dirty_price_from_ytm,clean_price_from_ytm,yield_to_maturity,current_yield,accrued_interest,dv01,dollar_duration,modified_duration,macaulay_duration,convexity_from_ytm,dirty_price_from_discount_curve,clean_price_from_discount_curve,value,__repr__,_print.financepy/products/bonds/__init__.py:from .bond_perpetual import *.tests/unit/test_FinBondPerpetual.py:Verification
pytest tests/unit/test_FinBondPerpetual.py: 8 passed in 1.98s.pytest tests/unit/test_FinBond.py tests/unit/test_FinBondPerpetual.py: 20 passed in 2.45s.ruff check financepy/products/bonds/bond_perpetual.py: All checks passed.git diff --check: Zero whitespace or formatting warnings.