1use quanta::Clock;
6use rust_decimal::Decimal;
7use serde::{Deserialize, Serialize};
8use smartstring::alias::String;
9
10pub use crate::enums::{OrderSide, OrderStatus, OrderType, TimeInForce};
12use crate::types::{ClientId, OrderId};
13use crate::venues::Venue;
14
15use rusty_common::pools::Poolable;
17
18#[repr(align(64))] #[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Order {
22 pub id: OrderId,
24
25 pub exchange_order_id: Option<String>,
27
28 pub venue: Venue,
30
31 pub symbol: String,
33
34 pub side: OrderSide,
36
37 pub order_type: OrderType,
39
40 pub price: Option<Decimal>,
42
43 pub stop_price: Option<Decimal>,
45
46 pub quantity: Decimal,
48
49 pub filled_quantity: Decimal,
51
52 pub average_fill_price: Option<Decimal>,
54
55 pub status: OrderStatus,
57
58 pub creation_time_ns: u64,
60
61 pub update_time_ns: u64,
63
64 pub client_id: ClientId,
66
67 pub time_in_force: TimeInForce,
69
70 pub metadata: rusty_common::json::Value,
72}
73
74#[cfg(test)]
78impl Order {
79 #[must_use]
96 pub fn for_testing() -> Self {
97 use crate::types::OrderId;
98 use uuid::Uuid;
99
100 Self {
101 id: OrderId::from_uuid(Uuid::nil()), exchange_order_id: None,
103 venue: Venue::Test,
104 symbol: String::from("TEST-INVALID"), side: OrderSide::Buy,
106 order_type: OrderType::Market,
107 price: None,
108 stop_price: None,
109 quantity: Decimal::ZERO, filled_quantity: Decimal::ZERO,
111 average_fill_price: None,
112 status: OrderStatus::New,
113 creation_time_ns: 0, update_time_ns: 0,
115 client_id: ClientId::from("TEST-ONLY"), time_in_force: TimeInForce::GTC,
117 metadata: rusty_common::json::json!({"test_only": true}),
118 }
119 }
120}
121
122impl Order {
123 #[must_use]
125 pub fn new(
126 venue: Venue,
127 symbol: impl AsRef<str>,
128 side: OrderSide,
129 order_type: OrderType,
130 quantity: Decimal,
131 price: Option<Decimal>,
132 client_id: ClientId,
133 ) -> Self {
134 Self::with_time_in_force(
135 venue,
136 symbol,
137 side,
138 order_type,
139 quantity,
140 price,
141 client_id,
142 TimeInForce::GTC, )
144 }
145
146 #[must_use]
148 #[allow(clippy::too_many_arguments)] pub fn with_time_in_force(
150 venue: Venue,
151 symbol: impl AsRef<str>,
152 side: OrderSide,
153 order_type: OrderType,
154 quantity: Decimal,
155 price: Option<Decimal>,
156 client_id: ClientId,
157 time_in_force: TimeInForce,
158 ) -> Self {
159 let clock = Clock::new();
161 let now = clock.raw();
162
163 Self {
170 id: OrderId::new(),
171 exchange_order_id: None,
172 venue,
173 symbol: String::from(symbol.as_ref()),
174 side,
175 order_type,
176 price,
177 stop_price: None,
178 quantity,
179 filled_quantity: Decimal::ZERO,
180 average_fill_price: None,
181 status: OrderStatus::New,
182 creation_time_ns: now,
183 update_time_ns: now,
184 client_id,
185 time_in_force,
186 metadata: rusty_common::json::json!(null),
187 }
188 }
189
190 #[must_use]
192 pub fn is_filled(&self) -> bool {
193 self.status == OrderStatus::Filled
194 }
195
196 #[must_use]
198 pub fn remaining_quantity(&self) -> Decimal {
199 self.quantity - self.filled_quantity
200 }
201
202 pub fn update_status(&mut self, status: OrderStatus) {
204 self.status = status;
205 let clock = Clock::new();
207 self.update_time_ns = clock.raw();
208 }
209}
210
211impl Poolable for Order {
217 fn new_for_pool() -> Self {
218 use uuid::Uuid;
219
220 Self {
221 id: OrderId::from_uuid(Uuid::nil()), exchange_order_id: None,
223 venue: Venue::Test,
224 symbol: String::from("POOL-INVALID"), side: OrderSide::Buy,
226 order_type: OrderType::Market,
227 price: None,
228 stop_price: None,
229 quantity: Decimal::ZERO, filled_quantity: Decimal::ZERO,
231 average_fill_price: None,
232 status: OrderStatus::New,
233 creation_time_ns: 0, update_time_ns: 0,
235 client_id: ClientId::from("POOL-ONLY"), time_in_force: TimeInForce::GTC,
237 metadata: rusty_common::json::json!({"pool_object": true}),
238 }
239 }
240
241 fn reset_for_pool(&mut self) {
242 use uuid::Uuid;
243
244 self.id = OrderId::from_uuid(Uuid::nil());
246 self.exchange_order_id = None;
247 self.venue = Venue::Test;
248 self.symbol = String::from("POOL-INVALID");
249 self.side = OrderSide::Buy;
250 self.order_type = OrderType::Market;
251 self.price = None;
252 self.stop_price = None;
253 self.quantity = Decimal::ZERO;
254 self.filled_quantity = Decimal::ZERO;
255 self.average_fill_price = None;
256 self.status = OrderStatus::New;
257 self.creation_time_ns = 0;
258 self.update_time_ns = 0;
259 self.client_id = ClientId::from("POOL-ONLY");
260 self.time_in_force = TimeInForce::GTC;
261 self.metadata = rusty_common::json::json!({"pool_object": true});
262 }
263}
264
265impl Default for Order {
271 fn default() -> Self {
272 Self::new_for_pool()
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::enums::{OrderSide, OrderStatus, OrderType};
280 use crate::types::ClientId;
281 use rust_decimal_macros::dec;
282 use std::thread;
283
284 fn create_test_venue() -> Venue {
286 Venue::Test
287 }
288
289 #[test]
290 fn test_order_new() {
291 let venue = Venue::Test;
292 let symbol = "BTCUSDT";
293 let side = OrderSide::Buy;
294 let order_type = OrderType::Limit;
295 let quantity = dec!(1.0);
296 let price = Some(dec!(50000.0));
297 let client_id = ClientId::from("test_client");
298
299 let order = Order::new(venue, symbol, side, order_type, quantity, price, client_id);
300
301 assert_eq!(order.venue, venue);
302 assert_eq!(order.symbol.as_str(), symbol);
303 assert_eq!(order.side, side);
304 assert_eq!(order.order_type, order_type);
305 assert_eq!(order.quantity, quantity);
306 assert_eq!(order.price, price);
307 assert_eq!(order.stop_price, None);
308 assert_eq!(order.filled_quantity, dec!(0));
309 assert_eq!(order.average_fill_price, None);
310 assert_eq!(order.status, OrderStatus::New);
311 assert!(order.creation_time_ns > 0);
312 assert_eq!(order.update_time_ns, order.creation_time_ns);
313 assert_eq!(order.client_id.as_str(), "test_client");
314 assert_eq!(order.metadata, rusty_common::json::json!(null));
315 }
316
317 #[test]
318 fn test_order_is_filled() {
319 let mut order = Order::new(
320 create_test_venue(),
321 "BTCUSDT",
322 OrderSide::Buy,
323 OrderType::Limit,
324 dec!(1.0),
325 Some(dec!(50000.0)),
326 ClientId::from("test_client"),
327 );
328
329 assert!(!order.is_filled());
331
332 order.status = OrderStatus::Filled;
334 assert!(order.is_filled());
335
336 order.status = OrderStatus::PartiallyFilled;
338 assert!(!order.is_filled());
339
340 order.status = OrderStatus::Cancelled;
341 assert!(!order.is_filled());
342 }
343
344 #[test]
345 fn test_order_remaining_quantity() {
346 let mut order = Order::new(
347 create_test_venue(),
348 "BTCUSDT",
349 OrderSide::Buy,
350 OrderType::Limit,
351 dec!(2.0),
352 Some(dec!(50000.0)),
353 ClientId::from("test_client"),
354 );
355
356 assert_eq!(order.remaining_quantity(), dec!(2.0));
358
359 order.filled_quantity = dec!(0.5);
361 assert_eq!(order.remaining_quantity(), dec!(1.5));
362
363 order.filled_quantity = dec!(2.0);
365 assert_eq!(order.remaining_quantity(), dec!(0));
366 }
367
368 #[test]
369 fn test_order_update_status() {
370 let mut order = Order::new(
371 create_test_venue(),
372 "BTCUSDT",
373 OrderSide::Buy,
374 OrderType::Limit,
375 dec!(1.0),
376 Some(dec!(50000.0)),
377 ClientId::from("test_client"),
378 );
379
380 let initial_update_time = order.update_time_ns;
381
382 thread::sleep(std::time::Duration::from_millis(1));
384
385 order.update_status(OrderStatus::Open);
387
388 assert_eq!(order.status, OrderStatus::Open);
389 assert!(order.update_time_ns > initial_update_time);
390 }
391
392 #[test]
393 fn test_order_side_conversion() {
394 assert_eq!(OrderSide::Buy, OrderSide::Buy);
396 assert_eq!(OrderSide::Sell, OrderSide::Sell);
397
398 assert_eq!(OrderSide::Buy, OrderSide::Buy);
400 assert_eq!(OrderSide::Sell, OrderSide::Sell);
401 }
402
403 #[test]
404 fn test_order_type_conversion() {
405 assert_eq!(OrderType::Market, OrderType::Market);
407 assert_eq!(OrderType::Limit, OrderType::Limit);
408 assert_eq!(OrderType::Stop, OrderType::Stop);
409 assert_eq!(OrderType::StopLimit, OrderType::StopLimit);
410 assert_eq!(OrderType::FillOrKill, OrderType::FillOrKill);
411 assert_eq!(OrderType::ImmediateOrCancel, OrderType::ImmediateOrCancel);
412 assert_eq!(OrderType::PostOnly, OrderType::PostOnly);
413
414 assert_eq!(OrderType::Market, OrderType::Market);
416 assert_eq!(OrderType::Limit, OrderType::Limit);
417 assert_eq!(OrderType::Stop, OrderType::Stop);
418 assert_eq!(OrderType::StopLimit, OrderType::StopLimit);
419 assert_eq!(OrderType::FillOrKill, OrderType::FillOrKill);
420 assert_eq!(OrderType::ImmediateOrCancel, OrderType::ImmediateOrCancel);
421 assert_eq!(OrderType::PostOnly, OrderType::PostOnly);
422 }
423
424 #[test]
425 fn test_order_status_conversion() {
426 assert_eq!(OrderStatus::New, OrderStatus::New);
428 assert_eq!(OrderStatus::Open, OrderStatus::Open);
429 assert_eq!(OrderStatus::PartiallyFilled, OrderStatus::PartiallyFilled);
430 assert_eq!(OrderStatus::Filled, OrderStatus::Filled);
431 assert_eq!(OrderStatus::Cancelled, OrderStatus::Cancelled);
432 assert_eq!(OrderStatus::Rejected, OrderStatus::Rejected);
433 assert_eq!(OrderStatus::Pending, OrderStatus::Pending);
434 assert_eq!(OrderStatus::Unknown, OrderStatus::Unknown);
435
436 assert_eq!(OrderStatus::New, OrderStatus::New);
438 assert_eq!(OrderStatus::Open, OrderStatus::Open);
439 assert_eq!(OrderStatus::PartiallyFilled, OrderStatus::PartiallyFilled);
440 assert_eq!(OrderStatus::Filled, OrderStatus::Filled);
441 assert_eq!(OrderStatus::Cancelled, OrderStatus::Cancelled);
442 assert_eq!(OrderStatus::Rejected, OrderStatus::Rejected);
443 assert_eq!(OrderStatus::Pending, OrderStatus::Pending);
444 assert_eq!(OrderStatus::Unknown, OrderStatus::Unknown);
445 }
446
447 #[test]
448 fn test_order_for_testing() {
449 let test_order = Order::for_testing();
450
451 assert_eq!(
453 test_order.id.to_string(),
454 "00000000-0000-0000-0000-000000000000"
455 );
456 assert_eq!(test_order.symbol.as_str(), "TEST-INVALID");
457 assert_eq!(test_order.client_id.as_str(), "TEST-ONLY");
458 assert_eq!(test_order.quantity, dec!(0)); assert_eq!(test_order.creation_time_ns, 0); assert_eq!(test_order.update_time_ns, 0);
461 assert_eq!(test_order.venue, Venue::Test);
462 assert_eq!(test_order.status, OrderStatus::New);
463 assert_eq!(test_order.metadata["test_only"], true);
464
465 assert!(test_order.symbol.contains("TEST"));
467 assert!(test_order.client_id.as_str().contains("TEST"));
468 }
469
470 #[test]
471 fn test_production_order_vs_test_order() {
472 let production_order = Order::new(
473 Venue::Binance,
474 "BTCUSDT",
475 OrderSide::Buy,
476 OrderType::Limit,
477 dec!(1.0),
478 Some(dec!(50000.0)),
479 ClientId::from("real_client"),
480 );
481
482 let test_order = Order::for_testing();
483
484 assert_ne!(
486 production_order.id.to_string(),
487 "00000000-0000-0000-0000-000000000000"
488 );
489 assert_ne!(production_order.symbol.as_str(), "TEST-INVALID");
490 assert_ne!(production_order.client_id.as_str(), "TEST-ONLY");
491 assert_ne!(production_order.quantity, dec!(0));
492 assert_ne!(production_order.creation_time_ns, 0);
493
494 assert_eq!(
496 test_order.id.to_string(),
497 "00000000-0000-0000-0000-000000000000"
498 );
499 assert_eq!(test_order.symbol.as_str(), "TEST-INVALID");
500 assert_eq!(test_order.client_id.as_str(), "TEST-ONLY");
501 assert_eq!(test_order.quantity, dec!(0));
502 assert_eq!(test_order.creation_time_ns, 0);
503 }
504
505 #[test]
506 fn test_poolable_trait_safety() {
507 let pool_order = Order::new_for_pool();
509
510 assert_eq!(
511 pool_order.id.to_string(),
512 "00000000-0000-0000-0000-000000000000"
513 );
514 assert_eq!(pool_order.symbol.as_str(), "POOL-INVALID");
515 assert_eq!(pool_order.client_id.as_str(), "POOL-ONLY");
516 assert_eq!(pool_order.quantity, dec!(0));
517 assert_eq!(pool_order.creation_time_ns, 0);
518 assert_eq!(pool_order.update_time_ns, 0);
519 assert_eq!(pool_order.venue, Venue::Test);
520 assert_eq!(pool_order.metadata["pool_object"], true);
521 }
522
523 #[test]
524 fn test_poolable_reset_functionality() {
525 let mut order = Order::new(
527 Venue::Binance,
528 "BTCUSDT",
529 OrderSide::Buy,
530 OrderType::Limit,
531 dec!(1.0),
532 Some(dec!(50000.0)),
533 ClientId::from("real_client"),
534 );
535
536 assert_ne!(order.id.to_string(), "00000000-0000-0000-0000-000000000000");
538 assert_eq!(order.symbol.as_str(), "BTCUSDT");
539 assert_eq!(order.client_id.as_str(), "real_client");
540 assert_eq!(order.quantity, dec!(1.0));
541 assert_ne!(order.creation_time_ns, 0);
542
543 order.reset_for_pool();
545
546 assert_eq!(order.id.to_string(), "00000000-0000-0000-0000-000000000000");
548 assert_eq!(order.symbol.as_str(), "POOL-INVALID");
549 assert_eq!(order.client_id.as_str(), "POOL-ONLY");
550 assert_eq!(order.quantity, dec!(0));
551 assert_eq!(order.creation_time_ns, 0);
552 assert_eq!(order.update_time_ns, 0);
553 assert_eq!(order.venue, Venue::Test);
554 assert_eq!(order.metadata["pool_object"], true);
555 }
556
557 #[test]
558 fn test_default_uses_poolable() {
559 let default_order = Order::default();
561 let pool_order = Order::new_for_pool();
562
563 assert_eq!(default_order.id.to_string(), pool_order.id.to_string());
565 assert_eq!(default_order.symbol, pool_order.symbol);
566 assert_eq!(default_order.client_id, pool_order.client_id);
567 assert_eq!(default_order.quantity, pool_order.quantity);
568 assert_eq!(default_order.creation_time_ns, pool_order.creation_time_ns);
569 assert_eq!(default_order.metadata, pool_order.metadata);
570 }
571
572 #[test]
573 fn test_pool_vs_test_order_differences() {
574 let test_order = Order::for_testing();
575 let pool_order = Order::new_for_pool();
576
577 assert_eq!(test_order.id.to_string(), pool_order.id.to_string());
579 assert_eq!(test_order.quantity, pool_order.quantity);
580 assert_eq!(test_order.creation_time_ns, pool_order.creation_time_ns);
581 assert_eq!(test_order.venue, pool_order.venue);
582
583 assert_ne!(test_order.symbol, pool_order.symbol);
585 assert_ne!(test_order.client_id, pool_order.client_id);
586 assert_ne!(test_order.metadata, pool_order.metadata);
587
588 assert!(test_order.symbol.contains("TEST"));
590 assert!(test_order.client_id.as_str().contains("TEST"));
591 assert!(pool_order.symbol.contains("POOL"));
592 assert!(pool_order.client_id.as_str().contains("POOL"));
593 }
594
595 #[test]
596 fn test_order_default_has_nil_uuid() {
597 let order = Order::default();
598 assert_eq!(order.id.to_string(), "00000000-0000-0000-0000-000000000000");
600 }
601
602 #[test]
603 fn test_order_default_has_pool_invalid_symbol() {
604 let order = Order::default();
605 assert_eq!(order.symbol.as_str(), "POOL-INVALID");
606 }
607
608 #[test]
609 fn test_order_default_has_pool_only_client_id() {
610 let order = Order::default();
611 assert_eq!(order.client_id.as_str(), "POOL-ONLY");
612 }
613
614 #[test]
615 fn test_order_default_has_zero_quantity() {
616 let order = Order::default();
617 assert_eq!(order.quantity, Decimal::ZERO);
618 assert_eq!(order.filled_quantity, Decimal::ZERO);
619 }
620
621 #[test]
622 fn test_order_default_has_zero_timestamps() {
623 let order = Order::default();
624 assert_eq!(order.creation_time_ns, 0);
625 assert_eq!(order.update_time_ns, 0);
626 }
627
628 #[test]
629 fn test_order_default_has_test_venue() {
630 let order = Order::default();
631 assert_eq!(order.venue, Venue::Test);
632 }
633
634 #[test]
635 fn test_order_default_has_pool_object_metadata() {
636 let order = Order::default();
637 assert_eq!(order.metadata["pool_object"], true);
638 }
639
640 #[test]
641 fn test_order_default_comprehensive() {
642 let order = Order::default();
644
645 assert_eq!(order.id.to_string(), "00000000-0000-0000-0000-000000000000");
647
648 assert_eq!(order.symbol.as_str(), "POOL-INVALID");
650
651 assert_eq!(order.client_id.as_str(), "POOL-ONLY");
653
654 assert_eq!(order.quantity, Decimal::ZERO);
656 assert_eq!(order.filled_quantity, Decimal::ZERO);
657
658 assert_eq!(order.creation_time_ns, 0);
660 assert_eq!(order.update_time_ns, 0);
661
662 assert_eq!(order.venue, Venue::Test);
664
665 assert_eq!(order.metadata["pool_object"], true);
667
668 assert_eq!(order.exchange_order_id, None);
670 assert_eq!(order.side, OrderSide::Buy);
671 assert_eq!(order.order_type, OrderType::Market);
672 assert_eq!(order.price, None);
673 assert_eq!(order.stop_price, None);
674 assert_eq!(order.average_fill_price, None);
675 assert_eq!(order.status, OrderStatus::New);
676 assert_eq!(order.time_in_force, TimeInForce::GTC);
677 }
678
679 #[test]
680 fn test_order_default_is_obviously_invalid() {
681 let order = Order::default();
683
684 assert!(order.id.to_string().contains("00000000"));
686 assert!(order.symbol.contains("INVALID"));
687 assert!(order.client_id.as_str().contains("POOL"));
688 assert!(order.quantity.is_zero());
689 assert_eq!(order.creation_time_ns, 0); assert_eq!(order.metadata["pool_object"], true);
693 }
694}