Add PiecewiseBlackVarianceSurface for ragged vol grids#2443
Add PiecewiseBlackVarianceSurface for ragged vol grids#2443lballabio merged 1 commit intolballabio:masterfrom
PiecewiseBlackVarianceSurface for ragged vol grids#2443Conversation
|
Hi @pandashark, thanks for doing that! I like this simple implementation. |
|
Thanks @paolodelia99! I've added calendar and butterfly arbitrage sanity checks to the midpoint smile in
Both pass — linear variance interpolation between the piecewise-linear smile sections preserves both properties for this test data. For the |
|
Yes, please add it when #2368 will be merged. |
5d7eca8 to
a217af1
Compare
| /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ | ||
|
|
||
| /* | ||
| Copyright (C) 2026 |
There was a problem hiding this comment.
We need a name here (either a person or a company). Same for the other new files.
There was a problem hiding this comment.
Thanks for catching that. I followed the existing template but wasn't sure what to use - happy to add whatever you prefer. Do you have a preferred convention for individual contributors?
There was a problem hiding this comment.
It's not a matter of what I prefer, it's who holds the copyright :)
There was a problem hiding this comment.
Fair enough! I'll update all new files with the correct copyright.
|
Nice, thanks. |
|
Thanks! Agreed — The tricky bit is I'd lean toward a separate deprecation round: deprecate Happy to open a separate issue to track the deprecation, or I can fold it into this one — whatever you prefer. |
|
Yes, I would also try to think of a shorter name for this class but I don't have any ideas right now :) |
|
Sounds good — separate deprecation round it is. For the factory method, I'm thinking a free function that constructs the surface from a rectangular grid (strikes + vol matrix), mirroring the ext::shared_ptr<...>
makeFromGrid(const Date& referenceDate,
const std::vector<Date>& dates,
const std::vector<Real>& strikes,
const Matrix& blackVols,
const DayCounter& dc = Actual365Fixed());That way users migrating from For shorter names, following the
I'd lean toward |
|
I'm ok with The free function is ok too, but the name will be a mouthful ( |
a217af1 to
df16a2c
Compare
|
Thanks! I've gone with Force-pushed with the rename + factory method. |
|
|
||
| inline Real | ||
| PiecewiseBlackVarianceSurface::minStrike() const { | ||
| return QL_MIN_REAL; |
There was a problem hiding this comment.
This might loop over the set of smile sections and ask them for their min strike. If the sections have different min strikes, we'd have to think if we want the largest or the smallest. It might be an expensive operation though, so we might have the further problem of precalculating and refreshing when something changes. What do you think?
There was a problem hiding this comment.
I think QL_MIN_REAL/QL_MAX_REAL is the right choice. The surface delegates to smile sections for the strike dimension, and sections handle out-of-range strikes via their interpolator's extrapolation — so it can produce a value at any strike.
The ragged case makes the alternative problematic: if section A covers [80, 120] and section B covers [90, 130], the intersection is [90, 120]. A query at strike 85 at section A's tenor gets rejected even though section A handles it within its own grid. The user would have to call enableExtrapolation() for something that isn't actually extrapolated.
There was a problem hiding this comment.
Agreed, I was also thinking of the ragged case and it is indeed problematic.
However, SmileSection does provide minStrike and maxStrike methods. We could use them here to range-check the calls to smileSections_[i]->variance(strike). As for other curves, the check would be also based on whether we called enableExtrapolation on this class. This would give us the correct check for each section.
There was a problem hiding this comment.
Good idea — done. Added a private sectionVariance(i, strike) helper that checks strike against smileSections_[i]->minStrike()/maxStrike() before querying variance. If the strike is out of range and allowsExtrapolation() is false on the surface, it throws. All variance queries in blackVarianceImpl route through this helper.
The surface-level minStrike()/maxStrike() still return QL_MIN_REAL/QL_MAX_REAL since there's no single range that works for the ragged case — the per-section check handles the actual enforcement.
Updated the ragged grid test to verify the throw (strike 75 outside section 0's [80, 120] range) and that enableExtrapolation() allows it through. Also switched all validation tests to use ExpectedErrorMessage.
| BOOST_CHECK_EXCEPTION( | ||
| PiecewiseBlackVarianceSurface(today, {}, {}, dc), | ||
| Error, | ||
| [](const Error& e) { | ||
| return std::string(e.what()).find("at least one date") != | ||
| std::string::npos; | ||
| }); |
There was a problem hiding this comment.
We have an existing ExpectedErrorMessage that you can use as
BOOST_CHECK_EXCEPTION(
PiecewiseBlackVarianceSurface(today, {}, {}, dc),
Error,
ExpectedErrorMessage("at least one date"));
if you include "utilities.hpp"
There was a problem hiding this comment.
Done — switched all validation tests to use ExpectedErrorMessage. Much cleaner, thanks for the pointer.
Black volatility surface built from per-tenor SmileSections with potentially different strike grids. Interpolates linearly in total variance between tenors. Includes static makeFromGrid factory for migration from BlackVarianceSurface, and per-section strike range checking gated by enableExtrapolation().
f6913f9 to
23678dd
Compare
PiecewiseBlackVarianceSurface for ragged vol grids
Closes #2173.
New
BlackVarianceTermStructuresubclass that builds a Black volatility surface from a vector ofSmileSectionobjects, one per tenor. Each tenor keeps its own strike grid, so the surface handles ragged (non-rectangular) market data natively. Time interpolation is linear in total variance; strike interpolation is delegated to eachSmileSection.New files:
ql/termstructures/volatility/equityfx/blackvariancesurfacefromsmilesections.hpp— class declarationql/termstructures/volatility/equityfx/blackvariancesurfacefromsmilesections.cpp— constructor validation,blackVarianceImpl, visitor patterntest-suite/blackvariancesurfacefromsmilesections.cpp— 7 test casesBuild system: registered in
ql/CMakeLists.txt,ql/.../Makefile.am,ql/.../all.hpp,test-suite/CMakeLists.txt,test-suite/Makefile.am.Test: 7 cases covering exact repricing at tenor points, linear variance interpolation between tenors, blackVol derivation from blackVariance, flat-vol extrapolation beyond the last tenor, observer notification propagation through SmileSections, strike-dependent smile handling, and multi-tenor interpolation with different smile shapes. All pass.