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.
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
RankWarningmessages. - 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.
- Complete public reproducer
- Submitted correction: FinancePy PR #258
- Released implementation
- Earlier vectorization PR #153
- Download the reproducible evidence ZIP — SHA-256
a1134a876cde22c50be55cdffbccad15b4ec7081feac0503fbf0981e16fd7f1a
The public reproducer and report are supplied under GPL-3.0-or-later. Source excerpts retain their upstream licensing.
