1use crate::vectorized_features::VectorizedFeatures;
4use rust_decimal::Decimal;
5use rust_decimal::prelude::*;
6use smallvec::SmallVec;
7use std::cell::RefCell;
8use std::str::FromStr;
9use std::sync::atomic::AtomicU64;
10
11mod decimal_math {
13 use rust_decimal::Decimal;
14 use rust_decimal::prelude::ToPrimitive;
15
16 pub fn ln(d: Decimal) -> Decimal {
18 Decimal::from_f64_retain(d.to_f64().unwrap_or(1.0).ln()).unwrap_or(Decimal::ZERO)
19 }
20
21 pub fn powi(d: Decimal, exp: i32) -> Decimal {
23 Decimal::from_f64_retain(rusty_common::decimal_utils::decimal_to_f64_or_nan(d).powi(exp))
24 .unwrap_or(Decimal::ZERO)
25 }
26}
27
28#[derive(Debug, Clone)]
99pub struct TradeFeatures {
100 pub tick_price: Decimal,
102 pub tick_qty: Decimal,
104 pub tick_direction: i8,
106 pub signed_tick_price: Decimal,
108 pub plus_tick_price: Decimal,
110 pub minus_tick_price: Decimal,
112 pub signed_tick_qty: Decimal,
114 pub plus_tick_qty: Decimal,
116 pub minus_tick_qty: Decimal,
118 pub weighted_tick_price: Decimal,
120 pub log_tick_price: Decimal,
122 pub tick_price_ratio: Decimal,
124 pub tick_price_power: Decimal,
126 pub relative_tick_price: Decimal,
128 pub log_return: Decimal,
130 pub percent_return: Decimal,
132 pub volume_weighted_tick_price: Decimal,
134 pub tick_price_acceleration: Decimal,
136 pub tick_price_momentum: Decimal,
138 pub relative_strength: Decimal,
140 pub tick_price_skewness: Decimal,
142 pub tick_price_kurtosis: Decimal,
144 pub tick_price_volatility: Decimal,
146 pub tick_price_entropy: Decimal,
148 pub tick_size: Decimal,
150 pub tick_type: i8,
152 pub tick_volume: Decimal,
154 pub tick_value: Decimal,
156 pub tick_efficiency: Decimal,
158 pub tick_impact: Decimal,
160 pub tick_aggressiveness: i8,
162 pub tick_frequency: Decimal,
164 pub tick_duration: Decimal,
166 pub tick_intensity: Decimal,
168 pub tick_persistence: Decimal,
170 pub tick_reversion: Decimal,
172 pub tick_momentum: Decimal,
174 pub tick_trend: Decimal,
176 pub tick_signal: Decimal,
178 pub tick_noise: Decimal,
180 pub tick_correlation: Decimal,
182 pub tick_beta: Decimal,
184 pub tick_alpha: Decimal,
186 pub tick_sharpe_ratio: Decimal,
188 pub tick_sortino_ratio: Decimal,
190 pub tick_information_ratio: Decimal,
192 pub tick_treynor_ratio: Decimal,
194 pub tick_jensen_alpha: Decimal,
196 pub tick_max_drawdown: Decimal,
198 pub tick_calmar_ratio: Decimal,
200 pub tick_sterling_ratio: Decimal,
202 pub tick_burke_ratio: Decimal,
204}
205
206impl TradeFeatures {
207 #[must_use]
209 pub fn new(price: Decimal, qty: Decimal, side: OrderSide) -> Self {
210 let tick_direction = match side {
211 OrderSide::Buy => 1,
212 OrderSide::Sell => -1,
213 };
214
215 let signed_tick_price = price * Decimal::from(tick_direction);
216 let plus_tick_price = (price + signed_tick_price) / Decimal::from(2);
217 let minus_tick_price = (price - signed_tick_price) / Decimal::from(2);
218
219 let signed_tick_qty = qty * Decimal::from(tick_direction);
220 let plus_tick_qty = (qty + signed_tick_qty) / Decimal::from(2);
221 let minus_tick_qty = (qty - signed_tick_qty) / Decimal::from(2);
222
223 let tick_value = price * qty;
225 let tick_volume = qty; let tick_aggressiveness = if side == OrderSide::Buy { 1 } else { 0 }; let tick_price_power = decimal_math::powi(price, 2); let tick_efficiency = if qty > Decimal::ZERO {
231 price / qty } else {
233 Decimal::ZERO
234 };
235 let tick_impact = if qty > Decimal::ZERO {
236 (price / qty) * Decimal::from(tick_direction) } else {
238 Decimal::ZERO
239 };
240
241 Self {
242 tick_price: price,
243 tick_qty: qty,
244 tick_direction,
245 signed_tick_price,
246 plus_tick_price,
247 minus_tick_price,
248 signed_tick_qty,
249 plus_tick_qty,
250 minus_tick_qty,
251 weighted_tick_price: price * qty,
252 log_tick_price: decimal_math::ln(price),
253 tick_value,
254 tick_volume,
255 tick_aggressiveness,
256
257 tick_price_power,
259 tick_efficiency,
260 tick_impact,
261
262 tick_price_ratio: Decimal::ZERO,
264 relative_tick_price: Decimal::ZERO,
265 log_return: Decimal::ZERO,
266 percent_return: Decimal::ZERO,
267 volume_weighted_tick_price: Decimal::ZERO,
268 tick_size: Decimal::ZERO,
269 tick_type: 0,
270 tick_frequency: Decimal::ZERO,
271 tick_duration: Decimal::ZERO,
272 tick_intensity: Decimal::ZERO,
273
274 tick_price_volatility: Decimal::ZERO,
276 tick_price_momentum: Decimal::ZERO,
277 tick_trend: Decimal::ZERO,
278
279 relative_strength: Decimal::ZERO, tick_price_acceleration: Decimal::ZERO, tick_price_skewness: Decimal::ZERO, tick_price_kurtosis: Decimal::ZERO, tick_price_entropy: Decimal::ZERO, tick_persistence: Decimal::ZERO, tick_reversion: Decimal::ZERO, tick_momentum: Decimal::ZERO, tick_signal: Decimal::ZERO, tick_noise: Decimal::ZERO, tick_correlation: Decimal::ZERO, tick_beta: Decimal::ZERO, tick_alpha: Decimal::ZERO, tick_sharpe_ratio: Decimal::ZERO, tick_sortino_ratio: Decimal::ZERO, tick_information_ratio: Decimal::ZERO, tick_treynor_ratio: Decimal::ZERO, tick_jensen_alpha: Decimal::ZERO, tick_max_drawdown: Decimal::ZERO, tick_calmar_ratio: Decimal::ZERO, tick_sterling_ratio: Decimal::ZERO, tick_burke_ratio: Decimal::ZERO, }
305 }
306
307 #[must_use]
319 pub fn with_previous_trade(
320 mut self,
321 prev_price: Decimal,
322 prev_qty: Decimal,
323 prev_timestamp_ns: u64,
324 current_timestamp_ns: u64,
325 ) -> Self {
326 if prev_price > Decimal::ZERO {
328 self.tick_price_ratio = self.tick_price / prev_price;
329 self.relative_tick_price = (self.tick_price - prev_price) / prev_price;
330 self.log_return = decimal_math::ln(self.tick_price / prev_price);
331 self.percent_return =
332 ((self.tick_price - prev_price) / prev_price) * Decimal::from(100);
333 self.tick_size = (self.tick_price - prev_price).abs();
334
335 let price_change = self.tick_price - prev_price;
337 if price_change > Decimal::ZERO {
338 self.relative_strength = price_change / prev_price; } else if price_change < Decimal::ZERO {
340 self.relative_strength = price_change / prev_price; } else {
342 self.relative_strength = Decimal::ZERO; }
344
345 self.tick_type = if self.tick_price > prev_price {
347 1 } else if self.tick_price < prev_price {
349 -1 } else {
351 0 };
353 }
354
355 if current_timestamp_ns > prev_timestamp_ns {
357 let duration_ns = current_timestamp_ns - prev_timestamp_ns;
358 self.tick_duration = Decimal::from(duration_ns);
359
360 if duration_ns > 0 {
362 self.tick_frequency = Decimal::from(1_000_000_000u64) / Decimal::from(duration_ns);
363 }
364
365 self.tick_intensity = self.tick_qty * self.tick_frequency;
367 }
368
369 if prev_qty > Decimal::ZERO {
371 let total_qty = self.tick_qty + prev_qty;
372 self.volume_weighted_tick_price =
373 (self.tick_price * self.tick_qty + prev_price * prev_qty) / total_qty;
374 }
375
376 self
377 }
378
379 #[must_use]
394 pub fn with_trade_history(
395 mut self,
396 price_history: &[Decimal],
397 qty_history: &[Decimal],
398 ) -> Self {
399 if price_history.len() < 2 {
400 return self;
401 }
402
403 let n = price_history.len();
405 let mean_price = price_history.iter().sum::<Decimal>() / Decimal::from(n);
406
407 let variance = price_history
409 .iter()
410 .map(|&price| {
411 let diff = price - mean_price;
412 diff * diff
413 })
414 .sum::<Decimal>()
415 / Decimal::from(n - 1);
416
417 self.tick_price_volatility = decimal_math::powi(variance, 1); if let (Some(&first), Some(&last)) = (price_history.first(), price_history.last())
421 && first > Decimal::ZERO
422 {
423 self.tick_price_momentum = (last - first) / first;
424 }
425
426 if n >= 3 {
428 let first_half_mean =
429 price_history[..n / 2].iter().sum::<Decimal>() / Decimal::from(n / 2);
430 let second_half_mean =
431 price_history[n / 2..].iter().sum::<Decimal>() / Decimal::from(n - n / 2);
432 self.tick_trend = second_half_mean - first_half_mean;
433
434 if n >= 4 {
436 let first_quarter_mean =
437 price_history[..n / 4].iter().sum::<Decimal>() / Decimal::from(n / 4);
438 let last_quarter_mean = price_history[3 * n / 4..].iter().sum::<Decimal>()
439 / Decimal::from(n - 3 * n / 4);
440 let quarter_trend = last_quarter_mean - first_quarter_mean;
441
442 self.tick_price_acceleration = quarter_trend - self.tick_trend;
444 }
445 }
446
447 self
448 }
449}
450
451pub struct MicrostructureCalculator<const N: usize = 128> {
499 window_size: usize,
501 spread_sum: AtomicU64,
503 mid_price_sum: AtomicU64,
505 volume_sum: AtomicU64,
507 trade_count: AtomicU64,
509 vectorized_features: RefCell<VectorizedFeatures<N>>,
512}
513
514impl<const N: usize> MicrostructureCalculator<N> {
515 #[must_use]
516 pub fn new(window_size: usize) -> Self {
518 Self {
519 window_size,
520 spread_sum: AtomicU64::new(0),
521 mid_price_sum: AtomicU64::new(0),
522 volume_sum: AtomicU64::new(0),
523 trade_count: AtomicU64::new(0),
524 vectorized_features: RefCell::new(VectorizedFeatures::new()),
526 }
527 }
528
529 #[inline(always)]
531 pub fn calc_spread(&self, ask_price: Decimal, bid_price: Decimal) -> Decimal {
532 ask_price - bid_price
533 }
534
535 #[inline(always)]
537 pub fn calc_mid_price(&self, ask_price: Decimal, bid_price: Decimal) -> Decimal {
538 (ask_price + bid_price) / Decimal::from(2)
539 }
540
541 #[inline(always)]
543 pub fn calc_relative_spread(&self, ask_price: Decimal, bid_price: Decimal) -> Decimal {
544 let spread = self.calc_spread(ask_price, bid_price);
545 let mid_price = self.calc_mid_price(ask_price, bid_price);
546 if mid_price != Decimal::ZERO {
547 spread / mid_price
548 } else {
549 Decimal::ZERO
550 }
551 }
552
553 pub fn calc_vpin(
555 &self,
556 trades: &[TradeFeatures],
557 bucket_size: usize,
558 ) -> SmallVec<[Decimal; N]> {
559 let n = trades.len();
560 let mut vpin_values = SmallVec::<[Decimal; N]>::with_capacity(n);
561 vpin_values.resize(n, Decimal::ZERO);
562
563 for i in bucket_size..n {
564 let bucket_trades = &trades[i - bucket_size + 1..=i];
565
566 let buys: Decimal = bucket_trades
567 .iter()
568 .filter(|t| t.tick_direction == 1)
569 .map(|t| t.tick_qty)
570 .sum();
571
572 let sells: Decimal = bucket_trades
573 .iter()
574 .filter(|t| t.tick_direction == -1)
575 .map(|t| t.tick_qty)
576 .sum();
577
578 let total = buys + sells;
579 if total != Decimal::ZERO {
580 let buy_ratio = buys / total;
581 let sell_ratio = Decimal::ONE - buy_ratio;
582 vpin_values[i] = (buy_ratio - sell_ratio).abs();
583 }
584 }
585
586 vpin_values
587 }
588
589 pub fn calc_ofi_simple(
591 &self,
592 ask_volumes: &[Decimal],
593 bid_volumes: &[Decimal],
594 prev_ask_volumes: &[Decimal],
595 prev_bid_volumes: &[Decimal],
596 ) -> Decimal {
597 if ask_volumes.is_empty()
598 || bid_volumes.is_empty()
599 || prev_ask_volumes.is_empty()
600 || prev_bid_volumes.is_empty()
601 {
602 return Decimal::ZERO;
603 }
604
605 let delta_ask = ask_volumes[0] - prev_ask_volumes[0];
606 let delta_bid = bid_volumes[0] - prev_bid_volumes[0];
607
608 delta_bid - delta_ask
609 }
610
611 #[allow(clippy::too_many_arguments)]
613 pub fn calc_ofi_detailed(
614 &self,
615 ask_prices: &[Decimal],
616 ask_volumes: &[Decimal],
617 bid_prices: &[Decimal],
618 bid_volumes: &[Decimal],
619 prev_ask_prices: &[Decimal],
620 prev_ask_volumes: &[Decimal],
621 prev_bid_prices: &[Decimal],
622 prev_bid_volumes: &[Decimal],
623 ) -> Decimal {
624 let mut ofi = Decimal::ZERO;
625
626 for (i, &prev_price) in prev_ask_prices.iter().enumerate() {
628 if let Some(j) = ask_prices.iter().position(|&p| p == prev_price) {
629 let delta_vol = ask_volumes[j] - prev_ask_volumes[i];
630 ofi += delta_vol * ask_prices[j];
631 }
632 }
633
634 for (i, &prev_price) in prev_bid_prices.iter().enumerate() {
636 if let Some(j) = bid_prices.iter().position(|&p| p == prev_price) {
637 let delta_vol = bid_volumes[j] - prev_bid_volumes[i];
638 ofi -= delta_vol * bid_prices[j];
639 }
640 }
641
642 ofi
643 }
644
645 pub fn calc_kyles_lambda(
647 &self,
648 price_changes: &[Decimal],
649 order_flows: &[Decimal],
650 ) -> Option<Decimal> {
651 if price_changes.len() != order_flows.len() || price_changes.is_empty() {
652 return None;
653 }
654
655 let n = price_changes.len() as i32;
656 let sum_x: Decimal = order_flows.iter().sum();
657 let sum_y: Decimal = price_changes.iter().sum();
658 let sum_xy: Decimal = order_flows
659 .iter()
660 .zip(price_changes.iter())
661 .map(|(x, y)| x * y)
662 .sum();
663 let sum_x2: Decimal = order_flows.iter().map(|x| x * x).sum();
664
665 let denom = Decimal::from(n) * sum_x2 - sum_x * sum_x;
666 if denom == Decimal::ZERO {
667 None
668 } else {
669 Some((Decimal::from(n) * sum_xy - sum_x * sum_y) / denom)
670 }
671 }
672
673 pub fn calc_order_book_slope(
675 &self,
676 ask_prices: &[Decimal],
677 ask_qty: &[Decimal],
678 bid_prices: &[Decimal],
679 bid_qty: &[Decimal],
680 depth: usize,
681 ) -> Decimal {
682 let depth = depth.min(ask_prices.len()).min(bid_prices.len());
683 if depth == 0 {
684 return Decimal::ZERO;
685 }
686
687 let ask_weighted_sum: Decimal = (0..depth).map(|i| ask_prices[i] * ask_qty[i]).sum();
689 let ask_qty_sum: Decimal = ask_qty[..depth].iter().sum();
690
691 let bid_weighted_sum: Decimal = (0..depth).map(|i| bid_prices[i] * bid_qty[i]).sum();
693 let bid_qty_sum: Decimal = bid_qty[..depth].iter().sum();
694
695 if ask_qty_sum == Decimal::ZERO || bid_qty_sum == Decimal::ZERO {
696 return Decimal::ZERO;
697 }
698
699 let ask_weighted_avg = ask_weighted_sum / ask_qty_sum;
700 let bid_weighted_avg = bid_weighted_sum / bid_qty_sum;
701
702 (ask_weighted_avg - bid_weighted_avg) / Decimal::from(depth)
703 }
704
705 pub fn calc_queue_imbalance(&self, ask_qty: &[Decimal], bid_qty: &[Decimal]) -> Decimal {
709 let depth = 5.min(ask_qty.len()).min(bid_qty.len());
710
711 if depth >= 4 {
713 let result = self
714 .vectorized_features
715 .borrow_mut()
716 .calc_order_imbalance_fast(&ask_qty[..depth], &bid_qty[..depth]);
717 Decimal::from_f64_retain(result).unwrap_or(Decimal::ZERO)
718 } else {
719 let ask_sum: Decimal = ask_qty[..depth].iter().sum();
721 let bid_sum: Decimal = bid_qty[..depth].iter().sum();
722
723 if ask_sum + bid_sum == Decimal::ZERO {
724 Decimal::ZERO
725 } else {
726 (bid_sum - ask_sum) / (ask_sum + bid_sum)
727 }
728 }
729 }
730
731 #[inline(always)]
735 pub fn calc_order_imbalance(&self, ask_qty: &[Decimal], bid_qty: &[Decimal]) -> Decimal {
736 if ask_qty.len() >= 4 && bid_qty.len() >= 4 {
738 let result = self
739 .vectorized_features
740 .borrow_mut()
741 .calc_order_imbalance_fast(ask_qty, bid_qty);
742 Decimal::from_f64_retain(result).unwrap_or(Decimal::ZERO)
743 } else {
744 let ask_sum: Decimal = ask_qty.iter().sum();
746 let bid_sum: Decimal = bid_qty.iter().sum();
747
748 if ask_sum + bid_sum == Decimal::ZERO {
749 Decimal::ZERO
750 } else {
751 (bid_sum - ask_sum) / (ask_sum + bid_sum)
752 }
753 }
754 }
755
756 pub fn calc_liquidity_shocks(&self, ask_qty: &[Decimal], bid_qty: &[Decimal]) -> Decimal {
758 let top_depth = 5.min(ask_qty.len()).min(bid_qty.len());
759
760 let top_qty: Decimal = ask_qty[..top_depth].iter().sum::<Decimal>()
761 + bid_qty[..top_depth].iter().sum::<Decimal>();
762 let total_qty: Decimal = ask_qty.iter().sum::<Decimal>() + bid_qty.iter().sum::<Decimal>();
763
764 if total_qty == Decimal::ZERO {
765 Decimal::ZERO
766 } else {
767 top_qty / total_qty
768 }
769 }
770
771 pub fn calc_realized_volatility(&self, prices: &[Decimal], window: usize) -> Decimal {
773 if prices.len() < window + 1 {
774 return Decimal::ZERO;
775 }
776
777 let returns: Vec<Decimal> = prices
778 .windows(2)
779 .take(window)
780 .map(|w| decimal_math::ln(w[1] / w[0]))
781 .collect();
782
783 let mean_return: Decimal =
784 returns.iter().sum::<Decimal>() / Decimal::from(returns.len() as i32);
785 let variance: Decimal = returns
786 .iter()
787 .map(|r| decimal_math::powi(r - mean_return, 2))
788 .sum::<Decimal>()
789 / Decimal::from(returns.len() as i32 - 1);
790
791 variance.sqrt().unwrap_or(Decimal::ZERO)
792 }
793
794 pub fn calc_amihud_lambda(
796 &self,
797 price_changes: &[Decimal],
798 volumes: &[Decimal],
799 ) -> Option<Decimal> {
800 if price_changes.len() != volumes.len() || price_changes.is_empty() {
801 return None;
802 }
803
804 let ratios: Vec<Decimal> = price_changes
805 .iter()
806 .zip(volumes.iter())
807 .filter(|(_, v)| **v != Decimal::ZERO)
808 .map(|(&p, &v)| p.abs() / v)
809 .collect();
810
811 if ratios.is_empty() {
812 None
813 } else {
814 Some(ratios.iter().sum::<Decimal>() / Decimal::from(ratios.len() as i32))
815 }
816 }
817
818 pub fn calc_price_impact(
820 &self,
821 price_changes: &[Decimal],
822 order_flows: &[Decimal],
823 ) -> Option<Decimal> {
824 let kyle_lambda = self.calc_kyles_lambda(price_changes, order_flows)?;
825 let effective_spread = kyle_lambda * Decimal::from(2);
826 let realized_spread = kyle_lambda;
827
828 Some(effective_spread - realized_spread)
829 }
830
831 pub fn calc_order_book_pressure(
835 &self,
836 ask_qty: &[Decimal],
837 bid_qty: &[Decimal],
838 depth: usize,
839 ) -> Decimal {
840 let depth = depth.min(ask_qty.len()).min(bid_qty.len());
841
842 if depth >= 4 {
844 let result = self
845 .vectorized_features
846 .borrow_mut()
847 .calc_weighted_imbalance_wide(ask_qty, bid_qty, depth);
848 Decimal::from_f64_retain(result).unwrap_or(Decimal::ZERO)
849 } else {
850 let ask_sum: Decimal = ask_qty[..depth].iter().sum();
852 let bid_sum: Decimal = bid_qty[..depth].iter().sum();
853 let total_sum = ask_sum + bid_sum;
854
855 if total_sum == Decimal::ZERO {
856 Decimal::ZERO
857 } else {
858 (bid_sum - ask_sum) / total_sum
859 }
860 }
861 }
862
863 pub fn calc_order_book_entropy(&self, ask_qty: &[Decimal], bid_qty: &[Decimal]) -> Decimal {
865 let ask_sum: Decimal = ask_qty.iter().sum();
866 let bid_sum: Decimal = bid_qty.iter().sum();
867 let total_sum = ask_sum + bid_sum;
868
869 if total_sum == Decimal::ZERO {
870 return Decimal::ZERO;
871 }
872
873 let mut entropy = Decimal::ZERO;
874
875 if ask_sum > Decimal::ZERO {
877 for &qty in ask_qty {
878 if qty > Decimal::ZERO {
879 let prob = qty / ask_sum;
880 entropy -= prob * decimal_math::ln(prob);
881 }
882 }
883 }
884
885 if bid_sum > Decimal::ZERO {
887 for &qty in bid_qty {
888 if qty > Decimal::ZERO {
889 let prob = qty / bid_sum;
890 entropy -= prob * decimal_math::ln(prob);
891 }
892 }
893 }
894
895 entropy
896 }
897
898 pub fn calc_order_book_imbalance(&self, ask_qty: &[Decimal], bid_qty: &[Decimal]) -> Decimal {
900 if ask_qty.is_empty() || bid_qty.is_empty() || bid_qty[0] == Decimal::ZERO {
901 return Decimal::ZERO;
902 }
903
904 ask_qty[0] / bid_qty[0]
905 }
906
907 pub fn calc_order_book_depth(&self, ask_qty: &[Decimal], bid_qty: &[Decimal]) -> Decimal {
909 let ask_sum: Decimal = ask_qty.iter().sum();
910 let bid_sum: Decimal = bid_qty.iter().sum();
911
912 ask_sum + bid_sum
913 }
914
915 pub fn calc_order_cancel_estimated_rate(
917 &self,
918 ask_qty: &[Decimal],
919 bid_qty: &[Decimal],
920 ) -> Decimal {
921 let ask_sum: Decimal = ask_qty.iter().sum();
922 let bid_sum: Decimal = bid_qty.iter().sum();
923 let total_sum = ask_sum + bid_sum;
924
925 if total_sum == Decimal::ZERO {
926 Decimal::ZERO
927 } else {
928 ask_sum / total_sum
929 }
930 }
931
932 pub fn calc_order_book_curvature(
934 &self,
935 ask_prices: &[Decimal],
936 bid_prices: &[Decimal],
937 depth: usize,
938 ) -> Decimal {
939 let depth = depth.min(ask_prices.len()).min(bid_prices.len());
940 if depth < 3 {
941 return Decimal::ZERO;
942 }
943
944 let mut ask_volumes: SmallVec<[Decimal; N]> = SmallVec::with_capacity(depth);
946 let mut bid_volumes: SmallVec<[Decimal; N]> = SmallVec::with_capacity(depth);
947
948 for i in 1..=depth {
949 ask_volumes.push(Decimal::from(i as i32));
950 bid_volumes.push(Decimal::from((depth - i + 1) as i32));
951 }
952
953 let ask_mean_x = ask_volumes.iter().sum::<Decimal>() / Decimal::from(depth as i32);
955 let ask_mean_y = ask_prices[..depth].iter().sum::<Decimal>() / Decimal::from(depth as i32);
956
957 let bid_mean_x = bid_volumes.iter().sum::<Decimal>() / Decimal::from(depth as i32);
959 let bid_mean_y = bid_prices[..depth].iter().sum::<Decimal>() / Decimal::from(depth as i32);
960
961 let mut ask_slope_numerator = Decimal::ZERO;
963 let mut ask_slope_denominator = Decimal::ZERO;
964 for i in 0..depth {
965 let x_diff = ask_volumes[i] - ask_mean_x;
966 let y_diff = ask_prices[i] - ask_mean_y;
967 ask_slope_numerator += x_diff * y_diff;
968 ask_slope_denominator += x_diff * x_diff;
969 }
970 let ask_slope = if ask_slope_denominator == Decimal::ZERO {
971 Decimal::ZERO
972 } else {
973 ask_slope_numerator / ask_slope_denominator
974 };
975
976 let mut bid_slope_numerator = Decimal::ZERO;
978 let mut bid_slope_denominator = Decimal::ZERO;
979 for i in 0..depth {
980 let x_diff = bid_volumes[i] - bid_mean_x;
981 let y_diff = bid_prices[i] - bid_mean_y;
982 bid_slope_numerator += x_diff * y_diff;
983 bid_slope_denominator += x_diff * x_diff;
984 }
985 let bid_slope = if bid_slope_denominator == Decimal::ZERO {
986 Decimal::ZERO
987 } else {
988 bid_slope_numerator / bid_slope_denominator
989 };
990
991 let mut ask_curvature = Decimal::ZERO;
993 let mut bid_curvature = Decimal::ZERO;
994
995 for i in 0..depth {
996 let ask_x_diff = ask_volumes[i] - ask_mean_x;
997 let ask_y_diff =
998 ask_prices[i] - (ask_slope * ask_volumes[i] + ask_mean_y - ask_slope * ask_mean_x);
999 ask_curvature += ask_x_diff * ask_x_diff * ask_y_diff;
1000
1001 let bid_x_diff = bid_volumes[i] - bid_mean_x;
1002 let bid_y_diff =
1003 bid_prices[i] - (bid_slope * bid_volumes[i] + bid_mean_y - bid_slope * bid_mean_x);
1004 bid_curvature += bid_x_diff * bid_x_diff * bid_y_diff;
1005 }
1006
1007 if ask_slope_denominator == Decimal::ZERO || bid_slope_denominator == Decimal::ZERO {
1008 Decimal::ZERO
1009 } else {
1010 (ask_curvature / ask_slope_denominator) - (bid_curvature / bid_slope_denominator)
1011 }
1012 }
1013
1014 pub fn calc_directional_volume_intensity(
1016 &self,
1017 buy_volumes: &[Decimal],
1018 sell_volumes: &[Decimal],
1019 window: usize,
1020 ) -> SmallVec<[Decimal; N]> {
1021 let n = buy_volumes.len().min(sell_volumes.len());
1022 let mut dvi = SmallVec::<[Decimal; N]>::with_capacity(n);
1023 dvi.resize(n, Decimal::ZERO);
1024
1025 for i in window..n {
1026 let buy_sum: Decimal = buy_volumes[i - window..i].iter().sum();
1027 let sell_sum: Decimal = sell_volumes[i - window..i].iter().sum();
1028 let total = buy_sum + sell_sum;
1029
1030 if total > Decimal::ZERO {
1031 dvi[i] = (buy_sum - sell_sum) / total;
1032 }
1033 }
1034
1035 dvi
1036 }
1037
1038 pub fn calc_price_displacement_ratio(
1040 &self,
1041 price_changes: &[Decimal],
1042 top_level_sizes: &[Decimal],
1043 window: usize,
1044 ) -> SmallVec<[Decimal; N]> {
1045 let n = price_changes.len().min(top_level_sizes.len());
1046 let mut pdr = SmallVec::<[Decimal; N]>::with_capacity(n);
1047 pdr.resize(n, Decimal::ZERO);
1048
1049 for i in window..n {
1050 let price_sum: Decimal = price_changes[i - window..i]
1051 .iter()
1052 .map(rust_decimal::Decimal::abs)
1053 .sum();
1054 let size_sum: Decimal = top_level_sizes[i - window..i].iter().sum();
1055
1056 if size_sum > Decimal::ZERO {
1057 pdr[i] = price_sum / size_sum;
1058 }
1059 }
1060
1061 pdr
1062 }
1063
1064 pub fn calc_trade_size_momentum(
1066 &self,
1067 trade_sizes: &[Decimal],
1068 window: usize,
1069 ) -> SmallVec<[Decimal; N]> {
1070 let n = trade_sizes.len();
1071 let mut tsm = SmallVec::<[Decimal; N]>::with_capacity(n);
1072 tsm.resize(n, Decimal::ZERO);
1073
1074 for i in window..n {
1075 let size_sum: Decimal = trade_sizes[i - window..i].iter().sum();
1076 let size_avg = size_sum / Decimal::from(window as i32);
1077
1078 if size_avg > Decimal::ZERO {
1079 tsm[i] = (trade_sizes[i] - size_avg) / size_avg;
1080 }
1081 }
1082
1083 tsm
1084 }
1085
1086 pub fn calc_imbalance_sensitivity(
1088 &self,
1089 price_changes: &[Decimal],
1090 book_imbalance: &[Decimal],
1091 window: usize,
1092 ) -> SmallVec<[Decimal; N]> {
1093 let n = price_changes.len().min(book_imbalance.len());
1094 let mut isens = SmallVec::<[Decimal; N]>::with_capacity(n);
1095 isens.resize(n, Decimal::ZERO);
1096
1097 for i in window..n {
1098 let price_sum: Decimal = price_changes[i - window..i]
1099 .iter()
1100 .map(rust_decimal::Decimal::abs)
1101 .sum();
1102 let imbalance_sum: Decimal = book_imbalance[i - window..i].iter().sum();
1103
1104 if imbalance_sum != Decimal::ZERO {
1105 isens[i] = price_sum / imbalance_sum;
1106 }
1107 }
1108
1109 isens
1110 }
1111
1112 pub fn calc_volatility_synchronized_order_flow(
1114 &self,
1115 order_flow: &[Decimal],
1116 volatility: &[Decimal],
1117 window: usize,
1118 ) -> SmallVec<[Decimal; N]> {
1119 let n = order_flow.len().min(volatility.len());
1120 let mut vsof = SmallVec::<[Decimal; N]>::with_capacity(n);
1121 vsof.resize(n, Decimal::ZERO);
1122
1123 for i in window..n {
1124 let flow_sum: Decimal = order_flow[i - window..i].iter().sum();
1125 let vol_sum: Decimal = volatility[i - window..i].iter().sum();
1126
1127 if vol_sum > Decimal::ZERO {
1128 vsof[i] = flow_sum / vol_sum;
1129 }
1130 }
1131
1132 vsof
1133 }
1134
1135 pub fn calc_cumulative_market_order_flow(
1137 &self,
1138 buy_orders: &[Decimal],
1139 sell_orders: &[Decimal],
1140 window: usize,
1141 ) -> SmallVec<[Decimal; N]> {
1142 let n = buy_orders.len().min(sell_orders.len());
1143 let mut cmof = SmallVec::<[Decimal; N]>::with_capacity(n);
1144 cmof.resize(n, Decimal::ZERO);
1145
1146 for i in window..n {
1147 let buy_sum: Decimal = buy_orders[i - window..i].iter().sum();
1148 let sell_sum: Decimal = sell_orders[i - window..i].iter().sum();
1149
1150 cmof[i] = buy_sum - sell_sum;
1151 }
1152
1153 cmof
1154 }
1155
1156 pub fn calc_price_volume_divergence(
1158 &self,
1159 price_changes: &[Decimal],
1160 volume_changes: &[Decimal],
1161 window: usize,
1162 ) -> SmallVec<[Decimal; N]> {
1163 let n = price_changes.len().min(volume_changes.len());
1164 let mut pvd = SmallVec::<[Decimal; N]>::with_capacity(n);
1165 pvd.resize(n, Decimal::ZERO);
1166
1167 for i in window..n {
1168 let price_sum: Decimal = price_changes[i - window..i]
1169 .iter()
1170 .map(rust_decimal::Decimal::abs)
1171 .sum();
1172 let volume_sum: Decimal = volume_changes[i - window..i].iter().sum();
1173 let total = price_sum + volume_sum;
1174
1175 if total > Decimal::ZERO {
1176 pvd[i] = (price_sum - volume_sum) / total;
1177 }
1178 }
1179
1180 pvd
1181 }
1182
1183 pub fn calc_weighted_order_imbalance(
1185 &self,
1186 bid_qty: &[Decimal],
1187 ask_qty: &[Decimal],
1188 depth: usize,
1189 ) -> Decimal {
1190 let depth = depth.min(bid_qty.len()).min(ask_qty.len());
1191 if depth == 0 {
1192 return Decimal::ZERO;
1193 }
1194
1195 let mut bid_weighted = Decimal::ZERO;
1196 let mut ask_weighted = Decimal::ZERO;
1197 let mut bid_sum = Decimal::ZERO;
1198 let mut ask_sum = Decimal::ZERO;
1199
1200 for i in 0..depth {
1201 let weight = Decimal::from((depth - i) as i32);
1202 bid_weighted += bid_qty[i] * weight;
1203 ask_weighted += ask_qty[i] * weight;
1204 bid_sum += bid_qty[i];
1205 ask_sum += ask_qty[i];
1206 }
1207
1208 let total_sum = bid_sum + ask_sum;
1209 if total_sum == Decimal::ZERO {
1210 Decimal::ZERO
1211 } else {
1212 (bid_weighted - ask_weighted) / total_sum
1213 }
1214 }
1215
1216 pub fn calc_trade_intensity(
1218 &self,
1219 volumes: &[Decimal],
1220 window: usize,
1221 ) -> SmallVec<[Decimal; N]> {
1222 let mut intensities = SmallVec::<[Decimal; N]>::with_capacity(volumes.len());
1223 intensities.resize(volumes.len(), Decimal::ZERO);
1224
1225 for i in window..volumes.len() {
1226 intensities[i] = volumes[i - window..i].iter().sum();
1227 }
1228
1229 intensities
1230 }
1231
1232 pub fn calc_bipower_variation(&self, prices: &[Decimal], window: usize) -> Decimal {
1234 if prices.len() < window + 1 {
1235 return Decimal::ZERO;
1236 }
1237
1238 let returns: Vec<Decimal> = prices
1239 .windows(2)
1240 .take(window)
1241 .map(|w| decimal_math::ln(w[1] / w[0]))
1242 .collect();
1243
1244 if returns.len() < 2 {
1245 return Decimal::ZERO;
1246 }
1247
1248 let mut sum = Decimal::ZERO;
1249 for i in 1..returns.len() {
1250 sum += returns[i].abs() * returns[i - 1].abs();
1251 }
1252
1253 let pi_over_2 = Decimal::from_str("1.5707963267948966").unwrap();
1255 pi_over_2 * sum / Decimal::from((returns.len() - 1) as i32)
1256 }
1257
1258 pub fn calc_hasbrouck_lambda(
1260 &self,
1261 price_changes: &[Decimal],
1262 order_flows: &[Decimal],
1263 ) -> Option<Decimal> {
1264 if price_changes.is_empty() || price_changes.len() != order_flows.len() {
1265 return None;
1266 }
1267
1268 let mean_price_change: Decimal =
1270 price_changes.iter().sum::<Decimal>() / Decimal::from(price_changes.len() as i32);
1271 let variance: Decimal = price_changes
1272 .iter()
1273 .map(|&p| decimal_math::powi(p - mean_price_change, 2))
1274 .sum::<Decimal>()
1275 / Decimal::from(price_changes.len() as i32 - 1);
1276
1277 if variance == Decimal::ZERO {
1278 return None;
1279 }
1280
1281 let mean_order_flow: Decimal =
1283 order_flows.iter().sum::<Decimal>() / Decimal::from(order_flows.len() as i32);
1284 let covariance: Decimal = price_changes
1285 .iter()
1286 .zip(order_flows.iter())
1287 .map(|(&p, &o)| (p - mean_price_change) * (o - mean_order_flow))
1288 .sum::<Decimal>()
1289 / Decimal::from(price_changes.len() as i32 - 1);
1290
1291 Some(covariance / variance)
1292 }
1293
1294 pub fn calc_effective_spread(
1296 &self,
1297 price_changes: &[Decimal],
1298 order_flows: &[Decimal],
1299 ) -> Option<Decimal> {
1300 self.calc_kyles_lambda(price_changes, order_flows)
1301 .map(|lambda| lambda * Decimal::from(2))
1302 }
1303
1304 pub fn calc_ml_features(
1306 &self,
1307 ask_prices: &[Decimal],
1308 ask_volumes: &[Decimal],
1309 bid_prices: &[Decimal],
1310 bid_volumes: &[Decimal],
1311 trades: &[TradeFeatures],
1312 window: usize,
1313 ) -> MLFeatures {
1314 let spread = self.calc_spread(ask_prices[0], bid_prices[0]);
1315 let mid_price = self.calc_mid_price(ask_prices[0], bid_prices[0]);
1316 let order_imbalance = self.calc_order_imbalance(ask_volumes, bid_volumes);
1317 let queue_imbalance = self.calc_queue_imbalance(ask_volumes, bid_volumes);
1318 let liquidity_shocks = self.calc_liquidity_shocks(ask_volumes, bid_volumes);
1319 let order_book_pressure = self.calc_order_book_pressure(ask_volumes, bid_volumes, 5);
1320 let order_book_entropy = self.calc_order_book_entropy(ask_volumes, bid_volumes);
1321 let weighted_imbalance = self.calc_weighted_order_imbalance(bid_volumes, ask_volumes, 5);
1322 let book_slope =
1323 self.calc_order_book_slope(ask_prices, ask_volumes, bid_prices, bid_volumes, 5);
1324
1325 let (vpin, trade_intensity) = if !trades.is_empty() && trades.len() >= window {
1327 let vpin = self.calc_vpin(trades, window);
1328 let volumes: SmallVec<[Decimal; 128]> = trades.iter().map(|t| t.tick_qty).collect();
1329 let intensity = self.calc_trade_intensity(&volumes, window);
1330 (
1331 vpin.last().copied().unwrap_or(Decimal::ZERO),
1332 intensity.last().copied().unwrap_or(Decimal::ZERO),
1333 )
1334 } else {
1335 (Decimal::ZERO, Decimal::ZERO)
1336 };
1337
1338 let order_cancel_rate = self.calc_order_cancel_estimated_rate(ask_volumes, bid_volumes);
1340 let order_book_imbalance = self.calc_order_book_imbalance(ask_volumes, bid_volumes);
1341 let order_book_depth = self.calc_order_book_depth(ask_volumes, bid_volumes);
1342
1343 let mid_price_volatility = Decimal::ZERO;
1345 let price_volatility = Decimal::ZERO;
1346 let decay_rates = Decimal::ZERO;
1347 let decay_rates_mean = Decimal::ZERO;
1348 let harmonic_oscillations = Decimal::ZERO;
1349 let asymmetry_index = Decimal::ZERO;
1350
1351 let rolling_price_impact = Decimal::ZERO;
1353 let bursts_in_trading_activity = Decimal::ZERO;
1354 let volume_to_price_sensitivity = Decimal::ZERO;
1355
1356 let ofi_simple = if ask_prices.len() > 1 && bid_prices.len() > 1 {
1358 self.calc_ofi_simple(ask_volumes, bid_volumes, &[], &[])
1359 } else {
1360 Decimal::ZERO
1361 };
1362
1363 let ofi_detailed = if ask_prices.len() > 1 && bid_prices.len() > 1 {
1364 self.calc_ofi_detailed(
1365 ask_prices,
1366 ask_volumes,
1367 bid_prices,
1368 bid_volumes,
1369 &[],
1370 &[],
1371 &[],
1372 &[],
1373 )
1374 } else {
1375 Decimal::ZERO
1376 };
1377
1378 MLFeatures {
1379 spread,
1380 mid_price,
1381 order_imbalance,
1382 queue_imbalance,
1383 liquidity_shocks,
1384 order_book_pressure,
1385 order_book_entropy,
1386 weighted_imbalance,
1387 book_slope,
1388 vpin,
1389 trade_intensity,
1390 mid_price_volatility,
1392 order_cancel_estimated_rate: order_cancel_rate,
1393 order_book_imbalance,
1394 order_book_depth,
1395 price_volatility,
1396 decay_rates,
1397 decay_rates_mean,
1398 rolling_price_impact,
1399 harmonic_oscillations,
1400 bursts_in_trading_activity,
1401 asymmetry_index,
1402 volume_to_price_sensitivity,
1403 ofi_simple,
1404 ofi_detailed,
1405 order_book_curvature: self.calc_order_book_curvature(ask_prices, bid_prices, 5),
1407 directional_volume_intensity: Decimal::ZERO, price_displacement_ratio: Decimal::ZERO, trade_size_momentum: Decimal::ZERO, imbalance_sensitivity: Decimal::ZERO, volatility_sync_order_flow: Decimal::ZERO, cumulative_market_order_flow: Decimal::ZERO, price_volume_divergence: Decimal::ZERO, }
1415 }
1416
1417 pub fn calc_ml_features_simd(
1426 &self,
1427 ask_prices: &[Decimal],
1428 ask_volumes: &[Decimal],
1429 bid_prices: &[Decimal],
1430 bid_volumes: &[Decimal],
1431 trades: &[TradeFeatures],
1432 window: usize,
1433 ) -> MLFeatures {
1434 let mut vectorized = self.vectorized_features.borrow_mut();
1435
1436 let volume_features = vectorized.calc_volume_features_batch(ask_volumes, bid_volumes);
1438
1439 let weighted_features =
1441 vectorized.calc_weighted_features_batch(ask_volumes, bid_volumes, 5);
1442
1443 let price_features =
1445 vectorized.calc_price_features_batch(ask_prices, bid_prices, ask_volumes, bid_volumes);
1446
1447 drop(vectorized);
1449
1450 let (vpin, trade_intensity) = if !trades.is_empty() && trades.len() >= window {
1452 let vpin = self.calc_vpin(trades, window);
1453 let volumes: SmallVec<[Decimal; 128]> = trades.iter().map(|t| t.tick_qty).collect();
1454 let intensity = self.calc_trade_intensity(&volumes, window);
1455 (
1456 vpin.last().copied().unwrap_or(Decimal::ZERO),
1457 intensity.last().copied().unwrap_or(Decimal::ZERO),
1458 )
1459 } else {
1460 (Decimal::ZERO, Decimal::ZERO)
1461 };
1462
1463 let order_book_entropy = self.calc_order_book_entropy(ask_volumes, bid_volumes);
1465
1466 let ofi_simple = if ask_prices.len() > 1 && bid_prices.len() > 1 {
1468 self.calc_ofi_simple(ask_volumes, bid_volumes, &[], &[])
1469 } else {
1470 Decimal::ZERO
1471 };
1472
1473 let ofi_detailed = if ask_prices.len() > 1 && bid_prices.len() > 1 {
1474 self.calc_ofi_detailed(
1475 ask_prices,
1476 ask_volumes,
1477 bid_prices,
1478 bid_volumes,
1479 &[],
1480 &[],
1481 &[],
1482 &[],
1483 )
1484 } else {
1485 Decimal::ZERO
1486 };
1487
1488 let mid_price_volatility = Decimal::ZERO;
1490 let price_volatility = Decimal::ZERO;
1491 let decay_rates = Decimal::ZERO;
1492 let decay_rates_mean = Decimal::ZERO;
1493 let harmonic_oscillations = Decimal::ZERO;
1494 let asymmetry_index = Decimal::ZERO;
1495 let rolling_price_impact = Decimal::ZERO;
1496 let bursts_in_trading_activity = Decimal::ZERO;
1497 let volume_to_price_sensitivity = Decimal::ZERO;
1498
1499 MLFeatures {
1501 spread: Decimal::from_f64_retain(price_features.spread).unwrap_or(Decimal::ZERO),
1503 mid_price: Decimal::from_f64_retain(price_features.mid_price).unwrap_or(Decimal::ZERO),
1504 book_slope: Decimal::from_f64_retain(price_features.book_slope)
1505 .unwrap_or(Decimal::ZERO),
1506
1507 order_imbalance: Decimal::from_f64_retain(volume_features.order_imbalance)
1509 .unwrap_or(Decimal::ZERO),
1510 order_book_depth: Decimal::from_f64_retain(volume_features.order_book_depth)
1511 .unwrap_or(Decimal::ZERO),
1512 liquidity_shocks: Decimal::from_f64_retain(volume_features.liquidity_shocks)
1513 .unwrap_or(Decimal::ZERO),
1514 order_cancel_estimated_rate: Decimal::from_f64_retain(
1515 volume_features.order_cancel_estimated_rate,
1516 )
1517 .unwrap_or(Decimal::ZERO),
1518 order_book_imbalance: Decimal::from_f64_retain(
1519 volume_features.order_book_imbalance_ratio,
1520 )
1521 .unwrap_or(Decimal::ZERO),
1522
1523 order_book_pressure: Decimal::from_f64_retain(weighted_features.order_book_pressure)
1525 .unwrap_or(Decimal::ZERO),
1526 weighted_imbalance: Decimal::from_f64_retain(weighted_features.weighted_imbalance)
1527 .unwrap_or(Decimal::ZERO),
1528
1529 queue_imbalance: Decimal::from_f64_retain(volume_features.order_imbalance)
1531 .unwrap_or(Decimal::ZERO),
1532
1533 order_book_entropy,
1535 vpin,
1536 trade_intensity,
1537 ofi_simple,
1538 ofi_detailed,
1539
1540 mid_price_volatility,
1542 price_volatility,
1543 decay_rates,
1544 decay_rates_mean,
1545 rolling_price_impact,
1546 harmonic_oscillations,
1547 bursts_in_trading_activity,
1548 asymmetry_index,
1549 volume_to_price_sensitivity,
1550
1551 order_book_curvature: self.calc_order_book_curvature(ask_prices, bid_prices, 5),
1553 directional_volume_intensity: Decimal::ZERO, price_displacement_ratio: Decimal::ZERO, trade_size_momentum: Decimal::ZERO, imbalance_sensitivity: Decimal::ZERO, volatility_sync_order_flow: Decimal::ZERO, cumulative_market_order_flow: Decimal::ZERO, price_volume_divergence: Decimal::ZERO, }
1561 }
1562}
1563
1564#[derive(Debug, Clone)]
1567pub struct MLFeatures {
1568 pub spread: Decimal,
1570 pub mid_price: Decimal,
1572 pub order_imbalance: Decimal,
1574 pub queue_imbalance: Decimal,
1576 pub liquidity_shocks: Decimal,
1578 pub order_book_pressure: Decimal,
1580 pub order_book_entropy: Decimal,
1582 pub weighted_imbalance: Decimal,
1584 pub book_slope: Decimal,
1586 pub vpin: Decimal,
1588 pub trade_intensity: Decimal,
1590 pub mid_price_volatility: Decimal,
1593 pub order_cancel_estimated_rate: Decimal,
1595 pub order_book_imbalance: Decimal,
1597 pub order_book_depth: Decimal,
1599 pub price_volatility: Decimal,
1601 pub decay_rates: Decimal,
1603 pub decay_rates_mean: Decimal,
1605 pub rolling_price_impact: Decimal,
1607 pub harmonic_oscillations: Decimal,
1609 pub bursts_in_trading_activity: Decimal,
1611 pub asymmetry_index: Decimal,
1613 pub volume_to_price_sensitivity: Decimal,
1615 pub ofi_simple: Decimal,
1617 pub ofi_detailed: Decimal,
1619 pub order_book_curvature: Decimal,
1622 pub directional_volume_intensity: Decimal,
1624 pub price_displacement_ratio: Decimal,
1626 pub trade_size_momentum: Decimal,
1628 pub imbalance_sensitivity: Decimal,
1630 pub volatility_sync_order_flow: Decimal,
1632 pub cumulative_market_order_flow: Decimal,
1634 pub price_volume_divergence: Decimal,
1636}
1637
1638impl MLFeatures {
1639 #[must_use]
1641 pub fn to_vec(&self) -> Vec<f64> {
1642 vec![
1643 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.spread),
1644 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.mid_price),
1645 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.order_imbalance),
1646 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.queue_imbalance),
1647 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.liquidity_shocks),
1648 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.order_book_pressure),
1649 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.order_book_entropy),
1650 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.weighted_imbalance),
1651 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.book_slope),
1652 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.vpin),
1653 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.trade_intensity),
1654 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.mid_price_volatility),
1656 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.order_cancel_estimated_rate),
1657 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.order_book_imbalance),
1658 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.order_book_depth),
1659 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.price_volatility),
1660 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.decay_rates),
1661 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.decay_rates_mean),
1662 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.rolling_price_impact),
1663 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.harmonic_oscillations),
1664 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.bursts_in_trading_activity),
1665 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.asymmetry_index),
1666 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.volume_to_price_sensitivity),
1667 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.ofi_simple),
1668 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.ofi_detailed),
1669 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.order_book_curvature),
1671 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.directional_volume_intensity),
1672 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.price_displacement_ratio),
1673 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.trade_size_momentum),
1674 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.imbalance_sensitivity),
1675 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.volatility_sync_order_flow),
1676 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.cumulative_market_order_flow),
1677 rusty_common::decimal_utils::decimal_to_f64_or_nan(self.price_volume_divergence),
1678 ]
1679 }
1680}
1681
1682#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1684pub enum OrderSide {
1685 Buy,
1687 Sell,
1689}
1690
1691pub type MicrostructureCalculator128 = MicrostructureCalculator<128>;
1693pub type MicrostructureCalculator64 = MicrostructureCalculator<64>;
1695pub type MicrostructureCalculator32 = MicrostructureCalculator<32>;
1697pub type MicrostructureCalculator256 = MicrostructureCalculator<256>;
1699
1700pub use MicrostructureCalculator128 as DefaultMicrostructureCalculator;
1702
1703#[cfg(test)]
1704mod tests {
1705 use super::*;
1706 use rust_decimal_macros::dec;
1707
1708 #[test]
1709 fn test_trade_features_basic() {
1710 let features = TradeFeatures::new(dec!(100.5), dec!(10), OrderSide::Buy);
1711
1712 assert_eq!(features.tick_price, dec!(100.5));
1714 assert_eq!(features.tick_qty, dec!(10));
1715 assert_eq!(features.tick_direction, 1);
1716
1717 assert_eq!(features.signed_tick_price, dec!(100.5));
1719 assert_eq!(features.weighted_tick_price, dec!(1005.0));
1720 assert_eq!(features.tick_value, dec!(1005.0));
1721 assert_eq!(features.tick_volume, dec!(10));
1722 assert_eq!(features.tick_aggressiveness, 1);
1723
1724 assert_eq!(features.tick_price_power, dec!(10100.25)); assert_eq!(features.tick_efficiency, dec!(10.05)); assert_eq!(features.tick_impact, dec!(10.05)); assert_eq!(features.tick_price_ratio, dec!(0));
1731 assert_eq!(features.relative_tick_price, dec!(0));
1732 assert_eq!(features.log_return, dec!(0));
1733 assert_eq!(features.tick_price_volatility, dec!(0));
1734 }
1735
1736 #[test]
1737 fn test_trade_features_sell_side() {
1738 let features = TradeFeatures::new(dec!(99.5), dec!(5), OrderSide::Sell);
1739
1740 assert_eq!(features.tick_direction, -1);
1741 assert_eq!(features.signed_tick_price, dec!(-99.5));
1742 assert_eq!(features.tick_aggressiveness, 0); assert_eq!(features.tick_impact, dec!(-19.9)); }
1745
1746 #[test]
1747 fn test_trade_features_with_previous_trade() {
1748 let features = TradeFeatures::new(dec!(101.0), dec!(12), OrderSide::Buy)
1749 .with_previous_trade(dec!(100.0), dec!(10), 1000000000, 1000001000);
1750
1751 assert_eq!(features.tick_price_ratio, dec!(1.01)); assert_eq!(features.relative_tick_price, dec!(0.01)); assert_eq!(features.percent_return, dec!(1.0)); assert_eq!(features.tick_size, dec!(1.0)); assert_eq!(features.tick_type, 1); assert_eq!(features.relative_strength, dec!(0.01)); assert_eq!(features.tick_duration, dec!(1000)); assert_eq!(features.tick_frequency, dec!(1000000)); assert_eq!(features.tick_intensity, dec!(12000000)); let expected_vwap =
1766 (dec!(101.0) * dec!(12) + dec!(100.0) * dec!(10)) / (dec!(12) + dec!(10));
1767 assert_eq!(features.volume_weighted_tick_price, expected_vwap);
1768 }
1769
1770 #[test]
1771 fn test_trade_features_with_trade_history() {
1772 let price_history = vec![
1773 dec!(99.0),
1774 dec!(100.0),
1775 dec!(101.0),
1776 dec!(102.0),
1777 dec!(101.5),
1778 ];
1779 let qty_history = vec![dec!(8), dec!(10), dec!(12), dec!(15), dec!(11)];
1780
1781 let features = TradeFeatures::new(dec!(101.5), dec!(11), OrderSide::Buy)
1782 .with_trade_history(&price_history, &qty_history);
1783
1784 assert!(features.tick_price_volatility >= dec!(0));
1786
1787 let expected_momentum = (dec!(101.5) - dec!(99.0)) / dec!(99.0);
1789 assert_eq!(features.tick_price_momentum, expected_momentum);
1790
1791 assert!(features.tick_trend != dec!(0)); assert!(features.tick_price_acceleration != dec!(0)); }
1797
1798 #[test]
1799 fn test_trade_features_edge_cases() {
1800 let features = TradeFeatures::new(dec!(100.0), dec!(0), OrderSide::Buy);
1802 assert_eq!(features.tick_efficiency, dec!(0));
1803 assert_eq!(features.tick_impact, dec!(0));
1804
1805 let features = TradeFeatures::new(dec!(100.0), dec!(10), OrderSide::Buy)
1807 .with_previous_trade(dec!(100.0), dec!(8), 1000000000, 1000001000);
1808 assert_eq!(features.tick_size, dec!(0));
1809 assert_eq!(features.tick_type, 0); assert_eq!(features.relative_strength, dec!(0)); assert_eq!(features.relative_tick_price, dec!(0));
1812
1813 let short_history = vec![dec!(100.0)];
1815 let qty_history = vec![dec!(10)];
1816 let features = TradeFeatures::new(dec!(100.0), dec!(10), OrderSide::Buy)
1817 .with_trade_history(&short_history, &qty_history);
1818 assert_eq!(features.tick_price_volatility, dec!(0));
1819 assert_eq!(features.tick_price_momentum, dec!(0));
1820 }
1821
1822 #[test]
1823 fn test_spread_calculation() {
1824 let calc = MicrostructureCalculator::<128>::new(100);
1825 let spread = calc.calc_spread(dec!(100.5), dec!(100.0));
1826 assert_eq!(spread, dec!(0.5));
1827 }
1828
1829 #[test]
1830 fn test_order_imbalance() {
1831 let calc = MicrostructureCalculator::<128>::new(100);
1832 let ask_qty = vec![dec!(10), dec!(20), dec!(30)];
1833 let bid_qty = vec![dec!(15), dec!(25), dec!(35)];
1834 let imbalance = calc.calc_order_imbalance(&ask_qty, &bid_qty);
1835 assert!(imbalance > Decimal::ZERO); }
1837
1838 #[test]
1839 fn test_vpin_calculation() {
1840 let calc = MicrostructureCalculator::<128>::new(100);
1841 let trades = vec![
1842 TradeFeatures::new(dec!(100), dec!(10), OrderSide::Buy),
1843 TradeFeatures::new(dec!(100.1), dec!(15), OrderSide::Sell),
1844 TradeFeatures::new(dec!(100.2), dec!(20), OrderSide::Buy),
1845 TradeFeatures::new(dec!(100.1), dec!(5), OrderSide::Sell),
1846 ];
1847 let vpin = calc.calc_vpin(&trades, 2);
1848 assert_eq!(vpin.len(), trades.len());
1849 }
1850
1851 #[test]
1852 fn test_order_book_curvature() {
1853 let calc = MicrostructureCalculator::<128>::new(100);
1854 let ask_prices = vec![
1855 dec!(100.5),
1856 dec!(101.0),
1857 dec!(101.5),
1858 dec!(102.0),
1859 dec!(102.5),
1860 ];
1861 let bid_prices = vec![dec!(100.0), dec!(99.5), dec!(99.0), dec!(98.5), dec!(98.0)];
1862
1863 let curvature = calc.calc_order_book_curvature(&ask_prices, &bid_prices, 5);
1864 assert_eq!(
1869 curvature,
1870 Decimal::ZERO,
1871 "Curvature of an order book with linearly spaced price levels should be zero."
1872 );
1873 let calc = MicrostructureCalculator::<128>::new(100);
1874 let buy_volumes = vec![dec!(10), dec!(15), dec!(20), dec!(25), dec!(30)];
1875 let sell_volumes = vec![dec!(5), dec!(10), dec!(15), dec!(20), dec!(25)];
1876
1877 let dvi = calc.calc_directional_volume_intensity(&buy_volumes, &sell_volumes, 3);
1878 assert_eq!(dvi.len(), buy_volumes.len());
1879 assert!(dvi[3] > Decimal::ZERO);
1881 assert!(dvi[4] > Decimal::ZERO);
1882 }
1883
1884 #[test]
1885 fn test_price_displacement_ratio() {
1886 let calc = MicrostructureCalculator::<128>::new(100);
1887 let price_changes = vec![dec!(0.1), dec!(-0.2), dec!(0.3), dec!(-0.1), dec!(0.2)];
1888 let top_level_sizes = vec![dec!(100), dec!(150), dec!(200), dec!(250), dec!(300)];
1889
1890 let pdr = calc.calc_price_displacement_ratio(&price_changes, &top_level_sizes, 3);
1891 assert_eq!(pdr.len(), price_changes.len());
1892 assert_eq!(pdr[0], Decimal::ZERO);
1894 assert_eq!(pdr[1], Decimal::ZERO);
1895 assert_eq!(pdr[2], Decimal::ZERO);
1896 assert!(pdr[3] >= Decimal::ZERO); assert!(pdr[4] >= Decimal::ZERO); }
1899
1900 #[test]
1901 fn test_trade_size_momentum() {
1902 let calc = MicrostructureCalculator::<128>::new(100);
1903 let trade_sizes = vec![dec!(10), dec!(15), dec!(20), dec!(30), dec!(50)];
1904
1905 let tsm = calc.calc_trade_size_momentum(&trade_sizes, 3);
1906 assert_eq!(tsm.len(), trade_sizes.len());
1907 assert!(tsm[4] > Decimal::ZERO);
1909 }
1910
1911 #[test]
1912 fn test_order_imbalance_simd_optimization() {
1913 let calc = MicrostructureCalculator::<128>::new(100);
1914
1915 let small_ask = vec![dec!(10), dec!(20)];
1917 let small_bid = vec![dec!(15), dec!(25)];
1918 let small_result = calc.calc_order_imbalance(&small_ask, &small_bid);
1919
1920 let large_ask = vec![dec!(10), dec!(20), dec!(30), dec!(40), dec!(50)];
1922 let large_bid = vec![dec!(15), dec!(25), dec!(35), dec!(45), dec!(55)];
1923 let large_result = calc.calc_order_imbalance(&large_ask, &large_bid);
1924
1925 assert!(small_result > Decimal::ZERO);
1927 assert!(large_result > Decimal::ZERO);
1928
1929 let empty_ask = vec![];
1931 let empty_bid = vec![];
1932 let empty_result = calc.calc_order_imbalance(&empty_ask, &empty_bid);
1933 assert_eq!(empty_result, Decimal::ZERO);
1934 }
1935
1936 #[test]
1937 fn test_calc_relative_spread() {
1938 let calc = MicrostructureCalculator::<128>::new(100);
1939
1940 let ask_price = dec!(100.5);
1942 let bid_price = dec!(100.0);
1943 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1944 let expected = dec!(0.5) / dec!(100.25); assert_eq!(relative_spread, expected);
1946
1947 let ask_price = dec!(100.0);
1949 let bid_price = dec!(100.0);
1950 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1951 assert_eq!(relative_spread, Decimal::ZERO);
1952
1953 let ask_price = dec!(100.001);
1955 let bid_price = dec!(100.000);
1956 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1957 let expected = dec!(0.001) / dec!(100.0005); assert_eq!(relative_spread, expected);
1959
1960 let ask_price = dec!(200.0);
1962 let bid_price = dec!(100.0);
1963 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1964 let expected = dec!(100.0) / dec!(150.0); assert_eq!(relative_spread, expected);
1966
1967 let ask_price = dec!(100.0);
1969 let bid_price = dec!(0.0);
1970 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1971 let expected = dec!(100.0) / dec!(50.0); assert_eq!(relative_spread, dec!(2.0));
1973
1974 let ask_price = dec!(0.0);
1976 let bid_price = dec!(100.0);
1977 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1978 let expected = dec!(-100.0) / dec!(50.0); assert_eq!(relative_spread, dec!(-2.0));
1980
1981 let ask_price = dec!(0.0);
1983 let bid_price = dec!(0.0);
1984 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1985 assert_eq!(relative_spread, Decimal::ZERO);
1986
1987 let ask_price = dec!(0.0001);
1989 let bid_price = dec!(0.00005);
1990 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1991 let expected = dec!(0.00005) / dec!(0.000075); assert_eq!(relative_spread, expected);
1993
1994 let ask_price = dec!(1000.123456);
1996 let bid_price = dec!(1000.123450);
1997 let relative_spread = calc.calc_relative_spread(ask_price, bid_price);
1998 let expected = dec!(0.000006) / dec!(1000.123453); assert_eq!(relative_spread, expected);
2000 }
2001}