wickra_core/indicators/
term_structure_basis.rs1use crate::derivatives::DerivativesTick;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
40pub struct TermStructureBasis {
41 has_emitted: bool,
42}
43
44impl TermStructureBasis {
45 #[must_use]
47 pub const fn new() -> Self {
48 Self { has_emitted: false }
49 }
50}
51
52impl Indicator for TermStructureBasis {
53 type Input = DerivativesTick;
54 type Output = f64;
55
56 fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
57 self.has_emitted = true;
58 Some((tick.futures_price - tick.index_price) / tick.index_price)
59 }
60
61 fn reset(&mut self) {
62 self.has_emitted = false;
63 }
64
65 fn warmup_period(&self) -> usize {
66 1
67 }
68
69 fn is_ready(&self) -> bool {
70 self.has_emitted
71 }
72
73 fn name(&self) -> &'static str {
74 "TermStructureBasis"
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::traits::BatchExt;
82
83 fn tick(futures: f64, index: f64) -> DerivativesTick {
84 DerivativesTick::new_unchecked(
85 0.0, index, index, futures, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0,
86 )
87 }
88
89 #[test]
90 fn accessors_and_metadata() {
91 let ts = TermStructureBasis::new();
92 assert_eq!(ts.name(), "TermStructureBasis");
93 assert_eq!(ts.warmup_period(), 1);
94 assert!(!ts.is_ready());
95 }
96
97 #[test]
98 fn contango_is_positive() {
99 let mut ts = TermStructureBasis::new();
100 let out = ts.update(tick(102.0, 100.0)).unwrap();
101 assert!((out - 0.02).abs() < 1e-12);
102 assert!(ts.is_ready());
103 }
104
105 #[test]
106 fn backwardation_is_negative() {
107 let mut ts = TermStructureBasis::new();
108 let out = ts.update(tick(98.0, 100.0)).unwrap();
109 assert!((out + 0.02).abs() < 1e-12);
110 }
111
112 #[test]
113 fn at_par_is_zero() {
114 let mut ts = TermStructureBasis::new();
115 assert_eq!(ts.update(tick(100.0, 100.0)), Some(0.0));
116 }
117
118 #[test]
119 fn batch_equals_streaming() {
120 let ticks: Vec<DerivativesTick> = (0..20)
121 .map(|i| tick(100.0 + f64::from(i % 5), 100.0))
122 .collect();
123 let mut a = TermStructureBasis::new();
124 let mut b = TermStructureBasis::new();
125 assert_eq!(
126 a.batch(&ticks),
127 ticks.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
128 );
129 }
130
131 #[test]
132 fn reset_clears_state() {
133 let mut ts = TermStructureBasis::new();
134 ts.update(tick(102.0, 100.0));
135 assert!(ts.is_ready());
136 ts.reset();
137 assert!(!ts.is_ready());
138 }
139}