wickra_core/indicators/
perpetual_premium_index.rs1use crate::derivatives::DerivativesTick;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
37pub struct PerpetualPremiumIndex {
38 ready: bool,
39}
40
41impl PerpetualPremiumIndex {
42 #[must_use]
44 pub const fn new() -> Self {
45 Self { ready: false }
46 }
47}
48
49impl Indicator for PerpetualPremiumIndex {
50 type Input = DerivativesTick;
51 type Output = f64;
52
53 #[inline]
54 fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
55 let premium = (tick.mark_price - tick.index_price) / tick.index_price;
56 self.ready = true;
57 Some(premium)
58 }
59
60 fn reset(&mut self) {
61 self.ready = false;
62 }
63
64 #[inline]
65 fn warmup_period(&self) -> usize {
66 1
67 }
68
69 #[inline]
70 fn is_ready(&self) -> bool {
71 self.ready
72 }
73
74 #[inline]
75 fn name(&self) -> &'static str {
76 "PerpetualPremiumIndex"
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::traits::BatchExt;
84 use approx::assert_relative_eq;
85
86 fn tick(mark: f64, index: f64) -> DerivativesTick {
87 DerivativesTick::new_unchecked(0.0, mark, index, mark, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)
88 }
89
90 #[test]
91 fn accessors_and_metadata() {
92 let p = PerpetualPremiumIndex::new();
93 assert_eq!(p.warmup_period(), 1);
94 assert_eq!(p.name(), "PerpetualPremiumIndex");
95 assert!(!p.is_ready());
96 }
97
98 #[test]
99 fn premium_reference_value() {
100 let mut p = PerpetualPremiumIndex::new();
101 assert_relative_eq!(p.update(tick(101.0, 100.0)).unwrap(), 0.01, epsilon = 1e-12);
102 }
103
104 #[test]
105 fn discount_is_negative() {
106 let mut p = PerpetualPremiumIndex::new();
107 assert!(p.update(tick(99.0, 100.0)).unwrap() < 0.0);
108 }
109
110 #[test]
111 fn at_par_is_zero() {
112 let mut p = PerpetualPremiumIndex::new();
113 assert_relative_eq!(p.update(tick(100.0, 100.0)).unwrap(), 0.0, epsilon = 1e-12);
114 }
115
116 #[test]
117 fn ready_after_first_update() {
118 let mut p = PerpetualPremiumIndex::new();
119 assert!(!p.is_ready());
120 p.update(tick(100.0, 100.0));
121 assert!(p.is_ready());
122 }
123
124 #[test]
125 fn reset_clears_state() {
126 let mut p = PerpetualPremiumIndex::new();
127 p.update(tick(101.0, 100.0));
128 assert!(p.is_ready());
129 p.reset();
130 assert!(!p.is_ready());
131 }
132
133 #[test]
134 fn batch_equals_streaming() {
135 let ticks: Vec<DerivativesTick> = (0..40)
136 .map(|i| tick(100.0 + (f64::from(i) * 0.3).sin(), 100.0))
137 .collect();
138 let batch = PerpetualPremiumIndex::new().batch(&ticks);
139 let mut b = PerpetualPremiumIndex::new();
140 let streamed: Vec<_> = ticks.iter().map(|x| b.update(*x)).collect();
141 assert_eq!(batch, streamed);
142 }
143}