Fix negative-volatility guard silently skipped in FX option greeks - #276
Merged
domokane merged 1 commit intoSep 24, 2026
Merged
Conversation
FXVanillaOption.gamma/vega/theta guarded against negative volatility with "if np.any(volatility) < 0.0:", which checks the truth value of np.any(volatility) (a bool) against 0.0 rather than checking each element of volatility against 0.0. Since a bool is never < 0.0, the guard could never raise, so a negative volatility silently produced a (wrong, non-error) greek instead of raising FinError as the analogous check in delta() correctly does. Fixed by comparing element-wise before reducing: np.any(volatility < 0.0). Applied the same fix to the identical pattern in utils/helpers.py:input_time (np.any(t) < 0 -> np.any(t < 0)), which had the same bug for its curve-date-in-the-past check on ndarray inputs. Added a regression test (test_negative_volatility_raises) confirming gamma/vega/theta now raise FinError for negative volatility. Verified red/green: the new test fails on the old code and passes with the fix; full unit test suite (1051 tests) passes with the fix applied, positive- volatility results are unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
domokane
approved these changes
Sep 24, 2026
Owner
|
Thanks. |
Owner
|
Thanks |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
FXVanillaOption.gamma,.vega, and.thetainfinancepy/products/fx/fx_vanilla_option.pyeach guard against a negative volatility with:This checks the truth value of
np.any(volatility)(a Python bool) against0.0, not each element ofvolatilityagainst0.0. Since a bool is never< 0.0, the condition is alwaysFalse, so the guard can never raise. A negative volatility silently flows through into the pricing math and produces a wrong (non-error) greek instead of raising, while the sibling methoddelta()(which correctly usesnp.any(v < 0.0)) does raise for the same input — an inconsistency within the same class.Root cause
Operator precedence:
np.any(x) < 0.0reduces first, then compares the reduced boolean; the intended check isnp.any(x < 0.0), which compares element-wise first, then reduces.The identical pattern existed in
financepy/utils/helpers.py:input_time, in thendarraybranch's check that a curve date isn't before the curve's value date (np.any(t) < 0), with the same effect: negative time-in-the-past inputs on an array of dates silently pass instead of raising.Fix
fx_vanilla_option.py: changed all three occurrences tonp.any(volatility < 0.0)/np.any(vol < 0.0).utils/helpers.py: changednp.any(t) < 0tonp.any(t < 0).Verification
test_negative_volatility_raisesintests/unit/test_FinFXVanillaOption.py, assertinggamma/vega/thetaraiseFinErrorfor a negative-volatilityBlackScholesmodel.pytest tests) before and after: 1050 passing before (plus the new test failing), 1051 passing after — no regressions, and existing positive-volatility results for gamma/vega/theta are numerically unchanged (spot-checked againsttest_vega_theta's Hull example values).