Skip to content

Commit ece925b

Browse files
committed
implement IdsGenerator for OrderMatchingEngine
1 parent 931d103 commit ece925b

File tree

3 files changed

+324
-239
lines changed

3 files changed

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

0 commit comments

Comments
 (0)