nautilus_execution/matching_engine/
ids_generator.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{cell::RefCell, rc::Rc};
17
18use nautilus_common::cache::Cache;
19use nautilus_model::{
20    enums::OmsType,
21    identifiers::{PositionId, TradeId, Venue, VenueOrderId},
22    orders::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 IdsGenerator {
39    pub const fn new(
40        venue: Venue,
41        oms_type: OmsType,
42        raw_id: u32,
43        use_random_ids: bool,
44        use_position_ids: bool,
45        cache: Rc<RefCell<Cache>>,
46    ) -> Self {
47        Self {
48            venue,
49            raw_id,
50            oms_type,
51            cache,
52            use_random_ids,
53            use_position_ids,
54            position_count: 0,
55            order_count: 0,
56            execution_count: 0,
57        }
58    }
59
60    pub const fn reset(&mut self) {
61        self.position_count = 0;
62        self.order_count = 0;
63        self.execution_count = 0;
64    }
65
66    pub fn get_venue_order_id(&mut self, order: &OrderAny) -> anyhow::Result<VenueOrderId> {
67        // check existing on order
68        if let Some(venue_order_id) = order.venue_order_id() {
69            return Ok(venue_order_id);
70        }
71
72        // check existing in cache
73        if let Some(venue_order_id) = self.cache.borrow().venue_order_id(&order.client_order_id()) {
74            return Ok(venue_order_id.to_owned());
75        }
76
77        let venue_order_id = self.generate_venue_order_id();
78        self.cache.borrow_mut().add_venue_order_id(
79            &order.client_order_id(),
80            &venue_order_id,
81            false,
82        )?;
83        Ok(venue_order_id)
84    }
85
86    pub fn get_position_id(
87        &mut self,
88        order: &OrderAny,
89        generate: Option<bool>,
90    ) -> Option<PositionId> {
91        let generate = generate.unwrap_or(true);
92        if self.oms_type == OmsType::Hedging {
93            {
94                let cache = self.cache.as_ref().borrow();
95                let position_id_result = cache.position_id(&order.client_order_id());
96                if let Some(position_id) = position_id_result {
97                    return Some(position_id.to_owned());
98                }
99            }
100            if generate {
101                self.generate_venue_position_id()
102            } else {
103                panic!(
104                    "Position id should be generated. Hedging Oms type order matching engine doesnt exists in cache."
105                )
106            }
107        } else {
108            // Netting OMS (position id will be derived from instrument and strategy)
109            let cache = self.cache.as_ref().borrow();
110            let positions_open =
111                cache.positions_open(None, Some(&order.instrument_id()), None, None);
112            if positions_open.is_empty() {
113                None
114            } else {
115                Some(positions_open[0].id)
116            }
117        }
118    }
119
120    pub fn generate_trade_id(&mut self) -> TradeId {
121        self.execution_count += 1;
122        let trade_id = if self.use_random_ids {
123            Uuid::new_v4().to_string()
124        } else {
125            format!("{}-{}-{}", self.venue, self.raw_id, self.execution_count)
126        };
127        TradeId::from(trade_id.as_str())
128    }
129
130    pub fn generate_venue_position_id(&mut self) -> Option<PositionId> {
131        if !self.use_position_ids {
132            return None;
133        }
134
135        self.position_count += 1;
136        if self.use_random_ids {
137            Some(PositionId::new(Uuid::new_v4().to_string()))
138        } else {
139            Some(PositionId::new(
140                format!("{}-{}-{}", self.venue, self.raw_id, self.position_count).as_str(),
141            ))
142        }
143    }
144
145    pub fn generate_venue_order_id(&mut self) -> VenueOrderId {
146        self.order_count += 1;
147        if self.use_random_ids {
148            VenueOrderId::new(Uuid::new_v4().to_string())
149        } else {
150            VenueOrderId::new(
151                format!("{}-{}-{}", self.venue, self.raw_id, self.order_count).as_str(),
152            )
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use std::{cell::RefCell, rc::Rc};
160
161    use nautilus_common::cache::Cache;
162    use nautilus_model::{
163        enums::OmsType,
164        events::OrderFilled,
165        identifiers::{PositionId, Venue, VenueOrderId},
166        instruments::InstrumentAny,
167        orders::OrderAny,
168        position::Position,
169    };
170    use rstest::rstest;
171
172    use crate::matching_engine::{
173        ids_generator::IdsGenerator,
174        tests::{instrument_eth_usdt, market_order_buy, market_order_fill, market_order_sell},
175    };
176
177    fn get_ids_generator(
178        cache: Rc<RefCell<Cache>>,
179        use_position_ids: bool,
180        oms_type: OmsType,
181    ) -> IdsGenerator {
182        IdsGenerator::new(
183            Venue::from("BINANCE"),
184            oms_type,
185            1,
186            false,
187            use_position_ids,
188            cache,
189        )
190    }
191
192    #[rstest]
193    fn test_get_position_id_hedging_with_existing_position(
194        instrument_eth_usdt: InstrumentAny,
195        market_order_buy: OrderAny,
196        market_order_fill: OrderFilled,
197    ) {
198        let cache = Rc::new(RefCell::new(Cache::default()));
199        let mut ids_generator = get_ids_generator(cache.clone(), false, OmsType::Hedging);
200
201        let position = Position::new(&instrument_eth_usdt, market_order_fill);
202
203        // Add position to cache
204        cache
205            .borrow_mut()
206            .add_position(position.clone(), OmsType::Hedging)
207            .unwrap();
208
209        let position_id = ids_generator.get_position_id(&market_order_buy, None);
210        assert_eq!(position_id, Some(position.id));
211    }
212
213    #[rstest]
214    fn test_get_position_id_hedging_with_generated_position(market_order_buy: OrderAny) {
215        let cache = Rc::new(RefCell::new(Cache::default()));
216        let mut ids_generator = get_ids_generator(cache, true, OmsType::Hedging);
217
218        let position_id = ids_generator.get_position_id(&market_order_buy, None);
219        assert_eq!(position_id, Some(PositionId::new("BINANCE-1-1")));
220    }
221
222    #[rstest]
223    fn test_get_position_id_netting(
224        instrument_eth_usdt: InstrumentAny,
225        market_order_buy: OrderAny,
226        market_order_fill: OrderFilled,
227    ) {
228        let cache = Rc::new(RefCell::new(Cache::default()));
229        let mut ids_generator = get_ids_generator(cache.clone(), false, OmsType::Netting);
230
231        // position id should be none in non-initialized position id for this instrument
232        let position_id = ids_generator.get_position_id(&market_order_buy, None);
233        assert_eq!(position_id, None);
234
235        // create and add position in cache
236        let position = Position::new(&instrument_eth_usdt, market_order_fill);
237        cache
238            .as_ref()
239            .borrow_mut()
240            .add_position(position.clone(), OmsType::Netting)
241            .unwrap();
242
243        // position id should be returned for the existing position
244        let position_id = ids_generator.get_position_id(&market_order_buy, None);
245        assert_eq!(position_id, Some(position.id));
246    }
247
248    #[rstest]
249    fn test_generate_venue_position_id() {
250        let cache = Rc::new(RefCell::new(Cache::default()));
251        let mut ids_generator_with_position_ids =
252            get_ids_generator(cache.clone(), true, OmsType::Netting);
253        let mut ids_generator_no_position_ids = get_ids_generator(cache, false, OmsType::Netting);
254
255        assert_eq!(
256            ids_generator_no_position_ids.generate_venue_position_id(),
257            None
258        );
259
260        let position_id_1 = ids_generator_with_position_ids.generate_venue_position_id();
261        let position_id_2 = ids_generator_with_position_ids.generate_venue_position_id();
262        assert_eq!(position_id_1, Some(PositionId::new("BINANCE-1-1")));
263        assert_eq!(position_id_2, Some(PositionId::new("BINANCE-1-2")));
264    }
265
266    #[rstest]
267    fn get_venue_position_id(market_order_buy: OrderAny, market_order_sell: OrderAny) {
268        let cache = Rc::new(RefCell::new(Cache::default()));
269        let mut ids_generator = get_ids_generator(cache, true, OmsType::Netting);
270
271        let venue_order_id1 = ids_generator.get_venue_order_id(&market_order_buy).unwrap();
272        let venue_order_id2 = ids_generator
273            .get_venue_order_id(&market_order_sell)
274            .unwrap();
275        assert_eq!(venue_order_id1, VenueOrderId::from("BINANCE-1-1"));
276        assert_eq!(venue_order_id2, VenueOrderId::from("BINANCE-1-2"));
277
278        // check if venue order id is cached again
279        let venue_order_id3 = ids_generator.get_venue_order_id(&market_order_buy).unwrap();
280        assert_eq!(venue_order_id3, VenueOrderId::from("BINANCE-1-1"));
281    }
282}