Skip to content

Add BlackVolatilitySurfaceDelta class for FX options#2368

Merged
lballabio merged 32 commits intolballabio:masterfrom
paolodelia99:feature/fx-options-utils
Mar 13, 2026
Merged

Add BlackVolatilitySurfaceDelta class for FX options#2368
lballabio merged 32 commits intolballabio:masterfrom
paolodelia99:feature/fx-options-utils

Conversation

@paolodelia99
Copy link
Contributor

Added BlackVolatilitySurfaceDelta class from ORE, a Black volatility surface parameterized by market deltas, mainly used in Building FX/equity volatility surfaces from market delta quotes.

QL changes @lballabio:

  • BlackVolatilitySurfaceDelta class
  • time extrapolation logic imported from the ORE's QL fork
  • added the possibility to a have a flat extrapolation in the strike space in the InterpolatedSmileSection class as an additional constructor parameter, required from the BlackVolatilitySurfaceDelta class and from ORE's implementation of InterpolatedSmileSection class
  • strikes sanity check added in the InterepolatedSmileSection class (logic implemented in checkStrikes() private method), thought you guys might need it since it was underlined in the comments

ORE Changes @pcaspers:

  • used the QL's InterpolatedSmileSection class to retrive the SmileSection from the blackVolSmile method in the BlackVolatilitySurfaceDelta class instead of the QLE InterpolatedSmileSection since they provide the same functionalities, (QL's InterpolatedSmileSection is actually more complete than the QLE ones, but the latter is derived form FxSmileSection not present in QL) thus there no need to introduce another redundant class.

@coveralls
Copy link

coveralls commented Nov 6, 2025

Coverage Status

coverage: 74.273% (+0.007%) from 74.266%
when pulling d9af61e on paolodelia99:feature/fx-options-utils
into 0a02cce on lballabio:master.

Copy link
Owner

@lballabio lballabio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! A few comments, some of them for @pcaspers.



//! Extrapolate black variance using flat vol extrapolation in time direction
Real timeExtrapolatationBlackVarianceFlat(const Time t, const std::vector<double>& times,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"extrapolatation" (here and everywhere)

Copyright (C) 2019 Quaternion Risk Management Ltd
Copyright (C) 2022 Skandinaviska Enskilda Banken AB (publ)
Copyright (C) 2025 Paolo D'Elia
All rights reserved.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"All rights reserved" is in contradiction with the terms of the license below. If it's in the ORE code (it looks like it), we need to ask for its removal. @pcaspers, any idea why it's there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems this is outdated, see here - feel free to remove this line in this PR. We will update the ORE files accordingly.

Comment on lines +92 to +103
DeltaVolQuote::DeltaType dt = DeltaVolQuote::DeltaType::Spot,
DeltaVolQuote::AtmType at = DeltaVolQuote::AtmType::AtmDeltaNeutral,
ext::optional<DeltaVolQuote::DeltaType> atmDeltaType = ext::nullopt,
const Period& switchTenor = 0 * Days,
DeltaVolQuote::DeltaType ltdt = DeltaVolQuote::DeltaType::Fwd,
DeltaVolQuote::AtmType ltat = DeltaVolQuote::AtmType::AtmDeltaNeutral,
ext::optional<DeltaVolQuote::DeltaType> longTermAtmDeltaType = ext::nullopt,
SmileInterpolationMethod interpolationMethod =
SmileInterpolationMethod::Linear,
bool flatStrikeExtrapolation = false,
BlackVolTimeExtrapolation timeExtrapolation =
BlackVolTimeExtrapolation::FlatVolatility);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of default parameters here—@pcaspers, is their order based on your usage patterns? (i.e., are the last ones also the least likely to be passed?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I don't think so to be honest, they were added over time at the back to keep backwards compatibility. Feel free to reorder them if that seems more reasonable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reorder the optional parameters in the following way:

  1. Short-term conventions (dt_, at_, atmDeltaType_)
  2. Interpolation & extrapolation (interpolationMethod_, flatStrikeExtrapolation_, timeExtrapolation_)
  3. Long-term conventions (switchTenor_, ltdt_, ltat_, longTermAtmDeltaType_)

It made more sense to me. Let me know what do you think.

@paolodelia99
Copy link
Contributor Author

Whenever you guys have time @lballabio @pcaspers

paolodelia99 and others added 3 commits December 18, 2025 12:57
@github-actions
Copy link
Contributor

This PR was automatically marked as stale because it has been open 60 days with no activity. Remove stale label or comment, or this will be closed in two weeks.

@github-actions github-actions bot added the stale label Feb 17, 2026
@lballabio lballabio removed the stale label Feb 17, 2026
@lballabio
Copy link
Owner

@pcaspers — there were a couple of questions for you here if you have some time. Thanks!

@pcaspers
Copy link
Contributor

this is fine, we can move to ql's interpolated smilesection and remove fxsmilesection in ore:

ORE Changes @pcaspers:

used the QL's InterpolatedSmileSection class to retrive the SmileSection from the blackVolSmile method in the BlackVolatilitySurfaceDelta class instead of the QLE InterpolatedSmileSection since they provide the same functionalities, (QL's InterpolatedSmileSection is actually more complete than the QLE ones, but the latter is derived form FxSmileSection not present in QL) thus there no need to introduce another redundant class.

@paolodelia99
Copy link
Contributor Author

@pcaspers actually I think you still need the FxSmileSection for the VannaVolgaSmileSection since it needs the spot, rd, and rf to calculate the vols. But in this case the InterpolatedSmileSection in ORE was redudant to the one already present in QuantLib.



//! Extrapolate black variance using flat vol extrapolation in time direction
Real timeExtrapolationBlackVarianceFlat(const Time t, const std::vector<double>& times,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions in this file seem to belong together with BlackVolTimeExtrapolation. Instead of having an enum class and a separate set of functions, How about something like

class BlackVolTimeExtrapolation {
  public:
    enum Type { FlatVolatility, UseInterpolatorVariance, UseInterpolatorVolatility };
    static Real extrapolate(Type type, const Time t, const std::vector<double>& times, etc);
};

where extrapolate calls one of these functions (that can be declared in namespace detail) based on type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, actually it's more elegant to have a class to do that, then just those plain functions.

return varianceCurve_(times_.back(), true)*t/times_.back();
return timeExtrapolationBlackVarianceFlat(t, times_, varianceCurve_);
} else if (timeExtrapolation_ == BlackVolTimeExtrapolation::UseInterpolatorVolatility) {
return timeExtrapolationBlackVarianceInVolatility(t, times_, varianceCurve_);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected this case to use the existing interpolator, but timeExtrapolationBlackVarianceInVolatility always uses linear interpolation. Maybe the enum name is misleading?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, it's because if you interpolate/extrapolate variances linearly you are sure that you are not going to have calendar spreads.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think so, but then the enum should be something like "LinearVariance", not "UseInterpolatorVolatility".

@lballabio
Copy link
Owner

Thanks, Paolo. I'll probably make some more refactoring to wrap my head around this and push it.

@lballabio
Copy link
Owner

@pcaspers : a question outside the scope of this PR (otherwise we'll never merge it). Given the recent work in #2443 and #2478, is there any difference between this class and what we would get from instantiating PiecewiseBlackVarianceSurface with smiles based on the same data?

Comment on lines +34 to +37
vols[0] = close_enough(times[0], 0.0) ? 0.0 : std::sqrt(variances[0] / times[0]);
vols[1] = close_enough(times[1], 0.0) ? 0.0 : std::sqrt(variances[1] / times[1]);
LinearInterpolation interpolation(times.begin(), times.end(), vols.begin());
return std::max(interpolation(t, true), 0.0);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interpolates linearly between volatilities. It should interpolate linearly between variances, shouldn't it?

Copy link
Contributor Author

@paolodelia99 paolodelia99 Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, an oversight of mine. I thought it was interpolating variances, for the non arb reasons.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, I'll modify it while I'm at it.

Also, use std::function to avoid templates (allowing us to keep the
implementation in the cpp file).
@paolodelia99
Copy link
Contributor Author

@pcaspers : a question outside the scope of this PR (otherwise we'll never merge it). Given the recent work in #2443 and #2478, is there any difference between this class and what we would get from instantiating PiecewiseBlackVarianceSurface with smiles based on the same data?

I guess it's fine @lballabio. We just need to update the code that is initializing the interpolators_ in the ctor. If we also want to keep the current ctor then we can use the makeFromGrid static method in the PiecewiseBlackVarianceSurface class.

@lballabio
Copy link
Owner

I guess it's fine @lballabio. We just need to update the code that is initializing the interpolators_ in the ctor. If we also want to keep the current ctor then we can use the makeFromGrid static method in the PiecewiseBlackVarianceSurface class.

Not sure if I'm getting it — the interpolators are constant delta, the piecewise surface works for a set of smiles which are constant time. It would be more like replacing the ones with the others, I guess?

@lballabio lballabio changed the title Fx options utils - BlackVolatilitySurfaceDelta class Add BlackVolatilitySurfaceDelta class for FX options Mar 13, 2026
@lballabio lballabio added this to the Release 1.42 milestone Mar 13, 2026
@lballabio lballabio merged commit 7f06be1 into lballabio:master Mar 13, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants