1use crate::error::{Error, Result};
14
15#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct DerivativesTick {
30 pub funding_rate: f64,
32 pub mark_price: f64,
34 pub index_price: f64,
36 pub futures_price: f64,
38 pub open_interest: f64,
40 pub long_size: f64,
42 pub short_size: f64,
44 pub taker_buy_volume: f64,
46 pub taker_sell_volume: f64,
48 pub long_liquidation: f64,
50 pub short_liquidation: f64,
52 pub timestamp: i64,
54}
55
56impl DerivativesTick {
57 #[allow(clippy::too_many_arguments)]
66 pub fn new(
67 funding_rate: f64,
68 mark_price: f64,
69 index_price: f64,
70 futures_price: f64,
71 open_interest: f64,
72 long_size: f64,
73 short_size: f64,
74 taker_buy_volume: f64,
75 taker_sell_volume: f64,
76 long_liquidation: f64,
77 short_liquidation: f64,
78 timestamp: i64,
79 ) -> Result<Self> {
80 if !funding_rate.is_finite() {
81 return Err(Error::InvalidDerivatives {
82 message: "funding_rate must be finite",
83 });
84 }
85 for price in [mark_price, index_price, futures_price] {
86 if !price.is_finite() || price <= 0.0 {
87 return Err(Error::InvalidDerivatives {
88 message:
89 "mark_price, index_price and futures_price must be finite and positive",
90 });
91 }
92 }
93 for amount in [
94 open_interest,
95 long_size,
96 short_size,
97 taker_buy_volume,
98 taker_sell_volume,
99 long_liquidation,
100 short_liquidation,
101 ] {
102 if !amount.is_finite() || amount < 0.0 {
103 return Err(Error::InvalidDerivatives {
104 message: "open interest, sizes, volumes and liquidations must be finite and non-negative",
105 });
106 }
107 }
108 Ok(Self {
109 funding_rate,
110 mark_price,
111 index_price,
112 futures_price,
113 open_interest,
114 long_size,
115 short_size,
116 taker_buy_volume,
117 taker_sell_volume,
118 long_liquidation,
119 short_liquidation,
120 timestamp,
121 })
122 }
123
124 #[allow(clippy::too_many_arguments)]
127 #[must_use]
128 pub const fn new_unchecked(
129 funding_rate: f64,
130 mark_price: f64,
131 index_price: f64,
132 futures_price: f64,
133 open_interest: f64,
134 long_size: f64,
135 short_size: f64,
136 taker_buy_volume: f64,
137 taker_sell_volume: f64,
138 long_liquidation: f64,
139 short_liquidation: f64,
140 timestamp: i64,
141 ) -> Self {
142 Self {
143 funding_rate,
144 mark_price,
145 index_price,
146 futures_price,
147 open_interest,
148 long_size,
149 short_size,
150 taker_buy_volume,
151 taker_sell_volume,
152 long_liquidation,
153 short_liquidation,
154 timestamp,
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 fn valid() -> DerivativesTick {
166 DerivativesTick::new(
167 0.0001, 100.0, 99.5, 100.5, 1_000.0, 600.0, 400.0, 50.0, 40.0, 5.0, 3.0, 42,
168 )
169 .unwrap()
170 }
171
172 #[test]
173 fn new_accepts_valid() {
174 let tick = valid();
175 assert_eq!(tick.funding_rate, 0.0001);
176 assert_eq!(tick.mark_price, 100.0);
177 assert_eq!(tick.index_price, 99.5);
178 assert_eq!(tick.futures_price, 100.5);
179 assert_eq!(tick.open_interest, 1_000.0);
180 assert_eq!(tick.long_size, 600.0);
181 assert_eq!(tick.short_size, 400.0);
182 assert_eq!(tick.taker_buy_volume, 50.0);
183 assert_eq!(tick.taker_sell_volume, 40.0);
184 assert_eq!(tick.long_liquidation, 5.0);
185 assert_eq!(tick.short_liquidation, 3.0);
186 assert_eq!(tick.timestamp, 42);
187 }
188
189 #[test]
190 fn new_accepts_negative_funding_and_zero_amounts() {
191 let tick = DerivativesTick::new(
192 -0.0005, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0,
193 )
194 .unwrap();
195 assert_eq!(tick.funding_rate, -0.0005);
196 assert_eq!(tick.open_interest, 0.0);
197 }
198
199 #[test]
200 fn new_rejects_non_finite_funding() {
201 assert!(matches!(
202 DerivativesTick::new(
203 f64::NAN,
204 100.0,
205 100.0,
206 100.0,
207 0.0,
208 0.0,
209 0.0,
210 0.0,
211 0.0,
212 0.0,
213 0.0,
214 0
215 ),
216 Err(Error::InvalidDerivatives { .. })
217 ));
218 assert!(matches!(
219 DerivativesTick::new(
220 f64::INFINITY,
221 100.0,
222 100.0,
223 100.0,
224 0.0,
225 0.0,
226 0.0,
227 0.0,
228 0.0,
229 0.0,
230 0.0,
231 0
232 ),
233 Err(Error::InvalidDerivatives { .. })
234 ));
235 }
236
237 #[test]
238 fn new_rejects_non_positive_mark() {
239 assert!(matches!(
240 DerivativesTick::new(0.0, 0.0, 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0),
241 Err(Error::InvalidDerivatives { .. })
242 ));
243 }
244
245 #[test]
246 fn new_rejects_non_positive_index() {
247 assert!(matches!(
248 DerivativesTick::new(0.0, 100.0, -1.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0),
249 Err(Error::InvalidDerivatives { .. })
250 ));
251 }
252
253 #[test]
254 fn new_rejects_non_finite_futures() {
255 assert!(matches!(
256 DerivativesTick::new(
257 0.0,
258 100.0,
259 100.0,
260 f64::NAN,
261 0.0,
262 0.0,
263 0.0,
264 0.0,
265 0.0,
266 0.0,
267 0.0,
268 0
269 ),
270 Err(Error::InvalidDerivatives { .. })
271 ));
272 }
273
274 #[test]
275 fn new_rejects_negative_open_interest() {
276 assert!(matches!(
277 DerivativesTick::new(0.0, 100.0, 100.0, 100.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0),
278 Err(Error::InvalidDerivatives { .. })
279 ));
280 }
281
282 #[test]
283 fn new_rejects_non_finite_size() {
284 assert!(matches!(
285 DerivativesTick::new(
286 0.0,
287 100.0,
288 100.0,
289 100.0,
290 0.0,
291 f64::INFINITY,
292 0.0,
293 0.0,
294 0.0,
295 0.0,
296 0.0,
297 0
298 ),
299 Err(Error::InvalidDerivatives { .. })
300 ));
301 }
302
303 #[test]
304 fn new_rejects_negative_liquidation() {
305 assert!(matches!(
306 DerivativesTick::new(0.0, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, 0),
307 Err(Error::InvalidDerivatives { .. })
308 ));
309 }
310
311 #[test]
312 fn new_unchecked_preserves_fields() {
313 let tick = DerivativesTick::new_unchecked(
314 -1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0, -10.0, -11.0, 7,
315 );
316 assert_eq!(tick.funding_rate, -1.0);
317 assert_eq!(tick.mark_price, -2.0);
318 assert_eq!(tick.short_liquidation, -11.0);
319 assert_eq!(tick.timestamp, 7);
320 }
321}