use crate::error::MetricsError;
use crate::geometrics::{
BasicMetrics, Metrics, RangeMetrics, RiskMetrics, ShapeMetrics, TrendMetrics,
};
use crate::utils::Len;
pub trait MetricsExtractor: Len {
fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError>;
fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError>;
fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError>;
fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError>;
fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError>;
fn compute_curve_metrics(&self) -> Result<Metrics, MetricsError> {
let basic = self.compute_basic_metrics()?;
let shape = self.compute_shape_metrics()?;
let range = self.compute_range_metrics()?;
let trend = self.compute_trend_metrics()?;
let risk = self.compute_risk_metrics()?;
Ok(Metrics::new(basic, shape, range, trend, risk))
}
fn compute_surface_metrics(&self) -> Result<Metrics, MetricsError> {
let basic = self.compute_basic_metrics()?;
let shape = self.compute_shape_metrics()?;
let range = self.compute_range_metrics()?;
let trend = self.compute_trend_metrics()?;
let risk = self.compute_risk_metrics()?;
Ok(Metrics::new(basic, shape, range, trend, risk))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::curves::Point2D;
use num_traits::FromPrimitive;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
struct MockCurve;
fn create_test_point(x: f64, y: f64) -> Point2D {
Point2D::new(Decimal::from_f64(x).unwrap(), Decimal::from_f64(y).unwrap())
}
impl Len for MockCurve {
fn len(&self) -> usize {
1
}
}
impl MetricsExtractor for MockCurve {
fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
Ok(BasicMetrics {
mean: dec!(10.5),
median: dec!(10.0),
mode: dec!(9.0),
std_dev: dec!(1.2),
})
}
fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
Ok(ShapeMetrics {
skewness: dec!(0.5),
kurtosis: dec!(3.0),
peaks: vec![create_test_point(1.0, 10.0)],
valleys: vec![create_test_point(2.0, 5.0)],
inflection_points: vec![create_test_point(1.5, 7.5)],
})
}
fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
Ok(RangeMetrics {
min: create_test_point(0.0, 5.0),
max: create_test_point(10.0, 15.0),
range: dec!(10.0),
quartiles: (dec!(7.0), dec!(10.0), dec!(13.0)),
interquartile_range: dec!(6.0),
})
}
fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
Ok(TrendMetrics {
slope: dec!(1.5),
intercept: dec!(2.0),
r_squared: dec!(0.95),
moving_average: vec![create_test_point(1.0, 3.5), create_test_point(2.0, 5.0)],
})
}
fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
Ok(RiskMetrics {
volatility: dec!(0.15),
value_at_risk: dec!(0.05),
expected_shortfall: dec!(0.07),
beta: dec!(1.2),
sharpe_ratio: dec!(2.5),
})
}
}
struct ErrorMockCurve;
impl Len for ErrorMockCurve {
fn len(&self) -> usize {
1
}
}
impl MetricsExtractor for ErrorMockCurve {
fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
Err(MetricsError::BasicError(
"Basic metrics computation failed".to_string(),
))
}
fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
Err(MetricsError::ShapeError(
"Shape metrics computation failed".to_string(),
))
}
fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
Err(MetricsError::RangeError(
"Range metrics computation failed".to_string(),
))
}
fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
Err(MetricsError::TrendError(
"Trend metrics computation failed".to_string(),
))
}
fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
Err(MetricsError::RiskError(
"Risk metrics computation failed".to_string(),
))
}
}
mod test_successful_computations {
use super::*;
#[test]
fn test_compute_basic_metrics() {
let curve = MockCurve;
let result = curve.compute_basic_metrics();
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.mean, dec!(10.5));
assert_eq!(metrics.median, dec!(10.0));
assert_eq!(metrics.mode, dec!(9.0));
assert_eq!(metrics.std_dev, dec!(1.2));
}
#[test]
fn test_compute_shape_metrics() {
let curve = MockCurve;
let result = curve.compute_shape_metrics();
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.skewness, dec!(0.5));
assert_eq!(metrics.kurtosis, dec!(3.0));
assert_eq!(metrics.peaks.len(), 1);
assert_eq!(metrics.valleys.len(), 1);
assert_eq!(metrics.inflection_points.len(), 1);
}
#[test]
fn test_compute_range_metrics() {
let curve = MockCurve;
let result = curve.compute_range_metrics();
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.range, dec!(10.0));
assert_eq!(metrics.quartiles.0, dec!(7.0));
assert_eq!(metrics.interquartile_range, dec!(6.0));
}
#[test]
fn test_compute_trend_metrics() {
let curve = MockCurve;
let result = curve.compute_trend_metrics();
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.slope, dec!(1.5));
assert_eq!(metrics.intercept, dec!(2.0));
assert_eq!(metrics.r_squared, dec!(0.95));
assert_eq!(metrics.moving_average.len(), 2);
}
#[test]
fn test_compute_risk_metrics() {
let curve = MockCurve;
let result = curve.compute_risk_metrics();
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.volatility, dec!(0.15));
assert_eq!(metrics.value_at_risk, dec!(0.05));
assert_eq!(metrics.expected_shortfall, dec!(0.07));
assert_eq!(metrics.beta, dec!(1.2));
assert_eq!(metrics.sharpe_ratio, dec!(2.5));
}
#[test]
fn test_compute_curve_metrics() {
let curve = MockCurve;
let result = curve.compute_curve_metrics();
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.basic.mean, dec!(10.5));
assert_eq!(metrics.shape.skewness, dec!(0.5));
assert_eq!(metrics.range.range, dec!(10.0));
assert_eq!(metrics.trend.slope, dec!(1.5));
assert_eq!(metrics.risk.volatility, dec!(0.15));
}
}
mod test_error_cases {
use super::*;
#[test]
fn test_basic_metrics_error() {
let curve = ErrorMockCurve;
let result = curve.compute_basic_metrics();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Basic Error: Basic metrics computation failed"
);
}
#[test]
fn test_shape_metrics_error() {
let curve = ErrorMockCurve;
let result = curve.compute_shape_metrics();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Shape Error: Shape metrics computation failed"
);
}
#[test]
fn test_range_metrics_error() {
let curve = ErrorMockCurve;
let result = curve.compute_range_metrics();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Range Error: Range metrics computation failed"
);
}
#[test]
fn test_trend_metrics_error() {
let curve = ErrorMockCurve;
let result = curve.compute_trend_metrics();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Trend Error: Trend metrics computation failed"
);
}
#[test]
fn test_risk_metrics_error() {
let curve = ErrorMockCurve;
let result = curve.compute_risk_metrics();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Risk Error: Risk metrics computation failed"
);
}
#[test]
fn test_curve_metrics_error_propagation() {
let curve = ErrorMockCurve;
let result = curve.compute_curve_metrics();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Basic Error: Basic metrics computation failed"
);
}
}
mod test_surface_metrics {
use super::*;
#[test]
fn test_compute_surface_metrics_success() {
let curve = MockCurve;
let result = curve.compute_surface_metrics();
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.basic.mean, dec!(10.5));
assert_eq!(metrics.basic.median, dec!(10.0));
assert_eq!(metrics.basic.std_dev, dec!(1.2));
assert_eq!(metrics.shape.skewness, dec!(0.5));
assert_eq!(metrics.shape.kurtosis, dec!(3.0));
assert!(!metrics.shape.peaks.is_empty());
assert_eq!(metrics.range.range, dec!(10.0));
assert_eq!(metrics.range.interquartile_range, dec!(6.0));
assert_eq!(metrics.trend.slope, dec!(1.5));
assert_eq!(metrics.trend.r_squared, dec!(0.95));
assert_eq!(metrics.risk.volatility, dec!(0.15));
assert_eq!(metrics.risk.sharpe_ratio, dec!(2.5));
}
#[test]
fn test_compute_surface_metrics_error() {
let curve = ErrorMockCurve;
let result = curve.compute_surface_metrics();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Basic Error: Basic metrics computation failed"
);
}
#[test]
fn test_surface_metrics_matches_curve_metrics() {
let curve = MockCurve;
let surface_metrics = curve.compute_surface_metrics().unwrap();
let curve_metrics = curve.compute_curve_metrics().unwrap();
assert_eq!(surface_metrics.basic.mean, curve_metrics.basic.mean);
assert_eq!(surface_metrics.shape.skewness, curve_metrics.shape.skewness);
assert_eq!(surface_metrics.range.range, curve_metrics.range.range);
assert_eq!(surface_metrics.trend.slope, curve_metrics.trend.slope);
assert_eq!(
surface_metrics.risk.volatility,
curve_metrics.risk.volatility
);
}
}
}