nautilus_execution/matching_engine/
ids_generator.rs1use std::{cell::RefCell, fmt::Debug, rc::Rc};
17
18use nautilus_common::cache::Cache;
19use nautilus_model::{
20 enums::OmsType,
21 identifiers::{PositionId, TradeId, Venue, VenueOrderId},
22 orders::{Order, OrderAny},
23};
24use uuid::Uuid;
25
26pub struct IdsGenerator {
27 venue: Venue,
28 raw_id: u32,
29 oms_type: OmsType,
30 use_random_ids: bool,
31 use_position_ids: bool,
32 cache: Rc<RefCell<Cache>>,
33 position_count: usize,
34 order_count: usize,
35 execution_count: usize,
36}
37
38impl Debug for IdsGenerator {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct(stringify!(IdsGenerator))
41 .field("venue", &self.venue)
42 .field("raw_id", &self.raw_id)
43 .finish()
44 }
45}
46
47impl IdsGenerator {
48 pub const fn new(
49 venue: Venue,
50 oms_type: OmsType,
51 raw_id: u32,
52 use_random_ids: bool,
53 use_position_ids: bool,
54 cache: Rc<RefCell<Cache>>,
55 ) -> Self {
56 Self {
57 venue,
58 raw_id,
59 oms_type,
60 cache,
61 use_random_ids,
62 use_position_ids,
63 position_count: 0,
64 order_count: 0,
65 execution_count: 0,
66 }
67 }
68
69 pub const fn reset(&mut self) {
70 self.position_count = 0;
71 self.order_count = 0;
72 self.execution_count = 0;
73 }
74
75 pub fn get_venue_order_id(&mut self, order: &OrderAny) -> anyhow::Result<VenueOrderId> {
81 if let Some(venue_order_id) = order.venue_order_id() {
83 return Ok(venue_order_id);
84 }
85
86 if let Some(venue_order_id) = self.cache.borrow().venue_order_id(&order.client_order_id()) {
88 return Ok(venue_order_id.to_owned());
89 }
90
91 let venue_order_id = self.generate_venue_order_id();
92 self.cache.borrow_mut().add_venue_order_id(
93 &order.client_order_id(),
94 &venue_order_id,
95 false,
96 )?;
97 Ok(venue_order_id)
98 }
99
100 pub fn get_position_id(
106 &mut self,
107 order: &OrderAny,
108 generate: Option<bool>,
109 ) -> Option<PositionId> {
110 let generate = generate.unwrap_or(true);
111 if self.oms_type == OmsType::Hedging {
112 {
113 let cache = self.cache.as_ref().borrow();
114 let position_id_result = cache.position_id(&order.client_order_id());
115 if let Some(position_id) = position_id_result {
116 return Some(position_id.to_owned());
117 }
118 }
119 if generate {
120 self.generate_venue_position_id()
121 } else {
122 panic!(
123 "Position id should be generated. Hedging Oms type order matching engine doesn't exist in cache."
124 )
125 }
126 } else {
127 let cache = self.cache.as_ref().borrow();
129 let positions_open =
130 cache.positions_open(None, Some(&order.instrument_id()), None, None);
131 if positions_open.is_empty() {
132 None
133 } else {
134 Some(positions_open[0].id)
135 }
136 }
137 }
138
139 pub fn generate_trade_id(&mut self) -> TradeId {
140 self.execution_count += 1;
141 let trade_id = if self.use_random_ids {
142 Uuid::new_v4().to_string()
143 } else {
144 format!("{}-{}-{}", self.venue, self.raw_id, self.execution_count)
145 };
146 TradeId::from(trade_id.as_str())
147 }
148
149 pub fn generate_venue_position_id(&mut self) -> Option<PositionId> {
150 if !self.use_position_ids {
151 return None;
152 }
153
154 self.position_count += 1;
155 if self.use_random_ids {
156 Some(PositionId::new(Uuid::new_v4().to_string()))
157 } else {
158 Some(PositionId::new(
159 format!("{}-{}-{}", self.venue, self.raw_id, self.position_count).as_str(),
160 ))
161 }
162 }
163
164 pub fn generate_venue_order_id(&mut self) -> VenueOrderId {
165 self.order_count += 1;
166 if self.use_random_ids {
167 VenueOrderId::new(Uuid::new_v4().to_string())
168 } else {
169 VenueOrderId::new(
170 format!("{}-{}-{}", self.venue, self.raw_id, self.order_count).as_str(),
171 )
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use std::{cell::RefCell, rc::Rc};
179
180 use nautilus_common::cache::Cache;
181 use nautilus_core::{UUID4, UnixNanos};
182 use nautilus_model::{
183 enums::{LiquiditySide, OmsType, OrderSide, OrderType},
184 events::OrderFilled,
185 identifiers::{
186 AccountId, ClientOrderId, PositionId, TradeId, Venue, VenueOrderId, stubs::account_id,
187 },
188 instruments::{
189 CryptoPerpetual, Instrument, InstrumentAny, stubs::crypto_perpetual_ethusdt,
190 },
191 orders::{Order, OrderAny, OrderTestBuilder},
192 position::Position,
193 types::{Price, Quantity},
194 };
195 use rstest::{fixture, rstest};
196
197 use crate::matching_engine::ids_generator::IdsGenerator;
198
199 #[fixture]
200 fn instrument_eth_usdt(crypto_perpetual_ethusdt: CryptoPerpetual) -> InstrumentAny {
201 InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt)
202 }
203
204 #[fixture]
205 fn market_order_buy(instrument_eth_usdt: InstrumentAny) -> OrderAny {
206 OrderTestBuilder::new(OrderType::Market)
207 .instrument_id(instrument_eth_usdt.id())
208 .side(OrderSide::Buy)
209 .quantity(Quantity::from("1.000"))
210 .client_order_id(ClientOrderId::from("O-19700101-000000-001-001-1"))
211 .submit(true)
212 .build()
213 }
214
215 #[fixture]
216 fn market_order_sell(instrument_eth_usdt: InstrumentAny) -> OrderAny {
217 OrderTestBuilder::new(OrderType::Market)
218 .instrument_id(instrument_eth_usdt.id())
219 .side(OrderSide::Sell)
220 .quantity(Quantity::from("1.000"))
221 .client_order_id(ClientOrderId::from("O-19700101-000000-001-001-2"))
222 .submit(true)
223 .build()
224 }
225
226 #[fixture]
227 fn market_order_fill(
228 instrument_eth_usdt: InstrumentAny,
229 account_id: AccountId,
230 market_order_buy: OrderAny,
231 ) -> OrderFilled {
232 OrderFilled::new(
233 market_order_buy.trader_id(),
234 market_order_buy.strategy_id(),
235 market_order_buy.instrument_id(),
236 market_order_buy.client_order_id(),
237 VenueOrderId::new("BINANCE-1"),
238 account_id,
239 TradeId::new("1"),
240 market_order_buy.order_side(),
241 market_order_buy.order_type(),
242 Quantity::from("1"),
243 Price::from("1000.000"),
244 instrument_eth_usdt.quote_currency(),
245 LiquiditySide::Taker,
246 UUID4::new(),
247 UnixNanos::default(),
248 UnixNanos::default(),
249 false,
250 Some(PositionId::new("P-1")),
251 None,
252 )
253 }
254
255 fn get_ids_generator(
256 cache: Rc<RefCell<Cache>>,
257 use_position_ids: bool,
258 oms_type: OmsType,
259 ) -> IdsGenerator {
260 IdsGenerator::new(
261 Venue::from("BINANCE"),
262 oms_type,
263 1,
264 false,
265 use_position_ids,
266 cache,
267 )
268 }
269
270 #[rstest]
271 fn test_get_position_id_hedging_with_existing_position(
272 instrument_eth_usdt: InstrumentAny,
273 market_order_buy: OrderAny,
274 market_order_fill: OrderFilled,
275 ) {
276 let cache = Rc::new(RefCell::new(Cache::default()));
277 let mut ids_generator = get_ids_generator(cache.clone(), false, OmsType::Hedging);
278
279 let position = Position::new(&instrument_eth_usdt, market_order_fill);
280
281 cache
283 .borrow_mut()
284 .add_position(position.clone(), OmsType::Hedging)
285 .unwrap();
286
287 let position_id = ids_generator.get_position_id(&market_order_buy, None);
288 assert_eq!(position_id, Some(position.id));
289 }
290
291 #[rstest]
292 fn test_get_position_id_hedging_with_generated_position(market_order_buy: OrderAny) {
293 let cache = Rc::new(RefCell::new(Cache::default()));
294 let mut ids_generator = get_ids_generator(cache, true, OmsType::Hedging);
295
296 let position_id = ids_generator.get_position_id(&market_order_buy, None);
297 assert_eq!(position_id, Some(PositionId::new("BINANCE-1-1")));
298 }
299
300 #[rstest]
301 fn test_get_position_id_netting(
302 instrument_eth_usdt: InstrumentAny,
303 market_order_buy: OrderAny,
304 market_order_fill: OrderFilled,
305 ) {
306 let cache = Rc::new(RefCell::new(Cache::default()));
307 let mut ids_generator = get_ids_generator(cache.clone(), false, OmsType::Netting);
308
309 let position_id = ids_generator.get_position_id(&market_order_buy, None);
311 assert_eq!(position_id, None);
312
313 let position = Position::new(&instrument_eth_usdt, market_order_fill);
315 cache
316 .as_ref()
317 .borrow_mut()
318 .add_position(position.clone(), OmsType::Netting)
319 .unwrap();
320
321 let position_id = ids_generator.get_position_id(&market_order_buy, None);
323 assert_eq!(position_id, Some(position.id));
324 }
325
326 #[rstest]
327 fn test_generate_venue_position_id() {
328 let cache = Rc::new(RefCell::new(Cache::default()));
329 let mut ids_generator_with_position_ids =
330 get_ids_generator(cache.clone(), true, OmsType::Netting);
331 let mut ids_generator_no_position_ids = get_ids_generator(cache, false, OmsType::Netting);
332
333 assert_eq!(
334 ids_generator_no_position_ids.generate_venue_position_id(),
335 None
336 );
337
338 let position_id_1 = ids_generator_with_position_ids.generate_venue_position_id();
339 let position_id_2 = ids_generator_with_position_ids.generate_venue_position_id();
340 assert_eq!(position_id_1, Some(PositionId::new("BINANCE-1-1")));
341 assert_eq!(position_id_2, Some(PositionId::new("BINANCE-1-2")));
342 }
343
344 #[rstest]
345 fn get_venue_position_id(market_order_buy: OrderAny, market_order_sell: OrderAny) {
346 let cache = Rc::new(RefCell::new(Cache::default()));
347 let mut ids_generator = get_ids_generator(cache, true, OmsType::Netting);
348
349 let venue_order_id1 = ids_generator.get_venue_order_id(&market_order_buy).unwrap();
350 let venue_order_id2 = ids_generator
351 .get_venue_order_id(&market_order_sell)
352 .unwrap();
353 assert_eq!(venue_order_id1, VenueOrderId::from("BINANCE-1-1"));
354 assert_eq!(venue_order_id2, VenueOrderId::from("BINANCE-1-2"));
355
356 let venue_order_id3 = ids_generator.get_venue_order_id(&market_order_buy).unwrap();
358 assert_eq!(venue_order_id3, VenueOrderId::from("BINANCE-1-1"));
359 }
360}