Skip to main content

apple_quant_algorithmic/
liquidity.rs

1use 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}