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,
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}
123
124impl OrderInitialized {
125    /// Creates a new [`OrderInitialized`] instance.
126    #[expect(clippy::too_many_arguments)]
127    #[expect(
128        clippy::fn_params_excessive_bools,
129        reason = "domain event constructor requires multiple boolean flags"
130    )]
131    #[must_use]
132    pub fn new(
133        trader_id: TraderId,
134        strategy_id: StrategyId,
135        instrument_id: InstrumentId,
136        client_order_id: ClientOrderId,
137        order_side: OrderSide,
138        order_type: OrderType,
139        quantity: Quantity,
140        time_in_force: TimeInForce,
141        post_only: bool,
142        reduce_only: bool,
143        quote_quantity: bool,
144        reconciliation: bool,
145        event_id: UUID4,
146        ts_event: UnixNanos,
147        ts_init: UnixNanos,
148        price: Option<Price>,
149        trigger_price: Option<Price>,
150        trigger_type: Option<TriggerType>,
151        limit_offset: Option<Decimal>,
152        trailing_offset: Option<Decimal>,
153        trailing_offset_type: Option<TrailingOffsetType>,
154        expire_time: Option<UnixNanos>,
155        display_qty: Option<Quantity>,
156        emulation_trigger: Option<TriggerType>,
157        trigger_instrument_id: Option<InstrumentId>,
158        contingency_type: Option<ContingencyType>,
159        order_list_id: Option<OrderListId>,
160        linked_order_ids: Option<Vec<ClientOrderId>>,
161        parent_order_id: Option<ClientOrderId>,
162        exec_algorithm_id: Option<ExecAlgorithmId>,
163        exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
164        exec_spawn_id: Option<ClientOrderId>,
165        tags: Option<Vec<Ustr>>,
166    ) -> Self {
167        Self {
168            trader_id,
169            strategy_id,
170            instrument_id,
171            client_order_id,
172            order_side,
173            order_type,
174            quantity,
175            time_in_force,
176            post_only,
177            reduce_only,
178            quote_quantity,
179            reconciliation,
180            event_id,
181            ts_event,
182            ts_init,
183            price,
184            trigger_price,
185            trigger_type,
186            limit_offset,
187            trailing_offset,
188            trailing_offset_type,
189            expire_time,
190            display_qty,
191            emulation_trigger,
192            trigger_instrument_id,
193            contingency_type,
194            order_list_id,
195            linked_order_ids,
196            parent_order_id,
197            exec_algorithm_id,
198            exec_algorithm_params,
199            exec_spawn_id,
200            tags,
201        }
202    }
203}
204
205impl Debug for OrderInitialized {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        write!(
208            f,
209            "{}(\
210            trader_id={}, \
211            strategy_id={}, \
212            instrument_id={}, \
213            client_order_id={}, \
214            side={}, \
215            type={}, \
216            quantity={}, \
217            time_in_force={}, \
218            post_only={}, \
219            reduce_only={}, \
220            quote_quantity={}, \
221            price={}, \
222            emulation_trigger={}, \
223            trigger_instrument_id={}, \
224            contingency_type={}, \
225            order_list_id={}, \
226            linked_order_ids=[{}], \
227            parent_order_id={}, \
228            exec_algorithm_id={}, \
229            exec_algorithm_params={}, \
230            exec_spawn_id={}, \
231            tags={}, \
232            event_id={}, \
233            ts_init={})",
234            stringify!(OrderInitialized),
235            self.trader_id,
236            self.strategy_id,
237            self.instrument_id,
238            self.client_order_id,
239            self.order_side,
240            self.order_type,
241            self.quantity,
242            self.time_in_force,
243            self.post_only,
244            self.reduce_only,
245            self.quote_quantity,
246            self.price
247                .map_or("None".to_string(), |price| format!("{price}")),
248            self.emulation_trigger
249                .map_or("None".to_string(), |trigger| format!("{trigger}")),
250            self.trigger_instrument_id
251                .map_or("None".to_string(), |instrument_id| format!(
252                    "{instrument_id}"
253                )),
254            self.contingency_type
255                .map_or("None".to_string(), |contingency_type| format!(
256                    "{contingency_type}"
257                )),
258            self.order_list_id
259                .map_or("None".to_string(), |order_list_id| format!(
260                    "{order_list_id}"
261                )),
262            self.linked_order_ids
263                .as_ref()
264                .map_or("None".to_string(), |linked_order_ids| linked_order_ids
265                    .iter()
266                    .map(ToString::to_string)
267                    .collect::<Vec<_>>()
268                    .join(", ")),
269            self.parent_order_id
270                .map_or("None".to_string(), |parent_order_id| format!(
271                    "{parent_order_id}"
272                )),
273            self.exec_algorithm_id
274                .map_or("None".to_string(), |exec_algorithm_id| format!(
275                    "{exec_algorithm_id}"
276                )),
277            self.exec_algorithm_params
278                .as_ref()
279                .map_or("None".to_string(), |exec_algorithm_params| format!(
280                    "{exec_algorithm_params:?}"
281                )),
282            self.exec_spawn_id
283                .map_or("None".to_string(), |exec_spawn_id| format!(
284                    "{exec_spawn_id}"
285                )),
286            self.tags.as_ref().map_or("None".to_string(), |tags| tags
287                .iter()
288                .map(|x| x.to_string())
289                .collect::<Vec<String>>()
290                .join(", ")),
291            self.event_id,
292            self.ts_init
293        )
294    }
295}
296
297impl Display for OrderInitialized {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        write!(
300            f,
301            "{}(\
302            instrument_id={}, \
303            client_order_id={}, \
304            side={}, \
305            type={}, \
306            quantity={}, \
307            time_in_force={}, \
308            post_only={}, \
309            reduce_only={}, \
310            quote_quantity={}, \
311            price={}, \
312            emulation_trigger={}, \
313            trigger_instrument_id={}, \
314            contingency_type={}, \
315            order_list_id={}, \
316            linked_order_ids=[{}], \
317            parent_order_id={}, \
318            exec_algorithm_id={}, \
319            exec_algorithm_params={}, \
320            exec_spawn_id={}, \
321            tags={})",
322            stringify!(OrderInitialized),
323            self.instrument_id,
324            self.client_order_id,
325            self.order_side,
326            self.order_type,
327            self.quantity,
328            self.time_in_force,
329            self.post_only,
330            self.reduce_only,
331            self.quote_quantity,
332            self.price
333                .map_or("None".to_string(), |price| format!("{price}")),
334            self.emulation_trigger
335                .map_or("None".to_string(), |trigger| format!("{trigger}")),
336            self.trigger_instrument_id
337                .map_or("None".to_string(), |instrument_id| format!(
338                    "{instrument_id}"
339                )),
340            self.contingency_type
341                .map_or("None".to_string(), |contingency_type| format!(
342                    "{contingency_type}"
343                )),
344            self.order_list_id
345                .map_or("None".to_string(), |order_list_id| format!(
346                    "{order_list_id}"
347                )),
348            self.linked_order_ids
349                .as_ref()
350                .map_or("None".to_string(), |linked_order_ids| linked_order_ids
351                    .iter()
352                    .map(ToString::to_string)
353                    .collect::<Vec<_>>()
354                    .join(", ")),
355            self.parent_order_id
356                .map_or("None".to_string(), |parent_order_id| format!(
357                    "{parent_order_id}"
358                )),
359            self.exec_algorithm_id
360                .map_or("None".to_string(), |exec_algorithm_id| format!(
361                    "{exec_algorithm_id}"
362                )),
363            self.exec_algorithm_params
364                .as_ref()
365                .map_or("None".to_string(), |exec_algorithm_params| format!(
366                    "{exec_algorithm_params:?}"
367                )),
368            self.exec_spawn_id
369                .map_or("None".to_string(), |exec_spawn_id| format!(
370                    "{exec_spawn_id}"
371                )),
372            self.tags.as_ref().map_or("None".to_string(), |tags| tags
373                .iter()
374                .map(|s| s.to_string())
375                .collect::<Vec<String>>()
376                .join(", ")),
377        )
378    }
379}
380
381impl OrderEvent for OrderInitialized {
382    fn id(&self) -> UUID4 {
383        self.event_id
384    }
385
386    fn type_name(&self) -> &'static str {
387        stringify!(OrderInitialized)
388    }
389
390    fn order_type(&self) -> Option<OrderType> {
391        Some(self.order_type)
392    }
393
394    fn order_side(&self) -> Option<OrderSide> {
395        Some(self.order_side)
396    }
397
398    fn trader_id(&self) -> TraderId {
399        self.trader_id
400    }
401
402    fn strategy_id(&self) -> StrategyId {
403        self.strategy_id
404    }
405
406    fn instrument_id(&self) -> InstrumentId {
407        self.instrument_id
408    }
409
410    fn trade_id(&self) -> Option<TradeId> {
411        None
412    }
413
414    fn currency(&self) -> Option<Currency> {
415        None
416    }
417
418    fn client_order_id(&self) -> ClientOrderId {
419        self.client_order_id
420    }
421
422    fn reason(&self) -> Option<Ustr> {
423        None
424    }
425
426    fn quantity(&self) -> Option<Quantity> {
427        Some(self.quantity)
428    }
429
430    fn time_in_force(&self) -> Option<TimeInForce> {
431        Some(self.time_in_force)
432    }
433
434    fn liquidity_side(&self) -> Option<LiquiditySide> {
435        None
436    }
437
438    fn post_only(&self) -> Option<bool> {
439        Some(self.post_only)
440    }
441
442    fn reduce_only(&self) -> Option<bool> {
443        Some(self.reduce_only)
444    }
445
446    fn quote_quantity(&self) -> Option<bool> {
447        Some(self.quote_quantity)
448    }
449
450    fn reconciliation(&self) -> bool {
451        false
452    }
453
454    fn price(&self) -> Option<Price> {
455        self.price
456    }
457
458    fn last_px(&self) -> Option<Price> {
459        None
460    }
461
462    fn last_qty(&self) -> Option<Quantity> {
463        None
464    }
465
466    fn trigger_price(&self) -> Option<Price> {
467        self.trigger_price
468    }
469
470    fn trigger_type(&self) -> Option<TriggerType> {
471        self.trigger_type
472    }
473
474    fn limit_offset(&self) -> Option<Decimal> {
475        self.limit_offset
476    }
477
478    fn trailing_offset(&self) -> Option<Decimal> {
479        self.trailing_offset
480    }
481
482    fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
483        self.trailing_offset_type
484    }
485
486    fn expire_time(&self) -> Option<UnixNanos> {
487        self.expire_time
488    }
489
490    fn display_qty(&self) -> Option<Quantity> {
491        self.display_qty
492    }
493
494    fn emulation_trigger(&self) -> Option<TriggerType> {
495        self.emulation_trigger
496    }
497
498    fn trigger_instrument_id(&self) -> Option<InstrumentId> {
499        self.trigger_instrument_id
500    }
501
502    fn contingency_type(&self) -> Option<ContingencyType> {
503        self.contingency_type
504    }
505
506    fn order_list_id(&self) -> Option<OrderListId> {
507        self.order_list_id
508    }
509
510    fn linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
511        self.linked_order_ids.clone()
512    }
513
514    fn parent_order_id(&self) -> Option<ClientOrderId> {
515        self.parent_order_id
516    }
517
518    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
519        self.exec_algorithm_id
520    }
521
522    fn exec_spawn_id(&self) -> Option<ClientOrderId> {
523        self.exec_spawn_id
524    }
525
526    fn venue_order_id(&self) -> Option<VenueOrderId> {
527        None
528    }
529
530    fn account_id(&self) -> Option<AccountId> {
531        None
532    }
533
534    fn position_id(&self) -> Option<PositionId> {
535        None
536    }
537
538    fn commission(&self) -> Option<Money> {
539        None
540    }
541
542    fn ts_event(&self) -> UnixNanos {
543        self.ts_event
544    }
545
546    fn ts_init(&self) -> UnixNanos {
547        self.ts_init
548    }
549}
550
551impl From<OrderInitialized> for OrderAny {
552    fn from(order: OrderInitialized) -> Self {
553        match order.order_type {
554            OrderType::Limit => Self::Limit(order.into()),
555            OrderType::Market => Self::Market(order.into()),
556            OrderType::StopMarket => Self::StopMarket(order.into()),
557            OrderType::StopLimit => Self::StopLimit(order.into()),
558            OrderType::LimitIfTouched => Self::LimitIfTouched(order.into()),
559            OrderType::TrailingStopLimit => Self::TrailingStopLimit(order.into()),
560            OrderType::TrailingStopMarket => Self::TrailingStopMarket(order.into()),
561            OrderType::MarketToLimit => Self::MarketToLimit(order.into()),
562            OrderType::MarketIfTouched => Self::MarketIfTouched(order.into()),
563        }
564    }
565}
566
567#[cfg(test)]
568mod test {
569    use rstest::rstest;
570
571    use crate::events::order::{initialized::OrderInitialized, stubs::*};
572    #[rstest]
573    fn test_order_initialized(order_initialized_buy_limit: OrderInitialized) {
574        let display = format!("{order_initialized_buy_limit}");
575        assert_eq!(
576            display,
577            "OrderInitialized(instrument_id=BTCUSDT.COINBASE, client_order_id=O-19700101-000000-001-001-1, \
578            side=BUY, type=LIMIT, quantity=0.561, time_in_force=DAY, post_only=true, reduce_only=true, \
579            quote_quantity=false, price=22000, emulation_trigger=BID_ASK, trigger_instrument_id=BTCUSDT.COINBASE, \
580            contingency_type=OTO, order_list_id=1, linked_order_ids=[O-2020872378424], parent_order_id=None, \
581            exec_algorithm_id=None, exec_algorithm_params=None, exec_spawn_id=None, tags=None)"
582        );
583    }
584
585    #[rstest]
586    fn test_order_initialized_serialization() {
587        let original = OrderInitialized::default();
588        let json = serde_json::to_string(&original).unwrap();
589        let deserialized: OrderInitialized = serde_json::from_str(&json).unwrap();
590        assert_eq!(original, deserialized);
591    }
592}