1use std::fmt::Display;
17
18use derive_builder::Builder;
19use nautilus_core::{Params, UUID4, UnixNanos};
20use nautilus_model::identifiers::{
21 ClientId, ClientOrderId, InstrumentId, TraderId, Venue, VenueOrderId,
22};
23use serde::{Deserialize, Serialize};
24
25use crate::enums::LogLevel;
26
27const fn default_report_log_level() -> LogLevel {
28 LogLevel::Info
29}
30
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Builder)]
32#[cfg_attr(
33 feature = "python",
34 pyo3::pyclass(module = "nautilus_trader.live", frozen, from_py_object)
35)]
36#[cfg_attr(
37 feature = "python",
38 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
39)]
40pub struct GenerateOrderStatusReport {
41 #[builder(default = "UUID4::new()")]
42 pub command_id: UUID4,
43 pub ts_init: UnixNanos,
44 #[builder(default)]
45 pub instrument_id: Option<InstrumentId>,
46 #[builder(default)]
47 pub client_order_id: Option<ClientOrderId>,
48 #[builder(default)]
49 pub venue_order_id: Option<VenueOrderId>,
50 #[builder(default)]
51 pub params: Option<Params>,
52 #[builder(default)]
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub correlation_id: Option<UUID4>,
55 #[builder(default)]
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub causation_id: Option<UUID4>,
58}
59
60impl GenerateOrderStatusReport {
61 #[must_use]
62 pub fn new(
63 command_id: UUID4,
64 ts_init: UnixNanos,
65 instrument_id: Option<InstrumentId>,
66 client_order_id: Option<ClientOrderId>,
67 venue_order_id: Option<VenueOrderId>,
68 params: Option<Params>,
69 correlation_id: Option<UUID4>,
70 ) -> Self {
71 Self {
72 command_id,
73 ts_init,
74 instrument_id,
75 client_order_id,
76 venue_order_id,
77 params,
78 correlation_id,
79 causation_id: None,
80 }
81 }
82}
83
84impl Display for GenerateOrderStatusReport {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(
87 f,
88 "{}(instrument_id={:?}, client_order_id={:?}, venue_order_id={:?}, command_id={})",
89 stringify!(GenerateOrderStatusReport),
90 self.instrument_id,
91 self.client_order_id,
92 self.venue_order_id,
93 self.command_id,
94 )
95 }
96}
97
98#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Builder)]
99#[cfg_attr(
100 feature = "python",
101 pyo3::pyclass(module = "nautilus_trader.live", frozen, from_py_object)
102)]
103#[cfg_attr(
104 feature = "python",
105 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
106)]
107pub struct GenerateOrderStatusReports {
108 #[builder(default = "UUID4::new()")]
109 pub command_id: UUID4,
110 pub ts_init: UnixNanos,
111 pub open_only: bool,
112 #[builder(default)]
113 pub instrument_id: Option<InstrumentId>,
114 #[builder(default)]
115 pub start: Option<UnixNanos>,
116 #[builder(default)]
117 pub end: Option<UnixNanos>,
118 #[builder(default)]
119 pub params: Option<Params>,
120 #[builder(default = "default_report_log_level()")]
122 #[serde(default = "default_report_log_level")]
123 pub log_receipt_level: LogLevel,
124 #[builder(default)]
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub correlation_id: Option<UUID4>,
127 #[builder(default)]
128 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub causation_id: Option<UUID4>,
130}
131
132impl GenerateOrderStatusReports {
133 #[expect(clippy::too_many_arguments)]
134 #[must_use]
135 pub fn new(
136 command_id: UUID4,
137 ts_init: UnixNanos,
138 open_only: bool,
139 instrument_id: Option<InstrumentId>,
140 start: Option<UnixNanos>,
141 end: Option<UnixNanos>,
142 params: Option<Params>,
143 correlation_id: Option<UUID4>,
144 ) -> Self {
145 Self {
146 command_id,
147 ts_init,
148 open_only,
149 instrument_id,
150 start,
151 end,
152 params,
153 log_receipt_level: LogLevel::Info,
154 correlation_id,
155 causation_id: None,
156 }
157 }
158}
159
160impl Display for GenerateOrderStatusReports {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 write!(
163 f,
164 "{}(open_only={}, instrument_id={:?}, command_id={})",
165 stringify!(GenerateOrderStatusReports),
166 self.open_only,
167 self.instrument_id,
168 self.command_id,
169 )
170 }
171}
172
173#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Builder)]
174#[cfg_attr(
175 feature = "python",
176 pyo3::pyclass(module = "nautilus_trader.live", frozen, from_py_object)
177)]
178#[cfg_attr(
179 feature = "python",
180 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
181)]
182pub struct GenerateFillReports {
183 #[builder(default = "UUID4::new()")]
184 pub command_id: UUID4,
185 pub ts_init: UnixNanos,
186 #[builder(default)]
187 pub instrument_id: Option<InstrumentId>,
188 #[builder(default)]
189 pub venue_order_id: Option<VenueOrderId>,
190 #[builder(default)]
191 pub start: Option<UnixNanos>,
192 #[builder(default)]
193 pub end: Option<UnixNanos>,
194 #[builder(default)]
195 pub params: Option<Params>,
196 #[builder(default = "default_report_log_level()")]
198 #[serde(default = "default_report_log_level")]
199 pub log_receipt_level: LogLevel,
200 #[builder(default)]
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub correlation_id: Option<UUID4>,
203 #[builder(default)]
204 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub causation_id: Option<UUID4>,
206}
207
208impl GenerateFillReports {
209 #[expect(clippy::too_many_arguments)]
210 #[must_use]
211 pub fn new(
212 command_id: UUID4,
213 ts_init: UnixNanos,
214 instrument_id: Option<InstrumentId>,
215 venue_order_id: Option<VenueOrderId>,
216 start: Option<UnixNanos>,
217 end: Option<UnixNanos>,
218 params: Option<Params>,
219 correlation_id: Option<UUID4>,
220 ) -> Self {
221 Self {
222 command_id,
223 ts_init,
224 instrument_id,
225 venue_order_id,
226 start,
227 end,
228 params,
229 log_receipt_level: LogLevel::Info,
230 correlation_id,
231 causation_id: None,
232 }
233 }
234}
235
236impl Display for GenerateFillReports {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 write!(
239 f,
240 "{}(instrument_id={:?}, venue_order_id={:?}, command_id={})",
241 stringify!(GenerateFillReports),
242 self.instrument_id,
243 self.venue_order_id,
244 self.command_id,
245 )
246 }
247}
248
249#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Builder)]
250#[cfg_attr(
251 feature = "python",
252 pyo3::pyclass(module = "nautilus_trader.live", frozen, from_py_object)
253)]
254#[cfg_attr(
255 feature = "python",
256 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
257)]
258pub struct GeneratePositionStatusReports {
259 #[builder(default = "UUID4::new()")]
260 pub command_id: UUID4,
261 pub ts_init: UnixNanos,
262 #[builder(default)]
263 pub instrument_id: Option<InstrumentId>,
264 #[builder(default)]
265 pub start: Option<UnixNanos>,
266 #[builder(default)]
267 pub end: Option<UnixNanos>,
268 #[builder(default)]
269 pub params: Option<Params>,
270 #[builder(default = "default_report_log_level()")]
272 #[serde(default = "default_report_log_level")]
273 pub log_receipt_level: LogLevel,
274 #[builder(default)]
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub correlation_id: Option<UUID4>,
277 #[builder(default)]
278 #[serde(default, skip_serializing_if = "Option::is_none")]
279 pub causation_id: Option<UUID4>,
280}
281
282impl GeneratePositionStatusReports {
283 #[must_use]
284 pub fn new(
285 command_id: UUID4,
286 ts_init: UnixNanos,
287 instrument_id: Option<InstrumentId>,
288 start: Option<UnixNanos>,
289 end: Option<UnixNanos>,
290 params: Option<Params>,
291 correlation_id: Option<UUID4>,
292 ) -> Self {
293 Self {
294 command_id,
295 ts_init,
296 instrument_id,
297 start,
298 end,
299 params,
300 log_receipt_level: LogLevel::Info,
301 correlation_id,
302 causation_id: None,
303 }
304 }
305}
306
307impl Display for GeneratePositionStatusReports {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 write!(
310 f,
311 "{}(instrument_id={:?}, command_id={})",
312 stringify!(GeneratePositionStatusReports),
313 self.instrument_id,
314 self.command_id,
315 )
316 }
317}
318
319#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Builder)]
320pub struct GenerateExecutionMassStatus {
321 pub trader_id: TraderId,
322 pub client_id: ClientId,
323 #[builder(default)]
324 pub venue: Option<Venue>,
325 #[builder(default = "UUID4::new()")]
326 pub command_id: UUID4,
327 pub ts_init: UnixNanos,
328 #[builder(default)]
329 pub params: Option<Params>,
330 #[builder(default)]
331 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub correlation_id: Option<UUID4>,
333 #[builder(default)]
334 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub causation_id: Option<UUID4>,
336}
337
338impl GenerateExecutionMassStatus {
339 #[must_use]
340 pub fn new(
341 trader_id: TraderId,
342 client_id: ClientId,
343 venue: Option<Venue>,
344 command_id: UUID4,
345 ts_init: UnixNanos,
346 params: Option<Params>,
347 correlation_id: Option<UUID4>,
348 ) -> Self {
349 Self {
350 trader_id,
351 client_id,
352 venue,
353 command_id,
354 ts_init,
355 params,
356 correlation_id,
357 causation_id: None,
358 }
359 }
360}
361
362impl Display for GenerateExecutionMassStatus {
363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364 write!(
365 f,
366 "{}(trader_id={}, client_id={}, venue={:?}, command_id={})",
367 stringify!(GenerateExecutionMassStatus),
368 self.trader_id,
369 self.client_id,
370 self.venue,
371 self.command_id,
372 )
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use rstest::rstest;
379
380 use super::*;
381
382 const COMMAND_ID: &str = "00000000-0000-4000-8000-000000000001";
383
384 fn command_id() -> UUID4 {
385 UUID4::from(COMMAND_ID)
386 }
387
388 fn correlation_id() -> UUID4 {
389 UUID4::from("00000000-0000-4000-8000-000000000002")
390 }
391
392 fn instrument_id() -> InstrumentId {
393 InstrumentId::from("ETHUSDT.BINANCE")
394 }
395
396 fn params() -> Params {
397 let mut params = Params::new();
398 params.insert("depth".into(), "10".into());
399 params
400 }
401
402 #[rstest]
403 fn test_generate_order_status_report_assigns_every_field() {
404 let command = GenerateOrderStatusReport::new(
405 command_id(),
406 UnixNanos::from(3),
407 Some(instrument_id()),
408 Some(ClientOrderId::from("O-1")),
409 Some(VenueOrderId::from("V-1")),
410 Some(params()),
411 Some(correlation_id()),
412 );
413
414 assert_eq!(command.command_id, command_id());
415 assert_eq!(command.ts_init, UnixNanos::from(3));
416 assert_eq!(command.instrument_id, Some(instrument_id()));
417 assert_eq!(command.client_order_id, Some(ClientOrderId::from("O-1")));
418 assert_eq!(command.venue_order_id, Some(VenueOrderId::from("V-1")));
419 assert_eq!(command.params, Some(params()));
420 assert_eq!(command.correlation_id, Some(correlation_id()));
421 assert_eq!(command.causation_id, None);
422 assert_eq!(
423 command.to_string(),
424 format!(
425 "GenerateOrderStatusReport(instrument_id=Some(\"ETHUSDT.BINANCE\"), \
426 client_order_id=Some(\"O-1\"), venue_order_id=Some(\"V-1\"), command_id={COMMAND_ID})"
427 )
428 );
429 }
430
431 #[rstest]
432 fn test_generate_order_status_reports_assigns_every_field() {
433 let command = GenerateOrderStatusReports::new(
434 command_id(),
435 UnixNanos::from(5),
436 true,
437 Some(instrument_id()),
438 Some(UnixNanos::from(7)),
439 Some(UnixNanos::from(11)),
440 Some(params()),
441 Some(correlation_id()),
442 );
443
444 assert_eq!(command.command_id, command_id());
445 assert_eq!(command.ts_init, UnixNanos::from(5));
446 assert!(command.open_only);
447 assert_eq!(command.instrument_id, Some(instrument_id()));
448 assert_eq!(command.start, Some(UnixNanos::from(7)));
449 assert_eq!(command.end, Some(UnixNanos::from(11)));
450 assert_eq!(command.params, Some(params()));
451 assert_eq!(command.log_receipt_level, LogLevel::Info);
452 assert_eq!(command.correlation_id, Some(correlation_id()));
453 assert_eq!(command.causation_id, None);
454 assert_eq!(
455 command.to_string(),
456 format!(
457 "GenerateOrderStatusReports(open_only=true, \
458 instrument_id=Some(\"ETHUSDT.BINANCE\"), command_id={COMMAND_ID})"
459 )
460 );
461 }
462
463 #[rstest]
464 fn test_generate_fill_reports_assigns_every_field() {
465 let command = GenerateFillReports::new(
466 command_id(),
467 UnixNanos::from(13),
468 Some(instrument_id()),
469 Some(VenueOrderId::from("V-2")),
470 Some(UnixNanos::from(17)),
471 Some(UnixNanos::from(19)),
472 Some(params()),
473 Some(correlation_id()),
474 );
475
476 assert_eq!(command.command_id, command_id());
477 assert_eq!(command.ts_init, UnixNanos::from(13));
478 assert_eq!(command.instrument_id, Some(instrument_id()));
479 assert_eq!(command.venue_order_id, Some(VenueOrderId::from("V-2")));
480 assert_eq!(command.start, Some(UnixNanos::from(17)));
481 assert_eq!(command.end, Some(UnixNanos::from(19)));
482 assert_eq!(command.params, Some(params()));
483 assert_eq!(command.log_receipt_level, LogLevel::Info);
484 assert_eq!(command.correlation_id, Some(correlation_id()));
485 assert_eq!(command.causation_id, None);
486 assert_eq!(
487 command.to_string(),
488 format!(
489 "GenerateFillReports(instrument_id=Some(\"ETHUSDT.BINANCE\"), \
490 venue_order_id=Some(\"V-2\"), command_id={COMMAND_ID})"
491 )
492 );
493 }
494
495 #[rstest]
496 fn test_generate_position_status_reports_assigns_every_field() {
497 let command = GeneratePositionStatusReports::new(
498 command_id(),
499 UnixNanos::from(23),
500 Some(instrument_id()),
501 Some(UnixNanos::from(29)),
502 Some(UnixNanos::from(31)),
503 Some(params()),
504 Some(correlation_id()),
505 );
506
507 assert_eq!(command.command_id, command_id());
508 assert_eq!(command.ts_init, UnixNanos::from(23));
509 assert_eq!(command.instrument_id, Some(instrument_id()));
510 assert_eq!(command.start, Some(UnixNanos::from(29)));
511 assert_eq!(command.end, Some(UnixNanos::from(31)));
512 assert_eq!(command.params, Some(params()));
513 assert_eq!(command.log_receipt_level, LogLevel::Info);
514 assert_eq!(command.correlation_id, Some(correlation_id()));
515 assert_eq!(command.causation_id, None);
516 assert_eq!(
517 command.to_string(),
518 format!(
519 "GeneratePositionStatusReports(instrument_id=Some(\"ETHUSDT.BINANCE\"), \
520 command_id={COMMAND_ID})"
521 )
522 );
523 }
524
525 #[rstest]
526 fn test_generate_execution_mass_status_assigns_every_field() {
527 let command = GenerateExecutionMassStatus::new(
528 TraderId::from("TESTER-001"),
529 ClientId::from("BINANCE"),
530 Some(Venue::from("BINANCE")),
531 command_id(),
532 UnixNanos::from(37),
533 Some(params()),
534 Some(correlation_id()),
535 );
536
537 assert_eq!(command.trader_id, TraderId::from("TESTER-001"));
538 assert_eq!(command.client_id, ClientId::from("BINANCE"));
539 assert_eq!(command.venue, Some(Venue::from("BINANCE")));
540 assert_eq!(command.command_id, command_id());
541 assert_eq!(command.ts_init, UnixNanos::from(37));
542 assert_eq!(command.params, Some(params()));
543 assert_eq!(command.correlation_id, Some(correlation_id()));
544 assert_eq!(command.causation_id, None);
545 assert_eq!(
546 command.to_string(),
547 format!(
548 "GenerateExecutionMassStatus(trader_id=TESTER-001, client_id=BINANCE, \
549 venue=Some(\"BINANCE\"), command_id={COMMAND_ID})"
550 )
551 );
552 }
553
554 #[rstest]
555 fn test_builders_default_the_optional_fields() {
556 let command = GenerateFillReportsBuilder::default()
557 .ts_init(UnixNanos::from(41))
558 .build()
559 .unwrap();
560
561 assert_eq!(command.ts_init, UnixNanos::from(41));
562 assert_eq!(command.instrument_id, None);
563 assert_eq!(command.venue_order_id, None);
564 assert_eq!(command.start, None);
565 assert_eq!(command.end, None);
566 assert_eq!(command.params, None);
567 assert_eq!(command.log_receipt_level, LogLevel::Info);
568 assert_eq!(command.correlation_id, None);
569 assert_eq!(command.causation_id, None);
570 }
571
572 #[rstest]
573 fn test_report_commands_round_trip_through_json() {
574 let command = GenerateOrderStatusReports::new(
575 command_id(),
576 UnixNanos::from(43),
577 false,
578 None,
579 None,
580 None,
581 None,
582 None,
583 );
584
585 let json = serde_json::to_string(&command).unwrap();
586
587 assert_eq!(
588 serde_json::from_str::<GenerateOrderStatusReports>(&json).unwrap(),
589 command
590 );
591 }
592}