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