optionstratlib_math/geometrics/analysis/metrics.rs
1use crate::curves::Point2D;
2use crate::error::{CurveError, MetricsError};
3use crate::geometrics::{AnalysisResult, powu_checked};
4use optionstratlib_core::error::DecimalError;
5use optionstratlib_core::model::decimal::{d_add, d_div, d_sqrt, d_sub};
6use optionstratlib_core::{impl_json_debug_pretty, impl_json_display};
7use rust_decimal::Decimal;
8use serde::Serialize;
9
10/// Represents a comprehensive set of statistical and analytical metrics for curve data.
11///
12/// # Overview
13/// The `CurveMetrics` structure aggregates various metrics that describe different
14/// aspects of a curve in a unified form. It provides an encapsulated representation of
15/// curve information that spans different categories, including basic statistical measures,
16/// shape characteristics, range details, trend analysis, and risk evaluation.
17///
18/// ## Usage
19/// This structure is particularly helpful in domains requiring holistic curve analysis,
20/// such as:
21///
22/// - **Financial Analysis**:
23/// Used to analyze return curves and assess the risk-return trade-offs for financial products or strategies.
24/// - **Data Science**:
25/// Provides comprehensive insights into a dataset's distribution, shape, and trends over time.
26/// - **Scientific Research**:
27/// Useful for analyzing phenomena modeled by curves in domains like physics, biology, or economics.
28///
29/// ## Field Descriptions
30///
31/// - **basic**: [`BasicMetrics`]
32/// - Contains mean, median, mode, and standard deviation of the dataset.
33/// - **shape**: [`ShapeMetrics`]
34/// - Captures the shape-related characteristics such as skewness, kurtosis, and extrema (peaks/valleys).
35/// - **range**: [`RangeMetrics`]
36/// - Includes range, min/max points, and quartiles for the dataset.
37/// - **trend**: [`TrendMetrics`]
38/// - Represents the directional tendencies, moving averages, and regression characteristics.
39/// - **risk**: [`RiskMetrics`]
40/// - Evaluates financial risk metrics such as volatility and Sharpe ratio.
41///
42/// ## Example Workflow
43/// The `CurveMetrics` structure is usually constructed by combining its fields using the individual
44/// metric structures (`BasicMetrics`, `ShapeMetrics`, `RangeMetrics`, `TrendMetrics`, `RiskMetrics`).
45/// It is often initialized as part of a larger curve analysis operation and may be transformed or queried for
46/// generating insights.
47///
48/// ## Related Concepts
49/// - [`BasicMetrics`]: Encodes fundamental statistics.
50/// - [`ShapeMetrics`]: Provides characteristics associated with curve shape.
51/// - [`RangeMetrics`]: Range and quartile information for a curve.
52/// - [`TrendMetrics`]: Trendline and regression fit metrics.
53/// - [`RiskMetrics`]: Quantifies financial risk.
54///
55/// ## Examples of Associated Tools
56/// - Statistical Analysis: Plots, descriptive statistics, trend analysis.
57/// - Visualizations: Understand curve behavior (e.g., peaks, valleys).
58/// - Financial Metrics: Sharpe ratio, coefficient of variation, and VaR for understanding portfolio risks.
59///
60/// ## Remarks
61/// The `CurveMetrics` struct is designed to be reusable across various analytical contexts,
62/// providing a versatile and standardized way to represent curve characteristics.
63#[derive(Clone, Serialize)]
64#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
65pub struct Metrics {
66 /// - **Basic Metrics (`basic`)**:
67 /// Includes fundamental statistical measures such as mean, median, mode, and standard deviation.
68 /// These measures provide a quick overview of the distribution of the observations within the curve.
69 pub basic: BasicMetrics,
70 /// - **Shape Metrics (`shape`)**:
71 /// Captures the structural characteristics of the curve, such as skewness, kurtosis,
72 /// the locations of peaks and valleys, and the points where the curve inflects.
73 /// Useful for understanding curve symmetry, tail behavior, and general shape nuances.
74 pub shape: ShapeMetrics,
75 /// - **Range Metrics (`range`)**:
76 /// Describes the range of the data, including minimum and maximum observed points,
77 /// the extent between these points, and quartile-based statistical details such as
78 /// interquartile range. Particularly helpful when analyzing variability in data distribution.
79 pub range: RangeMetrics,
80 /// - **Trend Metrics (`trend`)**:
81 /// Measures the directional tendencies of the curve over time. Includes the slope,
82 /// intercept, and statistical goodness-of-fit (R² value) as well as moving averages.
83 /// Ideal for identifying long-term trends and evaluating the predictive nature of the curve.
84 pub trend: TrendMetrics,
85 /// - **Risk Metrics (`risk`)**:
86 /// Quantifies curve risk using various financial metrics, such as volatility, value-at-risk (VaR),
87 /// expected shortfall, coefficient of variation, and the Sharpe ratio. These metrics are often used to evaluate
88 /// the risk-return profile in financial contexts.
89 pub risk: RiskMetrics,
90}
91
92impl_json_debug_pretty!(Metrics);
93impl_json_display!(Metrics);
94
95/// Represents a set of metrics associated with analyzing and interpreting a curve.
96///
97/// This structure encapsulates multiple types of metrics, each responsible for
98/// a specific aspect of curve analysis. These include basic statistical measures,
99/// shape-related properties, range characteristics, trend analysis, and risk factors.
100///
101/// ## Fields
102/// - `basic`: Basic statistical metrics such as mean, median, mode, and standard deviation.
103/// - `shape`: Metrics that describe the shape of the curve, such as skewness, kurtosis,
104/// and points of interest (peaks, valleys, and inflection points).
105/// - `range`: Range-based metrics specifying properties like the minimum and maximum
106/// values on the curve, quartiles, and interquartile range.
107/// - `trend`: Metrics related to overall trends in the curve, such as slope,
108/// intercept, R-squared value, and moving average data points.
109/// - `risk`: Risk-related metrics, including volatility, value at risk (VaR),
110/// expected shortfall, coefficient of variation, and the Sharpe ratio.
111///
112/// ## Notes
113/// This implementation ensures modularity by separating distinct aspects of curve
114/// analysis into specific metric structures. These metrics can be used individually
115/// or collectively for advanced data analysis or curve interpretation tasks.
116///
117/// ## Related Structures
118/// - [`BasicMetrics`]: Provides basic statistical metrics about the dataset.
119/// - [`ShapeMetrics`]: Describes the geometric properties of the curve.
120/// - [`RangeMetrics`]: Assesses the range and quartile characteristics of the curve.
121/// - [`TrendMetrics`]: Analyzes trends within the data to understand directional behavior.
122/// - [`RiskMetrics`]: Highlights risk-based metrics for financial, statistical, or analytical use cases.
123/// - [`AnalysisResult`]: The result type combining key metrics into a single analytic perspective.
124/// - [`CurveError`]: Represents potential errors that may arise during curve analysis operations.
125impl Metrics {
126 /// ### `new`
127 /// Constructs a new instance of `CurveMetrics` and initializes all relevant fields with the provided
128 /// metric structures.
129 ///
130 /// #### Parameters:
131 /// - `basic`: An instance of [`BasicMetrics`], holding essential statistical information.
132 /// - `shape`: An instance of [`ShapeMetrics`], measuring the geometric properties of the curve.
133 /// - `range`: An instance of [`RangeMetrics`], describing the range and distribution of the curve.
134 /// - `trend`: An instance of [`TrendMetrics`], detailing trend-based analytical results.
135 /// - `risk`: An instance of [`RiskMetrics`], specifying the risk characteristics related to the curve.
136 ///
137 /// #### Returns:
138 /// - A new `CurveMetrics` instance containing the provided metrics.
139 ///
140 #[must_use]
141 pub fn new(
142 basic: BasicMetrics,
143 shape: ShapeMetrics,
144 range: RangeMetrics,
145 trend: TrendMetrics,
146 risk: RiskMetrics,
147 ) -> Self {
148 Self {
149 basic,
150 shape,
151 range,
152 trend,
153 risk,
154 }
155 }
156
157 /// ### `curve_analysis_result`
158 /// Generates a high-level analysis result from the metrics encapsulated within the `CurveMetrics` instance.
159 ///
160 /// #### Returns:
161 /// - `Ok(CurveAnalysisResult)`: A result that contains analyzed data in the form of a
162 /// [`AnalysisResult`] structure with basic statistics and shape metrics.
163 /// - `Err(CurvesError)`: An error of type [`CurveError`] when analysis fails.
164 ///
165 /// The result provides the basic statistical measures (`BasicMetrics`) and
166 /// shape metrics (`ShapeMetrics`) that were part of the `CurveMetrics` instance.
167 ///
168 /// # Errors
169 ///
170 /// Currently infallible - the conversion is a direct field
171 /// re-pack. The `Result` signature is retained so future
172 /// implementations that validate the metrics invariants (e.g.
173 /// non-negative variance) can surface
174 /// [`CurveError::MetricsError`] without breaking callers.
175 pub fn analysis_result(&self) -> Result<AnalysisResult, CurveError> {
176 Ok(AnalysisResult {
177 statistics: self.basic,
178 shape_metrics: self.shape.clone(),
179 })
180 }
181}
182
183/// Represents a collection of fundamental statistical metrics.
184///
185/// # Overview
186/// The `BasicMetrics` structure encapsulates the core descriptive statistics
187/// for a dataset, providing a quick summary of its central tendency, spread,
188/// and distribution properties. These metrics are widely used in statistical analysis,
189/// curve analysis, and various financial or scientific computations.
190///
191/// # Fields
192///
193/// * `mean` - The arithmetic average of the dataset. Represents the central value where the sum
194/// of all data points is divided by the total number of points.
195/// Useful for understanding the overall trend or expected value of the dataset.
196///
197/// * `median` - The middle value in the dataset when sorted. If the dataset has an even number
198/// of elements, the median is the average of the two middle elements.
199/// A robust measure of central tendency, particularly in datasets with outliers.
200///
201/// * `mode` - The most frequently occurring value in the dataset. If no value repeats, the
202/// mode might not be well-defined (or may represent multi-modal distributions).
203/// Useful for identifying common or dominant values in a dataset.
204///
205/// * `std_dev` - The standard deviation of the dataset, which quantifies the amount of variation
206/// or dispersion from the mean.
207/// A key measure of data spread, often used for assessing volatility or risk in
208/// financial contexts.
209///
210/// # Applications
211/// The `BasicMetrics` structure is utilized in various domains requiring descriptive statistics:
212///
213/// - **Financial Analysis**:
214/// - Evaluate price variations, returns, and risks.
215///
216/// - **Scientific Research**:
217/// - Summarize observations and patterns in experimental data.
218///
219/// - **Data Science**:
220/// - Understand distributions, clean data, and preprocess datasets for machine learning models.
221///
222/// # Integration
223/// The `BasicMetrics` structure is often used as part of larger metric aggregations, such as:
224/// - [`Metrics`]: Combines `BasicMetrics` with other
225/// metrics for a detailed analysis of curve behavior.
226/// - [`AnalysisResult`]: Provides a high-level
227/// result of statistical and shape analysis for curves.
228///
229/// # Remarks
230/// - The values are expressed as [`Decimal`] to maintain precision, important in financial
231/// computations or datasets requiring high accuracy.
232/// - The `BasicMetrics` structure is immutable and can be cloned/copied, making it efficient for use in
233/// concurrent or parallel computations.
234///
235/// # Example Workflow
236/// Typically, `BasicMetrics` is computed from a dataset using statistical functions
237/// and then integrated into a more comprehensive analysis pipeline.
238///
239/// # Related Concepts
240/// - [`ShapeMetrics`]: Captures shape-related properties like skewness and kurtosis.
241/// - [`RiskMetrics`]: Measures risk characteristics of financial instruments.
242/// - [`TrendMetrics`]: Represents time-based trends in data series.
243#[derive(Clone, Copy, Serialize)]
244#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
245pub struct BasicMetrics {
246 /// The arithmetic mean (average) of the dataset.
247 /// Calculated by summing all values and dividing by the count of values.
248 /// Provides a measure of central tendency that is affected by all values,
249 /// including outliers.
250 pub mean: Decimal,
251
252 /// The median value of the dataset.
253 /// Represents the middle value when all data points are arranged in ascending order.
254 /// For datasets with even count, it's the average of the two middle values.
255 /// Less sensitive to outliers than the mean.
256 pub median: Decimal,
257
258 /// The most frequently occurring value in the dataset.
259 /// If multiple values occur with equal frequency, this typically represents
260 /// the first encountered mode or a calculated central mode.
261 /// For multi-modal distributions, this is a simplification.
262 pub mode: Decimal,
263
264 /// The standard deviation of the dataset.
265 /// Measures the amount of variation or dispersion from the mean.
266 /// A low standard deviation indicates values tend to be close to the mean,
267 /// while a high standard deviation indicates values are spread over a wider range.
268 pub std_dev: Decimal,
269}
270
271impl_json_debug_pretty!(BasicMetrics);
272impl_json_display!(BasicMetrics);
273
274/// Represents shape-related analysis metrics for a given curve.
275///
276/// # Overview
277/// The `ShapeMetrics` structure encapsulates shape-related properties and critical
278/// points describing a curve's geometrical behavior. It's primarily used for
279/// mathematical and statistical analysis of curves representing data distributions
280/// or mathematical functions.
281///
282/// # Fields
283/// * `skewness` - A measure of distribution asymmetry. Positive values indicate right
284/// tailing while negative values indicate left tailing.
285///
286/// * `kurtosis` - A measure of the "tailedness" or presence of outliers in the distribution.
287/// Higher values indicate heavier tails and more outliers compared to a normal distribution.
288///
289/// * `peaks` - Collection of points representing local or global maxima along the curve,
290/// stored as `Point2D` coordinates.
291///
292/// * `valleys` - Collection of points representing local or global minima along the curve,
293/// stored as `Point2D` coordinates.
294///
295/// * `inflection_points` - Points where the curve changes concavity (transitioning from
296/// concave up to concave down or vice versa), stored as `Point2D` coordinates.
297///
298/// # Applications
299/// This structure is commonly used in:
300/// - Financial analysis to identify trend reversals or extreme price levels
301/// - Statistical distribution analysis to characterize data shape properties
302/// - Mathematical modeling to identify critical points in curve functions
303/// - Visualization and interpretation of complex curve behaviors
304///
305/// # Relationship with Other Structures
306/// `ShapeMetrics` is typically part of the larger `AnalysisResult` structure
307/// that provides comprehensive curve analysis. It uses `Point2D` to represent
308/// all positional data with high-precision `Decimal` values.
309#[derive(Clone, Serialize)]
310#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
311pub struct ShapeMetrics {
312 /// Describes the asymmetry of the curve's distribution.
313 /// A positive value indicates a right tail (right-skewed distribution),
314 /// while a negative value indicates a left tail (left-skewed distribution).
315 /// A value close to zero suggests symmetry.
316 pub skewness: Decimal,
317
318 /// Indicates the "tailedness" or presence of outliers in the curve's distribution.
319 /// Higher values correspond to heavier tails and more outliers compared to
320 /// a normal distribution. Standard normal distribution has kurtosis of 3.0.
321 /// Often expressed as excess kurtosis (kurtosis - 3).
322 pub kurtosis: Decimal,
323
324 /// Collection of `Point2D` instances representing local or global maxima (peaks)
325 /// along the curve. These points have higher y-values than their immediate
326 /// neighboring points.
327 pub peaks: Vec<Point2D>,
328
329 /// Collection of `Point2D` instances representing local or global minima (valleys)
330 /// along the curve. These points have lower y-values than their immediate
331 /// neighboring points.
332 pub valleys: Vec<Point2D>,
333
334 /// Points where the curve changes concavity, transitioning from concave up to
335 /// concave down (or vice versa). At these points, the second derivative equals zero
336 /// while changing sign.
337 pub inflection_points: Vec<Point2D>,
338}
339
340impl_json_debug_pretty!(ShapeMetrics);
341impl_json_display!(ShapeMetrics);
342
343/// Represents statistical and range-related metrics for a dataset.
344///
345/// `RangeMetrics` provides key statistical measures related to data ranges and distributions.
346/// This structure is particularly useful when analyzing numerical datasets from financial curves,
347/// scientific measurements, or computational geometry applications.
348///
349/// # Fields
350///
351/// * `min` - The minimum point in the dataset, containing both x and y coordinates as the smallest values.
352/// This defines the lower boundary of the data range.
353///
354/// * `max` - The maximum point in the dataset, containing both x and y coordinates as the largest values.
355/// This defines the upper boundary of the data range.
356///
357/// * `range` - The numerical difference between the maximum and minimum values.
358/// Provides a simple measure of data dispersion or spread.
359///
360/// * `quartiles` - A tuple containing first quartile (Q1), median (Q2), and third quartile (Q3).
361/// These values divide the dataset into four equal parts, providing insight into data distribution.
362///
363/// * `interquartile_range` - The difference between the third and first quartiles (Q3 - Q1).
364/// A robust statistical measure of dispersion that is less sensitive to outliers than the full range.
365///
366/// # Usage
367///
368/// `RangeMetrics` is typically calculated as part of a larger statistical analysis workflow.
369/// It provides essential information for box plots, outlier detection, and distribution analysis.
370///
371/// This structure is part of the curves analysis module that provides comprehensive
372/// statistical and financial analysis tools for mathematical curves.
373#[derive(Clone, Copy, Serialize)]
374#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
375pub struct RangeMetrics {
376 /// The minimum point in the dataset, containing the smallest x and y coordinates observed.
377 pub min: Point2D,
378
379 /// The maximum point in the dataset, containing the largest x and y coordinates observed.
380 pub max: Point2D,
381
382 /// The numerical difference between the maximum and minimum values,
383 /// representing the total span of the data.
384 pub range: Decimal,
385
386 /// A tuple of three values representing the first quartile (Q1),
387 /// median (Q2), and third quartile (Q3) of the dataset.
388 pub quartiles: (Decimal, Decimal, Decimal),
389
390 /// The difference between the third and first quartiles (Q3 - Q1),
391 /// providing a measure of statistical dispersion that ignores extremes.
392 pub interquartile_range: Decimal,
393}
394
395impl_json_debug_pretty!(RangeMetrics);
396impl_json_display!(RangeMetrics);
397
398/// Represents key metrics for analyzing trends within a dataset or curve.
399///
400/// # Overview
401/// This structure stores comprehensive metrics that describe the linear relationship
402/// and trend behavior in a dataset. It provides statistical values derived from
403/// linear regression analysis along with smoothed data points for visualization
404/// and further analysis.
405///
406/// # Fields
407/// - **slope**: The rate of change in the dependent variable (y-axis) with respect
408/// to the independent variable (x-axis), calculated from linear regression.
409/// - **intercept**: The y-coordinate where the regression line intersects the y-axis,
410/// representing the value when x = 0.
411/// - **r_squared**: The coefficient of determination that measures how well the
412/// data fits the regression model, with values closer to 1 indicating a better fit.
413/// - **moving_average**: A series of smoothed data points derived from the original
414/// dataset to reduce noise and highlight the underlying trend pattern.
415///
416/// # Purpose
417/// `TrendMetrics` is particularly useful for:
418/// - Analyzing time-series data in financial markets
419/// - Evaluating performance trends over time
420/// - Quantifying the strength and direction of relationships between variables
421/// - Providing a basis for predictive modeling and forecasting
422///
423/// This structure is a key component of the curve analysis module and works
424/// alongside other metric types to provide comprehensive statistical analysis.
425#[derive(Clone, Serialize)]
426#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
427pub struct TrendMetrics {
428 /// The slope coefficient of the linear regression line, indicating the
429 /// rate and direction of change in the dataset.
430 pub slope: Decimal,
431
432 /// The y-intercept of the regression line, showing the baseline value
433 /// when the independent variable equals zero.
434 pub intercept: Decimal,
435
436 /// The coefficient of determination (R²), measuring how well the regression
437 /// line fits the actual data points. Values range from 0 to 1, where 1
438 /// represents a perfect fit.
439 pub r_squared: Decimal,
440
441 /// A collection of points representing the smoothed version of the original
442 /// data, calculated using moving average techniques to reduce noise and
443 /// highlight the underlying trend.
444 pub moving_average: Vec<Point2D>,
445}
446
447impl_json_debug_pretty!(TrendMetrics);
448impl_json_display!(TrendMetrics);
449
450/// Represents a collection of key financial risk metrics used in risk analysis
451/// and performance evaluation.
452///
453/// # Overview
454/// The `RiskMetrics` structure encapsulates a set of quantitative measures
455/// that assess the risk and performance characteristics of a financial portfolio,
456/// asset, or investment strategy. These metrics provide insights into the
457/// variability, potential losses, and overall return of the analyzed entity.
458///
459/// # Applications
460/// The `RiskMetrics` structure is commonly utilized in:
461///
462/// - **Portfolio Management**: To assess and balance risk-return trade-offs
463/// - **Risk Management**: To estimate potential losses and evaluate market exposures
464/// - **Performance Analysis**: To compare against benchmarks and alternative strategies
465/// - **Financial Modeling**: To perform stress testing and scenario analysis
466///
467/// # Related Concepts
468/// - [`Metrics`]: Aggregates risk metrics alongside other statistical measures
469/// - **Financial Risk Indicators**: Additional measures like maximum drawdown,
470/// Treynor ratio, and Sortino ratio
471///
472/// # Note
473/// For accuracy, ensure the data used to compute these metrics represents a
474/// sufficiently long time horizon and is free from anomalies.
475#[derive(Clone, Copy, Serialize)]
476#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
477pub struct RiskMetrics {
478 /// Measures the degree of variation in returns over time.
479 /// Represents the standard deviation of returns and is a widely used
480 /// indicator of market risk.
481 pub volatility: Decimal,
482
483 /// Quantifies the maximum expected loss over a defined time horizon
484 /// with a given confidence level (typically 95% or 99%).
485 /// Essential for understanding tail-risk and evaluating potential losses.
486 pub value_at_risk: Decimal,
487
488 /// Represents the average loss in worst-case scenarios beyond the VaR threshold.
489 /// Provides a more comprehensive assessment of tail risk than VaR alone.
490 pub expected_shortfall: Decimal,
491
492 /// The coefficient of variation, `std_dev / mean`: the population
493 /// standard deviation of the values (`sqrt(sum((x - mean)^2) / n)`, the
494 /// `std_dev` of [`BasicMetrics`]) over their mean, signed as the mean.
495 /// It measures dispersion relative to level and is unitless. Curves and
496 /// surfaces compute it the same way; it is undefined at a zero mean,
497 /// which the computation reports as [`MetricsError::ZeroMean`], and zero
498 /// for empty data, like every field here. It replaces `beta`, which
499 /// promised a sensitivity to market returns that no curve or surface has
500 /// the data for (#824).
501 pub coefficient_of_variation: Decimal,
502
503 /// Indicates the risk-adjusted return by comparing the portfolio's excess return
504 /// (return above the risk-free rate) to its volatility.
505 /// Helps evaluate whether returns justify the associated risk profile.
506 pub sharpe_ratio: Decimal,
507}
508
509impl_json_debug_pretty!(RiskMetrics);
510impl_json_display!(RiskMetrics);
511
512/// The population standard deviation `sqrt(sum((x - mean)^2) / n)` of a
513/// non-empty sample about `mean`: the [`RiskMetrics::volatility`] of curves
514/// and surfaces, shared so both compute it with the same operations in the
515/// same order (#840).
516///
517/// # Errors
518///
519/// Returns [`DecimalError`] when a deviation, its square, their sum, the
520/// division by `n` or the square root leaves the `Decimal` range, and when
521/// `values` is empty (`n = 0`).
522pub(crate) fn population_std_dev(
523 values: &[Decimal],
524 mean: Decimal,
525 op: &'static str,
526) -> Result<Decimal, DecimalError> {
527 let mut sum_sq = Decimal::ZERO;
528 for &value in values {
529 let centered = d_sub(value, mean, op)?;
530 sum_sq = d_add(sum_sq, powu_checked(centered, 2, op)?, op)?;
531 }
532 let variance = d_div(sum_sq, Decimal::from(values.len()), op)?;
533 d_sqrt(variance, op)
534}
535
536/// The coefficient of variation `std_dev / mean` of
537/// [`RiskMetrics::coefficient_of_variation`], shared so curves and surfaces
538/// compute it the same way (#824).
539///
540/// # Errors
541///
542/// Returns [`MetricsError::ZeroMean`] when `mean` is zero and
543/// [`MetricsError::RiskError`] when the quotient leaves the `Decimal` range.
544pub(crate) fn coefficient_of_variation(
545 std_dev: Decimal,
546 mean: Decimal,
547 op: &'static str,
548) -> Result<Decimal, MetricsError> {
549 if mean.is_zero() {
550 return Err(MetricsError::ZeroMean {
551 metric: "coefficient of variation",
552 });
553 }
554 d_div(std_dev, mean, op).map_err(|e| MetricsError::RiskError(e.to_string()))
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use num_traits::FromPrimitive;
561 use rust_decimal_macros::dec;
562
563 // Helper function to create a Point2D for testing
564 fn create_test_point(x: f64, y: f64) -> Point2D {
565 Point2D::new(Decimal::from_f64(x).unwrap(), Decimal::from_f64(y).unwrap())
566 }
567
568 mod test_basic_metrics {
569 use super::*;
570
571 #[test]
572 fn test_basic_metrics_creation() {
573 let metrics = BasicMetrics {
574 mean: dec!(10.5),
575 median: dec!(10.0),
576 mode: dec!(9.0),
577 std_dev: dec!(1.2),
578 };
579
580 assert_eq!(metrics.mean, dec!(10.5));
581 assert_eq!(metrics.median, dec!(10.0));
582 assert_eq!(metrics.mode, dec!(9.0));
583 assert_eq!(metrics.std_dev, dec!(1.2));
584 }
585
586 #[test]
587 fn test_basic_metrics_clone() {
588 let metrics = BasicMetrics {
589 mean: dec!(10.5),
590 median: dec!(10.0),
591 mode: dec!(9.0),
592 std_dev: dec!(1.2),
593 };
594
595 let cloned_metrics = metrics;
596 assert_eq!(metrics.mean, cloned_metrics.mean);
597 assert_eq!(metrics.median, cloned_metrics.median);
598 assert_eq!(metrics.mode, cloned_metrics.mode);
599 assert_eq!(metrics.std_dev, cloned_metrics.std_dev);
600 }
601 }
602
603 mod test_shape_metrics {
604 use super::*;
605
606 #[test]
607 fn test_shape_metrics_creation() {
608 let metrics = ShapeMetrics {
609 skewness: dec!(0.5),
610 kurtosis: dec!(3.0),
611 peaks: vec![create_test_point(1.0, 10.0)],
612 valleys: vec![create_test_point(2.0, 5.0)],
613 inflection_points: vec![create_test_point(1.5, 7.5)],
614 };
615
616 assert_eq!(metrics.skewness, dec!(0.5));
617 assert_eq!(metrics.kurtosis, dec!(3.0));
618 assert_eq!(metrics.peaks.len(), 1);
619 assert_eq!(metrics.valleys.len(), 1);
620 assert_eq!(metrics.inflection_points.len(), 1);
621 }
622
623 #[test]
624 fn test_shape_metrics_clone() {
625 let metrics = ShapeMetrics {
626 skewness: dec!(0.5),
627 kurtosis: dec!(3.0),
628 peaks: vec![create_test_point(1.0, 10.0)],
629 valleys: vec![create_test_point(2.0, 5.0)],
630 inflection_points: vec![create_test_point(1.5, 7.5)],
631 };
632
633 let cloned_metrics = metrics.clone();
634 assert_eq!(metrics.skewness, cloned_metrics.skewness);
635 assert_eq!(metrics.kurtosis, cloned_metrics.kurtosis);
636 assert_eq!(metrics.peaks.len(), cloned_metrics.peaks.len());
637 assert_eq!(metrics.valleys.len(), cloned_metrics.valleys.len());
638 assert_eq!(
639 metrics.inflection_points.len(),
640 cloned_metrics.inflection_points.len()
641 );
642 }
643 }
644
645 mod test_range_metrics {
646 use super::*;
647
648 #[test]
649 fn test_range_metrics_creation() {
650 let metrics = RangeMetrics {
651 min: create_test_point(0.0, 5.0),
652 max: create_test_point(10.0, 15.0),
653 range: dec!(10.0),
654 quartiles: (dec!(7.0), dec!(10.0), dec!(13.0)),
655 interquartile_range: dec!(6.0),
656 };
657
658 assert_eq!(metrics.min.x, dec!(0.0));
659 assert_eq!(metrics.max.x, dec!(10.0));
660 assert_eq!(metrics.range, dec!(10.0));
661 assert_eq!(metrics.quartiles.0, dec!(7.0));
662 assert_eq!(metrics.interquartile_range, dec!(6.0));
663 }
664
665 #[test]
666 fn test_range_metrics_clone() {
667 let metrics = RangeMetrics {
668 min: create_test_point(0.0, 5.0),
669 max: create_test_point(10.0, 15.0),
670 range: dec!(10.0),
671 quartiles: (dec!(7.0), dec!(10.0), dec!(13.0)),
672 interquartile_range: dec!(6.0),
673 };
674
675 let cloned_metrics = metrics;
676 assert_eq!(metrics.min.x, cloned_metrics.min.x);
677 assert_eq!(metrics.max.x, cloned_metrics.max.x);
678 assert_eq!(metrics.range, cloned_metrics.range);
679 assert_eq!(metrics.quartiles.0, cloned_metrics.quartiles.0);
680 assert_eq!(
681 metrics.interquartile_range,
682 cloned_metrics.interquartile_range
683 );
684 }
685 }
686
687 mod test_trend_metrics {
688 use super::*;
689
690 #[test]
691 fn test_trend_metrics_creation() {
692 let metrics = TrendMetrics {
693 slope: dec!(1.5),
694 intercept: dec!(2.0),
695 r_squared: dec!(0.95),
696 moving_average: vec![create_test_point(1.0, 3.5), create_test_point(2.0, 5.0)],
697 };
698
699 assert_eq!(metrics.slope, dec!(1.5));
700 assert_eq!(metrics.intercept, dec!(2.0));
701 assert_eq!(metrics.r_squared, dec!(0.95));
702 assert_eq!(metrics.moving_average.len(), 2);
703 }
704
705 #[test]
706 fn test_trend_metrics_clone() {
707 let metrics = TrendMetrics {
708 slope: dec!(1.5),
709 intercept: dec!(2.0),
710 r_squared: dec!(0.95),
711 moving_average: vec![create_test_point(1.0, 3.5), create_test_point(2.0, 5.0)],
712 };
713
714 let cloned_metrics = metrics.clone();
715 assert_eq!(metrics.slope, cloned_metrics.slope);
716 assert_eq!(metrics.intercept, cloned_metrics.intercept);
717 assert_eq!(metrics.r_squared, cloned_metrics.r_squared);
718 assert_eq!(
719 metrics.moving_average.len(),
720 cloned_metrics.moving_average.len()
721 );
722 }
723 }
724
725 mod test_risk_metrics {
726 use super::*;
727
728 #[test]
729 fn test_risk_metrics_creation() {
730 let metrics = RiskMetrics {
731 volatility: dec!(0.15),
732 value_at_risk: dec!(0.05),
733 expected_shortfall: dec!(0.07),
734 coefficient_of_variation: dec!(1.2),
735 sharpe_ratio: dec!(2.5),
736 };
737
738 assert_eq!(metrics.volatility, dec!(0.15));
739 assert_eq!(metrics.value_at_risk, dec!(0.05));
740 assert_eq!(metrics.expected_shortfall, dec!(0.07));
741 assert_eq!(metrics.coefficient_of_variation, dec!(1.2));
742 assert_eq!(metrics.sharpe_ratio, dec!(2.5));
743 }
744
745 #[test]
746 fn test_risk_metrics_clone() {
747 let metrics = RiskMetrics {
748 volatility: dec!(0.15),
749 value_at_risk: dec!(0.05),
750 expected_shortfall: dec!(0.07),
751 coefficient_of_variation: dec!(1.2),
752 sharpe_ratio: dec!(2.5),
753 };
754
755 let cloned_metrics = metrics;
756 assert_eq!(metrics.volatility, cloned_metrics.volatility);
757 assert_eq!(metrics.value_at_risk, cloned_metrics.value_at_risk);
758 assert_eq!(
759 metrics.expected_shortfall,
760 cloned_metrics.expected_shortfall
761 );
762 assert_eq!(
763 metrics.coefficient_of_variation,
764 cloned_metrics.coefficient_of_variation
765 );
766 assert_eq!(metrics.sharpe_ratio, cloned_metrics.sharpe_ratio);
767 }
768 }
769
770 mod test_curve_metrics {
771 use super::*;
772
773 fn create_test_curve_metrics() -> Metrics {
774 Metrics {
775 basic: BasicMetrics {
776 mean: dec!(10.5),
777 median: dec!(10.0),
778 mode: dec!(9.0),
779 std_dev: dec!(1.2),
780 },
781 shape: ShapeMetrics {
782 skewness: dec!(0.5),
783 kurtosis: dec!(3.0),
784 peaks: vec![create_test_point(1.0, 10.0)],
785 valleys: vec![create_test_point(2.0, 5.0)],
786 inflection_points: vec![create_test_point(1.5, 7.5)],
787 },
788 range: RangeMetrics {
789 min: create_test_point(0.0, 5.0),
790 max: create_test_point(10.0, 15.0),
791 range: dec!(10.0),
792 quartiles: (dec!(7.0), dec!(10.0), dec!(13.0)),
793 interquartile_range: dec!(6.0),
794 },
795 trend: TrendMetrics {
796 slope: dec!(1.5),
797 intercept: dec!(2.0),
798 r_squared: dec!(0.95),
799 moving_average: vec![create_test_point(1.0, 3.5), create_test_point(2.0, 5.0)],
800 },
801 risk: RiskMetrics {
802 volatility: dec!(0.15),
803 value_at_risk: dec!(0.05),
804 expected_shortfall: dec!(0.07),
805 coefficient_of_variation: dec!(1.2),
806 sharpe_ratio: dec!(2.5),
807 },
808 }
809 }
810
811 #[test]
812 fn test_curve_metrics_creation() {
813 let metrics = create_test_curve_metrics();
814
815 assert_eq!(metrics.basic.mean, dec!(10.5));
816 assert_eq!(metrics.shape.skewness, dec!(0.5));
817 assert_eq!(metrics.range.range, dec!(10.0));
818 assert_eq!(metrics.trend.slope, dec!(1.5));
819 assert_eq!(metrics.risk.volatility, dec!(0.15));
820 }
821
822 #[test]
823 fn test_curve_metrics_clone() {
824 let metrics = create_test_curve_metrics();
825 let cloned_metrics = metrics.clone();
826
827 assert_eq!(metrics.basic.mean, cloned_metrics.basic.mean);
828 assert_eq!(metrics.shape.skewness, cloned_metrics.shape.skewness);
829 assert_eq!(metrics.range.range, cloned_metrics.range.range);
830 assert_eq!(metrics.trend.slope, cloned_metrics.trend.slope);
831 assert_eq!(metrics.risk.volatility, cloned_metrics.risk.volatility);
832 }
833
834 #[test]
835 fn test_curve_analysis_result() {
836 let metrics = create_test_curve_metrics();
837 let result = metrics.analysis_result();
838
839 assert!(result.is_ok());
840 let analysis = result.unwrap();
841
842 assert_eq!(analysis.statistics.mean, metrics.basic.mean);
843 assert_eq!(analysis.shape_metrics.skewness, metrics.shape.skewness);
844 }
845
846 #[test]
847 fn test_curve_metrics_new() {
848 let basic = BasicMetrics {
849 mean: dec!(10.5),
850 median: dec!(10.0),
851 mode: dec!(9.0),
852 std_dev: dec!(1.2),
853 };
854
855 let shape = ShapeMetrics {
856 skewness: dec!(0.5),
857 kurtosis: dec!(3.0),
858 peaks: vec![create_test_point(1.0, 10.0)],
859 valleys: vec![create_test_point(2.0, 5.0)],
860 inflection_points: vec![create_test_point(1.5, 7.5)],
861 };
862
863 let range = RangeMetrics {
864 min: create_test_point(0.0, 5.0),
865 max: create_test_point(10.0, 15.0),
866 range: dec!(10.0),
867 quartiles: (dec!(7.0), dec!(10.0), dec!(13.0)),
868 interquartile_range: dec!(6.0),
869 };
870
871 let trend = TrendMetrics {
872 slope: dec!(1.5),
873 intercept: dec!(2.0),
874 r_squared: dec!(0.95),
875 moving_average: vec![create_test_point(1.0, 3.5), create_test_point(2.0, 5.0)],
876 };
877
878 let risk = RiskMetrics {
879 volatility: dec!(0.15),
880 value_at_risk: dec!(0.05),
881 expected_shortfall: dec!(0.07),
882 coefficient_of_variation: dec!(1.2),
883 sharpe_ratio: dec!(2.5),
884 };
885
886 let metrics = Metrics::new(basic, shape, range, trend, risk);
887
888 assert_eq!(metrics.basic.mean, dec!(10.5));
889 assert_eq!(metrics.shape.skewness, dec!(0.5));
890 assert_eq!(metrics.range.range, dec!(10.0));
891 assert_eq!(metrics.trend.slope, dec!(1.5));
892 assert_eq!(metrics.risk.volatility, dec!(0.15));
893 }
894 }
895}