Skip to main content

optionstratlib_math/geometrics/analysis/
traits.rs

1use crate::error::MetricsError;
2use crate::geometrics::{
3    BasicMetrics, Metrics, RangeMetrics, RiskMetrics, ShapeMetrics, TrendMetrics,
4};
5use optionstratlib_core::utils::Len;
6
7/// A trait for extracting comprehensive metrics from a curve.
8///
9/// # Overview
10/// The `CurveMetricsExtractor` trait provides methods to compute various statistical,
11/// analytical, and risk-related metrics for a given curve. It allows for a systematic
12/// and extensible approach to curve analysis across different curve types and contexts.
13///
14/// # Methods
15///
16/// ## Metric Computation Methods
17/// - `compute_basic_metrics`: Calculates fundamental statistical measures.
18/// - `compute_shape_metrics`: Analyzes curve shape characteristics.
19/// - `compute_range_metrics`: Determines range and distribution properties.
20/// - `compute_trend_metrics`: Evaluates directional and regression-related metrics.
21/// - `compute_risk_metrics`: Quantifies financial and statistical risk indicators.
22///
23/// ## Comprehensive Metrics
24/// - `compute_curve_metrics`: Computes all metrics and combines them into a `CurveMetrics` struct.
25///
26/// # Usage
27/// Implement this trait for specific curve types or analysis strategies to provide
28/// custom metric computation logic tailored to different domains or requirements.
29///
30pub trait MetricsExtractor: Len {
31    /// Computes basic statistical metrics for the curve.
32    ///
33    /// # Returns
34    /// - `Ok(BasicMetrics)`: Struct containing mean, median, mode, and standard deviation.
35    /// - `Err(CurvesError)`: If metrics computation fails.
36    ///
37    /// # Errors
38    ///
39    /// Returns [`MetricsError::BasicError`] when the sample is
40    /// empty, when the variance cannot be computed in `Decimal`
41    /// precision, or when the mode aggregation is ambiguous for the
42    /// provided samples.
43    fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError>;
44
45    /// Computes shape-related metrics for the curve.
46    ///
47    /// # Returns
48    /// - `Ok(ShapeMetrics)`: Struct containing skewness, kurtosis, peaks, valleys, and inflection points.
49    /// - `Err(CurvesError)`: If metrics computation fails.
50    ///
51    /// # Errors
52    ///
53    /// Returns [`MetricsError::ShapeError`] when the sample has
54    /// fewer than three points (skewness/kurtosis undefined) or when
55    /// the central-moment computation overflows the `Decimal` range.
56    fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError>;
57
58    /// Computes range-related metrics for the curve.
59    ///
60    /// # Returns
61    /// - `Ok(RangeMetrics)`: Struct containing min/max points, range, quartiles, and interquartile range.
62    /// - `Err(CurvesError)`: If metrics computation fails.
63    ///
64    /// # Errors
65    ///
66    /// Returns [`MetricsError::RangeError`] when the sample is
67    /// empty (no min/max available) or when the quartile
68    /// interpolation fails for a sparse sample.
69    fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError>;
70
71    /// Computes trend-related metrics for the curve.
72    ///
73    /// # Returns
74    /// - `Ok(TrendMetrics)`: Struct containing slope, intercept, R-squared, and moving average.
75    /// - `Err(CurvesError)`: If metrics computation fails.
76    ///
77    /// # Errors
78    ///
79    /// Returns [`MetricsError::TrendError`] when the linear
80    /// regression cannot be fit (fewer than two samples or zero
81    /// variance on the independent axis).
82    fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError>;
83
84    /// Computes risk-related metrics for the curve.
85    ///
86    /// # Returns
87    /// - `Ok(RiskMetrics)`: Struct containing volatility, VaR, expected shortfall, coefficient of variation, and Sharpe ratio.
88    /// - `Err(CurvesError)`: If metrics computation fails.
89    ///
90    /// # Errors
91    ///
92    /// Returns [`MetricsError::RiskError`] when the sample cannot
93    /// support VaR/ES estimation (fewer than the required quantile
94    /// sample count), when the Sharpe ratio denominator is zero, or when a
95    /// checked step (including a square root) fails, and
96    /// [`MetricsError::ZeroMean`] when the values have a zero mean, where
97    /// the coefficient of variation is undefined (#824).
98    fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError>;
99
100    /// Computes and aggregates all curve metrics into a comprehensive `CurveMetrics` struct.
101    ///
102    /// # Returns
103    /// - `Ok(CurveMetrics)`: A struct containing all computed metrics.
104    /// - `Err(CurvesError)`: If any metrics computation fails.
105    ///
106    /// # Errors
107    ///
108    /// Propagates any [`MetricsError`] returned by
109    /// `compute_basic_metrics`, `compute_shape_metrics`,
110    /// `compute_range_metrics`, `compute_trend_metrics` or
111    /// `compute_risk_metrics` (the first failing call short-circuits).
112    fn compute_curve_metrics(&self) -> Result<Metrics, MetricsError> {
113        let basic = self.compute_basic_metrics()?;
114        let shape = self.compute_shape_metrics()?;
115        let range = self.compute_range_metrics()?;
116        let trend = self.compute_trend_metrics()?;
117        let risk = self.compute_risk_metrics()?;
118
119        Ok(Metrics::new(basic, shape, range, trend, risk))
120    }
121
122    /// Computes comprehensive metrics for a surface representation.
123    ///
124    /// This method aggregates multiple specialized metric calculations into a single cohesive
125    /// result. It collects basic statistical properties, shape characteristics, range information,
126    /// trend analysis, and risk assessments to provide a complete analytical view of the surface.
127    ///
128    /// # Returns
129    /// - `Ok(Metrics)`: A composite structure containing all computed metrics categories:
130    ///   - Basic metrics (statistical fundamentals)
131    ///   - Shape metrics (geometric properties)
132    ///   - Range metrics (distribution characteristics)
133    ///   - Trend metrics (directional patterns)
134    ///   - Risk metrics (uncertainty measurements)
135    /// - `Err(MetricsError)`: If any of the individual metric computations fails
136    ///
137    /// # Errors
138    /// This method can return various error types depending on which computation fails:
139    /// - `MetricsError::BasicError` - If basic metrics computation fails
140    /// - `MetricsError::ShapeError` - If shape metrics computation fails
141    /// - `MetricsError::RangeError` - If range metrics computation fails
142    /// - `MetricsError::TrendError` - If trend metrics computation fails
143    /// - `MetricsError::RiskError` - If risk metrics computation fails
144    /// - `MetricsError::ZeroMean` - If the values have a zero mean, where the
145    ///   coefficient of variation of the risk metrics is undefined
146    ///
147    fn compute_surface_metrics(&self) -> Result<Metrics, MetricsError> {
148        let basic = self.compute_basic_metrics()?;
149        let shape = self.compute_shape_metrics()?;
150        let range = self.compute_range_metrics()?;
151        let trend = self.compute_trend_metrics()?;
152        let risk = self.compute_risk_metrics()?;
153        Ok(Metrics::new(basic, shape, range, trend, risk))
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::curves::Point2D;
161    use num_traits::FromPrimitive;
162    use rust_decimal::Decimal;
163    use rust_decimal_macros::dec;
164
165    // Mock struct to test trait implementation
166    struct MockCurve;
167
168    // Helper function to create test points
169    fn create_test_point(x: f64, y: f64) -> Point2D {
170        Point2D::new(Decimal::from_f64(x).unwrap(), Decimal::from_f64(y).unwrap())
171    }
172
173    impl Len for MockCurve {
174        fn len(&self) -> usize {
175            1
176        }
177    }
178
179    // Implementation of CurveMetricsExtractor for our mock struct
180    impl MetricsExtractor for MockCurve {
181        fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
182            Ok(BasicMetrics {
183                mean: dec!(10.5),
184                median: dec!(10.0),
185                mode: dec!(9.0),
186                std_dev: dec!(1.2),
187            })
188        }
189
190        fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
191            Ok(ShapeMetrics {
192                skewness: dec!(0.5),
193                kurtosis: dec!(3.0),
194                peaks: vec![create_test_point(1.0, 10.0)],
195                valleys: vec![create_test_point(2.0, 5.0)],
196                inflection_points: vec![create_test_point(1.5, 7.5)],
197            })
198        }
199
200        fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
201            Ok(RangeMetrics {
202                min: create_test_point(0.0, 5.0),
203                max: create_test_point(10.0, 15.0),
204                range: dec!(10.0),
205                quartiles: (dec!(7.0), dec!(10.0), dec!(13.0)),
206                interquartile_range: dec!(6.0),
207            })
208        }
209
210        fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
211            Ok(TrendMetrics {
212                slope: dec!(1.5),
213                intercept: dec!(2.0),
214                r_squared: dec!(0.95),
215                moving_average: vec![create_test_point(1.0, 3.5), create_test_point(2.0, 5.0)],
216            })
217        }
218
219        fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
220            Ok(RiskMetrics {
221                volatility: dec!(0.15),
222                value_at_risk: dec!(0.05),
223                expected_shortfall: dec!(0.07),
224                coefficient_of_variation: dec!(1.2),
225                sharpe_ratio: dec!(2.5),
226            })
227        }
228    }
229
230    // Mock struct for testing error cases
231    struct ErrorMockCurve;
232
233    impl Len for ErrorMockCurve {
234        fn len(&self) -> usize {
235            1
236        }
237    }
238
239    impl MetricsExtractor for ErrorMockCurve {
240        fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
241            Err(MetricsError::BasicError(
242                "Basic metrics computation failed".to_string(),
243            ))
244        }
245
246        fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
247            Err(MetricsError::ShapeError(
248                "Shape metrics computation failed".to_string(),
249            ))
250        }
251
252        fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
253            Err(MetricsError::RangeError(
254                "Range metrics computation failed".to_string(),
255            ))
256        }
257
258        fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
259            Err(MetricsError::TrendError(
260                "Trend metrics computation failed".to_string(),
261            ))
262        }
263
264        fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
265            Err(MetricsError::RiskError(
266                "Risk metrics computation failed".to_string(),
267            ))
268        }
269    }
270
271    mod test_successful_computations {
272        use super::*;
273
274        #[test]
275        fn test_compute_basic_metrics() {
276            let curve = MockCurve;
277            let result = curve.compute_basic_metrics();
278            assert!(result.is_ok());
279            let metrics = result.unwrap();
280            assert_eq!(metrics.mean, dec!(10.5));
281            assert_eq!(metrics.median, dec!(10.0));
282            assert_eq!(metrics.mode, dec!(9.0));
283            assert_eq!(metrics.std_dev, dec!(1.2));
284        }
285
286        #[test]
287        fn test_compute_shape_metrics() {
288            let curve = MockCurve;
289            let result = curve.compute_shape_metrics();
290            assert!(result.is_ok());
291            let metrics = result.unwrap();
292            assert_eq!(metrics.skewness, dec!(0.5));
293            assert_eq!(metrics.kurtosis, dec!(3.0));
294            assert_eq!(metrics.peaks.len(), 1);
295            assert_eq!(metrics.valleys.len(), 1);
296            assert_eq!(metrics.inflection_points.len(), 1);
297        }
298
299        #[test]
300        fn test_compute_range_metrics() {
301            let curve = MockCurve;
302            let result = curve.compute_range_metrics();
303            assert!(result.is_ok());
304            let metrics = result.unwrap();
305            assert_eq!(metrics.range, dec!(10.0));
306            assert_eq!(metrics.quartiles.0, dec!(7.0));
307            assert_eq!(metrics.interquartile_range, dec!(6.0));
308        }
309
310        #[test]
311        fn test_compute_trend_metrics() {
312            let curve = MockCurve;
313            let result = curve.compute_trend_metrics();
314            assert!(result.is_ok());
315            let metrics = result.unwrap();
316            assert_eq!(metrics.slope, dec!(1.5));
317            assert_eq!(metrics.intercept, dec!(2.0));
318            assert_eq!(metrics.r_squared, dec!(0.95));
319            assert_eq!(metrics.moving_average.len(), 2);
320        }
321
322        #[test]
323        fn test_compute_risk_metrics() {
324            let curve = MockCurve;
325            let result = curve.compute_risk_metrics();
326            assert!(result.is_ok());
327            let metrics = result.unwrap();
328            assert_eq!(metrics.volatility, dec!(0.15));
329            assert_eq!(metrics.value_at_risk, dec!(0.05));
330            assert_eq!(metrics.expected_shortfall, dec!(0.07));
331            assert_eq!(metrics.coefficient_of_variation, dec!(1.2));
332            assert_eq!(metrics.sharpe_ratio, dec!(2.5));
333        }
334
335        #[test]
336        fn test_compute_curve_metrics() {
337            let curve = MockCurve;
338            let result = curve.compute_curve_metrics();
339            assert!(result.is_ok());
340
341            let metrics = result.unwrap();
342            assert_eq!(metrics.basic.mean, dec!(10.5));
343            assert_eq!(metrics.shape.skewness, dec!(0.5));
344            assert_eq!(metrics.range.range, dec!(10.0));
345            assert_eq!(metrics.trend.slope, dec!(1.5));
346            assert_eq!(metrics.risk.volatility, dec!(0.15));
347        }
348    }
349
350    mod test_error_cases {
351        use super::*;
352
353        #[test]
354        fn test_basic_metrics_error() {
355            let curve = ErrorMockCurve;
356            let result = curve.compute_basic_metrics();
357            assert!(result.is_err());
358            assert_eq!(
359                result.unwrap_err().to_string(),
360                "Basic Error: Basic metrics computation failed"
361            );
362        }
363
364        #[test]
365        fn test_shape_metrics_error() {
366            let curve = ErrorMockCurve;
367            let result = curve.compute_shape_metrics();
368            assert!(result.is_err());
369            assert_eq!(
370                result.unwrap_err().to_string(),
371                "Shape Error: Shape metrics computation failed"
372            );
373        }
374
375        #[test]
376        fn test_range_metrics_error() {
377            let curve = ErrorMockCurve;
378            let result = curve.compute_range_metrics();
379            assert!(result.is_err());
380            assert_eq!(
381                result.unwrap_err().to_string(),
382                "Range Error: Range metrics computation failed"
383            );
384        }
385
386        #[test]
387        fn test_trend_metrics_error() {
388            let curve = ErrorMockCurve;
389            let result = curve.compute_trend_metrics();
390            assert!(result.is_err());
391            assert_eq!(
392                result.unwrap_err().to_string(),
393                "Trend Error: Trend metrics computation failed"
394            );
395        }
396
397        #[test]
398        fn test_risk_metrics_error() {
399            let curve = ErrorMockCurve;
400            let result = curve.compute_risk_metrics();
401            assert!(result.is_err());
402            assert_eq!(
403                result.unwrap_err().to_string(),
404                "Risk Error: Risk metrics computation failed"
405            );
406        }
407
408        #[test]
409        fn test_curve_metrics_error_propagation() {
410            let curve = ErrorMockCurve;
411            let result = curve.compute_curve_metrics();
412            assert!(result.is_err());
413            assert_eq!(
414                result.unwrap_err().to_string(),
415                "Basic Error: Basic metrics computation failed"
416            );
417        }
418    }
419
420    mod test_surface_metrics {
421        use super::*;
422
423        #[test]
424        fn test_compute_surface_metrics_success() {
425            let curve = MockCurve;
426            let result = curve.compute_surface_metrics();
427            assert!(result.is_ok());
428
429            let metrics = result.unwrap();
430            assert_eq!(metrics.basic.mean, dec!(10.5));
431            assert_eq!(metrics.basic.median, dec!(10.0));
432            assert_eq!(metrics.basic.std_dev, dec!(1.2));
433
434            assert_eq!(metrics.shape.skewness, dec!(0.5));
435            assert_eq!(metrics.shape.kurtosis, dec!(3.0));
436            assert!(!metrics.shape.peaks.is_empty());
437
438            assert_eq!(metrics.range.range, dec!(10.0));
439            assert_eq!(metrics.range.interquartile_range, dec!(6.0));
440
441            assert_eq!(metrics.trend.slope, dec!(1.5));
442            assert_eq!(metrics.trend.r_squared, dec!(0.95));
443
444            assert_eq!(metrics.risk.volatility, dec!(0.15));
445            assert_eq!(metrics.risk.sharpe_ratio, dec!(2.5));
446        }
447
448        #[test]
449        fn test_compute_surface_metrics_error() {
450            let curve = ErrorMockCurve;
451            let result = curve.compute_surface_metrics();
452
453            assert!(result.is_err());
454            assert_eq!(
455                result.unwrap_err().to_string(),
456                "Basic Error: Basic metrics computation failed"
457            );
458        }
459
460        #[test]
461        fn test_surface_metrics_matches_curve_metrics() {
462            let curve = MockCurve;
463            let surface_metrics = curve.compute_surface_metrics().unwrap();
464            let curve_metrics = curve.compute_curve_metrics().unwrap();
465
466            assert_eq!(surface_metrics.basic.mean, curve_metrics.basic.mean);
467            assert_eq!(surface_metrics.shape.skewness, curve_metrics.shape.skewness);
468            assert_eq!(surface_metrics.range.range, curve_metrics.range.range);
469            assert_eq!(surface_metrics.trend.slope, curve_metrics.trend.slope);
470            assert_eq!(
471                surface_metrics.risk.volatility,
472                curve_metrics.risk.volatility
473            );
474        }
475    }
476}