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