regit_svi/surface/
term_structure.rs1use crate::error::ParamError;
7use crate::market::units::{Maturity, TotalVariance};
8
9#[derive(Debug, Clone, PartialEq)]
24pub struct TermStructure(Vec<(Maturity, TotalVariance)>);
25
26impl TermStructure {
27 pub fn new(knots: Vec<(Maturity, TotalVariance)>) -> Result<Self, ParamError> {
38 if knots.is_empty() {
39 return Err(ParamError::EmptyCollection {
40 name: "term structure",
41 });
42 }
43 for &(_, theta) in &knots {
44 if theta.get() <= 0.0 {
45 return Err(ParamError::NonPositiveTheta { theta: theta.get() });
46 }
47 }
48 for (index, pair) in knots.windows(2).enumerate() {
49 if pair[1].0 <= pair[0].0 {
50 return Err(ParamError::NotStrictlyIncreasing {
51 name: "maturity",
52 index: index + 1,
53 previous: pair[0].0.get(),
54 value: pair[1].0.get(),
55 });
56 }
57 if pair[1].1 < pair[0].1 {
58 return Err(ParamError::DecreasingAtmVariance {
59 index: index + 1,
60 previous: pair[0].1.get(),
61 value: pair[1].1.get(),
62 });
63 }
64 }
65 Ok(Self(knots))
66 }
67
68 #[must_use]
70 pub fn knots(&self) -> &[(Maturity, TotalVariance)] {
71 &self.0
72 }
73
74 #[must_use]
76 pub fn len(&self) -> usize {
77 self.0.len()
78 }
79
80 #[must_use]
82 pub fn is_empty(&self) -> bool {
83 self.0.is_empty()
84 }
85}
86
87impl TryFrom<Vec<(f64, f64)>> for TermStructure {
88 type Error = ParamError;
89
90 fn try_from(knots: Vec<(f64, f64)>) -> Result<Self, Self::Error> {
91 let typed = knots
92 .into_iter()
93 .map(|(t, theta)| Ok((Maturity::new(t)?, TotalVariance::new(theta)?)))
94 .collect::<Result<Vec<_>, ParamError>>()?;
95 Self::new(typed)
96 }
97}
98
99#[cfg(test)]
100#[allow(clippy::expect_used)] mod tests {
102 use super::*;
103
104 #[test]
105 fn rejects_unordered_and_decreasing_knots() {
106 assert!(TermStructure::try_from(vec![(0.5, 0.02), (1.0, 0.04)]).is_ok());
107 assert!(matches!(
108 TermStructure::try_from(vec![(1.0, 0.04), (0.5, 0.02)]),
109 Err(ParamError::NotStrictlyIncreasing { .. })
110 ));
111 assert!(matches!(
112 TermStructure::try_from(vec![(0.5, 0.04), (1.0, 0.02)]),
113 Err(ParamError::DecreasingAtmVariance { .. })
114 ));
115 }
116}