Skip to main content

nautilus_model/orders/
stubs.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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};
19use rust_decimal_macros::dec;
20
21use super::{
22    any::OrderAny, limit::LimitOrder, limit_if_touched::LimitIfTouchedOrder, market::MarketOrder,
23    market_if_touched::MarketIfTouchedOrder, market_to_limit::MarketToLimitOrder,
24    stop_limit::StopLimitOrder, stop_market::StopMarketOrder,
25    trailing_stop_limit::TrailingStopLimitOrder, trailing_stop_market::TrailingStopMarketOrder,
26};
27use crate::{
28    enums::{LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
29    events::{
30        OrderEventAny,
31        order::spec::{OrderAcceptedSpec, OrderCanceledSpec, OrderFilledSpec, OrderSubmittedSpec},
32    },
33    identifiers::{
34        AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId, Venue,
35        VenueOrderId,
36    },
37    instruments::{Instrument, InstrumentAny},
38    orders::{Order, OrderTestBuilder},
39    stubs::TestDefault,
40    types::{Money, Price, Quantity},
41};
42
43impl TestDefault for LimitOrder {
44    /// Creates a new test default [`LimitOrder`] instance.
45    fn test_default() -> Self {
46        Self::new(
47            TraderId::test_default(),
48            StrategyId::test_default(),
49            InstrumentId::test_default(),
50            ClientOrderId::test_default(),
51            OrderSide::Buy,
52            Quantity::from(100_000),
53            Price::from("1.00000"),
54            TimeInForce::Gtc,
55            None,
56            false,
57            false,
58            false,
59            None,
60            None,
61            None,
62            None,
63            None,
64            None,
65            None,
66            None,
67            None,
68            None,
69            None,
70            UUID4::default(),
71            UnixNanos::default(),
72        )
73    }
74}
75
76impl TestDefault for LimitIfTouchedOrder {
77    /// Creates a new test default [`LimitIfTouchedOrder`] instance.
78    fn test_default() -> Self {
79        Self::new(
80            TraderId::test_default(),
81            StrategyId::test_default(),
82            InstrumentId::test_default(),
83            ClientOrderId::test_default(),
84            OrderSide::Buy,
85            Quantity::from(100_000),
86            Price::from("1.00000"),
87            Price::from("1.00000"),
88            TriggerType::BidAsk,
89            TimeInForce::Gtc,
90            None,
91            false,
92            false,
93            false,
94            None,
95            None,
96            None,
97            None,
98            None,
99            None,
100            None,
101            None,
102            None,
103            None,
104            None,
105            UUID4::default(),
106            UnixNanos::default(),
107        )
108    }
109}
110
111impl TestDefault for MarketOrder {
112    /// Creates a new test default [`MarketOrder`] instance.
113    fn test_default() -> Self {
114        Self::new(
115            TraderId::test_default(),
116            StrategyId::test_default(),
117            InstrumentId::test_default(),
118            ClientOrderId::test_default(),
119            OrderSide::Buy,
120            Quantity::from(100_000),
121            TimeInForce::Day,
122            UUID4::default(),
123            UnixNanos::default(),
124            false,
125            false,
126            None,
127            None,
128            None,
129            None,
130            None,
131            None,
132            None,
133            None,
134        )
135    }
136}
137
138impl TestDefault for MarketIfTouchedOrder {
139    /// Creates a new test default [`MarketIfTouchedOrder`] instance.
140    fn test_default() -> Self {
141        Self::new(
142            TraderId::test_default(),
143            StrategyId::test_default(),
144            InstrumentId::test_default(),
145            ClientOrderId::test_default(),
146            OrderSide::Buy,
147            Quantity::from(100_000),
148            Price::from("1.00000"),
149            TriggerType::BidAsk,
150            TimeInForce::Gtc,
151            None,
152            false,
153            false,
154            None,
155            None,
156            None,
157            None,
158            None,
159            None,
160            None,
161            None,
162            None,
163            None,
164            UUID4::default(),
165            UnixNanos::default(),
166        )
167    }
168}
169
170impl TestDefault for MarketToLimitOrder {
171    /// Creates a new test default [`MarketToLimitOrder`] instance.
172    fn test_default() -> Self {
173        Self::new(
174            TraderId::test_default(),
175            StrategyId::test_default(),
176            InstrumentId::test_default(),
177            ClientOrderId::test_default(),
178            OrderSide::Buy,
179            Quantity::from(100_000),
180            TimeInForce::Gtc,
181            None,
182            false,
183            false,
184            false,
185            None,
186            None,
187            None,
188            None,
189            None,
190            None,
191            None,
192            None,
193            None,
194            UUID4::default(),
195            UnixNanos::default(),
196        )
197    }
198}
199
200impl TestDefault for StopLimitOrder {
201    /// Creates a new test default [`StopLimitOrder`] instance.
202    fn test_default() -> Self {
203        Self::new(
204            TraderId::test_default(),
205            StrategyId::test_default(),
206            InstrumentId::test_default(),
207            ClientOrderId::test_default(),
208            OrderSide::Buy,
209            Quantity::from(100_000),
210            Price::from("1.00000"),
211            Price::from("1.00000"),
212            TriggerType::BidAsk,
213            TimeInForce::Gtc,
214            None,
215            false,
216            false,
217            false,
218            None,
219            None,
220            None,
221            None,
222            None,
223            None,
224            None,
225            None,
226            None,
227            None,
228            None,
229            UUID4::default(),
230            UnixNanos::default(),
231        )
232    }
233}
234
235impl TestDefault for StopMarketOrder {
236    /// Creates a new test default [`StopMarketOrder`] instance.
237    fn test_default() -> Self {
238        Self::new(
239            TraderId::test_default(),
240            StrategyId::test_default(),
241            InstrumentId::test_default(),
242            ClientOrderId::test_default(),
243            OrderSide::Buy,
244            Quantity::from(100_000),
245            Price::from("1.00000"),
246            TriggerType::BidAsk,
247            TimeInForce::Gtc,
248            None,
249            false,
250            false,
251            None,
252            None,
253            None,
254            None,
255            None,
256            None,
257            None,
258            None,
259            None,
260            None,
261            None,
262            UUID4::default(),
263            UnixNanos::default(),
264        )
265    }
266}
267
268impl TestDefault for TrailingStopLimitOrder {
269    /// Creates a new test default [`TrailingStopLimitOrder`] instance.
270    fn test_default() -> Self {
271        Self::new(
272            TraderId::test_default(),
273            StrategyId::test_default(),
274            InstrumentId::test_default(),
275            ClientOrderId::test_default(),
276            OrderSide::Buy,
277            Quantity::from(100_000),
278            Price::from("1.00000"),
279            Price::from("1.00000"),
280            TriggerType::BidAsk,
281            dec!(0.001),
282            dec!(0.001),
283            TrailingOffsetType::Price,
284            TimeInForce::Gtc,
285            None,
286            false,
287            false,
288            false,
289            None,
290            None,
291            None,
292            None,
293            None,
294            None,
295            None,
296            None,
297            None,
298            None,
299            None,
300            UUID4::default(),
301            UnixNanos::default(),
302        )
303    }
304}
305
306impl TestDefault for TrailingStopMarketOrder {
307    /// Creates a new test default [`TrailingStopMarketOrder`] instance.
308    fn test_default() -> Self {
309        Self::new(
310            TraderId::test_default(),
311            StrategyId::test_default(),
312            InstrumentId::test_default(),
313            ClientOrderId::test_default(),
314            OrderSide::Buy,
315            Quantity::from(100_000),
316            Price::from("1.00000"),
317            TriggerType::BidAsk,
318            dec!(0.001),
319            TrailingOffsetType::Price,
320            TimeInForce::Gtc,
321            None,
322            false,
323            false,
324            None,
325            None,
326            None,
327            None,
328            None,
329            None,
330            None,
331            None,
332            None,
333            None,
334            None,
335            UUID4::default(),
336            UnixNanos::default(),
337        )
338    }
339}
340
341#[derive(Debug)]
342pub struct TestOrderEventStubs;
343
344impl TestOrderEventStubs {
345    #[must_use]
346    pub fn submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
347        let event = OrderSubmittedSpec::builder()
348            .trader_id(order.trader_id())
349            .strategy_id(order.strategy_id())
350            .instrument_id(order.instrument_id())
351            .client_order_id(order.client_order_id())
352            .account_id(account_id)
353            .build();
354        OrderEventAny::Submitted(event)
355    }
356
357    #[must_use]
358    pub fn accepted(
359        order: &OrderAny,
360        account_id: AccountId,
361        venue_order_id: VenueOrderId,
362    ) -> OrderEventAny {
363        let event = OrderAcceptedSpec::builder()
364            .trader_id(order.trader_id())
365            .strategy_id(order.strategy_id())
366            .instrument_id(order.instrument_id())
367            .client_order_id(order.client_order_id())
368            .venue_order_id(venue_order_id)
369            .account_id(account_id)
370            .build();
371        OrderEventAny::Accepted(event)
372    }
373
374    #[must_use]
375    pub fn canceled(
376        order: &OrderAny,
377        account_id: AccountId,
378        venue_order_id: Option<VenueOrderId>,
379    ) -> OrderEventAny {
380        let event = OrderCanceledSpec::builder()
381            .trader_id(order.trader_id())
382            .strategy_id(order.strategy_id())
383            .instrument_id(order.instrument_id())
384            .client_order_id(order.client_order_id())
385            .account_id(account_id)
386            .maybe_venue_order_id(venue_order_id)
387            .build();
388        OrderEventAny::Canceled(event)
389    }
390
391    /// # Panics
392    ///
393    /// Panics if parsing the fallback price string fails or unwrapping default values fails.
394    #[expect(clippy::too_many_arguments)]
395    pub fn filled(
396        order: &OrderAny,
397        instrument: &InstrumentAny,
398        trade_id: Option<TradeId>,
399        position_id: Option<PositionId>,
400        last_px: Option<Price>,
401        last_qty: Option<Quantity>,
402        liquidity_side: Option<LiquiditySide>,
403        commission: Option<Money>,
404        ts_filled_ns: Option<UnixNanos>,
405        account_id: Option<AccountId>,
406    ) -> OrderEventAny {
407        let venue_order_id = order
408            .venue_order_id()
409            .unwrap_or_else(VenueOrderId::test_default);
410        let account_id = account_id
411            .or(order.account_id())
412            .unwrap_or(AccountId::from("SIM-001"));
413        let trade_id = trade_id.unwrap_or(TradeId::new(
414            order.client_order_id().as_str().replace('O', "E").as_str(),
415        ));
416        let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
417        let position_id = position_id
418            .or_else(|| order.position_id())
419            .unwrap_or(PositionId::new("1"));
420        let commission = commission.unwrap_or(Money::from("2 USD"));
421        let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
422        let last_qty = last_qty.unwrap_or(order.quantity());
423        let event = OrderFilledSpec::builder()
424            .trader_id(order.trader_id())
425            .strategy_id(order.strategy_id())
426            .instrument_id(instrument.id())
427            .client_order_id(order.client_order_id())
428            .venue_order_id(venue_order_id)
429            .account_id(account_id)
430            .trade_id(trade_id)
431            .order_side(order.order_side())
432            .order_type(order.order_type())
433            .last_qty(last_qty)
434            .last_px(last_px)
435            .currency(instrument.quote_currency())
436            .liquidity_side(liquidity_side)
437            .ts_event(ts_filled_ns.unwrap_or_default())
438            .position_id(position_id)
439            .commission(commission)
440            .build();
441        OrderEventAny::Filled(event)
442    }
443}
444
445#[derive(Debug)]
446pub struct TestOrderStubs;
447
448impl TestOrderStubs {
449    /// # Panics
450    ///
451    /// Panics if applying the accepted event via `new_order.apply(...)` fails.
452    #[must_use]
453    pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
454        let mut new_order = order.clone();
455        let accepted_event = TestOrderEventStubs::accepted(
456            &new_order,
457            AccountId::from("SIM-001"),
458            VenueOrderId::from("V-001"),
459        );
460        new_order.apply(accepted_event).unwrap();
461        new_order
462    }
463
464    /// # Panics
465    ///
466    /// Panics if applying the filled event via `accepted_order.apply(...)` fails.
467    #[must_use]
468    pub fn make_filled_order(
469        order: &OrderAny,
470        instrument: &InstrumentAny,
471        liquidity_side: LiquiditySide,
472    ) -> OrderAny {
473        let mut accepted_order = Self::make_accepted_order(order);
474        let fill = TestOrderEventStubs::filled(
475            &accepted_order,
476            instrument,
477            None,
478            None,
479            None,
480            None,
481            Some(liquidity_side),
482            None,
483            None,
484            None,
485        );
486        accepted_order.apply(fill).unwrap();
487        accepted_order
488    }
489}
490
491#[derive(Debug)]
492pub struct TestOrdersGenerator {
493    order_type: OrderType,
494    venue_instruments: HashMap<Venue, u32>,
495    orders_per_instrument: u32,
496}
497
498impl TestOrdersGenerator {
499    #[must_use]
500    pub fn new(order_type: OrderType) -> Self {
501        Self {
502            order_type,
503            venue_instruments: HashMap::new(),
504            orders_per_instrument: 5,
505        }
506    }
507
508    pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
509        self.orders_per_instrument = total_orders;
510    }
511
512    pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
513        self.venue_instruments.insert(venue, total_instruments);
514    }
515
516    fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
517        let client_order_id =
518            ClientOrderId::from(format!("O-{instrument_id}-{client_order_id_index}"));
519        OrderTestBuilder::new(self.order_type)
520            .quantity(Quantity::from("1"))
521            .price(Price::from("1"))
522            .instrument_id(instrument_id)
523            .client_order_id(client_order_id)
524            .build()
525    }
526
527    #[must_use]
528    pub fn build(&self) -> Vec<OrderAny> {
529        let mut orders = Vec::new();
530
531        for (venue, total_instruments) in &self.venue_instruments {
532            for i in 0..*total_instruments {
533                let instrument_id = InstrumentId::from(format!("SYMBOL-{i}.{venue}"));
534                for order_index in 0..self.orders_per_instrument {
535                    let order = self.generate_order(instrument_id, order_index);
536                    orders.push(order);
537                }
538            }
539        }
540        orders
541    }
542}
543
544#[must_use]
545pub fn create_order_list_sample(
546    total_venues: u8,
547    total_instruments: u32,
548    orders_per_instrument: u32,
549) -> Vec<OrderAny> {
550    // Create Limit orders list from order generator with spec:
551    // x venues * x instruments * x orders per instrument
552    let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
553
554    for i in 0..total_venues {
555        let venue = Venue::from(format!("VENUE-{i}"));
556        order_generator.add_venue_and_total_instruments(venue, total_instruments);
557    }
558    order_generator.set_orders_per_instrument(orders_per_instrument);
559
560    order_generator.build()
561}