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 #[inline]
57 fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
58 self.has_emitted = true;
59 Some((tick.futures_price - tick.index_price) / tick.index_price)
60 }
61
62 fn reset(&mut self) {
63 self.has_emitted = false;
64 }
65
66 #[inline]
67 fn warmup_period(&self) -> usize {
68 1
69 }
70
71 #[inline]
72 fn is_ready(&self) -> bool {
73 self.has_emitted
74 }
75
76 #[inline]
77 fn name(&self) -> &'static str {
78 "TermStructureBasis"
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::traits::BatchExt;
86
87 fn tick(futures: f64, index: f64) -> DerivativesTick {
88 DerivativesTick::new_unchecked(
89 0.0, index, index, futures, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0,
90 )
91 }
92
93 #[test]
94 fn accessors_and_metadata() {
95 let ts = TermStructureBasis::new();
96 assert_eq!(ts.name(), "TermStructureBasis");
97 assert_eq!(ts.warmup_period(), 1);
98 assert!(!ts.is_ready());
99 }
100
101 #[test]
102 fn contango_is_positive() {
103 let mut ts = TermStructureBasis::new();
104 let out = ts.update(tick(102.0, 100.0)).unwrap();
105 assert!((out - 0.02).abs() < 1e-12);
106 assert!(ts.is_ready());
107 }
108
109 #[test]
110 fn backwardation_is_negative() {
111 let mut ts = TermStructureBasis::new();
112 let out = ts.update(tick(98.0, 100.0)).unwrap();
113 assert!((out + 0.02).abs() < 1e-12);
114 }
115
116 #[test]
117 fn at_par_is_zero() {
118 let mut ts = TermStructureBasis::new();
119 assert_eq!(ts.update(tick(100.0, 100.0)), Some(0.0));
120 }
121
122 #[test]
123 fn batch_equals_streaming() {
124 let ticks: Vec<DerivativesTick> = (0..20)
125 .map(|i| tick(100.0 + f64::from(i % 5), 100.0))
126 .collect();
127 let mut a = TermStructureBasis::new();
128 let mut b = TermStructureBasis::new();
129 assert_eq!(
130 a.batch(&ticks),
131 ticks.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
132 );
133 }
134
135 #[test]
136 fn reset_clears_state() {
137 let mut ts = TermStructureBasis::new();
138 ts.update(tick(102.0, 100.0));
139 assert!(ts.is_ready());
140 ts.reset();
141 assert!(!ts.is_ready());
142 }
143}