← Research index
INDEPENDENT NUMERICAL AUDIT13 September 2026

FinancePy vector pricing uses the last expiry for every option

EquityVanillaOption accepts a list of expiry dates. In FinancePy 1.1.2 the list branch calculates every horizon but appends only the final one, which is then broadcast across the vector calculation.

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

A one-minute 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.

Released-wheel reproduction

The synthetic case contains three at-the-money European calls valued on 1 January 2015. Their expiries are 1 July 2015, 1 January 2016 and 1 January 2017. Spot and strike are 100; the continuously compounded interest rate is 5%, dividend yield 1%, and volatility 30%.

                         6 months       1 year        2 years
FinancePy vector         17.55845313     18.32964795  19.89308021
FinancePy scalar          9.30205599     13.61641464  19.89308021
vector − scalar           8.25639714      4.71323331   0.00000000

The scalar values agree within 3e-6 with an independent implementation of the closed-form Black–Scholes equation using only Python’s math.erf. The final vector element agrees because the sole retained horizon belongs to that expiry.

Why the final horizon wins

In both affected list branches, the loop updates t for every date but t_exp.append(t) is indented after the loop. The resulting one-element array contains approximately [2.0027] years instead of [0.4959, 1.0, 2.0027]. NumPy broadcasts that last horizon into each discount, volatility and distribution calculation.

# released structure
for expiry_dt in self.expiry_dt:
    t = (expiry_dt - value_dt) / g_days_in_year
t_exp.append(t)

# submitted correction
for expiry_dt in self.expiry_dt:
    t = (expiry_dt - value_dt) / g_days_in_year
    t_exp.append(t)

The intrinsic-value branch can conceal the wrong horizon under flat curves because its discounting terms cancel algebraically in the tested path. The stored horizon remains wrong. The full option value does not cancel because volatility is scaled by time.

Submitted correction and falsification

FinancePy pull request #258 moves the append inside both loops and adds distinct-expiry regression coverage. Vector expiries combined with scalar or vector strikes and mixed call/put types then match separate scalar valuations exactly.

  • The focused test file reports 5 passes.
  • The complete local FinancePy unit suite reports 959 passes, with four pre-existing LSMC RankWarning messages.
  • Restoring the released append position makes the new regression fail on the first two prices.
  • Both official upstream workflows—unit tests and regression tests—report success on commit 5f614f113acab591bf8539e7ce7e1198a354bae7.

At publication time the pull request remains open and unmerged. Passing checks establish the status of the submitted change; they do not imply maintainer acceptance.

How an earlier test missed it

The earlier vectorization pull request used three copies of the same expiry and its option comparison was an expression without an assert. Identical dates could not expose last-horizon broadcasting. A bounded search of FinancePy issues and pull requests for vector expiry, expiry list, distinct dates and the affected class found no separate report; this is not a worldwide priority guarantee.

Evidence and limits

The installed FinancePy 1.1.2 source file is byte-identical to the file at upstream base commit 2b9227fea9d832c4033421d6cd53a54316414fca: SHA-256 3a90ed5998e75713f1a8062a3afd1f24b971d8ff093dbc6e8d46ce3ee12fd9dc.

All dates, prices and market inputs are synthetic. The report establishes a released library calculation discrepancy. It does not establish use by a financial institution, downstream exposure, customer loss, security impact or production deployment. Performance was not benchmarked; computation and rendering ran sequentially with one numerical or encoding thread.

The public reproducer and report are supplied under GPL-3.0-or-later. Source excerpts retain their upstream licensing.