Skip to main content

optionstratlib_math/geometrics/analysis/
statistics.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 26/8/24
5******************************************************************************/
6use crate::geometrics::{BasicMetrics, ShapeMetrics};
7use optionstratlib_core::{impl_json_debug_pretty, impl_json_display};
8use serde::Serialize;
9
10/// Contains comprehensive analysis results for a curve or dataset.
11///
12/// # Overview
13/// The `AnalysisResult` structure encapsulates both basic statistical metrics and
14/// shape-related characteristics of a dataset or curve. It serves as a complete analysis
15/// output that can be used to understand both the statistical properties and the
16/// geometrical behavior of the data.
17///
18/// This structure is particularly useful for comprehensive data analysis in financial modeling,
19/// statistical analysis, and geometric curve evaluation. It combines fundamental statistical
20/// insights with shape-based properties to provide a holistic view of the data.
21///
22/// # Fields
23///
24/// - **statistics**: [`BasicMetrics`]
25///   - Contains fundamental statistical metrics like mean, median, mode, and standard deviation.
26///   - Provides insights into the central tendency and dispersion characteristics of the dataset.
27///   - Used for understanding the overall statistical properties of the analyzed data.
28///
29/// - **shape_metrics**: [`ShapeMetrics`]
30///   - Contains shape-related properties such as skewness, kurtosis, and critical points
31///     (peaks, valleys, and inflection points).
32///   - Provides insights into the geometrical behavior and distribution characteristics of the curve.
33///   - Used for understanding the asymmetry, tailedness, and notable features in the curve's shape.
34///
35/// # Applications
36/// The `AnalysisResult` structure is utilized in various domains requiring both statistical
37/// and geometrical analysis:
38///
39/// - **Financial Analysis**:
40///   - Evaluate price distributions and identify critical price levels.
41///   - Detect patterns in market data through both statistical and shape-based observations.
42///
43/// - **Data Science**:
44///   - Provide comprehensive analysis of datasets, combining statistical insights with
45///     distributional shape characteristics.
46///
47/// - **Geometric Modeling**:
48///   - Analyze curve properties for more accurate modeling and representation.
49///   - Identify key points that define the geometric behavior of a curve.
50///
51#[derive(Clone, Serialize)]
52#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
53pub struct AnalysisResult {
54    /// The fundamental statistical metrics of the dataset, including measures of
55    /// central tendency (mean, median, mode) and dispersion (standard deviation).
56    pub statistics: BasicMetrics,
57
58    /// The shape-related metrics of the curve or dataset, including distribution
59    /// characteristics (skewness, kurtosis) and critical geometric points
60    /// (peaks, valleys, inflection points).
61    pub shape_metrics: ShapeMetrics,
62}
63
64impl_json_debug_pretty!(AnalysisResult);
65impl_json_display!(AnalysisResult);
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::curves::Point2D;
71    use crate::geometrics::analysis::metrics::{BasicMetrics, ShapeMetrics};
72    use rust_decimal_macros::dec;
73
74    /// Creates a sample BasicMetrics for testing
75    fn create_sample_basic_metrics() -> BasicMetrics {
76        BasicMetrics {
77            mean: dec!(10.5),
78            median: dec!(10.0),
79            mode: dec!(9.0),
80            std_dev: dec!(1.2),
81        }
82    }
83
84    /// Creates a sample ShapeMetrics for testing
85    fn create_sample_shape_metrics() -> ShapeMetrics {
86        ShapeMetrics {
87            skewness: dec!(0.25),
88            kurtosis: dec!(3.2),
89            peaks: vec![
90                Point2D::new(dec!(1.0), dec!(10.0)),
91                Point2D::new(dec!(5.0), dec!(15.0)),
92            ],
93            valleys: vec![Point2D::new(dec!(3.0), dec!(5.0))],
94            inflection_points: vec![
95                Point2D::new(dec!(2.0), dec!(7.5)),
96                Point2D::new(dec!(4.0), dec!(12.0)),
97            ],
98        }
99    }
100
101    #[test]
102    fn test_analysis_result_creation() {
103        // Create an instance of AnalysisResult
104        let result = AnalysisResult {
105            statistics: create_sample_basic_metrics(),
106            shape_metrics: create_sample_shape_metrics(),
107        };
108
109        // Verify that it was created successfully
110        assert!(result.statistics.mean == dec!(10.5));
111        assert!(result.statistics.median == dec!(10.0));
112        assert!(result.statistics.mode == dec!(9.0));
113        assert!(result.statistics.std_dev == dec!(1.2));
114
115        assert!(result.shape_metrics.skewness == dec!(0.25));
116        assert!(result.shape_metrics.kurtosis == dec!(3.2));
117    }
118
119    #[test]
120    fn test_analysis_result_field_access() {
121        let result = AnalysisResult {
122            statistics: create_sample_basic_metrics(),
123            shape_metrics: create_sample_shape_metrics(),
124        };
125
126        // Test accessing statistics fields
127        assert_eq!(result.statistics.mean, dec!(10.5));
128        assert_eq!(result.statistics.median, dec!(10.0));
129        assert_eq!(result.statistics.mode, dec!(9.0));
130        assert_eq!(result.statistics.std_dev, dec!(1.2));
131
132        // Test accessing shape_metrics fields
133        assert_eq!(result.shape_metrics.skewness, dec!(0.25));
134        assert_eq!(result.shape_metrics.kurtosis, dec!(3.2));
135        assert_eq!(result.shape_metrics.peaks.len(), 2);
136        assert_eq!(result.shape_metrics.valleys.len(), 1);
137        assert_eq!(result.shape_metrics.inflection_points.len(), 2);
138    }
139
140    #[test]
141    fn test_analysis_result_point_access() {
142        let result = AnalysisResult {
143            statistics: create_sample_basic_metrics(),
144            shape_metrics: create_sample_shape_metrics(),
145        };
146
147        // Test specific point values in shape_metrics
148        // Check first peak
149        assert_eq!(result.shape_metrics.peaks[0].x, dec!(1.0));
150        assert_eq!(result.shape_metrics.peaks[0].y, dec!(10.0));
151
152        // Check second peak
153        assert_eq!(result.shape_metrics.peaks[1].x, dec!(5.0));
154        assert_eq!(result.shape_metrics.peaks[1].y, dec!(15.0));
155
156        // Check valley
157        assert_eq!(result.shape_metrics.valleys[0].x, dec!(3.0));
158        assert_eq!(result.shape_metrics.valleys[0].y, dec!(5.0));
159
160        // Check inflection points
161        assert_eq!(result.shape_metrics.inflection_points[0].x, dec!(2.0));
162        assert_eq!(result.shape_metrics.inflection_points[0].y, dec!(7.5));
163        assert_eq!(result.shape_metrics.inflection_points[1].x, dec!(4.0));
164        assert_eq!(result.shape_metrics.inflection_points[1].y, dec!(12.0));
165    }
166
167    #[test]
168    fn test_analysis_result_with_empty_points() {
169        // Create shape metrics with empty vectors
170        let shape_metrics = ShapeMetrics {
171            skewness: dec!(0.1),
172            kurtosis: dec!(2.5),
173            peaks: vec![],
174            valleys: vec![],
175            inflection_points: vec![],
176        };
177
178        let result = AnalysisResult {
179            statistics: create_sample_basic_metrics(),
180            shape_metrics,
181        };
182
183        // Verify the empty vectors
184        assert_eq!(result.shape_metrics.peaks.len(), 0);
185        assert_eq!(result.shape_metrics.valleys.len(), 0);
186        assert_eq!(result.shape_metrics.inflection_points.len(), 0);
187
188        // Other fields should still be accessible
189        assert_eq!(result.shape_metrics.skewness, dec!(0.1));
190        assert_eq!(result.shape_metrics.kurtosis, dec!(2.5));
191    }
192
193    // If AnalysisResult implements Debug (which it likely should)
194    #[test]
195    fn test_analysis_result_debug() {
196        let result = AnalysisResult {
197            statistics: create_sample_basic_metrics(),
198            shape_metrics: create_sample_shape_metrics(),
199        };
200
201        // This just ensures that debug formatting doesn't panic
202        let debug_str = format!("{result:?}");
203        assert!(!debug_str.is_empty());
204    }
205}