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