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}