Skip to content

Commit f5a747e

Browse files
authored
Feat: sabr interpolation using Ceres solver
1 parent 408dc61 commit f5a747e

2,575 files changed

Lines changed: 667697 additions & 243505 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cdr/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@ add_subdirectory(options)
88
add_subdirectory(fx)
99
add_subdirectory(market)
1010
add_subdirectory(model)
11+
12+
13+
cdr_cpp_test(
14+
NAME ceres_tests
15+
SRCS
16+
"ceres_tests.cc"
17+
DEPS
18+
Ceres::ceres
19+
GTest::gtest_main
20+
)

cdr/ceres_tests.cc

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Check that ceres works correctly
2+
#include <ceres/ceres.h>
3+
#include <gtest/gtest.h>
4+
5+
#include <cmath>
6+
#include <iostream>
7+
#include <vector>
8+
9+
template <typename T>
10+
T CalculateSABRVol(const T& alpha, const T& rho, const T& nu, double F, double K, double T_exp) {
11+
using std::abs;
12+
using std::log;
13+
using std::pow;
14+
using std::sqrt;
15+
16+
const double beta = 1.0;
17+
const double eps = 1e-7;
18+
19+
if (abs(F - K) < eps) {
20+
T sub_1 = T((1.0 - beta) * (1.0 - beta) / 24.0) * alpha * alpha / T(pow(F, 2.0 - 2.0 * beta));
21+
T sub_2 = T(0.25 * rho * beta * nu) * alpha / T(pow(F, 1.0 - beta));
22+
T sub_3 = (T(2.0) - T(3.0) * rho * rho) / T(24.0) * nu * nu;
23+
return (alpha / T(pow(F, 1.0 - beta))) * (T(1.0) + (sub_1 + sub_2 + sub_3) * T(T_exp));
24+
} else {
25+
T logFK = T(log(F / K)); // Явное приведение double -> T
26+
T f_k_beta = T(pow(F * K, (1.0 - beta) / 2.0));
27+
28+
T z = (nu / alpha) * f_k_beta * logFK;
29+
T x_z = log((sqrt(T(1.0) - T(2.0) * rho * z + z * z) + z - rho) / (T(1.0) - rho));
30+
31+
T numer_1 = T((1.0 - beta) * (1.0 - beta) / 24.0) * alpha * alpha / pow(f_k_beta, 2.0);
32+
T numer_2 = T(0.25) * rho * T(beta) * nu * alpha / f_k_beta;
33+
T numer_3 = (T(2.0) - T(3.0) * rho * rho) / T(24.0) * nu * nu;
34+
35+
T denum_1 = T((1.0 - beta) * (1.0 - beta) / 24.0) * logFK * logFK;
36+
T denum_2 = T(pow((1.0 - beta), 4.0) / 1920.0) * pow(logFK, 4.0);
37+
38+
return (alpha / (f_k_beta * (T(1.0) + denum_1 + denum_2))) * (z / x_z) *
39+
(T(1.0) + (numer_1 + numer_2 + numer_3) * T(T_exp));
40+
}
41+
}
42+
43+
// 2. Функтор для Ceres
44+
struct SabrCostFunctor {
45+
SabrCostFunctor(double market_vol, double F, double K, double T_exp)
46+
: m_vol(market_vol), m_F(F), m_K(K), m_T(T_exp) {
47+
}
48+
49+
template <typename T>
50+
bool operator()(const T* const alpha, const T* const rho, const T* const nu, T* residual) const {
51+
// Вычисляем волатильность по модели
52+
T model_vol = CalculateSABRVol(alpha[0], rho[0], nu[0], m_F, m_K, m_T);
53+
// Ошибка (residual)
54+
residual[0] = model_vol - T(m_vol);
55+
return true;
56+
}
57+
58+
const double m_vol, m_F, m_K, m_T;
59+
};
60+
61+
TEST(SABRProvider, CalibrationWithJet) {
62+
const double F = 100.0;
63+
const double K = 105.0; // Немного вне денег
64+
const double T = 1.0;
65+
const double market_vol = 0.22;
66+
67+
double alpha = 0.2;
68+
double rho = -0.2;
69+
double nu = 0.4;
70+
71+
ceres::Problem problem;
72+
73+
problem.AddResidualBlock(
74+
new ceres::AutoDiffCostFunction<SabrCostFunctor, 1, 1, 1, 1>(new SabrCostFunctor(market_vol, F, K, T)), nullptr,
75+
&alpha, &rho, &nu);
76+
77+
problem.SetParameterLowerBound(&alpha, 0, 0.001);
78+
problem.SetParameterLowerBound(&rho, 0, -0.99);
79+
problem.SetParameterUpperBound(&rho, 0, 0.99);
80+
problem.SetParameterLowerBound(&nu, 0, 0.001);
81+
82+
ceres::Solver::Options options;
83+
options.max_num_iterations = 50;
84+
options.linear_solver_type = ceres::DENSE_QR;
85+
86+
ceres::Solver::Summary summary;
87+
ceres::Solve(options, &problem, &summary);
88+
89+
std::cout << "Solver report:\n" << summary.BriefReport() << "\n";
90+
std::cout << "Final params: a=" << alpha << ", r=" << rho << ", n=" << nu << "\n";
91+
92+
EXPECT_TRUE(summary.IsSolutionUsable());
93+
double final_vol = CalculateSABRVol(alpha, rho, nu, F, K, T);
94+
EXPECT_NEAR(final_vol, market_vol, 1e-6);
95+
}

cdr/options/CMakeLists.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ cdr_cpp_library(
1010
"interpolation/quadratic_spline.h"
1111
"interpolation/lerp.h"
1212
"interpolation/flat_forward.h"
13+
"interpolation/sabr.h"
1314
SRCS
1415
"contract.cc"
15-
#"volatility.cc"
1616
"interpolation/cubic_spline.cc"
1717
"interpolation/quadratic_spline.cc"
1818
"interpolation/lerp.cc"
1919
"interpolation/flat_forward.cc"
20+
"interpolation/sabr.cc"
2021
DEPS
2122
cdr::calendar
2223
cdr::base
2324
cdr::types
2425
cdr::math
26+
Ceres::ceres
2527
PUBLIC
2628
)
2729

@@ -47,6 +49,16 @@ cdr_cpp_test(
4749
GTest::gtest_main
4850
)
4951

52+
cdr_cpp_test(
53+
NAME sabr_tests
54+
SRCS
55+
"interpolation/sabr_tests.cc"
56+
DEPS
57+
cdr::options
58+
Ceres::ceres
59+
GTest::gtest_main
60+
)
61+
5062
cdr_cpp_test(
5163
NAME option_tests
5264
SRCS

cdr/options/interpolation/sabr.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <cdr/options/interpolation/sabr.h>
2+
#include <ceres/ceres.h>
3+
4+
namespace cdr {
5+
6+
Expect<void, Error> SABRInterpolator::InitState(void* coefs_ptr, std::span<const f64> xs,
7+
std::span<const f64> ys) noexcept {
8+
auto* p = static_cast<SabrParameters*>(coefs_ptr);
9+
10+
// Начальное приближение (Initial Guess)
11+
// alpha берем как ATM волатильность (примерно посередине вектора ys)
12+
double params[3] = {ys[ys.size() / 2], 0.0, 0.5};
13+
14+
ceres::Problem problem;
15+
for (size_t i = 0; i < xs.size(); ++i) {
16+
auto* cost_function =
17+
new ceres::AutoDiffCostFunction<SabrCostFunctor, 1, 3>(new SabrCostFunctor(xs[i], ys[i], p->F, p->T));
18+
problem.AddResidualBlock(cost_function, nullptr, params);
19+
}
20+
21+
// Накладываем ограничения, чтобы параметры не улетели в космос
22+
problem.SetParameterLowerBound(params, 0, 1e-6); // alpha > 0
23+
problem.SetParameterLowerBound(params, 1, -0.99); // rho > -1
24+
problem.SetParameterUpperBound(params, 1, 0.99); // rho < 1
25+
problem.SetParameterLowerBound(params, 2, 1e-6); // nu > 0
26+
27+
ceres::Solver::Options options;
28+
options.linear_solver_type = ceres::DENSE_QR;
29+
options.max_num_iterations = 50;
30+
// Levenberg-Marquardt по умолчанию в Ceres
31+
32+
ceres::Solver::Summary summary;
33+
ceres::Solve(options, &problem, &summary);
34+
35+
if (!summary.IsSolutionUsable()) {
36+
return ErrorCalibrationFailed();
37+
}
38+
39+
p->alpha = params[0];
40+
p->rho = params[1];
41+
p->nu = params[2];
42+
43+
return Ok();
44+
}
45+
46+
[[nodiscard]] Expect<f64, Error> SABRInterpolator::Evaluate(const void* coefs_ptr,
47+
[[maybe_unused]] std::span<const f64> xs, f64 K) noexcept {
48+
const auto& p = *static_cast<const SabrParameters*>(coefs_ptr);
49+
return Ok(CalculateHagan<f64>(K, p.F, p.T, p.alpha, p.rho, p.nu));
50+
}
51+
52+
} // namespace cdr

cdr/options/interpolation/sabr.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#pragma once
2+
3+
#include <cdr/options/internal/export.h>
4+
#include <cdr/types/errors.h>
5+
#include <cdr/types/types.h>
6+
7+
#include <span>
8+
9+
namespace cdr {
10+
11+
class CDR_OPTIONS_EXPORT SABRInterpolator {
12+
public:
13+
struct SabrParameters {
14+
static constexpr f64 kBeta = 1.0;
15+
f64 alpha;
16+
f64 rho;
17+
f64 nu;
18+
f64 F; // Forward price
19+
f64 T; // Time to maturity
20+
f64 rd; // domestic curve
21+
f64 rf; // foreign curve
22+
};
23+
24+
using StrikeVolatilityType = SabrParameters;
25+
using DeltaVolatilityType = f64;
26+
27+
public:
28+
static constexpr size_t StateRequiredMemorySize(size_t /*n*/) noexcept {
29+
// SABR needs only one structure of parameters per date
30+
return sizeof(SabrParameters);
31+
}
32+
33+
static constexpr size_t StateRequiredMemoryAlignment(size_t /*n*/) noexcept {
34+
return alignof(SabrParameters);
35+
}
36+
37+
// Hagan approximation, implemented as template to support double and ceres::Jet
38+
// Uses the Hagan approximation for the SABR model with Beta = 1.0
39+
//
40+
// Formula: sigma(K) = alpha * (z / chi(z)) * [1 + correction_term * T]
41+
// where:
42+
// z = (nu / alpha) * log(F / K)
43+
// chi(z) = log((sqrt(1 - 2*rho*z + z^2) + z - rho) / (1 - rho))
44+
//
45+
// Note: Handle K = F (ATM) case separately to avoid 0/0 singularity,
46+
// resulting in: sigma_atm = alpha * [1 + correction_term * T].
47+
template <typename T>
48+
static T CalculateHagan(T K, T F, T Time, T alpha, T rho, T nu) {
49+
using std::log;
50+
using std::sqrt;
51+
using std::abs;
52+
using std::pow;
53+
54+
if (K <= T(0.0) || F <= T(0.0)) {
55+
return T(0.0);
56+
}
57+
58+
constexpr f64 beta = SabrParameters::kBeta;
59+
constexpr f64 one_minus_beta = 1.0 - beta;
60+
constexpr f64 one_minus_beta2 = one_minus_beta * one_minus_beta;
61+
62+
const T f_k_beta = pow(F * K, one_minus_beta / 2.0);
63+
const T log_f_k = log(F / K);
64+
65+
T den = f_k_beta * (T(1.0) + (one_minus_beta2 / 24.0) * log_f_k * log_f_k +
66+
(one_minus_beta2 * one_minus_beta2 / 1920.0) * pow(log_f_k, 4.0));
67+
68+
const T z = (nu / alpha) * f_k_beta * log_f_k;
69+
70+
const T xz = log((sqrt(T(1.0) - T(2.0) * rho * z + z * z) + z - rho) / (T(1.0) - rho));
71+
72+
const T term2 = T(1.0) + (
73+
(one_minus_beta2 / 24.0) * (alpha * alpha / pow(f_k_beta, 2.0)) +
74+
(0.25 * rho * beta * nu * alpha / f_k_beta) +
75+
((T(2.0) - T(3.0) * rho * rho) / 24.0) * nu * nu
76+
) * Time;
77+
78+
if (abs(log_f_k) < T(1e-8)) {
79+
return (alpha / pow(F, one_minus_beta)) * term2;
80+
}
81+
82+
return (alpha / den) * (z / xz) * term2;
83+
}
84+
85+
[[nodiscard]] static Expect<void, Error> InitState(void* coefs_ptr, std::span<const f64> xs,
86+
std::span<const f64> ys) noexcept;
87+
88+
[[nodiscard]] static Expect<f64, Error> Evaluate(const void* coefs_ptr, std::span<const f64> xs, f64 x) noexcept;
89+
};
90+
91+
struct CDR_OPTIONS_EXPORT SabrCostFunctor {
92+
SabrCostFunctor(f64 strike, f64 market_vol, f64 F, f64 T)
93+
: strike_(strike), market_vol_(market_vol), F_(F), T_(T)
94+
{}
95+
96+
template <typename T>
97+
bool operator()(const T* const params, T* residual) const {
98+
T model_vol = SABRInterpolator::CalculateHagan<T>(T(strike_), T(F_), T(T_), params[0], params[1], params[2]);
99+
100+
residual[0] = model_vol - T(market_vol_);
101+
102+
return true;
103+
}
104+
105+
private:
106+
const f64 strike_;
107+
const f64 market_vol_;
108+
const f64 F_;
109+
const f64 T_;
110+
};
111+
112+
} // namespace cdr

0 commit comments

Comments
 (0)