1use itertools::Itertools;
2
3use crate::{
4 quantities::{
5 BaseLots, BaseLotsPerBaseUnit, QuoteLots, QuoteLotsPerBaseUnitPerTick, Ticks, WrapperU64,
6 },
7 state::{matching_engine_response::MatchingEngineResponse, *},
8};
9use borsh::{BorshDeserialize, BorshSerialize};
10use sokoban::node_allocator::OrderedNodeAllocatorMap;
11
12use super::MarketEvent;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct LadderOrder {
16 pub price_in_ticks: u64,
17 pub size_in_base_lots: u64,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Ladder {
23 pub bids: Vec<LadderOrder>,
24 pub asks: Vec<LadderOrder>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct TypedLadderOrder {
29 pub price_in_ticks: Ticks,
30 pub size_in_base_lots: BaseLots,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct TypedLadder {
36 pub bids: Vec<TypedLadderOrder>,
37 pub asks: Vec<TypedLadderOrder>,
38}
39
40pub trait OrderId {
41 fn price_in_ticks(&self) -> u64;
42}
43
44pub trait RestingOrder {
45 fn size(&self) -> u64;
46 fn last_valid_slot(&self) -> Option<u64>;
47 fn last_valid_unix_timestamp_in_seconds(&self) -> Option<u64>;
48 fn is_expired(&self, current_slot: u64, current_unix_timestamp_in_seconds: u64) -> bool;
49}
50
51pub trait Market<
54 MarketTraderId: BorshDeserialize + BorshSerialize + Copy,
55 MarketOrderId: OrderId,
56 MarketRestingOrder: RestingOrder,
57 MarketOrderPacket: OrderPacketMetadata,
58>
59{
60 fn get_data_size(&self) -> usize {
61 unimplemented!()
62 }
63 fn get_collected_fee_amount(&self) -> QuoteLots {
64 unimplemented!()
65 }
66 fn get_uncollected_fee_amount(&self) -> QuoteLots {
67 unimplemented!()
68 }
69
70 fn get_ladder(&self, levels: u64) -> Ladder {
71 self.get_ladder_with_expiration(levels, None, None)
72 }
73
74 fn get_ladder_with_expiration(
75 &self,
76 levels: u64,
77 last_valid_slot: Option<u64>,
78 last_valid_unix_timestamp_in_seconds: Option<u64>,
79 ) -> Ladder {
80 let ladder = self.get_typed_ladder_with_expiration(
81 levels,
82 last_valid_slot,
83 last_valid_unix_timestamp_in_seconds,
84 );
85 Ladder {
86 bids: ladder
87 .bids
88 .iter()
89 .map(|order| LadderOrder {
90 price_in_ticks: order.price_in_ticks.as_u64(),
91 size_in_base_lots: order.size_in_base_lots.as_u64(),
92 })
93 .collect(),
94 asks: ladder
95 .asks
96 .iter()
97 .map(|order| LadderOrder {
98 price_in_ticks: order.price_in_ticks.as_u64(),
99 size_in_base_lots: order.size_in_base_lots.as_u64(),
100 })
101 .collect(),
102 }
103 }
104
105 fn get_typed_ladder(&self, levels: u64) -> TypedLadder {
106 self.get_typed_ladder_with_expiration(levels, None, None)
107 }
108
109 fn get_typed_ladder_with_expiration(
110 &self,
111 levels: u64,
112 last_valid_slot: Option<u64>,
113 last_valid_unix_timestamp_in_seconds: Option<u64>,
114 ) -> TypedLadder {
115 let slot_expiration = last_valid_slot.unwrap_or(0);
116 let unix_timestamp_expiration = last_valid_unix_timestamp_in_seconds.unwrap_or(0);
117 let mut bids = vec![];
118 let mut asks = vec![];
119 for (side, book) in [(Side::Bid, &mut bids), (Side::Ask, &mut asks)].iter_mut() {
120 book.extend_from_slice(
121 &self
122 .get_book(*side)
123 .iter()
124 .filter_map(|(order_id, resting_order)| {
125 if resting_order.is_expired(slot_expiration, unix_timestamp_expiration) {
126 None
127 } else {
128 Some((order_id.price_in_ticks(), resting_order.size()))
129 }
130 })
131 .group_by(|(price_in_ticks, _)| *price_in_ticks)
132 .into_iter()
133 .take(levels as usize)
134 .map(|(price_in_ticks, group)| TypedLadderOrder {
135 price_in_ticks: Ticks::new(price_in_ticks),
136 size_in_base_lots: BaseLots::new(group.map(|(_, size)| size).sum()),
137 })
138 .collect::<Vec<TypedLadderOrder>>(),
139 );
140 }
141 TypedLadder { bids, asks }
142 }
143
144 fn get_taker_fee_bps(&self) -> u64;
145 fn get_tick_size(&self) -> QuoteLotsPerBaseUnitPerTick;
146 fn get_base_lots_per_base_unit(&self) -> BaseLotsPerBaseUnit;
147 fn get_sequence_number(&self) -> u64;
148 fn get_registered_traders(&self) -> &dyn OrderedNodeAllocatorMap<MarketTraderId, TraderState>;
149 fn get_trader_state(&self, key: &MarketTraderId) -> Option<&TraderState>;
150 fn get_trader_state_from_index(&self, index: u32) -> &TraderState;
151 fn get_trader_index(&self, trader: &MarketTraderId) -> Option<u32>;
152 fn get_trader_id_from_index(&self, trader_index: u32) -> MarketTraderId;
153 fn get_book(
154 &self,
155 side: Side,
156 ) -> &dyn OrderedNodeAllocatorMap<MarketOrderId, MarketRestingOrder>;
157}
158
159pub(crate) trait WritableMarket<
160 MarketTraderId: BorshDeserialize + BorshSerialize + Copy,
161 MarketOrderId: OrderId,
162 MarketRestingOrder: RestingOrder,
163 MarketOrderPacket: OrderPacketMetadata,
164>: Market<MarketTraderId, MarketOrderId, MarketRestingOrder, MarketOrderPacket>
165{
166 fn initialize_with_params(
167 &mut self,
168 tick_size_in_quote_lots_per_base_unit: QuoteLotsPerBaseUnitPerTick,
169 base_lots_per_base_unit: BaseLotsPerBaseUnit,
170 );
171
172 fn set_fee(&mut self, taker_fee_bps: u64);
173
174 fn get_trader_state_mut(&mut self, key: &MarketTraderId) -> Option<&mut TraderState>;
175
176 fn get_registered_traders_mut(
177 &mut self,
178 ) -> &mut dyn OrderedNodeAllocatorMap<MarketTraderId, TraderState>;
179
180 fn get_trader_state_from_index_mut(&mut self, index: u32) -> &mut TraderState;
181
182 fn get_or_register_trader(&mut self, trader: &MarketTraderId) -> Option<u32> {
183 let registered_traders = self.get_registered_traders_mut();
184 if !registered_traders.contains(trader) {
185 registered_traders.insert(*trader, TraderState::default())?;
186 }
187 self.get_trader_index(trader)
188 }
189
190 fn try_remove_trader_state(&mut self, trader: &MarketTraderId) -> Option<()> {
191 let registered_traders = self.get_registered_traders_mut();
192 let trader_state = registered_traders.get(trader)?;
193 if *trader_state == TraderState::default() {
194 registered_traders.remove(trader)?;
195 }
196 Some(())
197 }
198
199 fn get_book_mut(
200 &mut self,
201 side: Side,
202 ) -> &mut dyn OrderedNodeAllocatorMap<MarketOrderId, MarketRestingOrder>;
203
204 fn place_order(
205 &mut self,
206 trader: &MarketTraderId,
207 order_packet: MarketOrderPacket,
208 record_event_fn: &mut dyn FnMut(MarketEvent<MarketTraderId>),
209 get_clock_fn: &mut dyn FnMut() -> (u64, u64),
210 ) -> Option<(Option<MarketOrderId>, MatchingEngineResponse)>;
211
212 fn cancel_order(
213 &mut self,
214 trader_id: &MarketTraderId,
215 order_id: &MarketOrderId,
216 side: Side,
217 claim_funds: bool,
218 record_event_fn: &mut dyn FnMut(MarketEvent<MarketTraderId>),
219 ) -> Option<MatchingEngineResponse> {
220 self.reduce_order(
221 trader_id,
222 order_id,
223 side,
224 None,
225 claim_funds,
226 record_event_fn,
227 )
228 }
229
230 fn reduce_order(
231 &mut self,
232 trader_id: &MarketTraderId,
233 order_id: &MarketOrderId,
234 side: Side,
235 size: Option<BaseLots>,
236 claim_funds: bool,
237 record_event_fn: &mut dyn FnMut(MarketEvent<MarketTraderId>),
238 ) -> Option<MatchingEngineResponse>;
239
240 fn cancel_all_orders(
241 &mut self,
242 trader_id: &MarketTraderId,
243 claim_funds: bool,
244 record_event_fn: &mut dyn FnMut(MarketEvent<MarketTraderId>),
245 ) -> Option<MatchingEngineResponse>;
246
247 #[allow(clippy::too_many_arguments)]
248 fn cancel_up_to(
249 &mut self,
250 trader_id: &MarketTraderId,
251 side: Side,
252 num_orders_to_search: Option<usize>,
253 num_orders_to_cancel: Option<usize>,
254 tick_limit: Option<Ticks>,
255 claim_funds: bool,
256 record_event_fn: &mut dyn FnMut(MarketEvent<MarketTraderId>),
257 ) -> Option<MatchingEngineResponse>;
258
259 fn cancel_multiple_orders_by_id(
260 &mut self,
261 trader_id: &MarketTraderId,
262 orders_to_cancel: &[MarketOrderId],
263 claim_funds: bool,
264 record_event_fn: &mut dyn FnMut(MarketEvent<MarketTraderId>),
265 ) -> Option<MatchingEngineResponse>;
266
267 fn claim_all_funds(
268 &mut self,
269 trader: &MarketTraderId,
270 allow_seat_eviction: bool,
271 ) -> Option<MatchingEngineResponse> {
272 self.claim_funds(trader, None, None, allow_seat_eviction)
273 }
274
275 fn claim_funds(
276 &mut self,
277 trader: &MarketTraderId,
278 num_quote_lots: Option<QuoteLots>,
279 num_base_lots: Option<BaseLots>,
280 allow_seat_eviction: bool,
281 ) -> Option<MatchingEngineResponse>;
282
283 fn collect_fees(
284 &mut self,
285 record_event_fn: &mut dyn FnMut(MarketEvent<MarketTraderId>),
286 ) -> QuoteLots;
287}