Skip to main content

wickra_core/indicators/
funding_rate.rs

1//! Funding Rate — the current perpetual funding rate.
2
3use crate::derivatives::DerivativesTick;
4use crate::traits::Indicator;
5
6/// Funding Rate — the funding rate carried by each derivatives tick.
7///
8/// The funding rate is the periodic payment exchanged between long and short
9/// perpetual-swap holders that tethers the perpetual mark to the spot index. A
10/// positive rate means longs pay shorts (the perpetual trades at a premium); a
11/// negative rate means shorts pay longs (a discount). This indicator simply
12/// surfaces the rate from the [`DerivativesTick`] feed so it can be charted,
13/// chained or fed to the rolling funding statistics ([`FundingRateMean`],
14/// [`FundingRateZScore`]).
15///
16/// `Input = DerivativesTick`, `Output = f64`. Stateless; ready after the first
17/// tick.
18///
19/// [`FundingRateMean`]: crate::FundingRateMean
20/// [`FundingRateZScore`]: crate::FundingRateZScore
21///
22/// # Example
23///
24/// ```
25/// use wickra_core::{DerivativesTick, FundingRate, Indicator};
26///
27/// let mut fr = FundingRate::new();
28/// let tick = DerivativesTick::new(
29///     0.0001, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0,
30/// )
31/// .unwrap();
32/// assert_eq!(fr.update(tick), Some(0.0001));
33/// ```
34#[derive(Debug, Clone, Default)]
35pub struct FundingRate {
36    has_emitted: bool,
37}
38
39impl FundingRate {
40    /// Construct a new funding-rate indicator.
41    #[must_use]
42    pub const fn new() -> Self {
43        Self { has_emitted: false }
44    }
45}
46
47impl Indicator for FundingRate {
48    type Input = DerivativesTick;
49    type Output = f64;
50
51    fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
52        self.has_emitted = true;
53        Some(tick.funding_rate)
54    }
55
56    fn reset(&mut self) {
57        self.has_emitted = false;
58    }
59
60    fn warmup_period(&self) -> usize {
61        1
62    }
63
64    fn is_ready(&self) -> bool {
65        self.has_emitted
66    }
67
68    fn name(&self) -> &'static str {
69        "FundingRate"
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::traits::BatchExt;
77
78    fn tick(funding_rate: f64) -> DerivativesTick {
79        DerivativesTick::new_unchecked(
80            funding_rate,
81            100.0,
82            100.0,
83            100.0,
84            0.0,
85            0.0,
86            0.0,
87            0.0,
88            0.0,
89            0.0,
90            0.0,
91            0,
92        )
93    }
94
95    #[test]
96    fn accessors_and_metadata() {
97        let fr = FundingRate::new();
98        assert_eq!(fr.name(), "FundingRate");
99        assert_eq!(fr.warmup_period(), 1);
100        assert!(!fr.is_ready());
101    }
102
103    #[test]
104    fn passes_through_funding_rate() {
105        let mut fr = FundingRate::new();
106        assert_eq!(fr.update(tick(0.0001)), Some(0.0001));
107        assert_eq!(fr.update(tick(-0.0003)), Some(-0.0003));
108        assert!(fr.is_ready());
109    }
110
111    #[test]
112    fn batch_equals_streaming() {
113        let ticks: Vec<DerivativesTick> =
114            (0..20).map(|i| tick(0.0001 * f64::from(i - 10))).collect();
115        let mut a = FundingRate::new();
116        let mut b = FundingRate::new();
117        assert_eq!(
118            a.batch(&ticks),
119            ticks.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
120        );
121    }
122
123    #[test]
124    fn reset_clears_state() {
125        let mut fr = FundingRate::new();
126        fr.update(tick(0.0001));
127        assert!(fr.is_ready());
128        fr.reset();
129        assert!(!fr.is_ready());
130    }
131}