wickra_core/indicators/
funding_basis.rs1use crate::derivatives::DerivativesTick;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
40pub struct FundingBasis {
41 has_emitted: bool,
42}
43
44impl FundingBasis {
45 #[must_use]
47 pub const fn new() -> Self {
48 Self { has_emitted: false }
49 }
50}
51
52impl Indicator for FundingBasis {
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.mark_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 "FundingBasis"
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::traits::BatchExt;
86
87 fn tick(mark: f64, index: f64) -> DerivativesTick {
88 DerivativesTick::new_unchecked(0.0, mark, index, mark, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)
89 }
90
91 #[test]
92 fn accessors_and_metadata() {
93 let fb = FundingBasis::new();
94 assert_eq!(fb.name(), "FundingBasis");
95 assert_eq!(fb.warmup_period(), 1);
96 assert!(!fb.is_ready());
97 }
98
99 #[test]
100 fn premium_is_positive() {
101 let mut fb = FundingBasis::new();
102 let out = fb.update(tick(100.5, 100.0)).unwrap();
103 assert!((out - 0.005).abs() < 1e-12);
104 assert!(fb.is_ready());
105 }
106
107 #[test]
108 fn discount_is_negative() {
109 let mut fb = FundingBasis::new();
110 let out = fb.update(tick(99.5, 100.0)).unwrap();
111 assert!((out + 0.005).abs() < 1e-12);
112 }
113
114 #[test]
115 fn at_par_is_zero() {
116 let mut fb = FundingBasis::new();
117 assert_eq!(fb.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) * 0.1, 100.0))
124 .collect();
125 let mut a = FundingBasis::new();
126 let mut b = FundingBasis::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 fb = FundingBasis::new();
136 fb.update(tick(100.5, 100.0));
137 assert!(fb.is_ready());
138 fb.reset();
139 assert!(!fb.is_ready());
140 }
141}