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 fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
57 self.has_emitted = true;
58 Some((tick.mark_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 "FundingBasis"
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::traits::BatchExt;
82
83 fn tick(mark: f64, index: f64) -> DerivativesTick {
84 DerivativesTick::new_unchecked(0.0, mark, index, mark, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)
85 }
86
87 #[test]
88 fn accessors_and_metadata() {
89 let fb = FundingBasis::new();
90 assert_eq!(fb.name(), "FundingBasis");
91 assert_eq!(fb.warmup_period(), 1);
92 assert!(!fb.is_ready());
93 }
94
95 #[test]
96 fn premium_is_positive() {
97 let mut fb = FundingBasis::new();
98 let out = fb.update(tick(100.5, 100.0)).unwrap();
99 assert!((out - 0.005).abs() < 1e-12);
100 assert!(fb.is_ready());
101 }
102
103 #[test]
104 fn discount_is_negative() {
105 let mut fb = FundingBasis::new();
106 let out = fb.update(tick(99.5, 100.0)).unwrap();
107 assert!((out + 0.005).abs() < 1e-12);
108 }
109
110 #[test]
111 fn at_par_is_zero() {
112 let mut fb = FundingBasis::new();
113 assert_eq!(fb.update(tick(100.0, 100.0)), Some(0.0));
114 }
115
116 #[test]
117 fn batch_equals_streaming() {
118 let ticks: Vec<DerivativesTick> = (0..20)
119 .map(|i| tick(100.0 + f64::from(i % 5) * 0.1, 100.0))
120 .collect();
121 let mut a = FundingBasis::new();
122 let mut b = FundingBasis::new();
123 assert_eq!(
124 a.batch(&ticks),
125 ticks.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
126 );
127 }
128
129 #[test]
130 fn reset_clears_state() {
131 let mut fb = FundingBasis::new();
132 fb.update(tick(100.5, 100.0));
133 assert!(fb.is_ready());
134 fb.reset();
135 assert!(!fb.is_ready());
136 }
137}