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    #[inline]
52    fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
53        self.has_emitted = true;
54        Some(tick.funding_rate)
55    }
56
57    fn reset(&mut self) {
58        self.has_emitted = false;
59    }
60
61    #[inline]
62    fn warmup_period(&self) -> usize {
63        1
64    }
65
66    #[inline]
67    fn is_ready(&self) -> bool {
68        self.has_emitted
69    }
70
71    #[inline]
72    fn name(&self) -> &'static str {
73        "FundingRate"
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::traits::BatchExt;
81
82    fn tick(funding_rate: f64) -> DerivativesTick {
83        DerivativesTick::new_unchecked(
84            funding_rate,
85            100.0,
86            100.0,
87            100.0,
88            0.0,
89            0.0,
90            0.0,
91            0.0,
92            0.0,
93            0.0,
94            0.0,
95            0,
96        )
97    }
98
99    #[test]
100    fn accessors_and_metadata() {
101        let fr = FundingRate::new();
102        assert_eq!(fr.name(), "FundingRate");
103        assert_eq!(fr.warmup_period(), 1);
104        assert!(!fr.is_ready());
105    }
106
107    #[test]
108    fn passes_through_funding_rate() {
109        let mut fr = FundingRate::new();
110        assert_eq!(fr.update(tick(0.0001)), Some(0.0001));
111        assert_eq!(fr.update(tick(-0.0003)), Some(-0.0003));
112        assert!(fr.is_ready());
113    }
114
115    #[test]
116    fn batch_equals_streaming() {
117        let ticks: Vec<DerivativesTick> =
118            (0..20).map(|i| tick(0.0001 * f64::from(i - 10))).collect();
119        let mut a = FundingRate::new();
120        let mut b = FundingRate::new();
121        assert_eq!(
122            a.batch(&ticks),
123            ticks.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
124        );
125    }
126
127    #[test]
128    fn reset_clears_state() {
129        let mut fr = FundingRate::new();
130        fr.update(tick(0.0001));
131        assert!(fr.is_ready());
132        fr.reset();
133        assert!(!fr.is_ready());
134    }
135}