Skip to main content

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