nautilus_backtest/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!("Position id should be generated. Hedging Oms type order matching engine doesnt exists in cache.")
104            }
105        } else {
106            // Netting OMS (position id will be derived from instrument and strategy)
107            let cache = self.cache.as_ref().borrow();
108            let positions_open =
109                cache.positions_open(None, Some(&order.instrument_id()), None, None);
110            if positions_open.is_empty() {
111                None
112            } else {
113                Some(positions_open[0].id)
114            }
115        }
116    }
117
118    pub fn generate_trade_id(&mut self) -> TradeId {
119        self.execution_count += 1;
120        let trade_id = if self.use_random_ids {
121            Uuid::new_v4().to_string()
122        } else {
123            format!("{}-{}-{}", self.venue, self.raw_id, self.execution_count)
124        };
125        TradeId::from(trade_id.as_str())
126    }
127
128    pub fn generate_venue_position_id(&mut self) -> Option<PositionId> {
129        if !self.use_position_ids {
130            return None;
131        }
132
133        self.position_count += 1;
134        if self.use_random_ids {
135            Some(PositionId::new(Uuid::new_v4().to_string()))
136        } else {
137            Some(PositionId::new(
138                format!("{}-{}-{}", self.venue, self.raw_id, self.position_count).as_str(),
139            ))
140        }
141    }
142
143    pub fn generate_venue_order_id(&mut self) -> VenueOrderId {
144        self.order_count += 1;
145        if self.use_random_ids {
146            VenueOrderId::new(Uuid::new_v4().to_string())
147        } else {
148            VenueOrderId::new(
149                format!("{}-{}-{}", self.venue, self.raw_id, self.order_count).as_str(),
150            )
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use std::{cell::RefCell, rc::Rc};
158
159    use nautilus_common::cache::Cache;
160    use nautilus_core::time::AtomicTime;
161    use nautilus_model::{
162        enums::OmsType,
163        events::OrderFilled,
164        identifiers::{stubs::account_id, AccountId, PositionId, Venue, VenueOrderId},
165        instruments::InstrumentAny,
166        orders::OrderAny,
167        position::Position,
168    };
169    use rstest::rstest;
170
171    use crate::matching_engine::{
172        ids_generator::IdsGenerator,
173        tests::{
174            instrument_eth_usdt, market_order_buy, market_order_fill, market_order_sell, time,
175        },
176    };
177
178    fn get_ids_generator(
179        cache: Rc<RefCell<Cache>>,
180        use_position_ids: bool,
181        oms_type: OmsType,
182    ) -> IdsGenerator {
183        IdsGenerator::new(
184            Venue::from("BINANCE"),
185            oms_type,
186            1,
187            false,
188            use_position_ids,
189            cache,
190        )
191    }
192
193    #[rstest]
194    fn test_get_position_id_hedging_with_existing_position(
195        account_id: AccountId,
196        time: AtomicTime,
197        instrument_eth_usdt: InstrumentAny,
198        market_order_buy: OrderAny,
199        market_order_fill: OrderFilled,
200    ) {
201        let cache = Rc::new(RefCell::new(Cache::default()));
202        let mut ids_generator = get_ids_generator(cache.clone(), false, OmsType::Hedging);
203
204        let position = Position::new(&instrument_eth_usdt, market_order_fill);
205
206        // Add position to cache
207        cache
208            .borrow_mut()
209            .add_position(position.clone(), OmsType::Hedging)
210            .unwrap();
211
212        let position_id = ids_generator.get_position_id(&market_order_buy, None);
213        assert_eq!(position_id, Some(position.id));
214    }
215
216    #[rstest]
217    fn test_get_position_id_hedging_with_generated_position(
218        instrument_eth_usdt: InstrumentAny,
219        account_id: AccountId,
220        market_order_buy: OrderAny,
221    ) {
222        let cache = Rc::new(RefCell::new(Cache::default()));
223        let mut ids_generator = get_ids_generator(cache, true, OmsType::Hedging);
224
225        let position_id = ids_generator.get_position_id(&market_order_buy, None);
226        assert_eq!(position_id, Some(PositionId::new("BINANCE-1-1")));
227    }
228
229    #[rstest]
230    fn test_get_position_id_netting(
231        instrument_eth_usdt: InstrumentAny,
232        account_id: AccountId,
233        market_order_buy: OrderAny,
234        market_order_fill: OrderFilled,
235    ) {
236        let cache = Rc::new(RefCell::new(Cache::default()));
237        let mut ids_generator = get_ids_generator(cache.clone(), false, OmsType::Netting);
238
239        // position id should be none in non-initialized position id for this instrument
240        let position_id = ids_generator.get_position_id(&market_order_buy, None);
241        assert_eq!(position_id, None);
242
243        // create and add position in cache
244        let position = Position::new(&instrument_eth_usdt, market_order_fill);
245        cache
246            .as_ref()
247            .borrow_mut()
248            .add_position(position.clone(), OmsType::Netting)
249            .unwrap();
250
251        // position id should be returned for the existing position
252        let position_id = ids_generator.get_position_id(&market_order_buy, None);
253        assert_eq!(position_id, Some(position.id));
254    }
255
256    #[rstest]
257    fn test_generate_venue_position_id(
258        account_id: AccountId,
259        time: AtomicTime,
260        instrument_eth_usdt: InstrumentAny,
261    ) {
262        let cache = Rc::new(RefCell::new(Cache::default()));
263        let mut ids_generator_with_position_ids =
264            get_ids_generator(cache.clone(), true, OmsType::Netting);
265        let mut ids_generator_no_position_ids = get_ids_generator(cache, false, OmsType::Netting);
266
267        assert_eq!(
268            ids_generator_no_position_ids.generate_venue_position_id(),
269            None
270        );
271
272        let position_id_1 = ids_generator_with_position_ids.generate_venue_position_id();
273        let position_id_2 = ids_generator_with_position_ids.generate_venue_position_id();
274        assert_eq!(position_id_1, Some(PositionId::new("BINANCE-1-1")));
275        assert_eq!(position_id_2, Some(PositionId::new("BINANCE-1-2")));
276    }
277
278    #[rstest]
279    fn get_venue_position_id(
280        instrument_eth_usdt: InstrumentAny,
281        account_id: AccountId,
282        market_order_buy: OrderAny,
283        market_order_sell: OrderAny,
284        market_order_fill: OrderFilled,
285    ) {
286        let cache = Rc::new(RefCell::new(Cache::default()));
287        let mut ids_generator = get_ids_generator(cache, true, OmsType::Netting);
288
289        let venue_order_id1 = ids_generator.get_venue_order_id(&market_order_buy).unwrap();
290        let venue_order_id2 = ids_generator
291            .get_venue_order_id(&market_order_sell)
292            .unwrap();
293        assert_eq!(venue_order_id1, VenueOrderId::from("BINANCE-1-1"));
294        assert_eq!(venue_order_id2, VenueOrderId::from("BINANCE-1-2"));
295
296        // check if venue order id is cached again
297        let venue_order_id3 = ids_generator.get_venue_order_id(&market_order_buy).unwrap();
298        assert_eq!(venue_order_id3, VenueOrderId::from("BINANCE-1-1"));
299    }
300}