← Research index
INDEPENDENT NUMERICAL AUDIT13 September 2026

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.

Xamit Kadirbekov
Xamit KadirbekovIndependent verification · GERO Research
Financial calculationsFinancePy 1.1.2Bond pricing
STATUS · REPORTED UPSTREAM — CORRECTION SUBMITTEDReproduced from the published PyPI wheel. FinancePy PR #256 is open; both official workflows pass. The correction has not been accepted or merged.

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.