Skip to main content

optionstratlib_pricing/error/
greeks.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 25/12/24
5******************************************************************************/
6
7//! # Greeks Error Module
8//!
9//! This module provides error handling for Greek calculations and equations in option pricing.
10//! It defines error types for various mathematical calculations and validations used in
11//! financial derivatives analysis.
12//!
13//! ## Error Types
14//!
15//! ### Greeks Error (`GreeksError`)
16//! Main error enum that encompasses:
17//! * Calculation errors in Greek values
18//! * Input validation errors
19//! * Mathematical operation errors
20//! * Boundary condition errors
21//!
22//! ### Mathematical Error (`MathErrorKind`)
23//! Handles specific mathematical errors:
24//! * Division by zero
25//! * Overflow conditions
26//! * Invalid domain errors
27//! * Convergence failures
28//!
29//! ### Input Validation Error (`InputErrorKind`)
30//! Manages validation of input parameters:
31//! * Invalid volatility values
32//! * Invalid time values
33//! * Invalid price values
34//! * Invalid rate values
35//!
36//! Target crate (ADR-0001 D6, roadmap M1-14): **pricing**. Owns `GreeksError`.
37//!
38//! The `...Kind` enums are canonical in this module (`optionstratlib_pricing::error::greeks`) and are not
39//! flattened into `optionstratlib_pricing::error`: the facade gathers every crate's errors
40//! in one `optionstratlib::error` module, where flat kind names would collide
41//! (`StrategyErrorKind` is defined in both core `error::position` and market
42//! `error::chains`, `PriceErrorKind` in both analytics `error::probability`
43//! and strategies `error::strategies`), so each crate keeps its kinds in its
44//! kind module and the facade re-exports that module (#550).
45
46use crate::error::VolatilityError;
47use optionstratlib_core::error::DecimalError;
48use optionstratlib_core::model::ExpirationDateError;
49use optionstratlib_core::model::Positive;
50use thiserror::Error;
51
52/// Represents errors that can occur during options Greek calculations.
53///
54/// This enum encapsulates the various types of errors that might arise during
55/// the calculation of option Greeks (delta, gamma, theta, vega, rho, vanna, vomma,
56/// veta, etc.) and related financial computations. It provides a structured approach
57/// to error handling by categorizing errors based on their nature and source.
58///
59/// `GreeksError` serves as the primary error type for the Greek calculation system,
60/// allowing for precise error reporting and handling across different calculation stages.
61#[derive(Error, Debug)]
62pub enum GreeksError {
63    /// Errors related to mathematical calculations such as division by zero,
64    /// overflow, domain errors, or convergence failures.
65    #[error("Mathematical error: {0}")]
66    MathError(MathErrorKind),
67
68    /// Errors related to input validation, including invalid volatility, time,
69    /// price, interest rate, or strike price values.
70    #[error("Input validation error: {0}")]
71    InputError(InputErrorKind),
72
73    /// Errors specific to the calculation of individual Greeks (delta, gamma,
74    /// theta, vega, rho) or other option-related computations.
75    #[error("Greek calculation error: {0}")]
76    CalculationError(CalculationErrorKind),
77
78    /// Errors specific to delta-neutrality calculations and adjustments.
79    #[error("Delta neutrality error: {0}")]
80    DeltaNeutrality(DeltaNeutralityErrorKind),
81
82    /// Expiration-date conversion error surfaced during Greeks calculations.
83    #[error(transparent)]
84    ExpirationDate(#[from] ExpirationDateError),
85
86    /// Pricing-layer error surfaced during Greeks calculations (e.g. numerical
87    /// Greeks falling back to a pricing engine).
88    #[error(transparent)]
89    Pricing(Box<crate::error::PricingError>),
90
91    /// Positive value errors
92    #[error(transparent)]
93    PositiveError(#[from] optionstratlib_core::model::PositiveError),
94
95    /// A Greeks kernel produced a non-finite `f64` value (`NaN` /
96    /// `±∞`) at an `f64` → `Decimal` boundary.
97    ///
98    /// Emitted when analytical or numerical Greeks (`delta`, `gamma`,
99    /// `vega`, `theta`, `rho`, `vanna`, `vomma`, `veta`, `charm`,
100    /// `color`, or their finite-difference variants) produce an
101    /// intermediate `f64` that would otherwise be silently cast into
102    /// `Decimal`. `context` is a static call-site tag following the
103    /// same convention as [`optionstratlib_core::error::DecimalError::Overflow`].
104    #[error("greeks non-finite {context}: {value}")]
105    NonFinite {
106        /// Static tag identifying the kernel and step that produced
107        /// the non-finite value.
108        context: &'static str,
109        /// The offending `f64` value (`NaN`, `+∞`, or `-∞`).
110        value: f64,
111    },
112}
113
114impl From<crate::error::PricingError> for GreeksError {
115    #[inline]
116    fn from(err: crate::error::PricingError) -> Self {
117        GreeksError::Pricing(Box::new(err))
118    }
119}
120
121/// Errors raised by delta-neutrality sizing and adjustment helpers.
122///
123/// These variants replace the former `GreeksError::StdError(String)` catch-all for
124/// the `greeks::calculate_delta_neutral_sizes` and strategy delta-neutrality flows,
125/// providing a precise, typed description of why a neutral size could not be
126/// produced.
127#[derive(Error, Debug)]
128pub enum DeltaNeutralityErrorKind {
129    /// One (or both) of the input deltas is zero, which makes neutrality impossible.
130    #[error("deltas cannot be zero for delta neutrality")]
131    ZeroDelta,
132
133    /// The two deltas are equal, so their linear combination cannot produce a zero delta.
134    #[error("deltas cannot be equal for delta neutrality")]
135    EqualDeltas,
136
137    /// The two deltas share the same sign, so they cannot offset each other.
138    #[error("deltas must have opposite signs for delta neutrality")]
139    SameSignDeltas,
140
141    /// A candidate sizing would require negative contract counts.
142    #[error("delta neutrality would require negative position sizes")]
143    NegativePositionSize,
144
145    /// The delta residual after sizing exceeds the neutrality threshold.
146    #[error("could not achieve delta neutrality: residual above threshold")]
147    NotAchievable,
148
149    /// The sum of the calculated sizes does not match the requested total size.
150    #[error("calculated sizes {calculated} do not match requested total size {expected}")]
151    SizeMismatch {
152        /// Sum of the sizes produced by the neutrality solver.
153        calculated: Positive,
154        /// Total size originally requested by the caller.
155        expected: Positive,
156    },
157
158    /// The strategy has no options to evaluate, so delta neutrality is undefined.
159    #[error("no options found for delta-neutral calculation")]
160    EmptyOptions,
161
162    /// An option's delta-per-contract is zero, which prevents neutral sizing.
163    #[error("option delta per contract cannot be zero")]
164    OptionDeltaZero,
165
166    /// The strategy does not hold enough contracts to perform the required sell adjustment.
167    #[error("insufficient contracts to perform the requested delta adjustment")]
168    InsufficientContracts,
169}
170
171/// Represents various types of mathematical errors that can occur during calculations.
172///
173/// This enum provides specific error categories for mathematical operations, allowing
174/// precise error handling and informative reporting. Each variant contains detailed
175/// information about the specific error condition that occurred.
176///
177/// `MathErrorKind` serves as part of the error handling system for numerical calculations,
178/// particularly in financial and statistical contexts where numerical stability and
179/// precision are critical.
180///
181/// # Variants
182///
183/// * `DivisionByZero` - Represents the fundamental error of attempting to divide by zero,
184///   which is mathematically undefined.
185///
186/// * `Overflow` - Represents errors when calculations exceed the numerical limits of
187///   the underlying data type, typically with very large numbers or exponential operations.
188///
189/// * `InvalidDomain` - Represents errors when a mathematical function is evaluated
190///   outside its valid domain, containing both the problematic value and reason.
191///
192/// * `ConvergenceFailure` - Represents errors when an iterative algorithm fails to
193///   converge to a solution within specified parameters.
194///
195/// # Usage
196///
197/// This error type is typically used in financial models, statistical calculations,
198/// and numerical algorithms where precise error identification is essential for
199/// debugging and proper error handling.
200///
201/// ```rust
202/// use optionstratlib_pricing::error::greeks::MathErrorKind;
203///
204/// fn calculate_square_root(value: f64) -> Result<f64, MathErrorKind> {
205///     if value < 0.0 {
206///         return Err(MathErrorKind::InvalidDomain {
207///             value,
208///             reason: "Cannot calculate square root of a negative number".to_string()
209///         });
210///     }
211///     Ok(value.sqrt())
212/// }
213/// ```
214#[derive(Error, Debug)]
215pub enum MathErrorKind {
216    /// Error that occurs when attempting to divide by zero.
217    ///
218    /// This is a fundamental mathematical error that must be caught to prevent undefined behavior.
219    /// In numerical calculations, division by zero is undefined and will cause program crashes
220    /// if not properly handled.
221    #[error("Division by zero")]
222    DivisionByZero,
223
224    /// Error that occurs when a calculation exceeds the numerical limits of the data type.
225    ///
226    /// This typically happens with very large numbers or during exponential operations.
227    /// Overflow errors can lead to incorrect results and should be caught to maintain
228    /// calculation integrity.
229    #[error("Numerical overflow")]
230    Overflow,
231
232    /// Error that occurs when a function is evaluated outside its valid domain.
233    ///
234    /// # Fields
235    /// * `value` - The input value that caused the domain error
236    /// * `reason` - A descriptive explanation of why the value is invalid
237    ///
238    /// Domain errors are common in mathematical functions like logarithms, square roots,
239    /// and trigonometric functions where certain input values are not allowed.
240    #[error("Invalid domain value {value}: {reason}")]
241    InvalidDomain {
242        /// The value that was outside the valid domain
243        value: f64,
244        /// Detailed explanation of why the value is invalid for the operation
245        reason: String,
246    },
247
248    /// Error that occurs when an iterative algorithm fails to converge to a solution.
249    ///
250    /// # Fields
251    /// * `iterations` - The number of iterations performed before failure
252    /// * `tolerance` - The convergence tolerance that was not satisfied
253    ///
254    /// Convergence failures typically occur in numerical methods like Newton-Raphson,
255    /// implied volatility calculations, or other root-finding algorithms.
256    #[error("Failed to converge after {iterations} iterations (tolerance: {tolerance})")]
257    ConvergenceFailure {
258        /// Number of iterations attempted before giving up
259        iterations: usize,
260        /// The tolerance threshold that wasn't met during convergence
261        tolerance: f64,
262    },
263}
264
265/// Represents different types of input validation errors that can occur during financial calculations.
266///
267/// This enum encapsulates various error conditions related to the validation of input parameters
268/// in financial models, particularly for options pricing and risk analysis. Each variant contains
269/// detailed information about the invalid input, including both the problematic value and a reason
270/// explaining why it was rejected.
271///
272/// # Variants
273///
274/// * `InvalidVolatility` - Represents errors related to improper volatility values
275///   such as negative values or unreasonably large inputs that would cause calculation issues.
276///
277/// * `InvalidTime` - Represents errors related to time inputs (typically time to expiration)
278///   that are outside acceptable bounds or otherwise unsuitable for financial calculations.
279///
280/// * `InvalidPrice` - Represents errors related to price inputs (like underlying asset prices)
281///   that are negative or otherwise invalid for the calculation context.
282///
283/// * `InvalidRate` - Represents errors related to interest rate values that are outside
284///   acceptable bounds for the specific financial modeling context.
285///
286/// * `InvalidStrike` - Represents errors related to strike price inputs that are malformed,
287///   out of bounds, or otherwise unsuitable for options calculations.
288///
289/// # Usage
290///
291/// These error kinds are typically used within higher-level error types to provide specific
292/// information about validation failures, enabling precise error handling and informative
293/// error messages for users.
294#[derive(Error, Debug)]
295pub enum InputErrorKind {
296    /// Error indicating an invalid volatility input.
297    ///
298    /// This error occurs when a volatility value is outside acceptable bounds
299    /// (typically negative values or unreasonably large values) or otherwise invalid
300    /// for the calculation being performed.
301    #[error("Invalid volatility {value}: {reason}")]
302    InvalidVolatility {
303        /// The invalid volatility value that was provided
304        value: f64,
305        /// Detailed explanation of why the volatility value is invalid
306        reason: String,
307    },
308
309    /// Error indicating an invalid time input.
310    ///
311    /// This error occurs when a time value (typically representing time to expiration)
312    /// is outside acceptable bounds or otherwise invalid for the calculation being performed.
313    #[error("Invalid time {value}: {reason}")]
314    InvalidTime {
315        /// The invalid time value that was provided (as a Positive type)
316        value: Positive,
317        /// Detailed explanation of why the time value is invalid
318        reason: String,
319    },
320
321    /// Error indicating an invalid price input.
322    ///
323    /// This error occurs when a price value (such as an underlying asset price)
324    /// is outside acceptable bounds (typically negative values) or otherwise
325    /// invalid for the calculation being performed.
326    #[error("Invalid price {value}: {reason}")]
327    InvalidPrice {
328        /// The invalid price value that was provided
329        value: f64,
330        /// Detailed explanation of why the price value is invalid
331        reason: String,
332    },
333
334    /// Error indicating an invalid interest rate input.
335    ///
336    /// This error occurs when an interest rate value is outside acceptable bounds
337    /// or otherwise invalid for the calculation being performed.
338    #[error("Invalid rate {value}: {reason}")]
339    InvalidRate {
340        /// The invalid interest rate value that was provided
341        value: f64,
342        /// Detailed explanation of why the rate value is invalid
343        reason: String,
344    },
345
346    /// Error indicating an invalid strike price input.
347    ///
348    /// This error occurs when a strike price value is outside acceptable bounds,
349    /// in an incorrect format, or otherwise invalid for the calculation being performed.
350    #[error("Invalid strike {value}: {reason}")]
351    InvalidStrike {
352        /// The invalid strike value that was provided (as a String)
353        value: String,
354        /// Detailed explanation of why the strike value is invalid
355        reason: String,
356    },
357}
358
359/// Represents specific error types that can occur during financial derivative calculations.
360///
361/// This enum categorizes errors that happen during the calculation of option Greeks and other
362/// financial metrics. Each variant provides detailed context about what went wrong during
363/// the specific calculation, allowing for precise error handling and debugging.
364///
365/// The enum is designed to be used within a broader error handling system for options pricing
366/// and financial calculations, providing specific error types for different aspects of
367/// the derivatives pricing process.
368#[derive(Error, Debug)]
369pub enum CalculationErrorKind {
370    /// Error in delta calculation
371    ///
372    /// Delta measures the rate of change of the option price with respect to changes
373    /// in the underlying asset's price.
374    #[error("Delta calculation error: {reason}")]
375    DeltaError {
376        /// Detailed description of what caused the delta calculation to fail
377        reason: String,
378    },
379    /// Error in gamma calculation
380    ///
381    /// Gamma measures the rate of change of delta with respect to changes in the
382    /// underlying asset's price.
383    #[error("Gamma calculation error: {reason}")]
384    GammaError {
385        /// Detailed description of what caused the gamma calculation to fail
386        reason: String,
387    },
388    /// Error in theta calculation
389    ///
390    /// Theta measures the rate of decay of the option's value over time, often
391    /// referred to as time decay.
392    #[error("Theta calculation error: {reason}")]
393    ThetaError {
394        /// Detailed description of what caused the theta calculation to fail
395        reason: String,
396    },
397    /// Error in vega calculation
398    ///
399    /// Vega measures the sensitivity of the option price to changes in the
400    /// underlying asset's volatility.
401    #[error("Vega calculation error: {reason}")]
402    VegaError {
403        /// Detailed description of what caused the vega calculation to fail
404        reason: String,
405    },
406    /// Error in rho calculation
407    ///
408    /// Rho measures the sensitivity of the option price to changes in the
409    /// risk-free interest rate.
410    #[error("Rho calculation error: {reason}")]
411    RhoError {
412        /// Detailed description of what caused the rho calculation to fail
413        reason: String,
414    },
415    /// Error in vanna calculation
416    ///
417    /// Vanna measures the sensitivity of the option delta to changes in the
418    /// underlying asset's volatility.
419    #[error("Vanna calculation error: {reason}")]
420    VannaError {
421        /// Detailed description of what caused the vanna calculation to fail
422        reason: String,
423    },
424    /// Error in vomma calculation
425    ///
426    /// Vomma measures the sensitivity of the option vega to changes in the
427    /// underlying asset's volatility.
428    #[error("Vomma calculation error: {reason}")]
429    VommaError {
430        /// Detailed description of what caused the vomma calculation to fail
431        reason: String,
432    },
433    /// Error in veta calculation
434    ///
435    /// Veta measures the sensitivity of the option vega with respect to the
436    /// passage of time.
437    #[error("Veta calculation error: {reason}")]
438    VetaError {
439        /// Detailed description of what caused the veta calculation to fail
440        reason: String,
441    },
442    /// Error in charm calculation
443    ///
444    /// Charm measures the sensitivity of the option delta with respect to the
445    /// passage of time.
446    #[error("Charm calculation error: {reason}")]
447    CharmError {
448        /// Detailed description of what caused the charm calculation to fail
449        reason: String,
450    },
451    /// Error in color calculation
452    ///
453    /// Color measures the sensitivity of the option gamma with respect to the
454    /// passage of time.
455    #[error("Color calculation error: {reason}")]
456    ColorError {
457        /// Detailed description of what caused the color calculation to fail
458        reason: String,
459    },
460    /// Error originating from decimal operations
461    ///
462    /// Wraps a decimal library error that occurred during option calculations,
463    /// typically related to precision, arithmetic operations, or invalid values.
464    #[error(transparent)]
465    DecimalError(#[from] DecimalError),
466}
467
468/// Type alias for Results returned from Greek calculation functions.
469///
470/// This alias wraps the standard Rust `Result` type to provide a specialized
471/// result type for Greek calculations, using `GreeksError` as the error type.
472///
473/// # Type Parameters
474///
475/// * `T` - The success value type that will be returned when operations succeed.
476///
477/// # Related Types
478///
479/// This type alias is part of the error handling system for Greek calculations
480/// and works with the `GreeksError` enum which provides detailed error information.
481///
482/// # Usage Context
483///
484/// Typically used in functions that calculate option Greeks (delta, gamma, theta,
485/// vega, rho, vanna, vomma, veta) and other financial metrics where specialized
486/// error handling for mathematical and input validation errors is needed.
487pub type GreeksResult<T> = Result<T, GreeksError>;
488
489/// Implementation of factory methods for creating specific `GreeksError` instances.
490///
491/// This implementation provides convenient constructor methods for creating different types
492/// of errors that can occur during options Greek calculations. These methods make error creation
493/// more concise and readable in the codebase, while ensuring consistent error formatting.
494impl GreeksError {
495    /// Creates an error for invalid volatility values.
496    ///
497    /// Use this method when a volatility input is outside acceptable bounds or otherwise
498    /// unsuitable for calculations. Common cases include negative values or unreasonably
499    /// large volatilities.
500    ///
501    /// # Parameters
502    /// * `value` - The invalid volatility value that triggered the error
503    /// * `reason` - An explanation of why the volatility value is invalid
504    ///
505    /// # Returns
506    /// A `GreeksError::InputError` with `InvalidVolatility` kind
507    #[must_use]
508    #[cold]
509    #[inline(never)]
510    pub fn invalid_volatility(value: f64, reason: &str) -> Self {
511        GreeksError::InputError(InputErrorKind::InvalidVolatility {
512            value,
513            reason: reason.to_string(),
514        })
515    }
516
517    /// Creates an error for invalid time values.
518    ///
519    /// Use this method when a time input (typically representing time to expiration)
520    /// is outside acceptable bounds or otherwise invalid for calculations.
521    ///
522    /// # Parameters
523    /// * `value` - The invalid time value (as a `Positive` type) that triggered the error
524    /// * `reason` - An explanation of why the time value is invalid
525    ///
526    /// # Returns
527    /// A `GreeksError::InputError` with `InvalidTime` kind
528    #[must_use]
529    #[cold]
530    #[inline(never)]
531    pub fn invalid_time(value: Positive, reason: &str) -> Self {
532        GreeksError::InputError(InputErrorKind::InvalidTime {
533            value,
534            reason: reason.to_string(),
535        })
536    }
537
538    /// Creates an error for delta calculation failures.
539    ///
540    /// Use this method when a calculation of the delta Greek value fails for any reason.
541    /// Delta measures the rate of change of option price with respect to changes in the
542    /// underlying asset price.
543    ///
544    /// # Parameters
545    /// * `reason` - A detailed explanation of what caused the delta calculation to fail
546    ///
547    /// # Returns
548    /// A `GreeksError::CalculationError` with `DeltaError` kind
549    #[must_use]
550    #[cold]
551    #[inline(never)]
552    pub fn delta_error(reason: &str) -> Self {
553        GreeksError::CalculationError(CalculationErrorKind::DeltaError {
554            reason: reason.to_string(),
555        })
556    }
557
558    /// Creates a [`GreeksError::NonFinite`] from a static call-site
559    /// tag and the offending `f64` value.
560    ///
561    /// Intended to be used at `f64` → `Decimal` boundaries inside
562    /// Greeks kernels, as a thin constructor paired with an
563    /// `if !value.is_finite() { .. }` guard.
564    #[cold]
565    #[inline(never)]
566    #[must_use]
567    pub fn non_finite(context: &'static str, value: f64) -> Self {
568        GreeksError::NonFinite { context, value }
569    }
570}
571
572/// Implements conversion from `DecimalError` to `GreeksError`.
573///
574/// This implementation allows decimal calculation errors to be automatically converted
575/// into the appropriate `GreeksError` variant, simplifying error handling when working
576/// with decimal operations in financial calculations.
577///
578impl From<DecimalError> for GreeksError {
579    fn from(error: DecimalError) -> Self {
580        GreeksError::CalculationError(CalculationErrorKind::DecimalError(error))
581    }
582}
583
584/// Implements conversion from `VolatilityError` to `GreeksError`.
585///
586/// This implementation allows volatility-related errors to be automatically converted
587/// into appropriate `InputErrorKind::InvalidVolatility` errors, providing consistent
588/// error handling for invalid volatility values.
589///
590impl From<VolatilityError> for GreeksError {
591    fn from(error: VolatilityError) -> Self {
592        GreeksError::InputError(InputErrorKind::InvalidVolatility {
593            value: 0.0,
594            reason: error.to_string(),
595        })
596    }
597}
598
599impl From<DeltaNeutralityErrorKind> for GreeksError {
600    #[inline]
601    fn from(kind: DeltaNeutralityErrorKind) -> Self {
602        GreeksError::DeltaNeutrality(kind)
603    }
604}
605
606#[cfg(test)]
607mod tests_error_greeks {
608    use super::*;
609
610    #[test]
611    fn test_invalid_volatility_error_creation() {
612        let error = GreeksError::invalid_volatility(-0.5, "Volatility cannot be negative");
613        match error {
614            GreeksError::InputError(InputErrorKind::InvalidVolatility { value, reason }) => {
615                assert_eq!(value, -0.5);
616                assert_eq!(reason, "Volatility cannot be negative");
617            }
618            _ => panic!("Wrong error type"),
619        }
620    }
621
622    #[test]
623    fn test_delta_error_creation() {
624        let error = GreeksError::delta_error("Failed to calculate delta");
625        match error {
626            GreeksError::CalculationError(CalculationErrorKind::DeltaError { reason }) => {
627                assert_eq!(reason, "Failed to calculate delta");
628            }
629            _ => panic!("Wrong error type"),
630        }
631    }
632
633    #[test]
634    fn test_math_error_display() {
635        let error = GreeksError::MathError(MathErrorKind::DivisionByZero);
636        assert_eq!(error.to_string(), "Mathematical error: Division by zero");
637
638        let error = GreeksError::MathError(MathErrorKind::InvalidDomain {
639            value: 1.5,
640            reason: "Value out of range".to_string(),
641        });
642        assert_eq!(
643            error.to_string(),
644            "Mathematical error: Invalid domain value 1.5: Value out of range"
645        );
646    }
647
648    #[test]
649    fn test_input_error_display() {
650        let error = GreeksError::InputError(InputErrorKind::InvalidPrice {
651            value: -100.0,
652            reason: "Price cannot be negative".to_string(),
653        });
654        assert_eq!(
655            error.to_string(),
656            "Input validation error: Invalid price -100: Price cannot be negative"
657        );
658
659        let error = GreeksError::InputError(InputErrorKind::InvalidRate {
660            value: 2.5,
661            reason: "Rate must be between 0 and 1".to_string(),
662        });
663        assert_eq!(
664            error.to_string(),
665            "Input validation error: Invalid rate 2.5: Rate must be between 0 and 1"
666        );
667    }
668
669    #[test]
670    fn test_calculation_error_display() {
671        let error = GreeksError::CalculationError(CalculationErrorKind::GammaError {
672            reason: "Invalid input parameters".to_string(),
673        });
674        assert_eq!(
675            error.to_string(),
676            "Greek calculation error: Gamma calculation error: Invalid input parameters"
677        );
678
679        let error = GreeksError::CalculationError(CalculationErrorKind::VegaError {
680            reason: "Calculation overflow".to_string(),
681        });
682        assert_eq!(
683            error.to_string(),
684            "Greek calculation error: Vega calculation error: Calculation overflow"
685        );
686    }
687
688    #[test]
689    fn test_convergence_failure_display() {
690        let error = GreeksError::MathError(MathErrorKind::ConvergenceFailure {
691            iterations: 1000,
692            tolerance: 0.0001,
693        });
694        assert_eq!(
695            error.to_string(),
696            "Mathematical error: Failed to converge after 1000 iterations (tolerance: 0.0001)"
697        );
698    }
699
700    #[test]
701    fn test_result_type() {
702        fn test_function() -> GreeksResult<f64> {
703            Err(GreeksError::delta_error("Test error"))
704        }
705
706        let result = test_function();
707        assert!(result.is_err());
708        match result {
709            Err(GreeksError::CalculationError(CalculationErrorKind::DeltaError { reason })) => {
710                assert_eq!(reason, "Test error");
711            }
712            _ => panic!("Wrong error type"),
713        }
714    }
715
716    #[test]
717    fn test_debug_implementation() {
718        let error = GreeksError::delta_error("Test error");
719        let debug_string = format!("{error:?}");
720        assert!(debug_string.contains("DeltaError"));
721        assert!(debug_string.contains("Test error"));
722    }
723}
724
725#[cfg(test)]
726mod tests_error_greeks_extended {
727    use super::*;
728    use optionstratlib_core::error::DecimalError::InvalidPrecision;
729    use optionstratlib_core::pos_or_panic;
730
731    #[test]
732    fn test_greeks_error_delta_neutrality() {
733        let error = GreeksError::DeltaNeutrality(DeltaNeutralityErrorKind::ZeroDelta);
734        assert_eq!(
735            format!("{error}"),
736            "Delta neutrality error: deltas cannot be zero for delta neutrality"
737        );
738    }
739
740    #[test]
741    fn test_math_error_overflow() {
742        let error = MathErrorKind::Overflow;
743        assert_eq!(format!("{error}"), "Numerical overflow");
744    }
745
746    #[test]
747    fn test_input_error_invalid_volatility() {
748        let error = InputErrorKind::InvalidVolatility {
749            value: 0.5,
750            reason: "Out of bounds".to_string(),
751        };
752        assert_eq!(format!("{error}"), "Invalid volatility 0.5: Out of bounds");
753    }
754
755    #[test]
756    fn test_calculation_error_delta() {
757        let error = CalculationErrorKind::DeltaError {
758            reason: "Unable to compute delta".to_string(),
759        };
760        assert_eq!(
761            format!("{error}"),
762            "Delta calculation error: Unable to compute delta"
763        );
764    }
765
766    #[test]
767    fn test_calculation_error_theta() {
768        let error = CalculationErrorKind::ThetaError {
769            reason: "Negative time decay".to_string(),
770        };
771        assert_eq!(
772            format!("{error}"),
773            "Theta calculation error: Negative time decay"
774        );
775    }
776
777    #[test]
778    fn test_calculation_error_rho() {
779        let error = CalculationErrorKind::RhoError {
780            reason: "Interest rate too high".to_string(),
781        };
782        assert_eq!(
783            format!("{error}"),
784            "Rho calculation error: Interest rate too high"
785        );
786    }
787
788    #[test]
789    fn test_calculation_error_vanna() {
790        let error = CalculationErrorKind::VannaError {
791            reason: "Unable to compute vanna".to_string(),
792        };
793        assert_eq!(
794            format!("{error}"),
795            "Vanna calculation error: Unable to compute vanna"
796        );
797    }
798
799    #[test]
800    fn test_calculation_error_vomma() {
801        let error = CalculationErrorKind::VommaError {
802            reason: "Unable to compute vomma".to_string(),
803        };
804        assert_eq!(
805            format!("{error}"),
806            "Vomma calculation error: Unable to compute vomma"
807        );
808    }
809
810    #[test]
811    fn test_calculation_error_veta() {
812        let error = CalculationErrorKind::VetaError {
813            reason: "Unable to compute veta".to_string(),
814        };
815        assert_eq!(
816            format!("{error}"),
817            "Veta calculation error: Unable to compute veta"
818        );
819    }
820
821    #[test]
822    fn test_calculation_error_charm() {
823        let error = CalculationErrorKind::CharmError {
824            reason: "Unable to compute charm".to_string(),
825        };
826        assert_eq!(
827            format!("{error}"),
828            "Charm calculation error: Unable to compute charm"
829        );
830    }
831
832    #[test]
833    fn test_calculation_error_color() {
834        let error = CalculationErrorKind::ColorError {
835            reason: "Unable to compute color".to_string(),
836        };
837        assert_eq!(
838            format!("{error}"),
839            "Color calculation error: Unable to compute color"
840        );
841    }
842
843    #[test]
844    fn test_calculation_error_decimal() {
845        use optionstratlib_core::error::DecimalError as DecErr;
846        let decimal_error = DecErr::InvalidPrecision {
847            precision: 0,
848            reason: "Precision error".to_string(),
849        };
850        let error =
851            GreeksError::CalculationError(CalculationErrorKind::DecimalError(decimal_error));
852        let error_string = format!("{error}");
853        assert!(error_string.contains("Invalid decimal precision"));
854    }
855
856    #[test]
857    fn test_invalid_time_constructor() {
858        let error = GreeksError::invalid_time(pos_or_panic!(5.0), "Time must be positive");
859        assert_eq!(
860            format!("{error}"),
861            "Input validation error: Invalid time 5: Time must be positive"
862        );
863    }
864
865    #[test]
866    fn test_decimal_error_conversion() {
867        let decimal_error = InvalidPrecision {
868            precision: 0,
869            reason: "Precision lost".to_string(),
870        };
871
872        let error: GreeksError = decimal_error.into();
873
874        match error {
875            GreeksError::CalculationError(CalculationErrorKind::DecimalError(_)) => {
876                // Conversion successful
877            }
878            _ => panic!("Wrong error variant"),
879        }
880    }
881
882    #[test]
883    fn test_implied_volatility_error_conversion() {
884        let iv_error = VolatilityError::ZeroVega;
885        let error: GreeksError = iv_error.into();
886        assert_eq!(
887            format!("{error}"),
888            "Input validation error: Invalid volatility 0: Vega is zero, cannot calculate implied volatility"
889        );
890    }
891
892    #[test]
893    fn test_delta_neutrality_zero_delta() {
894        let error = GreeksError::DeltaNeutrality(DeltaNeutralityErrorKind::ZeroDelta);
895        assert_eq!(
896            format!("{error}"),
897            "Delta neutrality error: deltas cannot be zero for delta neutrality"
898        );
899    }
900}