1use 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#[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 pub trader_id: TraderId,
58 pub strategy_id: StrategyId,
60 pub instrument_id: InstrumentId,
62 pub client_order_id: ClientOrderId,
64 pub order_side: OrderSide,
66 pub order_type: OrderType,
68 pub quantity: Quantity,
70 pub time_in_force: TimeInForce,
72 pub post_only: bool,
74 pub reduce_only: bool,
76 pub quote_quantity: bool,
78 pub reconciliation: bool,
80 pub event_id: UUID4,
82 pub ts_event: UnixNanos,
84 pub ts_init: UnixNanos,
86 pub price: Option<Price>,
88 pub trigger_price: Option<Price>,
90 pub trigger_type: Option<TriggerType>,
92 pub limit_offset: Option<Decimal>,
94 pub trailing_offset: Option<Decimal>,
96 pub trailing_offset_type: Option<TrailingOffsetType>,
98 pub expire_time: Option<UnixNanos>,
100 pub display_qty: Option<Quantity>,
102 pub emulation_trigger: Option<TriggerType>,
104 pub trigger_instrument_id: Option<InstrumentId>,
106 pub contingency_type: Option<ContingencyType>,
108 pub order_list_id: Option<OrderListId>,
110 pub linked_order_ids: Option<Vec<ClientOrderId>>,
112 pub parent_order_id: Option<ClientOrderId>,
114 pub exec_algorithm_id: Option<ExecAlgorithmId>,
116 pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
118 pub exec_spawn_id: Option<ClientOrderId>,
120 pub tags: Option<Vec<Ustr>>,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub causation_id: Option<UUID4>,
125}
126
127impl OrderInitialized {
128 #[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}