phoenix/state/markets/
market_traits.rs

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/// Helpful struct for processing the order book state
21#[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/// Helpful struct for processing the order book state
34#[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
51/// A wrapper around an matching algorithm implementation that allows arbitrary structs to be
52/// used as generic markets.
53pub 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}