wickra_core/indicators/
funding_rate_mean.rs1use std::collections::VecDeque;
4
5use crate::derivatives::DerivativesTick;
6use crate::error::{Error, Result};
7use crate::traits::Indicator;
8
9#[derive(Debug, Clone)]
42pub struct FundingRateMean {
43 window: usize,
44 history: VecDeque<f64>,
45 sum: f64,
46}
47
48impl FundingRateMean {
49 pub fn new(window: usize) -> Result<Self> {
55 if window == 0 {
56 return Err(Error::PeriodZero);
57 }
58 Ok(Self {
59 window,
60 history: VecDeque::with_capacity(window),
61 sum: 0.0,
62 })
63 }
64
65 #[must_use]
67 pub fn window(&self) -> usize {
68 self.window
69 }
70}
71
72impl Indicator for FundingRateMean {
73 type Input = DerivativesTick;
74 type Output = f64;
75
76 fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
77 self.history.push_back(tick.funding_rate);
78 self.sum += tick.funding_rate;
79 if self.history.len() > self.window {
80 let old = self.history.pop_front().expect("window >= 1, len > window");
81 self.sum -= old;
82 }
83 if self.history.len() < self.window {
84 return None;
85 }
86 Some(self.sum / self.window as f64)
87 }
88
89 fn reset(&mut self) {
90 self.history.clear();
91 self.sum = 0.0;
92 }
93
94 fn warmup_period(&self) -> usize {
95 self.window
96 }
97
98 fn is_ready(&self) -> bool {
99 self.history.len() >= self.window
100 }
101
102 fn name(&self) -> &'static str {
103 "FundingRateMean"
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::traits::BatchExt;
111
112 fn tick(rate: f64) -> DerivativesTick {
113 DerivativesTick::new_unchecked(
114 rate, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0,
115 )
116 }
117
118 #[test]
119 fn rejects_zero_window() {
120 assert!(matches!(FundingRateMean::new(0), Err(Error::PeriodZero)));
121 }
122
123 #[test]
124 fn accessors_and_metadata() {
125 let frm = FundingRateMean::new(5).unwrap();
126 assert_eq!(frm.name(), "FundingRateMean");
127 assert_eq!(frm.warmup_period(), 5);
128 assert_eq!(frm.window(), 5);
129 assert!(!frm.is_ready());
130 }
131
132 #[test]
133 fn warms_up_then_emits_mean() {
134 let mut frm = FundingRateMean::new(2).unwrap();
135 assert_eq!(frm.update(tick(0.001)), None);
136 assert!(!frm.is_ready());
137 assert_eq!(frm.update(tick(0.003)), Some(0.002));
138 assert!(frm.is_ready());
139 }
140
141 #[test]
142 fn rolls_off_old_values() {
143 let mut frm = FundingRateMean::new(2).unwrap();
144 frm.update(tick(0.001));
145 frm.update(tick(0.003)); let out = frm.update(tick(0.005)).unwrap(); assert!((out - 0.004).abs() < 1e-12);
148 }
149
150 #[test]
151 fn handles_negative_rates() {
152 let mut frm = FundingRateMean::new(2).unwrap();
153 frm.update(tick(-0.002));
154 let out = frm.update(tick(0.004)).unwrap();
155 assert!((out - 0.001).abs() < 1e-12);
156 }
157
158 #[test]
159 fn batch_equals_streaming() {
160 let ticks: Vec<DerivativesTick> = (0..30)
161 .map(|i| tick(0.0001 * f64::from(i % 7) - 0.0003))
162 .collect();
163 let mut a = FundingRateMean::new(5).unwrap();
164 let mut b = FundingRateMean::new(5).unwrap();
165 assert_eq!(
166 a.batch(&ticks),
167 ticks.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
168 );
169 }
170
171 #[test]
172 fn reset_clears_state() {
173 let mut frm = FundingRateMean::new(2).unwrap();
174 frm.update(tick(0.001));
175 frm.update(tick(0.003));
176 assert!(frm.is_ready());
177 frm.reset();
178 assert!(!frm.is_ready());
179 assert_eq!(frm.update(tick(0.002)), None);
180 }
181}