apple_quant_algorithmic/
liquidity.rs1use apple_quant_core::AddUnchecked;
2use tracing::instrument;
3
4use crate::{
5 price::{AskPriceVolumeLevel, BidPriceVolumeLevel},
6 aggregation::TradeTradeTimestamp, instrument::InstrumentSpec,
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10struct Levels<IS: InstrumentSpec> {
11 lowest_bid_level: BidPriceVolumeLevel<IS>,
12 highest_ask_level: AskPriceVolumeLevel<IS>,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct LiquidityEstimation<IS: InstrumentSpec>(Option<Levels<IS>>);
17
18impl<IS: InstrumentSpec> LiquidityEstimation<IS> {
19 pub fn reset(
20 &mut self,
21 ) {
22 self.0 = None;
23 }
24
25 pub fn collect_furthest_bid_ask(
26 &mut self,
27 ) -> Option<(
28 BidPriceVolumeLevel<IS>,
29 AskPriceVolumeLevel<IS>,
30 )> {
31 let Levels {
32 highest_ask_level,
33 lowest_bid_level,
34 } = self.0.take()?;
35
36 Some((lowest_bid_level, highest_ask_level))
37 }
38
39 pub fn furthest_bid_ask(
40 &self,
41 ) -> Option<(
42 &BidPriceVolumeLevel<IS>,
43 &AskPriceVolumeLevel<IS>,
44 )> {
45 let Some(Levels {
46 lowest_bid_level,
47 highest_ask_level,
48 }) = &self.0 else {
49 return None;
50 };
51
52 Some((lowest_bid_level, highest_ask_level))
53 }
54
55 #[instrument(skip_all)]
56 pub fn walk_trades<'a>(
57 &mut self,
58 mut trades: impl Iterator<Item = &'a TradeTradeTimestamp<IS>>,
59 )
60 where
61 IS: 'a,
62 {
63 if self.0.is_none() {
64 let Some(
65 first_trade_trade_timestamp,
66 ) = trades.next() else {
67 return;
68 };
69
70 let highest_ask_level = AskPriceVolumeLevel {
71 price: first_trade_trade_timestamp.trade().price,
72 directionless_volume: *first_trade_trade_timestamp
73 .trade()
74 .aggressive_volume.as_directionless_volume(),
75 };
76
77 let lowest_bid_level = BidPriceVolumeLevel {
78 price: highest_ask_level.price,
79 directionless_volume: highest_ask_level.directionless_volume,
80 };
81
82 self.0 = Some(Levels {
83 highest_ask_level,
84 lowest_bid_level,
85 });
86 }
87
88 let levels = self.0.as_mut().unwrap();
89
90 for trade_trade_timestamp in trades {
91 let trade_price = &trade_trade_timestamp.trade().price;
92
93 let trade_aggressive_volume = &trade_trade_timestamp.trade().aggressive_volume;
94
95 if trade_price == &levels.highest_ask_level.price {
96 levels.highest_ask_level.directionless_volume = levels.highest_ask_level.directionless_volume.add_unchecked(
97 *trade_aggressive_volume.as_directionless_volume(),
98 );
99 } else if trade_price > &levels.highest_ask_level.price {
100 levels.highest_ask_level.price = *trade_price;
101
102 levels.highest_ask_level.directionless_volume = *trade_aggressive_volume.as_directionless_volume();
103 }
104
105 if trade_price == &levels.lowest_bid_level.price {
106 levels.lowest_bid_level.directionless_volume = levels.lowest_bid_level.directionless_volume.add_unchecked(
107 *trade_aggressive_volume.as_directionless_volume(),
108 );
109 } else if trade_price < &levels.lowest_bid_level.price {
110 levels.lowest_bid_level.price = *trade_price;
111
112 levels.lowest_bid_level.directionless_volume = *trade_aggressive_volume.as_directionless_volume();
113 }
114 }
115 }
116}
117
118impl<IS: InstrumentSpec> Default for LiquidityEstimation<IS> {
119 fn default() -> Self {
120 Self(None)
121 }
122}