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 fn update(&mut self, tick: DerivativesTick) -> Option<f64> {
54 let premium = (tick.mark_price - tick.index_price) / tick.index_price;
55 self.ready = true;
56 Some(premium)
57 }
58
59 fn reset(&mut self) {
60 self.ready = false;
61 }
62
63 fn warmup_period(&self) -> usize {
64 1
65 }
66
67 fn is_ready(&self) -> bool {
68 self.ready
69 }
70
71 fn name(&self) -> &'static str {
72 "PerpetualPremiumIndex"
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::traits::BatchExt;
80 use approx::assert_relative_eq;
81
82 fn tick(mark: f64, index: f64) -> DerivativesTick {
83 DerivativesTick::new_unchecked(0.0, mark, index, mark, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)
84 }
85
86 #[test]
87 fn accessors_and_metadata() {
88 let p = PerpetualPremiumIndex::new();
89 assert_eq!(p.warmup_period(), 1);
90 assert_eq!(p.name(), "PerpetualPremiumIndex");
91 assert!(!p.is_ready());
92 }
93
94 #[test]
95 fn premium_reference_value() {
96 let mut p = PerpetualPremiumIndex::new();
97 assert_relative_eq!(p.update(tick(101.0, 100.0)).unwrap(), 0.01, epsilon = 1e-12);
98 }
99
100 #[test]
101 fn discount_is_negative() {
102 let mut p = PerpetualPremiumIndex::new();
103 assert!(p.update(tick(99.0, 100.0)).unwrap() < 0.0);
104 }
105
106 #[test]
107 fn at_par_is_zero() {
108 let mut p = PerpetualPremiumIndex::new();
109 assert_relative_eq!(p.update(tick(100.0, 100.0)).unwrap(), 0.0, epsilon = 1e-12);
110 }
111
112 #[test]
113 fn ready_after_first_update() {
114 let mut p = PerpetualPremiumIndex::new();
115 assert!(!p.is_ready());
116 p.update(tick(100.0, 100.0));
117 assert!(p.is_ready());
118 }
119
120 #[test]
121 fn reset_clears_state() {
122 let mut p = PerpetualPremiumIndex::new();
123 p.update(tick(101.0, 100.0));
124 assert!(p.is_ready());
125 p.reset();
126 assert!(!p.is_ready());
127 }
128
129 #[test]
130 fn batch_equals_streaming() {
131 let ticks: Vec<DerivativesTick> = (0..40)
132 .map(|i| tick(100.0 + (f64::from(i) * 0.3).sin(), 100.0))
133 .collect();
134 let batch = PerpetualPremiumIndex::new().batch(&ticks);
135 let mut b = PerpetualPremiumIndex::new();
136 let streamed: Vec<_> = ticks.iter().map(|x| b.update(*x)).collect();
137 assert_eq!(batch, streamed);
138 }
139}