nautilus_model/events/order/
initialized.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::fmt::{Debug, Display};
17
18use derive_builder::Builder;
19use indexmap::IndexMap;
20use nautilus_core::{UnixNanos, UUID4};
21use rust_decimal::Decimal;
22use serde::{Deserialize, Serialize};
23use ustr::Ustr;
24
25use crate::{
26    enums::{
27        ContingencyType, LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType,
28        TriggerType,
29    },
30    events::OrderEvent,
31    identifiers::{
32        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
33        StrategyId, TradeId, TraderId, VenueOrderId,
34    },
35    orders::OrderAny,
36    types::{Currency, Money, Price, Quantity},
37};
38
39/// Represents an event where an order has been initialized.
40///
41/// This is a seed event which can instantiate any order through a creation
42/// method. This event should contain enough information to be able to send it
43/// 'over the wire' and have a valid order created with exactly the same
44/// properties as if it had been instantiated locally.
45#[repr(C)]
46#[derive(Clone, PartialEq, Eq, Builder, Serialize, Deserialize)]
47#[builder(default)]
48#[serde(tag = "type")]
49#[cfg_attr(
50    feature = "python",
51    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
52)]
53pub struct OrderInitialized {
54    /// The trader ID associated with the event.
55    pub trader_id: TraderId,
56    /// The strategy ID associated with the event.
57    pub strategy_id: StrategyId,
58    /// The instrument ID associated with the event.
59    pub instrument_id: InstrumentId,
60    /// The client order ID associated with the event.
61    pub client_order_id: ClientOrderId,
62    /// The order side.
63    pub order_side: OrderSide,
64    /// The order type.
65    pub order_type: OrderType,
66    /// The order quantity.
67    pub quantity: Quantity,
68    /// The order time in force.
69    pub time_in_force: TimeInForce,
70    /// If the order will only provide liquidity (make a market).
71    pub post_only: bool,
72    /// If the order carries the 'reduce-only' execution instruction.
73    pub reduce_only: bool,
74    /// If the order quantity is denominated in the quote currency.
75    pub quote_quantity: bool,
76    /// If the event was generated during reconciliation.
77    pub reconciliation: bool,
78    /// The unique identifier for the event.
79    pub event_id: UUID4,
80    /// UNIX timestamp (nanoseconds) when the event occurred.
81    pub ts_event: UnixNanos,
82    /// UNIX timestamp (nanoseconds) when the event was initialized.
83    pub ts_init: UnixNanos,
84    /// The order price (LIMIT).
85    pub price: Option<Price>,
86    /// The order trigger price (STOP).
87    pub trigger_price: Option<Price>,
88    /// The trigger type for the order.
89    pub trigger_type: Option<TriggerType>,
90    /// The trailing offset for the orders limit price.
91    pub limit_offset: Option<Decimal>,
92    /// The trailing offset for the orders trigger price (STOP).
93    pub trailing_offset: Option<Decimal>,
94    /// The trailing offset type.
95    pub trailing_offset_type: Option<TrailingOffsetType>,
96    /// The order expiration, `None` for no expiration.
97    pub expire_time: Option<UnixNanos>,
98    /// The quantity of the `LIMIT` order to display on the public book (iceberg).
99    pub display_qty: Option<Quantity>,
100    /// The emulation trigger type for the order.
101    pub emulation_trigger: Option<TriggerType>,
102    /// The emulation trigger instrument ID for the order (if `None` then will be the `instrument_id`).
103    pub trigger_instrument_id: Option<InstrumentId>,
104    /// The order contingency type.
105    pub contingency_type: Option<ContingencyType>,
106    /// The order list ID associated with the order.
107    pub order_list_id: Option<OrderListId>,
108    ///  The order linked client order ID(s).
109    pub linked_order_ids: Option<Vec<ClientOrderId>>,
110    /// The orders parent client order ID.
111    pub parent_order_id: Option<ClientOrderId>,
112    /// The execution algorithm ID for the order.
113    pub exec_algorithm_id: Option<ExecAlgorithmId>,
114    /// The execution algorithm parameters for the order.
115    pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
116    /// The execution algorithm spawning primary client order ID.
117    pub exec_spawn_id: Option<ClientOrderId>,
118    /// The custom user tags for the order.
119    pub tags: Option<Vec<Ustr>>,
120}
121
122impl Default for OrderInitialized {
123    /// Creates a new default [`OrderInitialized`] instance for testing.
124    fn default() -> Self {
125        Self {
126            trader_id: TraderId::default(),
127            strategy_id: StrategyId::default(),
128            instrument_id: InstrumentId::default(),
129            client_order_id: ClientOrderId::default(),
130            order_side: OrderSide::Buy,
131            order_type: OrderType::Market,
132            quantity: Quantity::new(100_000.0, 0),
133            price: Default::default(),
134            trigger_price: Default::default(),
135            trigger_type: Default::default(),
136            time_in_force: TimeInForce::Day,
137            expire_time: Default::default(),
138            post_only: Default::default(),
139            reduce_only: Default::default(),
140            display_qty: Default::default(),
141            quote_quantity: Default::default(),
142            limit_offset: Default::default(),
143            trailing_offset: Default::default(),
144            trailing_offset_type: Default::default(),
145            emulation_trigger: Default::default(),
146            trigger_instrument_id: Default::default(),
147            contingency_type: Default::default(),
148            order_list_id: Default::default(),
149            linked_order_ids: Default::default(),
150            parent_order_id: Default::default(),
151            exec_algorithm_id: Default::default(),
152            exec_algorithm_params: Default::default(),
153            exec_spawn_id: Default::default(),
154            tags: Default::default(),
155            event_id: Default::default(),
156            ts_event: Default::default(),
157            ts_init: Default::default(),
158            reconciliation: Default::default(),
159        }
160    }
161}
162
163impl OrderInitialized {
164    /// Creates a new [`OrderInitialized`] instance.
165    #[allow(clippy::too_many_arguments)]
166    pub fn new(
167        trader_id: TraderId,
168        strategy_id: StrategyId,
169        instrument_id: InstrumentId,
170        client_order_id: ClientOrderId,
171        order_side: OrderSide,
172        order_type: OrderType,
173        quantity: Quantity,
174        time_in_force: TimeInForce,
175        post_only: bool,
176        reduce_only: bool,
177        quote_quantity: bool,
178        reconciliation: bool,
179        event_id: UUID4,
180        ts_event: UnixNanos,
181        ts_init: UnixNanos,
182        price: Option<Price>,
183        trigger_price: Option<Price>,
184        trigger_type: Option<TriggerType>,
185        limit_offset: Option<Decimal>,
186        trailing_offset: Option<Decimal>,
187        trailing_offset_type: Option<TrailingOffsetType>,
188        expire_time: Option<UnixNanos>,
189        display_qty: Option<Quantity>,
190        emulation_trigger: Option<TriggerType>,
191        trigger_instrument_id: Option<InstrumentId>,
192        contingency_type: Option<ContingencyType>,
193        order_list_id: Option<OrderListId>,
194        linked_order_ids: Option<Vec<ClientOrderId>>,
195        parent_order_id: Option<ClientOrderId>,
196        exec_algorithm_id: Option<ExecAlgorithmId>,
197        exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
198        exec_spawn_id: Option<ClientOrderId>,
199        tags: Option<Vec<Ustr>>,
200    ) -> Self {
201        Self {
202            trader_id,
203            strategy_id,
204            instrument_id,
205            client_order_id,
206            order_side,
207            order_type,
208            quantity,
209            time_in_force,
210            post_only,
211            reduce_only,
212            quote_quantity,
213            reconciliation,
214            event_id,
215            ts_event,
216            ts_init,
217            price,
218            trigger_price,
219            trigger_type,
220            limit_offset,
221            trailing_offset,
222            trailing_offset_type,
223            expire_time,
224            display_qty,
225            emulation_trigger,
226            trigger_instrument_id,
227            contingency_type,
228            order_list_id,
229            linked_order_ids,
230            parent_order_id,
231            exec_algorithm_id,
232            exec_algorithm_params,
233            exec_spawn_id,
234            tags,
235        }
236    }
237}
238
239impl Debug for OrderInitialized {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        write!(
242            f,
243            "{}(\
244            trader_id={}, \
245            strategy_id={}, \
246            instrument_id={}, \
247            client_order_id={}, \
248            side={}, \
249            type={}, \
250            quantity={}, \
251            time_in_force={}, \
252            post_only={}, \
253            reduce_only={}, \
254            quote_quantity={}, \
255            price={}, \
256            emulation_trigger={}, \
257            trigger_instrument_id={}, \
258            contingency_type={}, \
259            order_list_id={}, \
260            linked_order_ids=[{}], \
261            parent_order_id={}, \
262            exec_algorithm_id={}, \
263            exec_algorithm_params={}, \
264            exec_spawn_id={}, \
265            tags={}, \
266            event_id={}, \
267            ts_init={})",
268            stringify!(OrderInitialized),
269            self.trader_id,
270            self.strategy_id,
271            self.instrument_id,
272            self.client_order_id,
273            self.order_side,
274            self.order_type,
275            self.quantity,
276            self.time_in_force,
277            self.post_only,
278            self.reduce_only,
279            self.quote_quantity,
280            self.price
281                .map_or("None".to_string(), |price| format!("{price}")),
282            self.emulation_trigger
283                .map_or("None".to_string(), |trigger| format!("{trigger}")),
284            self.trigger_instrument_id
285                .map_or("None".to_string(), |instrument_id| format!(
286                    "{instrument_id}"
287                )),
288            self.contingency_type
289                .map_or("None".to_string(), |contingency_type| format!(
290                    "{contingency_type}"
291                )),
292            self.order_list_id
293                .map_or("None".to_string(), |order_list_id| format!(
294                    "{order_list_id}"
295                )),
296            self.linked_order_ids
297                .as_ref()
298                .map_or("None".to_string(), |linked_order_ids| linked_order_ids
299                    .iter()
300                    .map(ToString::to_string)
301                    .collect::<Vec<_>>()
302                    .join(", ")),
303            self.parent_order_id
304                .map_or("None".to_string(), |parent_order_id| format!(
305                    "{parent_order_id}"
306                )),
307            self.exec_algorithm_id
308                .map_or("None".to_string(), |exec_algorithm_id| format!(
309                    "{exec_algorithm_id}"
310                )),
311            self.exec_algorithm_params
312                .as_ref()
313                .map_or("None".to_string(), |exec_algorithm_params| format!(
314                    "{exec_algorithm_params:?}"
315                )),
316            self.exec_spawn_id
317                .map_or("None".to_string(), |exec_spawn_id| format!(
318                    "{exec_spawn_id}"
319                )),
320            self.tags.as_ref().map_or("None".to_string(), |tags| tags
321                .iter()
322                .map(|x| x.to_string())
323                .collect::<Vec<String>>()
324                .join(", ")),
325            self.event_id,
326            self.ts_init
327        )
328    }
329}
330
331impl Display for OrderInitialized {
332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        write!(
334            f,
335            "{}(\
336            instrument_id={}, \
337            client_order_id={}, \
338            side={}, \
339            type={}, \
340            quantity={}, \
341            time_in_force={}, \
342            post_only={}, \
343            reduce_only={}, \
344            quote_quantity={}, \
345            price={}, \
346            emulation_trigger={}, \
347            trigger_instrument_id={}, \
348            contingency_type={}, \
349            order_list_id={}, \
350            linked_order_ids=[{}], \
351            parent_order_id={}, \
352            exec_algorithm_id={}, \
353            exec_algorithm_params={}, \
354            exec_spawn_id={}, \
355            tags={})",
356            stringify!(OrderInitialized),
357            self.instrument_id,
358            self.client_order_id,
359            self.order_side,
360            self.order_type,
361            self.quantity,
362            self.time_in_force,
363            self.post_only,
364            self.reduce_only,
365            self.quote_quantity,
366            self.price
367                .map_or("None".to_string(), |price| format!("{price}")),
368            self.emulation_trigger
369                .map_or("None".to_string(), |trigger| format!("{trigger}")),
370            self.trigger_instrument_id
371                .map_or("None".to_string(), |instrument_id| format!(
372                    "{instrument_id}"
373                )),
374            self.contingency_type
375                .map_or("None".to_string(), |contingency_type| format!(
376                    "{contingency_type}"
377                )),
378            self.order_list_id
379                .map_or("None".to_string(), |order_list_id| format!(
380                    "{order_list_id}"
381                )),
382            self.linked_order_ids
383                .as_ref()
384                .map_or("None".to_string(), |linked_order_ids| linked_order_ids
385                    .iter()
386                    .map(ToString::to_string)
387                    .collect::<Vec<_>>()
388                    .join(", ")),
389            self.parent_order_id
390                .map_or("None".to_string(), |parent_order_id| format!(
391                    "{parent_order_id}"
392                )),
393            self.exec_algorithm_id
394                .map_or("None".to_string(), |exec_algorithm_id| format!(
395                    "{exec_algorithm_id}"
396                )),
397            self.exec_algorithm_params
398                .as_ref()
399                .map_or("None".to_string(), |exec_algorithm_params| format!(
400                    "{exec_algorithm_params:?}"
401                )),
402            self.exec_spawn_id
403                .map_or("None".to_string(), |exec_spawn_id| format!(
404                    "{exec_spawn_id}"
405                )),
406            self.tags.as_ref().map_or("None".to_string(), |tags| tags
407                .iter()
408                .map(|s| s.to_string())
409                .collect::<Vec<String>>()
410                .join(", ")),
411        )
412    }
413}
414
415impl OrderEvent for OrderInitialized {
416    fn id(&self) -> UUID4 {
417        self.event_id
418    }
419
420    fn kind(&self) -> &str {
421        stringify!(OrderInitialized)
422    }
423
424    fn order_type(&self) -> Option<OrderType> {
425        Some(self.order_type)
426    }
427
428    fn order_side(&self) -> Option<OrderSide> {
429        Some(self.order_side)
430    }
431
432    fn trader_id(&self) -> TraderId {
433        self.trader_id
434    }
435
436    fn strategy_id(&self) -> StrategyId {
437        self.strategy_id
438    }
439
440    fn instrument_id(&self) -> InstrumentId {
441        self.instrument_id
442    }
443
444    fn trade_id(&self) -> Option<TradeId> {
445        None
446    }
447
448    fn currency(&self) -> Option<Currency> {
449        None
450    }
451
452    fn client_order_id(&self) -> ClientOrderId {
453        self.client_order_id
454    }
455
456    fn reason(&self) -> Option<Ustr> {
457        None
458    }
459
460    fn quantity(&self) -> Option<Quantity> {
461        Some(self.quantity)
462    }
463
464    fn time_in_force(&self) -> Option<TimeInForce> {
465        Some(self.time_in_force)
466    }
467
468    fn liquidity_side(&self) -> Option<LiquiditySide> {
469        None
470    }
471
472    fn post_only(&self) -> Option<bool> {
473        Some(self.post_only)
474    }
475
476    fn reduce_only(&self) -> Option<bool> {
477        Some(self.reduce_only)
478    }
479
480    fn quote_quantity(&self) -> Option<bool> {
481        Some(self.quote_quantity)
482    }
483
484    fn reconciliation(&self) -> bool {
485        false
486    }
487
488    fn price(&self) -> Option<Price> {
489        self.price
490    }
491
492    fn last_px(&self) -> Option<Price> {
493        None
494    }
495
496    fn last_qty(&self) -> Option<Quantity> {
497        None
498    }
499
500    fn trigger_price(&self) -> Option<Price> {
501        self.trigger_price
502    }
503
504    fn trigger_type(&self) -> Option<TriggerType> {
505        self.trigger_type
506    }
507
508    fn limit_offset(&self) -> Option<Decimal> {
509        self.limit_offset
510    }
511
512    fn trailing_offset(&self) -> Option<Decimal> {
513        self.trailing_offset
514    }
515
516    fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
517        self.trailing_offset_type
518    }
519
520    fn expire_time(&self) -> Option<UnixNanos> {
521        self.expire_time
522    }
523
524    fn display_qty(&self) -> Option<Quantity> {
525        self.display_qty
526    }
527
528    fn emulation_trigger(&self) -> Option<TriggerType> {
529        self.emulation_trigger
530    }
531
532    fn trigger_instrument_id(&self) -> Option<InstrumentId> {
533        self.trigger_instrument_id
534    }
535
536    fn contingency_type(&self) -> Option<ContingencyType> {
537        self.contingency_type
538    }
539
540    fn order_list_id(&self) -> Option<OrderListId> {
541        self.order_list_id
542    }
543
544    fn linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
545        self.linked_order_ids.clone()
546    }
547
548    fn parent_order_id(&self) -> Option<ClientOrderId> {
549        self.parent_order_id
550    }
551
552    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
553        self.exec_algorithm_id
554    }
555
556    fn exec_spawn_id(&self) -> Option<ClientOrderId> {
557        self.exec_spawn_id
558    }
559
560    fn venue_order_id(&self) -> Option<VenueOrderId> {
561        None
562    }
563
564    fn account_id(&self) -> Option<AccountId> {
565        None
566    }
567
568    fn position_id(&self) -> Option<PositionId> {
569        None
570    }
571
572    fn commission(&self) -> Option<Money> {
573        None
574    }
575
576    fn ts_event(&self) -> UnixNanos {
577        self.ts_event
578    }
579
580    fn ts_init(&self) -> UnixNanos {
581        self.ts_init
582    }
583}
584
585impl From<OrderInitialized> for OrderAny {
586    fn from(order: OrderInitialized) -> Self {
587        match order.order_type {
588            OrderType::Limit => OrderAny::Limit(order.into()),
589            OrderType::Market => OrderAny::Market(order.into()),
590            OrderType::StopMarket => OrderAny::StopMarket(order.into()),
591            OrderType::StopLimit => OrderAny::StopLimit(order.into()),
592            OrderType::LimitIfTouched => OrderAny::LimitIfTouched(order.into()),
593            OrderType::TrailingStopLimit => OrderAny::TrailingStopLimit(order.into()),
594            OrderType::TrailingStopMarket => OrderAny::TrailingStopMarket(order.into()),
595            OrderType::MarketToLimit => OrderAny::MarketToLimit(order.into()),
596            OrderType::MarketIfTouched => OrderAny::MarketIfTouched(order.into()),
597        }
598    }
599}
600
601////////////////////////////////////////////////////////////////////////////////
602// Tests
603////////////////////////////////////////////////////////////////////////////////
604#[cfg(test)]
605mod test {
606    use rstest::rstest;
607
608    use crate::events::order::{initialized::OrderInitialized, stubs::*};
609    #[rstest]
610    fn test_order_initialized(order_initialized_buy_limit: OrderInitialized) {
611        let display = format!("{order_initialized_buy_limit}");
612        assert_eq!(
613            display,
614            "OrderInitialized(instrument_id=BTCUSDT.COINBASE, client_order_id=O-19700101-000000-001-001-1, \
615            side=BUY, type=LIMIT, quantity=0.561, time_in_force=DAY, post_only=true, reduce_only=true, \
616            quote_quantity=false, price=22000, emulation_trigger=BID_ASK, trigger_instrument_id=BTCUSDT.COINBASE, \
617            contingency_type=OTO, order_list_id=1, linked_order_ids=[O-2020872378424], parent_order_id=None, \
618            exec_algorithm_id=None, exec_algorithm_params=None, exec_spawn_id=None, tags=None)");
619    }
620}