Skip to content

Commit 9de0367

Browse files
committed
fix: bsm calibrator
1 parent cc5e7ed commit 9de0367

File tree

1 file changed

+51
-11
lines changed

1 file changed

+51
-11
lines changed

src/quant/calibration/bsm.rs

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl BSMCalibrator {
7878
println!("Calibration report: {:?}", result.params);
7979
}
8080

81-
pub fn set_intial_guess(&mut self, params: BSMParams) {
81+
pub fn set_initial_guess(&mut self, params: BSMParams) {
8282
self.params = params;
8383
}
8484
}
@@ -97,7 +97,9 @@ impl LeastSquaresProblem<f64, Dyn, Dyn> for BSMCalibrator {
9797
}
9898

9999
fn residuals(&self) -> Option<DVector<f64>> {
100-
let mut c_model = DVector::zeros(self.c_market.len());
100+
let n = self.c_market.len();
101+
let mut c_model = DVector::zeros(n);
102+
let mut vegas: Vec<f64> = Vec::with_capacity(n);
101103
let mut derivates = Vec::new();
102104

103105
for (idx, _) in self.c_market.iter().enumerate() {
@@ -106,8 +108,8 @@ impl LeastSquaresProblem<f64, Dyn, Dyn> for BSMCalibrator {
106108
self.params.v,
107109
self.k[idx],
108110
self.r,
109-
None,
110-
None,
111+
self.r_d,
112+
self.r_f,
111113
self.q,
112114
Some(self.tau),
113115
None,
@@ -122,6 +124,10 @@ impl LeastSquaresProblem<f64, Dyn, Dyn> for BSMCalibrator {
122124
OptionType::Put => c_model[idx] = put,
123125
}
124126

127+
// Collect vega for vega-weighted residuals (calibration in vol space)
128+
let vega = pricer.vega().abs().max(1e-8);
129+
vegas.push(vega);
130+
125131
self
126132
.calibration_history
127133
.borrow_mut()
@@ -145,18 +151,52 @@ impl LeastSquaresProblem<f64, Dyn, Dyn> for BSMCalibrator {
145151
}
146152

147153
let _ = std::mem::replace(&mut *self.derivates.borrow_mut(), derivates);
148-
Some(c_model - self.c_market.clone())
154+
155+
// Vega-weighted residuals approximate minimizing implied vol differences
156+
let mut residuals = DVector::zeros(n);
157+
for i in 0..n {
158+
residuals[i] = (c_model[i] - self.c_market[i]) / vegas[i];
159+
}
160+
161+
Some(residuals)
149162
}
150163

151164
fn jacobian(&self) -> Option<DMatrix<f64>> {
152-
let derivates = self.derivates.borrow();
153-
let derivates = derivates.iter().flatten().cloned().collect::<Vec<f64>>();
165+
// For vega-weighted residuals r = (C_model - C_mkt)/Vega,
166+
// dr/dsigma = 1 - r * (Vomma / Vega)
167+
let n = self.c_market.len();
168+
let mut J = DMatrix::zeros(n, 1);
154169

155-
// The Jacobian matrix is a matrix of partial derivatives
156-
// of the residuals with respect to the parameters.
157-
let jacobian = DMatrix::from_vec(derivates.len() / 5, 5, derivates);
170+
for idx in 0..n {
171+
let pricer = BSMPricer::new(
172+
self.s[idx],
173+
self.params.v,
174+
self.k[idx],
175+
self.r,
176+
self.r_d,
177+
self.r_f,
178+
self.q,
179+
Some(self.tau),
180+
None,
181+
None,
182+
self.option_type,
183+
BSMCoc::BSM1973,
184+
);
185+
186+
let (call, put) = pricer.calculate_call_put();
187+
let c_model_i = match self.option_type {
188+
OptionType::Call => call,
189+
OptionType::Put => put,
190+
};
191+
192+
let vega = pricer.vega().abs().max(1e-8);
193+
let vomma = pricer.vomma();
194+
let r_i = (c_model_i - self.c_market[idx]) / vega;
195+
196+
J[(idx, 0)] = 1.0 - r_i * (vomma / vega);
197+
}
158198

159-
Some(jacobian)
199+
Some(J)
160200
}
161201
}
162202

0 commit comments

Comments
 (0)