-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathflashcorr.cpp
More file actions
206 lines (164 loc) · 7.04 KB
/
flashcorr.cpp
File metadata and controls
206 lines (164 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/**
* FlashBaseCorr.cpp
*/
#include <iostream>
#include <cmath>
#include <algorithm>
#include <tuple>
#include <iomanip>
// --- Including QuantLib ---
#include <ql/quantlib.hpp>
#include <ql/math/distributions/bivariatenormaldistribution.hpp>
#include <ql/math/distributions/normaldistribution.hpp>
using QuantLib::Real;
using QuantLib::Integer;
using QuantLib::Size;
// Helper: Bivariate PDF for Analytical Gradient
namespace MathHelper {
const Real PI = 3.14159265358979323846;
inline Real bivariate_pdf(Real x, Real y, Real rho) {
Real one_m_rho2 = 1.0 - rho * rho;
if (one_m_rho2 < 1e-12) one_m_rho2 = 1e-12;
Real exponent = (x*x - 2.0*rho*x*y + y*y) / one_m_rho2;
return (1.0 / (2.0 * PI * std::sqrt(one_m_rho2))) * std::exp(-0.5 * exponent);
}
}
class FlashBaseCorrSolver {
private:
Real R;
Real K_prime;
Real alpha; // N^-1(PD)
Real beta; // N^-1(K')
Real LGD; // 1 - R
public:
FlashBaseCorrSolver(Real cumulative_pd_isda, Real recovery, Real detachment)
: R(recovery) {
cumulative_pd_isda = std::max(1e-7, std::min(0.9999, cumulative_pd_isda));
QuantLib::InverseCumulativeNormal inv_norm;
this->alpha = inv_norm(cumulative_pd_isda);
this->LGD = 1.0 - R;
this->K_prime = detachment / LGD;
// Safety clamp for K'
this->K_prime = std::min(this->K_prime, 0.9999);
this->beta = inv_norm(this->K_prime);
}
struct EvalResult {
Real value;
Real grad;
Real hess;
};
// --- Core Engine ---
// Returns {EL, dEL/drho, d2EL/drho2}
EvalResult compute_engine(Real rho) const {
const Real EPS = 1e-5;
// Clamp rho to avoid singularity at 0 and 1
Real r_safe = std::max(EPS, std::min(1.0 - EPS, rho));
Real sqrt_r = std::sqrt(r_safe);
Real sqrt_1_m_r = std::sqrt(1.0 - r_safe);
Real r_copula = -sqrt_r; // Correlation for Phi2
// 1. Calculate dynamic d2 (The Critical Fix)
// d2 = ( beta * sqrt(1-rho) - alpha ) / sqrt(rho)
Real d2 = (beta * sqrt_1_m_r - alpha) / sqrt_r;
// 2. Calculate Value
// EL = LGD * Phi2(alpha, d2; -sqrt(rho)) + K * (1 - N(d2))
// Note: K in formula is un-normalized detachment amount = K_prime * LGD
QuantLib::BivariateCumulativeNormalDistributionDr78 drezner(r_copula);
Real phi2 = drezner(alpha, d2);
// Standard Normal CDF for d2
// QuantLib's CumulativeNormalDistribution is slightly heavy to construct,
// using std::erfc based implementation for speed if possible, but QL is safer.
static const QuantLib::CumulativeNormalDistribution norm_cdf;
Real val_vas = norm_cdf(d2);
Real detachment_amt = K_prime * LGD;
Real total_val = LGD * phi2 + detachment_amt * (1.0 - val_vas);
// 3. Calculate Gradient (The Magic Cancellation Fix)
// dEL/drho = LGD * dPhi2/drho (Partial)
// The terms involving d(d2)/drho cancel out with d(Vasicek)/drho!
// d(Phi2)/d(rho_cop) = PDF(alpha, d2, rho_cop)
Real phi2_pdf = MathHelper::bivariate_pdf(alpha, d2, r_copula);
// Chain rule: rho_cop = -sqrt(rho)
// d(rho_cop)/drho = -0.5 / sqrt(rho)
Real d_rhocop_drho = -0.5 / sqrt_r;
// Exact Analytical Gradient
Real total_grad = LGD * phi2_pdf * d_rhocop_drho;
// 4. Calculate Hessian (The Engineering Fix)
// Analytical Hessian of the new formula is still complex (involves d(d2)/drho).
// Finite Differencing the Gradient is O(1) extra cost and very stable.
// We need grad at rho+h.
Real h = 1e-5;
// Re-compute grad at rho + h
Real r_up = std::min(1.0 - EPS, r_safe + h);
Real sqrt_r_up = std::sqrt(r_up);
Real r_cop_up = -sqrt_r_up;
Real d2_up = (beta * std::sqrt(1.0 - r_up) - alpha) / sqrt_r_up;
Real pdf_up = MathHelper::bivariate_pdf(alpha, d2_up, r_cop_up);
Real d_rhocop_up = -0.5 / sqrt_r_up;
Real grad_up = LGD * pdf_up * d_rhocop_up;
// Central Difference for Hessian
// We use (grad_up - grad) / h (Forward) or (grad_up - grad_down)/2h
// Forward diff is sufficient for Halley direction
Real total_hess = (grad_up - total_grad) / (r_up - r_safe);
return {total_val, total_grad, total_hess};
}
Real solve(Real market_el_val) {
// --- Smart Guess (Corrected Logic) ---
// Equity Tranche: EL decreases as Rho increases (typically)
// Probe endpoints
Real rho_min = 0.05;
Real rho_max = 0.95;
Real el_at_min = compute_engine(rho_min).value;
Real el_at_max = compute_engine(rho_max).value;
Real rho_guess;
// Handle monotonicity check
Real el_ceil = std::max(el_at_min, el_at_max);
Real el_floor = std::min(el_at_min, el_at_max);
if (market_el_val >= el_ceil) {
rho_guess = (el_at_min > el_at_max) ? rho_min : rho_max;
} else if (market_el_val <= el_floor) {
rho_guess = (el_at_min > el_at_max) ? rho_max : rho_min;
} else {
// Linear Interpolation
Real slope = (rho_max - rho_min) / (el_at_max - el_at_min);
rho_guess = rho_min + (market_el_val - el_at_min) * slope;
}
// Z-Transform
Real z = std::atanh(std::sqrt(rho_guess));
z = std::max(0.2, std::min(3.0, z));
// --- Halley Loop ---
for(Size i=0; i<10; ++i) {
Real tanh_z = std::tanh(z);
Real rho = tanh_z * tanh_z;
Real sech2 = 1.0 - tanh_z * tanh_z;
EvalResult ev = compute_engine(rho);
Real f = ev.value - market_el_val;
// Chain Rule to Z
Real drho_dz = 2.0 * tanh_z * sech2;
Real d2rho_dz2 = 2.0 * sech2 * (1.0 - 3.0 * rho);
Real f_z = f;
Real f_prime_z = ev.grad * drho_dz;
Real f_double_prime_z = ev.hess * (drho_dz * drho_dz) + ev.grad * d2rho_dz2;
if (std::abs(f_z) < 1e-9) return rho;
// Gradient vanishing check
if (std::abs(f_prime_z) < 1e-12) return rho;
// Halley Step
Real num = 2.0 * f_z * f_prime_z;
Real den = 2.0 * f_prime_z * f_prime_z - f_z * f_double_prime_z;
Real delta = (std::abs(den) < 1e-10) ? (f_z / f_prime_z) : (num / den);
z -= 0.8 * delta; // Damping 0.8
if (z < 1e-3) z = 1e-3;
if (z > 4.0) z = 4.0;
}
return std::tanh(z) * std::tanh(z);
}
};
int main() {
std::cout << "--- FlashBaseCorr ---" << std::endl;
Real pd = 0.049;
Real recovery = 0.4;
Real detachment = 0.03;
FlashBaseCorrSolver solver(pd, recovery, detachment);
// Assume market EL is relatively high (low correlation)
double rho = solver.solve(0.60 * detachment);
std::cout << "Implied Rho: " << rho << std::endl;
return 0;
}