FinancePy misprices three FX digital payout-currency paths
An FX digital can pay in either member of a currency pair. In FinancePy 1.0.1, three valuation paths do not consistently switch the probability and discount terms with the payout currency.
A one-minute explanation
Synthetic English narration using macOS Samantha. Original motion cards, no music or third-party imagery. Download the script, source ledger, renderer and QA record.
Three affected formula paths
FXDigitalOptionusesN(d2)for a foreign-currency payout. In domestic units the payoff isS_T × 1(condition), so this is asset-or-nothing and usesN(d1).FXDoubleDigitalOptiondiscounts a domestic-currency interval payout with the foreign discount factor instead of the domestic factor.- The same interval class uses
d2rather thand1for a foreign-currency payout.
The two domestic single-digital controls agree with their independent cash-or-nothing formulas. The finding is therefore bounded to the named paths.
The payoff determines the formula
Let S_T be domestic currency per unit of foreign currency. Under the domestic pricing measure:
PV[1 domestic × 1(S_T > K)] = df_domestic × N(d2)
PV[1 foreign × 1(S_T > K)] = S_0 × df_foreign × N(d1)
The second identity follows by converting the foreign unit at expiry. Its domestic payoff is S_T × 1(S_T > K). An interval digital is the difference of the matching lower- and upper-strike terms.
Synthetic released-wheel counterexample
The reproducible case uses EURUSD spot 1.20, one-year expiry, domestic rate 5%, foreign rate 1%, volatility 20%, a single strike of 1.25 and interval strikes 1.10–1.40.
path FinancePy 1.0.1 independent oracle
foreign single call 0.5449552491 0.6397169446
foreign single put 0.6430720022 0.5483103068
domestic interval 0.4474588468 0.4298665323
foreign interval 0.5369506162 0.5338519318
The oracle evaluates the payoff-derived Black–Scholes expressions with Python’s standard-library math.erf. It does not reuse FinancePy’s vector normal-CDF approximation.
Submitted correction and falsification
FinancePy pull request #260 calculates both strike-specific d1 and d2, then chooses the probability and discount factor that correspond to the payout currency.
- 6 focused oracle tests pass after the correction.
- The complete local unit suite reports 964 passes, with four pre-existing LSMC
RankWarningmessages. - Against the former source, all four affected tests fail while both unaffected domestic single-digital controls pass.
- Both existing FX digital regression scripts complete.
- Both official upstream workflows pass on commit
1a78f85d4576f20d1671338bad49640a9dbb7b87.
At publication time the pull request remains open and unmerged. Green checks validate the submitted change; they do not imply maintainer acceptance.
Evidence and limits
The public wheel has SHA-256 110e784122f485ae207239f44af473334f418d81bcbb958f5eda4e76956feca5. Its single- and double-digital source files hash to 415ec9bd1f3ffb65afd450f9589685b63401d800569d86b670edf2692ff7878c and 9a85bc68f56b0c55e7a5394efbb051e3ff1c00412667b485d1c952568db169db.
A bounded FinancePy GitHub search using four direct formulations found no matching issue or pull request. This is not a worldwide novelty or priority guarantee.
All market inputs are synthetic. The report establishes library-level formula discrepancies. It does not establish use by a financial institution, a customer position, a production deployment, security impact or financial loss.
- Complete public reproducer
- Submitted correction: FinancePy PR #260
- FinancePy 1.0.1 on PyPI
- Download the reproducible evidence ZIP — SHA-256
9c3d8535454ca8defe6bc17008a358f9f0772316381215fd8d768b3a2a411bc9
The public reproducer and report are supplied under GPL-3.0-or-later. Source excerpts retain their upstream licensing.
