Skip to content

Commit 89cc35a

Browse files
committed
fix: heston calibration initial params
1 parent 893e27e commit 89cc35a

File tree

1 file changed

+59
-12
lines changed

1 file changed

+59
-12
lines changed

src/quant/calibration/heston.rs

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub struct CalibrationHistory<T> {
6161
#[derive(ImplNew, Clone)]
6262
pub struct HestonCalibrator {
6363
/// Params to calibrate.
64-
pub params: HestonParams,
64+
pub params: Option<HestonParams>,
6565
/// Option prices from the market.
6666
pub c_market: DVector<f64>,
6767
/// Asset price vector.
@@ -88,6 +88,10 @@ impl HestonCalibrator {
8888
pub fn calibrate(&self) -> Result<Vec<CalibrationHistory<HestonParams>>> {
8989
println!("Initial guess: {:?}", self.params);
9090

91+
if self.params.is_none() {
92+
return panic!("Initial parameters are not set. You can use set_initial_params method to guess the initial parameters.");
93+
}
94+
9195
let (result, ..) = LevenbergMarquardt::new().minimize(self.clone());
9296

9397
// Print the c_market
@@ -111,7 +115,7 @@ impl HestonCalibrator {
111115
///
112116
/// Using NMLE (Normal Maximum Likelihood Estimation) method
113117
pub fn set_initial_params(&mut self, s: Array1<f64>, v: Array1<f64>, r: f64) {
114-
self.params = nmle_heston(s, v, r);
118+
self.params = Some(nmle_heston(s, v, r));
115119
}
116120
}
117121

@@ -121,28 +125,29 @@ impl<'a> LeastSquaresProblem<f64, Dyn, Dyn> for HestonCalibrator {
121125
type ResidualStorage = Owned<f64, Dyn>;
122126

123127
fn set_params(&mut self, params: &DVector<f64>) {
124-
self.params = HestonParams::from(params.clone());
128+
self.params = Some(HestonParams::from(params.clone()));
125129
}
126130

127131
fn params(&self) -> DVector<f64> {
128-
self.params.clone().into()
132+
self.params.clone().unwrap().into()
129133
}
130134

131135
fn residuals(&self) -> Option<DVector<f64>> {
132136
let mut c_model = DVector::zeros(self.c_market.len());
133137
let mut derivates = Vec::new();
138+
let params = self.params.clone().unwrap();
134139

135140
for (idx, _) in self.c_market.iter().enumerate() {
136141
let pricer = HestonPricer::new(
137142
self.s[idx],
138-
self.params.v0,
143+
params.v0,
139144
self.k[idx],
140145
self.r,
141146
self.q,
142-
self.params.rho,
143-
self.params.kappa,
144-
self.params.theta,
145-
self.params.sigma,
147+
params.rho,
148+
params.kappa,
149+
params.theta,
150+
params.sigma,
146151
None,
147152
Some(self.tau),
148153
None,
@@ -161,7 +166,7 @@ impl<'a> LeastSquaresProblem<f64, Dyn, Dyn> for HestonCalibrator {
161166
.push(CalibrationHistory {
162167
residuals: c_model.clone() - self.c_market.clone(),
163168
call_put: vec![(call, put)].into(),
164-
params: self.params.clone().into(),
169+
params: params.clone(),
165170
loss_scores: CalibrationLossScore {
166171
mae: self.mae(&self.c_market, &c_model),
167172
mse: self.mse(&self.c_market, &c_model),
@@ -195,6 +200,8 @@ impl<'a> LeastSquaresProblem<f64, Dyn, Dyn> for HestonCalibrator {
195200

196201
#[cfg(test)]
197202
mod tests {
203+
use std::cmp::Ordering;
204+
198205
use super::*;
199206

200207
use anyhow::Result;
@@ -221,13 +228,52 @@ mod tests {
221228

222229
for v in v0.iter() {
223230
let calibrator = HestonCalibrator::new(
224-
HestonParams {
231+
Some(HestonParams {
225232
v0: *v,
226233
theta: 6.47e-5,
227234
rho: -1.98e-3,
228235
kappa: 6.57e-3,
229236
sigma: 5.09e-4,
230-
},
237+
}),
238+
c_market.clone().into(),
239+
s.clone().into(),
240+
k.clone().into(),
241+
tau,
242+
6.40e-4,
243+
None,
244+
OptionType::Call,
245+
);
246+
247+
let data = calibrator.calibrate()?;
248+
println!("Calibration data: {:?}", data);
249+
}
250+
251+
Ok(())
252+
}
253+
254+
#[test]
255+
fn test_heston_calibrate_guess_params() -> Result<()> {
256+
let tau = 24.0 / 365.0;
257+
println!("Time to maturity: {}", tau);
258+
259+
let s = vec![
260+
425.73, 425.73, 425.73, 425.67, 425.68, 425.65, 425.65, 425.68, 425.65, 425.16, 424.78,
261+
425.19,
262+
];
263+
264+
let k = vec![
265+
395.0, 400.0, 405.0, 410.0, 415.0, 420.0, 425.0, 430.0, 435.0, 440.0, 445.0, 450.0,
266+
];
267+
268+
let c_market = vec![
269+
30.75, 25.88, 21.00, 16.50, 11.88, 7.69, 4.44, 2.10, 0.78, 0.25, 0.10, 0.10,
270+
];
271+
272+
let v0 = Array1::linspace(0.0, 0.01, 1);
273+
274+
for v in v0.iter() {
275+
let mut calibrator = HestonCalibrator::new(
276+
None,
231277
c_market.clone().into(),
232278
s.clone().into(),
233279
k.clone().into(),
@@ -236,6 +282,7 @@ mod tests {
236282
None,
237283
OptionType::Call,
238284
);
285+
calibrator.set_initial_params(s.clone().into(), Array1::from_elem(s.len(), *v), 6.40e-4);
239286

240287
let data = calibrator.calibrate()?;
241288
println!("Calibration data: {:?}", data);

0 commit comments

Comments
 (0)