nautilus_model/orders/
stubs.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::{collections::HashMap, str::FromStr};
17
18use nautilus_core::{UUID4, UnixNanos};
19
20use super::any::OrderAny;
21use crate::{
22    enums::{LiquiditySide, OrderType},
23    events::{OrderAccepted, OrderEventAny, OrderFilled, OrderSubmitted},
24    identifiers::{
25        AccountId, ClientOrderId, InstrumentId, PositionId, TradeId, Venue, VenueOrderId,
26    },
27    instruments::InstrumentAny,
28    orders::OrderTestBuilder,
29    types::{Money, Price, Quantity},
30};
31
32// Test Event Stubs
33pub struct TestOrderEventStubs;
34
35impl TestOrderEventStubs {
36    pub fn order_submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
37        let event = OrderSubmitted::new(
38            order.trader_id(),
39            order.strategy_id(),
40            order.instrument_id(),
41            order.client_order_id(),
42            account_id,
43            UUID4::new(),
44            UnixNanos::default(),
45            UnixNanos::default(),
46        );
47        OrderEventAny::Submitted(event)
48    }
49
50    pub fn order_accepted(
51        order: &OrderAny,
52        account_id: AccountId,
53        venue_order_id: VenueOrderId,
54    ) -> OrderEventAny {
55        let event = OrderAccepted::new(
56            order.trader_id(),
57            order.strategy_id(),
58            order.instrument_id(),
59            order.client_order_id(),
60            venue_order_id,
61            account_id,
62            UUID4::new(),
63            UnixNanos::default(),
64            UnixNanos::default(),
65            false,
66        );
67        OrderEventAny::Accepted(event)
68    }
69
70    #[allow(clippy::too_many_arguments)]
71    pub fn order_filled(
72        order: &OrderAny,
73        instrument: &InstrumentAny,
74        trade_id: Option<TradeId>,
75        position_id: Option<PositionId>,
76        last_px: Option<Price>,
77        last_qty: Option<Quantity>,
78        liquidity_side: Option<LiquiditySide>,
79        commission: Option<Money>,
80        ts_filled_ns: Option<UnixNanos>,
81        account_id: Option<AccountId>,
82    ) -> OrderEventAny {
83        let venue_order_id = order.venue_order_id().unwrap_or_default();
84        let account_id = account_id
85            .or(order.account_id())
86            .unwrap_or(AccountId::from("SIM-001"));
87        let trade_id = trade_id.unwrap_or(TradeId::new(
88            order.client_order_id().as_str().replace('O', "E").as_str(),
89        ));
90        let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
91        let event = UUID4::new();
92        let position_id = position_id
93            .or_else(|| order.position_id())
94            .unwrap_or(PositionId::new("1"));
95        let commission = commission.unwrap_or(Money::from("2 USD"));
96        let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
97        let last_qty = last_qty.unwrap_or(order.quantity());
98        let event = OrderFilled::new(
99            order.trader_id(),
100            order.strategy_id(),
101            instrument.id(),
102            order.client_order_id(),
103            venue_order_id,
104            account_id,
105            trade_id,
106            order.order_side(),
107            order.order_type(),
108            last_qty,
109            last_px,
110            instrument.quote_currency(),
111            liquidity_side,
112            event,
113            ts_filled_ns.unwrap_or_default(),
114            UnixNanos::default(),
115            false,
116            Some(position_id),
117            Some(commission),
118        );
119        OrderEventAny::Filled(event)
120    }
121}
122
123pub struct TestOrderStubs;
124
125impl TestOrderStubs {
126    pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
127        let mut new_order = order.clone();
128        let accepted_event = TestOrderEventStubs::order_accepted(
129            &new_order,
130            AccountId::from("SIM-001"),
131            VenueOrderId::from("V-001"),
132        );
133        new_order.apply(accepted_event).unwrap();
134        new_order
135    }
136
137    pub fn make_filled_order(
138        order: &OrderAny,
139        instrument: &InstrumentAny,
140        liquidity_side: LiquiditySide,
141    ) -> OrderAny {
142        let mut accepted_order = TestOrderStubs::make_accepted_order(order);
143        let fill = TestOrderEventStubs::order_filled(
144            &accepted_order,
145            instrument,
146            None,
147            None,
148            None,
149            None,
150            Some(liquidity_side),
151            None,
152            None,
153            None,
154        );
155        accepted_order.apply(fill).unwrap();
156        accepted_order
157    }
158}
159
160pub struct TestOrdersGenerator {
161    order_type: OrderType,
162    venue_instruments: HashMap<Venue, u32>,
163    orders_per_instrument: u32,
164}
165
166impl TestOrdersGenerator {
167    pub fn new(order_type: OrderType) -> Self {
168        Self {
169            order_type,
170            venue_instruments: HashMap::new(),
171            orders_per_instrument: 5,
172        }
173    }
174
175    pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
176        self.orders_per_instrument = total_orders;
177    }
178
179    pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
180        self.venue_instruments.insert(venue, total_instruments);
181    }
182
183    fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
184        let client_order_id =
185            ClientOrderId::from(format!("O-{}-{}", instrument_id, client_order_id_index));
186        OrderTestBuilder::new(self.order_type)
187            .quantity(Quantity::from("1"))
188            .price(Price::from("1"))
189            .instrument_id(instrument_id)
190            .client_order_id(client_order_id)
191            .build()
192    }
193
194    pub fn build(&self) -> Vec<OrderAny> {
195        let mut orders = Vec::new();
196        for (venue, total_instruments) in self.venue_instruments.iter() {
197            for i in 0..*total_instruments {
198                let instrument_id = InstrumentId::from(format!("SYMBOL-{}.{}", i, venue));
199                for order_index in 0..self.orders_per_instrument {
200                    let order = self.generate_order(instrument_id, order_index);
201                    orders.push(order);
202                }
203            }
204        }
205        orders
206    }
207}
208
209pub fn create_order_list_sample(
210    total_venues: u8,
211    total_instruments: u32,
212    orders_per_instrument: u32,
213) -> Vec<OrderAny> {
214    // Create Limit orders list from order generator with spec:
215    // x venues * x instruments * x orders per instrument
216    let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
217    for i in 0..total_venues {
218        let venue = Venue::from(format!("VENUE-{}", i));
219        order_generator.add_venue_and_total_instruments(venue, total_instruments);
220    }
221    order_generator.set_orders_per_instrument(orders_per_instrument);
222
223    order_generator.build()
224}