Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix lower/upper bounds for global bootstrap #2120

Merged
merged 1 commit into from
Nov 28, 2024

Conversation

eltoder
Copy link
Contributor

@eltoder eltoder commented Nov 27, 2024

Currently global bootstrap uses minValueAfter/maxValueAfter methods from Traits to come up with lower/upper bounds. This is problematic. These methods depend on the previous point in the curve. In the iterative bootstrap these methods are called after the previous point is already known exactly. However, in the global bootstrap all points are still filled with Traits::initialValue. This makes the bounds too tight and leads to bad results for curves with higher interest rates.

Instead add separate minValueGlobal/maxValueGlobal methods that do not rely on previous data.

Currently global bootstrap uses minValueAfter/maxValueAfter methods from
Traits to come up with lower/upper bounds. This is problematic. These
methods depend on the previous point in the curve. In the iterative
bootstrap these methods are called after the previous point is already
known exactly. However, in the global bootstrap all points are still
filled with Traits::initialValue. This makes the bounds too tight and
leads to bad results for curves with higher interest rates.

Instead add separate minValueGlobal/maxValueGlobal methods that do not
rely on previous data.
@eltoder eltoder force-pushed the feature/global-bootstrap-bounds branch from 3d38f49 to c987e2d Compare November 27, 2024 15:42
@eltoder
Copy link
Contributor Author

eltoder commented Nov 27, 2024

CC @pcaspers as the original author of the code

@coveralls
Copy link

coveralls commented Nov 27, 2024

Coverage Status

coverage: 73.107% (+0.003%) from 73.104%
when pulling c987e2d on eltoder:feature/global-bootstrap-bounds
into ce96c8e on lballabio:master.

@lballabio
Copy link
Owner

It seems like calling minValueAfter/maxValueAfter with validCurve=false would have the same result, wouldn't it? It would be less explicit but it would remove some duplication and avoid the need to update user-defined traits.

@eltoder
Copy link
Contributor Author

eltoder commented Nov 27, 2024

It wouldn't for the Discount trait, which is the most commonly used one AFAIK. It uses the previous point when validData==false:

return c->data()[i-1] * std::exp(- detail::maxRate * dt);

return c->data()[i-1] * std::exp(detail::maxRate * dt);

We'd need to introduce a new flag like usePrevious or abuse the currently unused firstAliveHelper. Other options are to add a check like

return c->data()[i-1] == 1.0 ? maxDF : c->data()[i-1] * std::exp(detail::maxRate * dt);

or to just change the code to return constants (0 and maxDF) even for the iterative bootstrap. We rarely use iterative bootstrap, so I don't know how valuable this calculation is for its performance.

All of this seems less clean than new methods, especially since the comment says pretty explicitly

// possible constraints based on previous values

For the global bootstrap I need methods that do not depend on previous values. However, let me know what your preference is.

@lballabio
Copy link
Owner

I see, thanks. I missed the difference in the discount traits.

@lballabio lballabio added this to the Release 1.37 milestone Nov 28, 2024
@lballabio lballabio merged commit e44806f into lballabio:master Nov 28, 2024
42 checks passed
@eltoder eltoder deleted the feature/global-bootstrap-bounds branch November 28, 2024 15:59
@eltoder
Copy link
Contributor Author

eltoder commented Nov 28, 2024

Thank you. I do agree that this causes a bit of duplication. Also, C++ doesn't have a nice way to make an alias when the logic is the same (e.g. minValueGlobal = minValueAfter). If you come up with a better way to do this, I'm happy to change it.

eltoder added a commit to eltoder/QuantLib that referenced this pull request Jan 8, 2025
Also, use exp instead of atan transformation for discount factors. This
is an improvement on lballabio#2120 based on these observations:

* Rates-based traits don't need any constraints in the optimization, so
  we don't need any transformation with them. This is achieved with
  identity transformation in their traits.

* Discount factors are only bounded on one side (have to be positive),
  so instead of estimating a max value to use atan, we can simply use
  exp. This is faster and uses fewer magic numbers.

  Another issue with atan is that changes the gradients too much when
  the value is far from the middle of the range. This pushes
  optimization away from the correct solution when rates are low. This
  makes it hard to find value of maxDF that works for both very low and
  very high rates.
eltoder added a commit to eltoder/QuantLib that referenced this pull request Jan 8, 2025
Also, use exp instead of atan transformation for discount factors. This
is an improvement on lballabio#2120 based on these observations:

* Rates-based traits don't need any constraints in the optimization, so
  we don't need any transformation with them. This removes unnecessary
  computations.

* Discount factors are only bounded on one side (have to be positive),
  so instead of estimating a max value to use atan, we can simply use
  exp. This is faster and uses fewer magic numbers.

  Another issue with atan is that it changes the gradients too much when
  the value is far from the middle of the range. This pushes
  optimization away from the correct solution when rates are low. This
  makes it hard to find value of maxDF that works for both very low and
  very high rates.
eltoder added a commit to eltoder/QuantLib that referenced this pull request Jan 8, 2025
Also, use exp instead of atan transformation for discount factors. This
is an improvement on lballabio#2120 based on these observations:

* Rates-based traits don't need any constraints in the optimization, so
  we don't need any transformation with them. This removes unnecessary
  computations.

* Discount factors are only bounded on one side (have to be positive),
  so instead of estimating a max value to use atan, we can simply use
  exp. This is faster and uses fewer magic numbers.

  Another issue with atan is that it changes the gradients too much when
  the value is far from the middle of the range. This pushes
  optimization away from the correct solution when rates are low. This
  makes it hard to find value of maxDF that works for both very low and
  very high rates.
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.

3 participants