feat(sheet): Excel-Bond-Funktionen mit Day-Count-Conventions - #374
Conversation
Second batch of the Excel gap: 19 security/bond functions on top of shared day-count machinery (basis 0-4) and a coupon schedule derived backwards from maturity with end-of-month clamping. COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPNCD, COUPPCD, COUPNUM, PRICE, YIELD, DURATION, MDURATION, ACCRINT, ACCRINTM, DISC, INTRATE, RECEIVED, PRICEDISC, YIELDDISC, PRICEMAT, YIELDMAT. Day counts cross-check against HyperFormula YEARFRAC for every basis. One deliberate divergence: HyperFormula applies the US 30/360 rules in the wrong order, so its YEARFRAC counts 181 days for 29-Feb to 31-Aug where Excel counts 180; the bond math follows Excel. Tests assert Microsoft documented example values plus PRICE/YIELD and PRICEMAT/YIELDMAT round trips. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR Summary by QodoAdd Excel-compatible bond functions and day-count conventions
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1. ACCRINT ignores coupon schedule
|
| const from = | ||
| !fromIssue && Math.trunc(settlement) > Math.trunc(first) ? this.coupons(settlement, first, f).pcd : Math.trunc(issue); | ||
| return par * rate * this.yearFrac(from, settlement, b); |
There was a problem hiding this comment.
1. Accrint ignores coupon schedule 🐞 Bug ≡ Correctness
With the default calc_method=TRUE, ACCRINT ignores first_interest and computes `YEARFRAC(issue, settlement)` instead of accruing across quasi-coupon periods. Actual/actual calculations with unequal or irregular coupon periods therefore return incorrect accrued interest.
Agent Prompt
## Issue description
ACCRINT's default path ignores the first-interest coupon schedule and applies one year fraction from issue to settlement. Implement schedule-aware accrual so each quasi-coupon segment uses its applicable coupon-period denominator, especially for basis 1 and irregular first periods.
## Issue Context
`first_interest` currently affects only the `calc_method=FALSE` branch. The existing coupon helpers already calculate schedule dates and actual coupon-period lengths.
## Fix Focus Areas
- ui/src/js/sheet/bondFunctions.ts[289-303]
- ui/src/js/sheet/bondFunctions.ts[108-140]
- ui/src/js/sheet/bondFunctions.test.ts[110-118]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| let lo = -0.99; | ||
| let hi = 10; | ||
| if (target(lo) * target(hi) > 0) return numErr('Yield could not be determined.'); |
There was a problem hiding this comment.
2. Negative yields outside bracket 🐞 Bug ≡ Correctness
YIELD searches only [-0.99, 10], although semiannual and quarterly pricing remains defined for yields below -0.99. Valid high-premium bonds therefore return #NUM! instead of their negative yield.
Agent Prompt
## Issue description
The YIELD solver's fixed lower bound excludes valid negative roots for frequencies 2 and 4. Use a frequency-aware domain and robust root bracketing while avoiding points where the pricing expression is undefined.
## Issue Context
For example, a zero-coupon semiannual bond with settlement on a coupon date, two remaining periods, redemption 100, and price 1600 has yield -1.5 under the implemented pricing equation. Both current endpoints produce prices below 1600, so the function returns `#NUM!` without searching the valid root.
## Fix Focus Areas
- ui/src/js/sheet/bondFunctions.ts[220-241]
- ui/src/js/sheet/bondFunctions.ts[149-168]
- ui/src/js/sheet/bondFunctions.test.ts[96-101]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Zweiter Batch der Excel-Lücke: Wertpapier-/Bond-Funktionen
Basis für alles ist geteilte Mechanik: die Day-Count-Conventions (basis 0–4) und der Coupon-Kalender, rückwärts von der Maturity abgeleitet, mit Monatsende-Clamping (eine 31.-August-Maturity driftet nicht über den Februar auf den 30.).
Basis: 0 = US 30/360, 1 = actual/actual, 2 = actual/360, 3 = actual/365, 4 = europäisch 30/360.
Bewusste Abweichung von HyperFormula
Die Day-Counts werden für jede Basis gegen HyperFormulas eigenes
YEARFRACgegengeprüft — mit einer Ausnahme: HyperFormula wendet die US-30/360-Regeln in falscher Reihenfolge an (toBasisUSprüft "Enddatum ist der 31." bevor ein Februar-Ende-Startdatum auf den 30. normalisiert wird). Für 29.02.2008 → 31.08.2008 zählt es 181 Tage, Excel zählt 180. Die Bond-Mathematik folgt Excel; der Test hält beide Werte fest, damit die Abweichung dokumentiert bleibt und nicht unbemerkt kippt.Zwei Bugs beim Testen gefunden und gefixt
Tests
20 neue Cases mit Microsofts dokumentierten Beispielwerten (PRICE 94.63436, DURATION 10.9191453, ACCRINT 16.66667, RECEIVED 1014584.654, PRICEMAT 99.98449888 …), plus PRICE↔YIELD- und PRICEMAT↔YIELDMAT-Round-Trips und Validierung (Settlement ≥ Maturity, Frequenz ≠ 1/2/4).
vitest run: 139 Tests grün.Bewusst ausgelassen
_xlfn.gespeichert.🤖 Generated with Claude Code