FinancePy bond principal does not scale with face amount
In FinancePy 1.1.2, Bond.principal() scales dirty price by the requested face but subtracts accrued interest left at face one. A synthetic released-wheel case exposes the resulting violation of linear cash scaling.
A 55-second explanation
Synthetic English narration using macOS Samantha. Original motion cards, no music or third-party imagery. Download the script, claim ledger, renderer and QA record.
Finding
A clean bond price quoted per 100 maps to a cash principal through one linear identity:
principal(face) = clean_price_per_100 × face / 100
The released method first asks for dirty price per 100, multiplies it by face, and then subtracts an accrued-interest value calculated on face 1.0. The two terms therefore use different notional scales.
Released-wheel reproduction
The public reproducer installs financepy==1.1.2 from PyPI and uses a synthetic semiannual 6% coupon bond settled on 1 March 2025 at a 5% yield. No private or customer data is involved.
dirty_per_100 = 102.7043335346
accrued_per_100 = 0.9779005525
clean_per_100 = 101.7264329821
face released result identity value difference
100 102.6945545291 101.7264329821 0.9681215470
1,000,000 1,027,043.3255671025 1,017,264.3298212462 9,778.9957458563
Face one is a useful control: it passes because the stored accrued amount happens to use that same face. Direct proportionality also fails: the face-one-million result differs from 10,000 times the face-100 result by 97.7802762430.
Submitted correction
FinancePy pull request #256 recalculates accrued interest with the supplied face before subtraction and adds regression coverage at faces 100 and 1,000,000. The correction is intentionally small; it changes the scale of the accrued term, not the pricing model.
The complete local FinancePy unit suite reports 959 passes. Both official upstream workflows—unit tests and regression tests—also report success. Replacing the corrected subtraction with the released behavior makes the new regression fail. At publication time the pull request remains open and mergeable; green checks are evidence that the submitted change passes those workflows, not evidence of acceptance.
Impact boundary
The demonstrated difference grows linearly with face because an accrued amount remains unscaled. This can make a cash principal returned by this method inconsistent with its clean-price identity. The report does not establish how often the method is called, whether downstream users compensate for the behavior, or whether any production valuation is affected.
All dates and amounts are synthetic. No bank, customer account, transaction, production deployment, financial loss or security impact was tested. This is an independent numerical-correctness report, not financial advice and not an endorsement by FinancePy.
