1use crate::curves::{Curve, Point2D};
7use crate::error::CurveError;
8use crate::geometrics::{BasicMetrics, MetricsExtractor, RangeMetrics, ShapeMetrics, TrendMetrics};
9use optionstratlib_core::error::OperationErrorKind;
10use optionstratlib_core::model::decimal::{d_sub, decimal_to_f64};
11use rand::rngs::StdRng;
12use rand::{RngExt, SeedableRng};
13use rust_decimal::Decimal;
14use statrs::distribution::{ContinuousCDF, Normal};
15use std::collections::BTreeSet;
16use tracing::error;
17
18pub trait Curvable {
54 fn curve(&self) -> Result<Curve, CurveError>;
75}
76
77pub trait StatisticalCurve: MetricsExtractor {
83 fn get_x_values(&self) -> Vec<Decimal>;
94
95 fn generate_statistical_curve(
122 &self,
123 basic_metrics: &BasicMetrics,
124 shape_metrics: &ShapeMetrics,
125 range_metrics: &RangeMetrics,
126 trend_metrics: &TrendMetrics,
127 num_points: usize,
128 seed: Option<u64>,
129 ) -> Result<Curve, CurveError> {
130 if num_points < 2 {
131 return Err(CurveError::OperationError(
132 OperationErrorKind::InvalidParameters {
133 operation: "generate_statistical_curve".to_string(),
134 reason: "Number of points must be at least 2".to_string(),
135 },
136 ));
137 }
138
139 let x_values: Vec<Decimal> = self.get_x_values();
142 if num_points > x_values.len() {
143 return Err(CurveError::OperationError(
144 OperationErrorKind::InvalidParameters {
145 operation: "generate_statistical_curve".to_string(),
146 reason: format!(
147 "Number of points ({num_points}) exceeds the {} x-values available on the curve",
148 x_values.len()
149 ),
150 },
151 ));
152 }
153
154 let to_f64 = |value: Decimal| -> Result<f64, CurveError> {
158 decimal_to_f64(value).map_err(|e| {
159 CurveError::invalid_parameters("generate_statistical_curve", &e.to_string())
160 })
161 };
162 let mean = to_f64(basic_metrics.mean)?;
163 let std_dev = to_f64(basic_metrics.std_dev)?;
164
165 let seed_value = seed.unwrap_or_else(rand::random);
167 let mut rng = StdRng::seed_from_u64(seed_value);
168 let mut y_values: Vec<f64> = if basic_metrics.std_dev != Decimal::ZERO {
171 let normal = Normal::new(mean, std_dev).map_err(|e| {
172 error!(
173 "Failed to create normal distribution with mean {} and std_dev {}: {}",
174 basic_metrics.mean, basic_metrics.std_dev, e
175 );
176 CurveError::MetricsError(e.to_string())
177 })?;
178
179 (0..num_points)
181 .map(|_| {
182 let u: f64 = rng.random_range(0.0..1.0); normal.inverse_cdf(u) })
185 .collect()
186 } else {
187 vec![mean; num_points]
188 };
189
190 let skewness = to_f64(shape_metrics.skewness)?;
192 let kurtosis = to_f64(shape_metrics.kurtosis)?;
193
194 if skewness.abs() > 0.01 {
196 for y in &mut y_values {
197 *y += skewness * (*y - mean).powi(2);
199 }
200 }
201
202 if kurtosis.abs() > 0.01 {
204 for y in &mut y_values {
205 let z = (*y - mean) / std_dev;
207 *y += kurtosis * 0.1 * z.powi(3);
208 }
209 }
210
211 let slope = to_f64(trend_metrics.slope)?;
213 if slope.abs() > 0.001 {
214 let intercept = to_f64(trend_metrics.intercept)?;
215 for (i, y) in y_values.iter_mut().enumerate() {
216 let x = x_values.get(i).ok_or_else(|| {
217 CurveError::invalid_parameters(
218 "generate_statistical_curve",
219 "fewer x-values than generated samples",
220 )
221 })?;
222 *y += slope * to_f64(*x)? + intercept;
223 }
224 }
225
226 let current_min = y_values.iter().cloned().fold(f64::INFINITY, f64::min);
228 let current_max = y_values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
229 let current_range = current_max - current_min;
230
231 let target_min = to_f64(range_metrics.min.y)?;
232 let target_max = to_f64(range_metrics.max.y)?;
233 let target_range = target_max - target_min;
234
235 if current_range > 0.0 {
237 for y in &mut y_values {
238 *y = ((*y - current_min) / current_range) * target_range + target_min;
239 }
240 }
241
242 if num_points > 3 {
244 let index = rng.random_range(0..(num_points / 3));
245 let mode = to_f64(basic_metrics.mode)?;
246 if let Some(slot) = y_values.get_mut(index) {
247 *slot = mode;
248 }
249 }
250
251 let mut points = BTreeSet::new();
253 for (x, y) in x_values.iter().zip(&y_values).take(num_points) {
254 let point = Point2D::from_f64_tuple(to_f64(*x)?, *y)?;
255 points.insert(point);
256 }
257
258 Ok(Curve::new(points))
260 }
261
262 #[allow(clippy::too_many_arguments)]
289 fn generate_refined_statistical_curve(
290 &self,
291 basic_metrics: &BasicMetrics,
292 shape_metrics: &ShapeMetrics,
293 range_metrics: &RangeMetrics,
294 trend_metrics: &TrendMetrics,
295 num_points: usize,
296 max_attempts: usize,
297 tolerance: Decimal,
298 seed: Option<u64>,
299 ) -> Result<Curve, CurveError> {
300 let max_tries = if max_attempts == 0 { 5 } else { max_attempts };
301 let mut seed_value = seed.unwrap_or_else(rand::random);
302
303 for _ in 0..max_tries {
304 let curve = self.generate_statistical_curve(
305 basic_metrics,
306 shape_metrics,
307 range_metrics,
308 trend_metrics,
309 num_points,
310 Some(seed_value),
311 )?;
312
313 if self.verify_curve_metrics(&curve, basic_metrics, tolerance)? {
314 return Ok(curve);
315 }
316
317 seed_value = seed_value.wrapping_add(1); }
320
321 self.generate_statistical_curve(
323 basic_metrics,
324 shape_metrics,
325 range_metrics,
326 trend_metrics,
327 num_points,
328 Some(seed_value),
329 )
330 }
331
332 fn verify_curve_metrics(
353 &self,
354 curve: &Curve,
355 target_metrics: &BasicMetrics,
356 tolerance: Decimal,
357 ) -> Result<bool, CurveError> {
358 let actual_metrics = curve
359 .compute_basic_metrics()
360 .map_err(|e| CurveError::MetricsError(format!("Failed to compute metrics: {e}")))?;
361
362 let mean_diff = d_sub(
367 actual_metrics.mean,
368 target_metrics.mean,
369 "StatisticalCurve::verify_curve_metrics::mean_diff",
370 )
371 .map_err(|e| CurveError::AnalysisError(e.to_string()))?
372 .abs();
373 let std_dev_diff = d_sub(
374 actual_metrics.std_dev,
375 target_metrics.std_dev,
376 "StatisticalCurve::verify_curve_metrics::std_dev_diff",
377 )
378 .map_err(|e| CurveError::AnalysisError(e.to_string()))?
379 .abs();
380
381 Ok(mean_diff <= tolerance && std_dev_diff <= tolerance)
382 }
383}
384
385#[cfg(test)]
386mod tests_statistical_curve {
387 use super::*;
388 use crate::error::MetricsError;
389 use crate::geometrics::RiskMetrics;
390 use optionstratlib_core::utils::Len;
391 use rust_decimal_macros::dec;
392 use std::collections::BTreeSet;
393
394 struct TestCurveGenerator;
396
397 impl Len for TestCurveGenerator {
398 fn len(&self) -> usize {
399 unreachable!()
400 }
401 }
402
403 impl MetricsExtractor for TestCurveGenerator {
404 fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
405 Ok(BasicMetrics {
407 mean: dec!(0.0),
408 median: dec!(0.0),
409 mode: dec!(0.0),
410 std_dev: dec!(1.0),
411 })
412 }
413
414 fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
415 Ok(ShapeMetrics {
416 skewness: dec!(0.0),
417 kurtosis: dec!(0.0),
418 peaks: vec![],
419 valleys: vec![],
420 inflection_points: vec![],
421 })
422 }
423
424 fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
425 Ok(RangeMetrics {
426 min: Point2D::new(dec!(0.0), dec!(0.0)),
427 max: Point2D::new(dec!(10.0), dec!(10.0)),
428 range: dec!(10.0),
429 quartiles: (dec!(2.5), dec!(5.0), dec!(7.5)),
430 interquartile_range: dec!(5.0),
431 })
432 }
433
434 fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
435 Ok(TrendMetrics {
436 slope: dec!(0.0),
437 intercept: dec!(0.0),
438 r_squared: dec!(0.0),
439 moving_average: vec![],
440 })
441 }
442
443 fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
444 unreachable!()
445 }
446 }
447
448 impl StatisticalCurve for TestCurveGenerator {
449 fn get_x_values(&self) -> Vec<Decimal> {
450 (0..10).map(Decimal::from).collect()
451 }
452 }
453
454 impl Curvable for TestCurveGenerator {
456 fn curve(&self) -> Result<Curve, CurveError> {
457 let points: BTreeSet<Point2D> = (0..10)
459 .map(|i| Point2D::new(Decimal::from(i), Decimal::from(i)))
460 .collect();
461 Ok(Curve::new(points))
462 }
463 }
464
465 #[test]
466 fn test_get_x_values() {
467 let generator = TestCurveGenerator;
468 let x_values = generator.get_x_values();
469
470 assert_eq!(x_values.len(), 10);
471 assert_eq!(x_values[0], dec!(0));
472 assert_eq!(x_values[9], dec!(9));
473 }
474
475 #[test]
476 fn test_generate_statistical_curve_invalid_points() {
477 let generator = TestCurveGenerator;
478
479 let basic_metrics = BasicMetrics::default();
480 let shape_metrics = ShapeMetrics::default();
481 let range_metrics = RangeMetrics::default();
482 let trend_metrics = TrendMetrics::default();
483
484 let result = generator.generate_statistical_curve(
486 &basic_metrics,
487 &shape_metrics,
488 &range_metrics,
489 &trend_metrics,
490 1, None,
492 );
493
494 assert!(result.is_err());
495 if let Err(CurveError::OperationError(OperationErrorKind::InvalidParameters {
496 operation,
497 reason,
498 })) = result
499 {
500 assert_eq!(operation, "generate_statistical_curve");
501 assert!(reason.contains("Number of points must be at least 2"));
502 } else {
503 panic!("Expected InvalidParameters error");
504 }
505 }
506
507 #[test]
508 fn test_verify_curve_metrics() {
509 let generator = TestCurveGenerator;
510
511 let curve = generator.curve().unwrap();
513
514 let target_metrics = BasicMetrics {
516 mean: dec!(4.5), median: dec!(4.5),
518 mode: dec!(0.0),
519 std_dev: dec!(3.0), };
521
522 let result = generator.verify_curve_metrics(&curve, &target_metrics, dec!(1.0));
524 assert!(result.is_ok());
525 assert!(result.unwrap());
526
527 let result = generator.verify_curve_metrics(&curve, &target_metrics, dec!(0.1));
529 assert!(result.is_ok());
530 }
532
533 impl Default for BasicMetrics {
535 fn default() -> Self {
536 Self {
537 mean: dec!(0.0),
538 median: dec!(0.0),
539 mode: dec!(0.0),
540 std_dev: dec!(1.0),
541 }
542 }
543 }
544
545 impl Default for ShapeMetrics {
546 fn default() -> Self {
547 Self {
548 skewness: dec!(0.0),
549 kurtosis: dec!(0.0),
550 peaks: vec![],
551 valleys: vec![],
552 inflection_points: vec![],
553 }
554 }
555 }
556
557 impl Default for RangeMetrics {
558 fn default() -> Self {
559 Self {
560 min: Point2D::new(dec!(0.0), dec!(0.0)),
561 max: Point2D::new(dec!(10.0), dec!(10.0)),
562 range: dec!(10.0),
563 quartiles: (dec!(2.5), dec!(5.0), dec!(7.5)),
564 interquartile_range: dec!(5.0),
565 }
566 }
567 }
568
569 impl Default for TrendMetrics {
570 fn default() -> Self {
571 Self {
572 slope: dec!(0.0),
573 intercept: dec!(0.0),
574 r_squared: dec!(0.0),
575 moving_average: vec![],
576 }
577 }
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use crate::error::MetricsError;
585 use crate::geometrics::RiskMetrics;
586 use optionstratlib_core::utils::Len;
587 use rust_decimal::Decimal;
588 use rust_decimal_macros::dec;
589 use std::collections::BTreeSet;
590
591 struct MockCurvable {
592 points: BTreeSet<Point2D>,
593 should_fail: bool,
594 }
595
596 impl MockCurvable {
597 fn new(should_fail: bool) -> Self {
598 let mut points = BTreeSet::new();
599
600 if !should_fail {
601 points.insert(Point2D::new(dec!(1.0), dec!(2.0)));
603 points.insert(Point2D::new(dec!(2.0), dec!(3.0)));
604 points.insert(Point2D::new(dec!(3.0), dec!(4.0)));
605 }
606
607 Self {
608 points,
609 should_fail,
610 }
611 }
612 }
613
614 impl Curvable for MockCurvable {
615 fn curve(&self) -> Result<Curve, CurveError> {
616 if self.should_fail {
617 Err(CurveError::OperationError(
618 OperationErrorKind::InvalidParameters {
619 operation: "curve".to_string(),
620 reason: "Test failure".to_string(),
621 },
622 ))
623 } else {
624 Ok(Curve::new(self.points.clone()))
625 }
626 }
627 }
628
629 struct MockStatisticalCurve {
631 x_values: Vec<Decimal>,
632 }
633
634 impl MockStatisticalCurve {
635 fn new() -> Self {
636 let x_values = vec![dec!(1.0), dec!(2.0), dec!(3.0), dec!(4.0), dec!(5.0)];
637 Self { x_values }
638 }
639 }
640
641 impl Len for MockStatisticalCurve {
642 fn len(&self) -> usize {
643 self.x_values.len()
644 }
645 }
646
647 impl MetricsExtractor for MockStatisticalCurve {
648 fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
649 Ok(BasicMetrics {
650 mean: dec!(3.0),
651 median: dec!(3.0),
652 mode: dec!(3.0),
653 std_dev: dec!(1.5),
654 })
655 }
656
657 fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
658 Ok(ShapeMetrics {
659 skewness: dec!(0.0),
660 kurtosis: dec!(0.0),
661 peaks: vec![],
662 valleys: vec![],
663 inflection_points: vec![],
664 })
665 }
666
667 fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
668 Ok(RangeMetrics {
669 min: Point2D::new(dec!(1.0), dec!(1.0)),
670 max: Point2D::new(dec!(5.0), dec!(5.0)),
671 range: dec!(4.0),
672 quartiles: (Default::default(), Default::default(), Default::default()),
673 interquartile_range: Default::default(),
674 })
675 }
676
677 fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
678 Ok(TrendMetrics {
679 slope: dec!(1.0),
680 intercept: dec!(0.0),
681 r_squared: dec!(1.0),
682 moving_average: vec![],
683 })
684 }
685
686 fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
687 Ok(RiskMetrics {
688 volatility: Default::default(),
689 value_at_risk: Default::default(),
690 expected_shortfall: Default::default(),
691 coefficient_of_variation: Default::default(),
692 sharpe_ratio: Default::default(),
693 })
694 }
695 }
696
697 impl StatisticalCurve for MockStatisticalCurve {
698 fn get_x_values(&self) -> Vec<Decimal> {
699 self.x_values.clone()
700 }
701 }
702
703 #[test]
705 fn test_curvable_success() {
706 let mock = MockCurvable::new(false);
707 let result = mock.curve();
708
709 assert!(result.is_ok(), "Curve generation should succeed");
710 let curve = result.unwrap();
711
712 assert_eq!(curve.len(), 3, "Curve should have 3 points");
713 }
714
715 #[test]
716 fn test_curvable_failure() {
717 let mock = MockCurvable::new(true);
718 let result = mock.curve();
719
720 assert!(result.is_err(), "Curve generation should fail");
721
722 if let Err(CurveError::OperationError(OperationErrorKind::InvalidParameters {
723 operation,
724 reason,
725 })) = result
726 {
727 assert_eq!(operation, "curve", "Operation name should match");
728 assert_eq!(reason, "Test failure", "Error reason should match");
729 } else {
730 panic!("Unexpected error type");
731 }
732 }
733
734 #[test]
736 fn test_get_x_values() {
737 let mock = MockStatisticalCurve::new();
738 let x_values = mock.get_x_values();
739
740 assert_eq!(x_values.len(), 5, "Should return 5 x values");
741 assert_eq!(x_values[0], dec!(1.0), "First x value should be 1.0");
742 assert_eq!(x_values[4], dec!(5.0), "Last x value should be 5.0");
743 }
744
745 #[test]
746 fn test_generate_statistical_curve_invalid_points() {
747 let mock = MockStatisticalCurve::new();
748
749 let basic_metrics = BasicMetrics {
750 mean: dec!(3.0),
751 median: dec!(3.0),
752 mode: dec!(3.0),
753 std_dev: dec!(1.5),
754 };
755
756 let shape_metrics = ShapeMetrics {
757 skewness: dec!(0.0),
758 kurtosis: dec!(0.0),
759 peaks: vec![],
760 valleys: vec![],
761 inflection_points: vec![],
762 };
763
764 let range_metrics = RangeMetrics {
765 min: Point2D::new(dec!(1.0), dec!(1.0)),
766 max: Point2D::new(dec!(5.0), dec!(5.0)),
767 range: dec!(4.0),
768 quartiles: (Default::default(), Default::default(), Default::default()),
769 interquartile_range: Default::default(),
770 };
771
772 let trend_metrics = TrendMetrics {
773 slope: dec!(1.0),
774 intercept: dec!(0.0),
775 r_squared: dec!(1.0),
776 moving_average: vec![],
777 };
778
779 let result = mock.generate_statistical_curve(
780 &basic_metrics,
781 &shape_metrics,
782 &range_metrics,
783 &trend_metrics,
784 1,
785 None,
786 );
787
788 assert!(result.is_err(), "Should fail with less than 2 points");
789 if let Err(CurveError::OperationError(OperationErrorKind::InvalidParameters {
790 operation,
791 reason,
792 })) = result
793 {
794 assert_eq!(operation, "generate_statistical_curve");
795 assert!(reason.contains("at least 2"));
796 } else {
797 panic!("Unexpected error type");
798 }
799 }
800
801 #[test]
802 fn test_generate_statistical_curve_success() {
803 let mock = MockStatisticalCurve::new();
804
805 let basic_metrics = BasicMetrics {
806 mean: dec!(3.0),
807 median: dec!(3.0),
808 mode: dec!(3.0),
809 std_dev: dec!(1.5),
810 };
811
812 let shape_metrics = ShapeMetrics {
813 skewness: dec!(0.0),
814 kurtosis: dec!(0.0),
815 peaks: vec![],
816 valleys: vec![],
817 inflection_points: vec![],
818 };
819
820 let range_metrics = RangeMetrics {
821 min: Point2D::new(dec!(1.0), dec!(1.0)),
822 max: Point2D::new(dec!(5.0), dec!(5.0)),
823 range: dec!(4.0),
824 quartiles: (Default::default(), Default::default(), Default::default()),
825 interquartile_range: Default::default(),
826 };
827
828 let trend_metrics = TrendMetrics {
829 slope: dec!(1.0),
830 intercept: dec!(0.0),
831 r_squared: dec!(1.0),
832 moving_average: vec![],
833 };
834
835 let result = mock.generate_statistical_curve(
837 &basic_metrics,
838 &shape_metrics,
839 &range_metrics,
840 &trend_metrics,
841 5, Some(42), );
844
845 assert!(result.is_ok(), "Should successfully generate a curve");
846 let curve = result.unwrap();
847 assert!(curve.len() > 0, "Generated curve should contain points");
848 }
849
850 #[test]
851 fn test_verify_curve_metrics() {
852 let mock = MockStatisticalCurve::new();
853
854 let mut points = BTreeSet::new();
856 points.insert(Point2D::new(dec!(1.0), dec!(2.0)));
857 points.insert(Point2D::new(dec!(2.0), dec!(3.0)));
858 points.insert(Point2D::new(dec!(3.0), dec!(4.0)));
859 let curve = Curve::new(points);
860
861 let target_metrics = BasicMetrics {
863 mean: dec!(3.1),
864 median: dec!(3.0),
865 mode: dec!(3.0),
866 std_dev: dec!(1.0),
867 };
868
869 let result = mock.verify_curve_metrics(&curve, &target_metrics, dec!(0.5));
871 assert!(result.is_ok(), "Verification should not fail");
872 assert!(result.unwrap(), "Metrics should be within tolerance");
873
874 let result = mock.verify_curve_metrics(&curve, &target_metrics, dec!(0.05));
876 assert!(result.is_ok(), "Verification should not fail");
877 assert!(!result.unwrap(), "Metrics should not be within tolerance");
878 }
879
880 #[test]
881 fn test_refined_statistical_curve() {
882 let mock = MockStatisticalCurve::new();
883
884 let basic_metrics = BasicMetrics {
885 mean: dec!(3.0),
886 median: dec!(3.0),
887 mode: dec!(3.0),
888 std_dev: dec!(1.5),
889 };
890
891 let shape_metrics = ShapeMetrics {
892 skewness: dec!(0.0),
893 kurtosis: dec!(0.0),
894 peaks: vec![],
895 valleys: vec![],
896 inflection_points: vec![],
897 };
898
899 let range_metrics = RangeMetrics {
900 min: Point2D::new(dec!(1.0), dec!(1.0)),
901 max: Point2D::new(dec!(5.0), dec!(5.0)),
902 range: dec!(4.0),
903 quartiles: (Default::default(), Default::default(), Default::default()),
904 interquartile_range: Default::default(),
905 };
906
907 let trend_metrics = TrendMetrics {
908 slope: dec!(1.0),
909 intercept: dec!(0.0),
910 r_squared: dec!(1.0),
911 moving_average: vec![],
912 };
913
914 let result = mock.generate_refined_statistical_curve(
915 &basic_metrics,
916 &shape_metrics,
917 &range_metrics,
918 &trend_metrics,
919 5,
920 3, dec!(0.2), Some(42), );
924
925 assert!(
926 result.is_ok(),
927 "Should successfully generate a refined curve"
928 );
929 let curve = result.unwrap();
930 assert!(curve.len() > 0, "Generated curve should contain points");
931 }
932}
933
934#[cfg(test)]
935mod tests_statistical_curve_generation {
936 use super::*;
937 use crate::error::MetricsError;
938 use crate::geometrics::RiskMetrics;
939 use optionstratlib_core::utils::Len;
940 use rust_decimal_macros::dec;
941
942 struct EnhancedTestCurveGenerator {
944 x_values: Vec<Decimal>,
945 fail_metrics: bool,
946 }
947
948 impl EnhancedTestCurveGenerator {
949 fn new(fail_metrics: bool) -> Self {
950 Self {
951 x_values: (0..10).map(Decimal::from).collect(),
952 fail_metrics,
953 }
954 }
955 }
956
957 impl Len for EnhancedTestCurveGenerator {
958 fn len(&self) -> usize {
959 self.x_values.len()
960 }
961
962 fn is_empty(&self) -> bool {
963 self.x_values.is_empty()
964 }
965 }
966
967 impl MetricsExtractor for EnhancedTestCurveGenerator {
968 fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
969 if self.fail_metrics {
970 Err(MetricsError::BasicError(
971 "compute_basic_metrics error".to_string(),
972 ))
973 } else {
974 Ok(BasicMetrics {
975 mean: dec!(5.0),
976 median: dec!(5.0),
977 mode: dec!(5.0),
978 std_dev: dec!(2.0),
979 })
980 }
981 }
982
983 fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
984 Ok(ShapeMetrics {
985 skewness: dec!(0.5), kurtosis: dec!(0.5), peaks: vec![],
988 valleys: vec![],
989 inflection_points: vec![],
990 })
991 }
992
993 fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
994 Ok(RangeMetrics {
995 min: Point2D::new(dec!(0.0), dec!(0.0)),
996 max: Point2D::new(dec!(10.0), dec!(10.0)),
997 range: dec!(10.0),
998 quartiles: (dec!(2.5), dec!(5.0), dec!(7.5)),
999 interquartile_range: dec!(5.0),
1000 })
1001 }
1002
1003 fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
1004 Ok(TrendMetrics {
1005 slope: dec!(1.0), intercept: dec!(0.5), r_squared: dec!(0.9),
1008 moving_average: vec![],
1009 })
1010 }
1011
1012 fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
1013 Ok(RiskMetrics {
1014 volatility: dec!(0.5),
1015 value_at_risk: dec!(1.0),
1016 expected_shortfall: dec!(1.5),
1017 coefficient_of_variation: dec!(0.8),
1018 sharpe_ratio: dec!(1.2),
1019 })
1020 }
1021 }
1022
1023 impl StatisticalCurve for EnhancedTestCurveGenerator {
1024 fn get_x_values(&self) -> Vec<Decimal> {
1025 self.x_values.clone()
1026 }
1027 }
1028
1029 impl Curvable for EnhancedTestCurveGenerator {
1030 fn curve(&self) -> Result<Curve, CurveError> {
1031 let points: BTreeSet<Point2D> = (0..10)
1033 .map(|i| Point2D::new(Decimal::from(i), Decimal::from(i)))
1034 .collect();
1035 Ok(Curve::new(points))
1036 }
1037 }
1038
1039 #[test]
1040 fn test_generate_statistical_curve_with_skewness() {
1041 let generator = EnhancedTestCurveGenerator::new(false);
1042
1043 let basic_metrics = generator.compute_basic_metrics().unwrap();
1044 let shape_metrics = generator.compute_shape_metrics().unwrap();
1045 let range_metrics = generator.compute_range_metrics().unwrap();
1046 let trend_metrics = generator.compute_trend_metrics().unwrap();
1047
1048 let result = generator.generate_statistical_curve(
1050 &basic_metrics,
1051 &shape_metrics,
1052 &range_metrics,
1053 &trend_metrics,
1054 10, Some(42), );
1057
1058 assert!(result.is_ok(), "Failed to generate curve with skewness");
1059
1060 let curve = result.unwrap();
1062 assert_eq!(curve.points.len(), 10);
1063 }
1064
1065 #[test]
1066 fn test_generate_statistical_curve_with_kurtosis() {
1067 let generator = EnhancedTestCurveGenerator::new(false);
1068
1069 let basic_metrics = generator.compute_basic_metrics().unwrap();
1070 let mut shape_metrics = generator.compute_shape_metrics().unwrap();
1071 shape_metrics.skewness = dec!(0.0); shape_metrics.kurtosis = dec!(1.0); let range_metrics = generator.compute_range_metrics().unwrap();
1074 let trend_metrics = generator.compute_trend_metrics().unwrap();
1075
1076 let result = generator.generate_statistical_curve(
1077 &basic_metrics,
1078 &shape_metrics,
1079 &range_metrics,
1080 &trend_metrics,
1081 10,
1082 Some(42),
1083 );
1084
1085 assert!(result.is_ok(), "Failed to generate curve with kurtosis");
1086 }
1087
1088 #[test]
1089 fn test_generate_statistical_curve_with_trend() {
1090 let generator = EnhancedTestCurveGenerator::new(false);
1092
1093 let basic_metrics = generator.compute_basic_metrics().unwrap();
1094 let shape_metrics = generator.compute_shape_metrics().unwrap();
1095 let range_metrics = generator.compute_range_metrics().unwrap();
1096 let mut trend_metrics = generator.compute_trend_metrics().unwrap();
1097 trend_metrics.slope = dec!(2.0); let result = generator.generate_statistical_curve(
1100 &basic_metrics,
1101 &shape_metrics,
1102 &range_metrics,
1103 &trend_metrics,
1104 10,
1105 Some(42),
1106 );
1107
1108 assert!(result.is_ok(), "Failed to generate curve with trend");
1109 }
1110
1111 #[test]
1117 fn test_verify_curve_metrics_failure() {
1118 let generator = EnhancedTestCurveGenerator::new(true);
1119
1120 let points = BTreeSet::from_iter(vec![
1122 Point2D::new(dec!(0.0), dec!(0.0)),
1123 Point2D::new(dec!(1.0), dec!(1.0)),
1124 ]);
1125 let curve = Curve::new(points);
1126
1127 let target_metrics = BasicMetrics {
1129 mean: dec!(0.0),
1130 median: dec!(-5.0),
1131 mode: dec!(5.0),
1132 std_dev: dec!(0.0),
1133 };
1134
1135 let result = generator.verify_curve_metrics(&curve, &target_metrics, dec!(0.0000001));
1136 assert!(matches!(result, Ok(false)));
1137
1138 let matching = BasicMetrics {
1140 mean: dec!(0.5),
1141 median: dec!(0.5),
1142 mode: dec!(0.0),
1143 std_dev: dec!(0.5),
1144 };
1145 let result = generator.verify_curve_metrics(&curve, &matching, dec!(0.0000001));
1146 assert!(matches!(result, Ok(true)));
1147 }
1148
1149 #[test]
1150 fn test_generate_refined_statistical_curve() {
1151 let generator = EnhancedTestCurveGenerator::new(false);
1153
1154 let basic_metrics = generator.compute_basic_metrics().unwrap();
1155 let shape_metrics = generator.compute_shape_metrics().unwrap();
1156 let range_metrics = generator.compute_range_metrics().unwrap();
1157 let trend_metrics = generator.compute_trend_metrics().unwrap();
1158
1159 let result = generator.generate_refined_statistical_curve(
1161 &basic_metrics,
1162 &shape_metrics,
1163 &range_metrics,
1164 &trend_metrics,
1165 10,
1166 3, dec!(0.01), Some(42),
1169 );
1170
1171 assert!(result.is_ok(), "Failed to generate refined curve");
1172
1173 let result = generator.generate_refined_statistical_curve(
1175 &basic_metrics,
1176 &shape_metrics,
1177 &range_metrics,
1178 &trend_metrics,
1179 10,
1180 0, dec!(0.5),
1182 Some(42),
1183 );
1184
1185 assert!(
1186 result.is_ok(),
1187 "Failed to generate refined curve with default attempts"
1188 );
1189 }
1190}
1191
1192#[cfg(test)]
1193mod tests_statistical_curve_edge_cases {
1194 use super::*;
1195 use crate::error::MetricsError;
1196 use crate::geometrics::RiskMetrics;
1197 use optionstratlib_core::utils::Len;
1198 use rust_decimal_macros::dec;
1199
1200 #[allow(dead_code)]
1201 struct EmptyXValuesGenerator;
1202
1203 impl Len for EmptyXValuesGenerator {
1204 fn len(&self) -> usize {
1205 0
1206 }
1207 fn is_empty(&self) -> bool {
1208 true
1209 }
1210 }
1211
1212 impl MetricsExtractor for EmptyXValuesGenerator {
1213 fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
1214 Ok(BasicMetrics {
1215 mean: dec!(0.0),
1216 median: dec!(0.0),
1217 mode: dec!(0.0),
1218 std_dev: dec!(0.0),
1219 })
1220 }
1221
1222 fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
1223 Ok(ShapeMetrics {
1224 skewness: dec!(0.0),
1225 kurtosis: dec!(0.0),
1226 peaks: vec![],
1227 valleys: vec![],
1228 inflection_points: vec![],
1229 })
1230 }
1231
1232 fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
1233 Ok(RangeMetrics {
1234 min: Point2D::new(dec!(0.0), dec!(0.0)),
1235 max: Point2D::new(dec!(0.0), dec!(0.0)),
1236 range: dec!(0.0),
1237 quartiles: (dec!(0.0), dec!(0.0), dec!(0.0)),
1238 interquartile_range: dec!(0.0),
1239 })
1240 }
1241
1242 fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
1243 Ok(TrendMetrics {
1244 slope: dec!(0.0),
1245 intercept: dec!(0.0),
1246 r_squared: dec!(0.0),
1247 moving_average: vec![],
1248 })
1249 }
1250
1251 fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
1252 Ok(RiskMetrics {
1253 volatility: dec!(0.0),
1254 value_at_risk: dec!(0.0),
1255 expected_shortfall: dec!(0.0),
1256 coefficient_of_variation: dec!(0.0),
1257 sharpe_ratio: dec!(0.0),
1258 })
1259 }
1260 }
1261
1262 impl StatisticalCurve for EmptyXValuesGenerator {
1263 fn get_x_values(&self) -> Vec<Decimal> {
1264 vec![] }
1266 }
1267
1268 struct SpecialStatisticalGenerator {
1270 zero_std_dev: bool,
1271 }
1272
1273 impl SpecialStatisticalGenerator {
1274 fn new(zero_std_dev: bool) -> Self {
1275 Self { zero_std_dev }
1276 }
1277 }
1278
1279 impl Len for SpecialStatisticalGenerator {
1280 fn len(&self) -> usize {
1281 10
1282 }
1283 fn is_empty(&self) -> bool {
1284 false
1285 }
1286 }
1287
1288 impl MetricsExtractor for SpecialStatisticalGenerator {
1289 fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
1290 Ok(BasicMetrics {
1291 mean: dec!(5.0),
1292 median: dec!(5.0),
1293 mode: dec!(5.0),
1294 std_dev: if self.zero_std_dev {
1295 dec!(0.0)
1296 } else {
1297 dec!(1.0)
1298 },
1299 })
1300 }
1301
1302 fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
1303 Ok(ShapeMetrics {
1304 skewness: dec!(0), kurtosis: dec!(0), peaks: vec![],
1307 valleys: vec![],
1308 inflection_points: vec![],
1309 })
1310 }
1311
1312 fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
1313 Ok(RangeMetrics {
1314 min: Point2D::new(dec!(0.0), dec!(0.0)),
1315 max: Point2D::new(dec!(10.0), dec!(10.0)),
1316 range: dec!(10.0),
1317 quartiles: (dec!(2.5), dec!(5.0), dec!(7.5)),
1318 interquartile_range: dec!(5.0),
1319 })
1320 }
1321
1322 fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
1323 Ok(TrendMetrics {
1324 slope: dec!(0.0), intercept: dec!(0.0),
1326 r_squared: dec!(1.0),
1327 moving_average: vec![],
1328 })
1329 }
1330
1331 fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
1332 Ok(RiskMetrics {
1333 volatility: dec!(0.0),
1334 value_at_risk: dec!(0.0),
1335 expected_shortfall: dec!(0.0),
1336 coefficient_of_variation: dec!(0.0),
1337 sharpe_ratio: dec!(0.0),
1338 })
1339 }
1340 }
1341
1342 impl StatisticalCurve for SpecialStatisticalGenerator {
1343 fn get_x_values(&self) -> Vec<Decimal> {
1344 (0..10).map(Decimal::from).collect()
1345 }
1346 }
1347
1348 #[test]
1349 fn test_generate_statistical_curve_mode_inclusion() {
1350 let generator = SpecialStatisticalGenerator::new(false);
1352
1353 let basic_metrics = BasicMetrics {
1354 mean: dec!(5.0),
1355 median: dec!(5.0),
1356 mode: dec!(7.5), std_dev: dec!(1.0),
1358 };
1359
1360 let shape_metrics = generator.compute_shape_metrics().unwrap();
1361 let range_metrics = generator.compute_range_metrics().unwrap();
1362 let trend_metrics = generator.compute_trend_metrics().unwrap();
1363
1364 let result = generator.generate_statistical_curve(
1365 &basic_metrics,
1366 &shape_metrics,
1367 &range_metrics,
1368 &trend_metrics,
1369 10,
1370 Some(42),
1371 );
1372
1373 assert!(result.is_ok(), "Failed to generate curve including mode");
1374 }
1375
1376 #[test]
1377 fn test_generate_statistical_curve_with_zero_std_dev() {
1378 let generator = SpecialStatisticalGenerator::new(true);
1380
1381 let basic_metrics = generator.compute_basic_metrics().unwrap();
1382 let shape_metrics = generator.compute_shape_metrics().unwrap();
1383 let range_metrics = generator.compute_range_metrics().unwrap();
1384 let trend_metrics = generator.compute_trend_metrics().unwrap();
1385
1386 let result = generator.generate_statistical_curve(
1388 &basic_metrics,
1389 &shape_metrics,
1390 &range_metrics,
1391 &trend_metrics,
1392 10,
1393 Some(42),
1394 );
1395
1396 assert!(result.is_ok(), "Failed to handle zero standard deviation");
1397 }
1398
1399 #[test]
1400 fn test_generate_statistical_curve_scale_range() {
1401 let generator = SpecialStatisticalGenerator::new(false);
1403
1404 let basic_metrics = generator.compute_basic_metrics().unwrap();
1405 let shape_metrics = generator.compute_shape_metrics().unwrap();
1406 let range_metrics = RangeMetrics {
1407 min: Point2D::new(dec!(0.0), dec!(-10.0)), max: Point2D::new(dec!(10.0), dec!(10.0)), range: dec!(20.0),
1410 quartiles: (dec!(2.5), dec!(5.0), dec!(7.5)),
1411 interquartile_range: dec!(5.0),
1412 };
1413 let trend_metrics = generator.compute_trend_metrics().unwrap();
1414
1415 let result = generator.generate_statistical_curve(
1416 &basic_metrics,
1417 &shape_metrics,
1418 &range_metrics,
1419 &trend_metrics,
1420 10,
1421 Some(42),
1422 );
1423
1424 assert!(result.is_ok(), "Failed to scale curve to target range");
1425
1426 let basic_metrics = BasicMetrics {
1428 mean: dec!(5.0),
1429 median: dec!(5.0),
1430 mode: dec!(5.0),
1431 std_dev: dec!(0.0), };
1433
1434 let result = generator.generate_statistical_curve(
1435 &basic_metrics,
1436 &shape_metrics,
1437 &range_metrics,
1438 &trend_metrics,
1439 10,
1440 Some(42),
1441 );
1442
1443 assert!(result.is_ok(), "Failed to handle zero current range");
1444 }
1445
1446 #[test]
1447 fn test_normal_distribution_error() {
1448 let generator = SpecialStatisticalGenerator::new(false);
1454
1455 let basic_metrics = BasicMetrics {
1456 mean: dec!(5.0),
1457 median: dec!(5.0),
1458 mode: dec!(5.0),
1459 std_dev: dec!(1.0),
1460 };
1461
1462 let shape_metrics = generator.compute_shape_metrics().unwrap();
1463 let range_metrics = generator.compute_range_metrics().unwrap();
1464 let trend_metrics = generator.compute_trend_metrics().unwrap();
1465
1466 let result = generator.generate_statistical_curve(
1467 &basic_metrics,
1468 &shape_metrics,
1469 &range_metrics,
1470 &trend_metrics,
1471 10,
1472 Some(42),
1473 );
1474
1475 assert!(
1476 result.is_ok(),
1477 "Failed to generate curve with normal distribution"
1478 );
1479 }
1480
1481 #[test]
1482 fn test_generate_statistical_curve_insufficient_points() {
1483 let generator = SpecialStatisticalGenerator::new(false);
1485
1486 let basic_metrics = generator.compute_basic_metrics().unwrap();
1487 let shape_metrics = generator.compute_shape_metrics().unwrap();
1488 let range_metrics = generator.compute_range_metrics().unwrap();
1489 let trend_metrics = generator.compute_trend_metrics().unwrap();
1490
1491 let result = generator.generate_statistical_curve(
1493 &basic_metrics,
1494 &shape_metrics,
1495 &range_metrics,
1496 &trend_metrics,
1497 1, Some(42),
1499 );
1500
1501 assert!(result.is_err());
1502 if let Err(CurveError::OperationError(OperationErrorKind::InvalidParameters {
1503 operation,
1504 reason,
1505 })) = result
1506 {
1507 assert_eq!(operation, "generate_statistical_curve");
1508 assert!(reason.contains("at least 2"));
1509 } else {
1510 panic!("Expected InvalidParameters error");
1511 }
1512 }
1513
1514 #[test]
1515 fn test_verify_metrics_within_tolerance() {
1516 let generator = SpecialStatisticalGenerator::new(false);
1518
1519 let points = BTreeSet::from_iter(vec![
1521 Point2D::new(dec!(0.0), dec!(1.0)),
1522 Point2D::new(dec!(1.0), dec!(2.0)),
1523 Point2D::new(dec!(2.0), dec!(3.0)),
1524 ]);
1525 let curve = Curve::new(points);
1526
1527 let target_metrics = BasicMetrics {
1529 mean: dec!(2.0), median: dec!(2.0),
1531 mode: dec!(2.0),
1532 std_dev: dec!(0.81), };
1534
1535 let result = generator.verify_curve_metrics(&curve, &target_metrics, dec!(0.2));
1537 assert!(result.is_ok());
1538 assert!(result.unwrap(), "Metrics should be within tolerance");
1539
1540 let result = generator.verify_curve_metrics(&curve, &target_metrics, dec!(0.001));
1542 assert!(result.is_ok());
1543 assert!(!result.unwrap(), "Metrics should be outside tolerance");
1544 }
1545}