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