surface_lib/lib.rs
1//! # Surface-Lib: Advanced Option Pricing and Volatility Surface Calibration
2//!
3//! `surface-lib` is a high-performance Rust library designed for quantitative finance applications,
4//! specifically focused on option pricing and volatility surface modeling. The library provides
5//! robust implementations of industry-standard models with advanced calibration capabilities.
6//!
7//! ## Core Features
8//!
9//! - **SVI Model**: Stochastic Volatility Inspired model for volatility surface representation
10//! - **Advanced Calibration**: CMA-ES and L-BFGS-B optimization with robust parameter estimation
11//! - **Option Pricing**: Black-Scholes pricing with model-derived implied volatilities
12//! - **Production Ready**: Optimized for real-time trading and backtesting systems
13//!
14//! ## Quick Start
15//!
16//! ```rust,no_run
17//! use surface_lib::{calibrate_svi, price_with_svi, default_configs, CalibrationParams, MarketDataRow, FixedParameters};
18//! use surface_lib::models::svi::svi_model::SVIParams;
19//!
20//! # fn load_market_data() -> Vec<MarketDataRow> { vec![] }
21//! // Load your market data
22//! let market_data: Vec<MarketDataRow> = load_market_data();
23//!
24//! // Calibrate SVI model parameters
25//! let config = default_configs::fast();
26//! let calib_params = CalibrationParams::default();
27//! let (objective, params, used_bounds) = calibrate_svi(market_data.clone(), config, calib_params, None)?;
28//!
29//! // Create SVI parameters for pricing
30//! let svi_params = SVIParams {
31//! t: 0.0274, a: params[0], b: params[1],
32//! rho: params[2], m: params[3], sigma: params[4]
33//! };
34//! let fixed_params = FixedParameters { r: 0.02, q: 0.0 };
35//!
36//! // Price options with calibrated model
37//! let pricing_results = price_with_svi(svi_params, market_data, fixed_params);
38//! # Ok::<(), Box<dyn std::error::Error>>(())
39//! ```
40//!
41//! ## Model Support
42//!
43//! Currently supported volatility models:
44//! - **SVI (Stochastic Volatility Inspired)**: Industry-standard single-slice model
45//!
46//! ## Configuration Presets
47//!
48//! The library provides several optimization configuration presets:
49//! - `production()`: High accuracy for live trading systems
50//! - `fast()`: Balanced speed/accuracy for development
51//! - `research()`: High-precision settings for research
52//! - `minimal()`: Quick validation settings
53
54// ================================================================================================
55// MODULES
56// ================================================================================================
57
58pub mod calibration;
59pub mod model_params;
60pub mod models;
61
62// ================================================================================================
63// IMPORTS
64// ================================================================================================
65
66// Note: HashMap removed as it's no longer used in the API
67
68use anyhow::Result;
69use std::cmp::Ordering;
70
71use calibration::{
72 config::OptimizationConfig as InternalOptimizationConfig,
73 types::MarketDataRow as InternalMarketDataRow,
74};
75use models::{
76 svi::{svi_calibrator::SVIModelCalibrator, svi_model::SVISlice},
77 utils::{price_option, OptionPricingResult},
78};
79// (removed - using public re-export instead)
80
81use crate::calibration::pipeline::calibrate_model_adaptive;
82
83// ================================================================================================
84// PUBLIC RE-EXPORTS
85// ================================================================================================
86
87// Core types for market data and configuration
88pub use calibration::{
89 config::{CmaEsConfig, OptimizationConfig},
90 types::{FixedParameters, MarketDataRow, PricingResult},
91};
92
93// SVI model types and parameters
94pub use models::svi::{svi_calibrator::SVIParamBounds, svi_model::SVIParams};
95
96// Linear IV model types and functions
97pub use models::linear_iv::{
98 build_fixed_time_metrics,
99 build_linear_iv,
100 build_linear_iv_from_market_data,
101 compute_atm_iv,
102 compute_fixed_delta_iv,
103 DeltaIv,
104 DeltaMetrics,
105 FixedTimeMetrics,
106 LinearIvConfig,
107 LinearIvOutput,
108 TemporalConfig,
109 // Temporal interpolation types and functions
110 TemporalInterpMethod,
111};
112
113// Model parameter types
114pub use model_params::{ModelParams, SviModelParams};
115
116// Model parameters for users
117
118// ================================================================================================
119// DEFAULT CONFIGURATIONS
120// ================================================================================================
121
122/// Pre-configured optimization settings for common use cases.
123///
124/// This module provides several optimization configuration presets tailored for different
125/// scenarios, from rapid development to high-precision production trading systems.
126///
127/// # Available Configurations
128///
129/// - [`production()`]: Production-grade settings for live trading
130/// - [`fast()`]: Development-optimized settings
131/// - [`research()`]: High-precision settings for research
132/// - [`minimal()`]: Quick validation settings
133pub mod default_configs {
134 use crate::calibration::config::OptimizationConfig;
135
136 /// Production-grade configuration optimized for live trading systems.
137 ///
138 /// **Characteristics:**
139 /// - Maximum iterations: 5,000
140 /// - Convergence tolerance: 1e-8
141 /// - Robust convergence with high accuracy
142 /// - Suitable for real-time trading environments
143 ///
144 /// **Use Cases:**
145 /// - Live option pricing systems
146 /// - Production volatility surface construction
147 /// - High-frequency trading applications
148 ///
149 /// # Example
150 ///
151 /// ```rust
152 /// use surface_lib::default_configs;
153 ///
154 /// let config = default_configs::production();
155 /// // Use for production calibration...
156 /// ```
157 pub fn production() -> OptimizationConfig {
158 OptimizationConfig::production()
159 }
160
161 /// Fast configuration optimized for development and testing.
162 ///
163 /// **Characteristics:**
164 /// - Maximum iterations: 1,000
165 /// - Convergence tolerance: 1e-6
166 /// - Balanced speed and accuracy
167 /// - Good convergence for most market conditions
168 ///
169 /// **Use Cases:**
170 /// - Development and prototyping
171 /// - Integration testing
172 /// - Quick market analysis
173 ///
174 /// # Example
175 ///
176 /// ```rust
177 /// use surface_lib::default_configs;
178 ///
179 /// let config = default_configs::fast();
180 /// // Use for development...
181 /// ```
182 pub fn fast() -> OptimizationConfig {
183 OptimizationConfig::fast()
184 }
185
186 /// High-precision configuration for research and backtesting.
187 ///
188 /// **Characteristics:**
189 /// - Maximum iterations: 10,000
190 /// - Convergence tolerance: 1e-9
191 /// - Maximum accuracy and precision
192 /// - Extensive parameter exploration
193 ///
194 /// **Use Cases:**
195 /// - Academic research
196 /// - Historical backtesting
197 /// - Model validation studies
198 /// - Parameter sensitivity analysis
199 ///
200 /// # Example
201 ///
202 /// ```rust
203 /// use surface_lib::default_configs;
204 ///
205 /// let config = default_configs::research();
206 /// // Use for research applications...
207 /// ```
208 pub fn research() -> OptimizationConfig {
209 OptimizationConfig::research()
210 }
211
212 /// Minimal configuration for quick validation and debugging.
213 ///
214 /// **Characteristics:**
215 /// - Maximum iterations: 100
216 /// - Convergence tolerance: 1e-4
217 /// - Very fast execution
218 /// - Lower accuracy, suitable for quick checks
219 ///
220 /// **Use Cases:**
221 /// - Quick validation
222 /// - Debugging and troubleshooting
223 /// - Unit tests
224 /// - Proof-of-concept work
225 ///
226 /// # Example
227 ///
228 /// ```rust
229 /// use surface_lib::default_configs;
230 ///
231 /// let config = default_configs::minimal();
232 /// // Use for quick validation...
233 /// ```
234 pub fn minimal() -> OptimizationConfig {
235 OptimizationConfig::minimal()
236 }
237}
238
239/// Configuration parameters for SVI model calibration.
240#[derive(Debug)]
241pub struct CalibrationParams {
242 /// Custom parameter bounds (None for adaptive bounds)
243 pub param_bounds: Option<SVIParamBounds>,
244 /// Optional model-specific parameters (type-erased)
245 pub model_params: Option<Box<dyn ModelParams>>,
246 /// Strength of temporal regularisation on raw parameters (λ).
247 /// None = library default (1e-2) when an initial guess is supplied.
248 pub reg_lambda: Option<f64>,
249}
250
251impl Default for CalibrationParams {
252 fn default() -> Self {
253 Self {
254 param_bounds: None,
255 model_params: Some(Box::new(model_params::SviModelParams::default())),
256 reg_lambda: None,
257 }
258 }
259}
260
261impl CalibrationParams {
262 pub fn conservative() -> Self {
263 Self::default()
264 }
265
266 pub fn aggressive() -> Self {
267 Self::default()
268 }
269
270 pub fn fast() -> Self {
271 Self::default()
272 }
273}
274
275/// Calibrate SVI model parameters to market option data.
276///
277/// This function performs advanced optimization to fit SVI model parameters to observed market
278/// implied volatilities. The optimization uses a two-stage approach: global search with CMA-ES
279/// followed by local refinement with L-BFGS-B for robust parameter estimation.
280///
281/// # Arguments
282///
283/// * `data` - Market option data for a single expiration. Must contain option type, strikes,
284/// underlying price, time to expiration, market implied volatilities, and vega values.
285/// * `config` - Optimization configuration specifying algorithm parameters, tolerances, and
286/// computational limits. Use [`default_configs`] for common presets.
287/// * `calib_params` - Calibration-specific parameters controlling log-moneyness range, arbitrage
288/// checking, and penalty weights. Use [`CalibrationParams::default()`] for standard settings.
289///
290/// # Returns
291///
292/// Returns a tuple containing:
293/// - `f64`: Final objective function value (lower is better)
294/// - `Vec<f64>`: Optimized SVI parameters `[a, b, rho, m, sigma]`
295/// - `SVIParamBounds`: The actual bounds used during optimization (can be fed back as input)
296///
297/// # Errors
298///
299/// * `anyhow::Error` if the data contains multiple expirations (SVI requires single expiration)
300/// * `anyhow::Error` if market data is insufficient or contains invalid values
301/// * `anyhow::Error` if optimization fails to converge within specified limits
302///
303/// # SVI Parameters
304///
305/// The SVI model parameterizes total variance as:
306/// ```text
307/// w(k) = a + b * (ρ(k-m) + sqrt((k-m)² + σ²))
308/// ```
309/// Where:
310/// - `a`: Base variance level (vertical shift)
311/// - `b`: Slope factor (overall variance level)
312/// - `ρ`: Asymmetry parameter (skew, must be in (-1, 1))
313/// - `m`: Horizontal shift (ATM location in log-moneyness)
314/// - `σ`: Curvature parameter (smile curvature, must be > 0)
315///
316/// # Example
317///
318/// ```rust,no_run
319/// use surface_lib::{calibrate_svi, default_configs, CalibrationParams, MarketDataRow};
320///
321/// // Load market data for a single expiration
322/// let market_data: Vec<MarketDataRow> = load_single_expiry_data();
323///
324/// // Use fast configuration for development
325/// let config = default_configs::fast();
326/// let calib_params = CalibrationParams::default();
327///
328/// // Calibrate SVI parameters
329/// match calibrate_svi(market_data, config, calib_params, None) {
330/// Ok((objective, params, used_bounds)) => {
331/// println!("Calibration successful!");
332/// println!("Final objective: {:.6}", objective);
333/// println!("SVI parameters: {:?}", params);
334/// println!("Used bounds: {:?}", used_bounds);
335/// }
336/// Err(e) => eprintln!("Calibration failed: {}", e),
337/// }
338/// # fn load_single_expiry_data() -> Vec<MarketDataRow> { vec![] }
339/// ```
340///
341/// # Performance Notes
342///
343/// - Calibration typically takes 1-10 seconds depending on configuration and data size
344/// - Memory usage scales linearly with the number of option contracts
345/// - For production systems, consider using [`default_configs::production()`] for optimal accuracy
346pub fn calibrate_svi(
347 data: Vec<InternalMarketDataRow>,
348 config: InternalOptimizationConfig,
349 calib_params: CalibrationParams,
350 initial_guess: Option<Vec<f64>>,
351) -> Result<(f64, Vec<f64>, SVIParamBounds)> {
352 // Create SVI calibrator with user-provided parameters
353 let mut calibrator =
354 SVIModelCalibrator::new(&data, calib_params.param_bounds, calib_params.model_params)?;
355
356 // If we have an initial guess, use it both as warm-start and as regularisation anchor
357 if let Some(ref guess) = initial_guess {
358 calibrator.set_prev_solution(guess.clone());
359 let lambda = calib_params.reg_lambda.unwrap_or(1e-2);
360 calibrator.set_temporal_reg_lambda(lambda);
361 }
362
363 // Execute calibration using adaptive pipeline directly
364 let (best_obj, best_params, bounds_vec) =
365 calibrate_model_adaptive(Box::new(calibrator), &data, &config, initial_guess);
366
367 // Convert the bounds vector back to SVIParamBounds
368 let used_bounds = SVIParamBounds::from(bounds_vec.as_slice());
369
370 Ok((best_obj, best_params, used_bounds))
371}
372
373/// Evaluate the SVI calibration objective for a fixed parameter set.
374///
375/// This produces **exactly the same loss value** that `calibrate_svi` minimises
376/// internally, honouring any ATM-boost and vega-weighting settings embedded in
377/// `calib_params`. It enables external callers (e.g. live monitoring) to
378/// measure model fit quality without re-running the optimiser.
379pub fn evaluate_svi(
380 data: Vec<MarketDataRow>,
381 params: SVIParams,
382 calib_params: CalibrationParams,
383) -> Result<f64> {
384 use crate::calibration::types::ModelCalibrator;
385 use crate::model_params::SviModelParams;
386 use models::svi::svi_calibrator::SVIModelCalibrator;
387
388 // Clone model_params if it is SviModelParams; otherwise pass None.
389 let mp_clone: Option<Box<dyn crate::model_params::ModelParams>> =
390 calib_params.model_params.as_ref().and_then(|mp| {
391 mp.as_any()
392 .downcast_ref::<SviModelParams>()
393 .map(|p| Box::new(p.clone()) as Box<dyn crate::model_params::ModelParams>)
394 });
395
396 let calibrator = SVIModelCalibrator::new(&data, calib_params.param_bounds.clone(), mp_clone)?;
397
398 let p_vec = vec![params.a, params.b, params.rho, params.m, params.sigma];
399 Ok(ModelCalibrator::evaluate_objective(
400 &calibrator,
401 &p_vec,
402 &data,
403 ))
404}
405
406/// Price European options using calibrated SVI model parameters.
407///
408/// This function takes pre-calibrated SVI parameters and applies them to price a set of options
409/// using the Black-Scholes framework with SVI-derived implied volatilities. The pricing is
410/// efficient and suitable for real-time applications.
411///
412/// # Arguments
413///
414/// * `params` - Calibrated SVI parameters containing the time to expiration and model coefficients
415/// * `market_data` - Option contracts to price (can be same or different from calibration data)
416/// * `fixed_params` - Market parameters including risk-free rate and dividend yield
417///
418/// # Returns
419///
420/// Vector of [`PricingResult`] containing option details, model prices, and implied volatilities.
421/// Results are sorted by strike price in ascending order.
422///
423/// # Pricing Methodology
424///
425/// 1. **Log-moneyness calculation**: `k = ln(K/S)` for each option
426/// 2. **SVI implied volatility**: `σ(k) = sqrt(w(k)/t)` where `w(k)` is SVI total variance
427/// 3. **Black-Scholes pricing**: European option price using SVI-derived volatility
428/// 4. **Result compilation**: Organized results with validation and error handling
429///
430/// # Example
431///
432/// ```rust,no_run
433/// use surface_lib::{
434/// price_with_svi, MarketDataRow, FixedParameters,
435/// models::svi::svi_model::SVIParams
436/// };
437///
438/// # let market_data: Vec<MarketDataRow> = vec![];
439/// // Create SVI parameters (typically from calibration)
440/// let svi_params = SVIParams {
441/// t: 0.0274, // ~10 days to expiration
442/// a: 0.04, // Base variance
443/// b: 0.2, // Slope factor
444/// rho: -0.3, // Negative skew
445/// m: 0.0, // ATM at log-moneyness 0
446/// sigma: 0.2, // Curvature
447/// };
448///
449/// // Market parameters
450/// let fixed_params = FixedParameters {
451/// r: 0.02, // 2% risk-free rate
452/// q: 0.0, // No dividend yield
453/// };
454///
455/// // Price options
456/// let pricing_results = price_with_svi(svi_params, market_data, fixed_params);
457///
458/// for result in &pricing_results {
459/// println!("Strike {}: Price ${:.2}, IV {:.1}%",
460/// result.strike_price,
461/// result.model_price,
462/// result.model_iv * 100.0);
463/// }
464/// ```
465///
466/// # Error Handling
467///
468/// The function uses robust error handling:
469/// - Invalid pricing results default to zero price and implied volatility
470/// - Time mismatches between SVI parameters and market data are handled gracefully
471/// - Non-finite or negative volatilities are caught and logged
472///
473/// # Performance Notes
474///
475/// - Pricing scales linearly with the number of options
476/// - Typical performance: 10,000+ options per second on modern hardware
477/// - Memory usage is minimal with in-place calculations
478pub fn price_with_svi(
479 params: SVIParams,
480 market_data: Vec<MarketDataRow>,
481 fixed_params: FixedParameters,
482) -> Vec<PricingResult> {
483 // Create SVI volatility slice from parameters
484 let slice = SVISlice::new(params);
485 let r = fixed_params.r;
486 let q = fixed_params.q;
487
488 // Pre-allocate results vector for efficiency
489 let mut results = Vec::with_capacity(market_data.len());
490
491 // Price each option using SVI-derived implied volatility
492 for row in market_data {
493 let pricing_result = price_option(
494 &row.option_type,
495 row.strike_price,
496 row.underlying_price,
497 r,
498 q,
499 row.years_to_exp,
500 &slice,
501 )
502 .unwrap_or(OptionPricingResult {
503 price: 0.0,
504 model_iv: 0.0,
505 });
506
507 results.push(PricingResult {
508 option_type: row.option_type,
509 strike_price: row.strike_price,
510 underlying_price: row.underlying_price,
511 years_to_exp: row.years_to_exp,
512 model_price: pricing_result.price,
513 model_iv: pricing_result.model_iv,
514 });
515 }
516
517 // Sort results by strike price for consistent ordering
518 results.sort_by(|a, b| {
519 a.strike_price
520 .partial_cmp(&b.strike_price)
521 .unwrap_or(Ordering::Equal)
522 });
523 results
524}