1use pricelevel::{Hash32, PriceLevelError, Side};
4use std::fmt;
5
6#[derive(Debug)]
8#[non_exhaustive]
9pub enum OrderBookError {
10 PriceLevelError(PriceLevelError),
12
13 OrderNotFound(String),
15
16 InvalidPriceLevel(u128),
18
19 PriceCrossing {
21 price: u128,
23 side: Side,
25 opposite_price: u128,
27 },
28
29 InsufficientLiquidity {
31 side: Side,
33 requested: u64,
35 available: u64,
37 },
38
39 InsufficientLiquidityNotional {
45 side: Side,
47 requested: u128,
49 spent: u128,
53 },
54
55 InvalidOperation {
57 message: String,
59 },
60
61 KillSwitchActive,
65
66 SerializationError {
68 message: String,
70 },
71
72 DeserializationError {
74 message: String,
76 },
77
78 ChecksumMismatch {
80 expected: String,
82 actual: String,
84 },
85
86 InvalidTickSize {
88 price: u128,
90 tick_size: u128,
92 },
93
94 InvalidLotSize {
96 quantity: u64,
98 lot_size: u64,
100 },
101
102 OrderSizeOutOfRange {
104 quantity: u64,
106 min: Option<u64>,
108 max: Option<u64>,
110 },
111
112 DuplicateOrderId {
124 order_id: pricelevel::Id,
126 },
127
128 QuantityOverflow {
136 visible: u64,
138 hidden: u64,
140 },
141
142 MissingUserId {
146 order_id: pricelevel::Id,
148 },
149
150 SelfTradePrevented {
153 mode: crate::orderbook::stp::STPMode,
155 taker_order_id: pricelevel::Id,
157 user_id: pricelevel::Hash32,
159 },
160
161 RiskMaxOpenOrders {
168 account: Hash32,
170 current: u64,
172 limit: u64,
174 },
175
176 RiskMaxNotional {
182 account: Hash32,
184 current: u128,
186 attempted: u128,
188 limit: u128,
190 },
191
192 RiskPriceBand {
198 submitted: u128,
200 reference: u128,
202 deviation_bps: u32,
204 limit_bps: u32,
206 },
207
208 #[cfg(feature = "nats")]
210 NatsPublishError {
211 message: String,
213 },
214
215 #[cfg(feature = "nats")]
217 NatsSerializationError {
218 message: String,
220 },
221}
222
223impl fmt::Display for OrderBookError {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 match self {
226 OrderBookError::PriceLevelError(err) => write!(f, "Price level error: {err}"),
227 OrderBookError::OrderNotFound(id) => write!(f, "Order not found: {id}"),
228 OrderBookError::InvalidPriceLevel(price) => write!(f, "Invalid price level: {price}"),
229 OrderBookError::PriceCrossing {
230 price,
231 side,
232 opposite_price,
233 } => {
234 write!(
235 f,
236 "Price crossing: {side} {price} would cross opposite at {opposite_price}"
237 )
238 }
239 OrderBookError::InsufficientLiquidity {
240 side,
241 requested,
242 available,
243 } => {
244 write!(
245 f,
246 "Insufficient liquidity for {side} order: requested {requested}, available {available}"
247 )
248 }
249 OrderBookError::InsufficientLiquidityNotional {
250 side,
251 requested,
252 spent,
253 } => {
254 write!(
255 f,
256 "Insufficient liquidity by notional for {side} order: requested {requested}, spent {spent}"
257 )
258 }
259 OrderBookError::InvalidOperation { message } => {
260 write!(f, "Invalid operation: {message}")
261 }
262 OrderBookError::KillSwitchActive => {
263 write!(
264 f,
265 "kill switch active: new order entry and modifications are halted"
266 )
267 }
268 OrderBookError::SerializationError { message } => {
269 write!(f, "Serialization error: {message}")
270 }
271 OrderBookError::DeserializationError { message } => {
272 write!(f, "Deserialization error: {message}")
273 }
274 OrderBookError::ChecksumMismatch { expected, actual } => {
275 write!(
276 f,
277 "Checksum mismatch: expected {expected}, but computed {actual}"
278 )
279 }
280 OrderBookError::InvalidTickSize { price, tick_size } => {
281 write!(
282 f,
283 "invalid tick size: price {price} is not a multiple of tick size {tick_size}"
284 )
285 }
286 OrderBookError::InvalidLotSize { quantity, lot_size } => {
287 write!(
288 f,
289 "invalid lot size: quantity {quantity} is not a multiple of lot size {lot_size}"
290 )
291 }
292 OrderBookError::OrderSizeOutOfRange { quantity, min, max } => {
293 write!(
294 f,
295 "order size out of range: quantity {quantity}, min {min:?}, max {max:?}"
296 )
297 }
298 OrderBookError::DuplicateOrderId { order_id } => {
299 write!(
300 f,
301 "duplicate order id: order {order_id} is already resting on the book"
302 )
303 }
304 OrderBookError::MissingUserId { order_id } => {
305 write!(
306 f,
307 "missing user_id: order {order_id} rejected because STP is enabled and user_id is zero"
308 )
309 }
310 OrderBookError::QuantityOverflow { visible, hidden } => {
311 write!(
312 f,
313 "quantity overflow: visible {visible} + hidden {hidden} exceeds u64"
314 )
315 }
316 OrderBookError::SelfTradePrevented {
317 mode,
318 taker_order_id,
319 user_id,
320 } => {
321 write!(
322 f,
323 "self-trade prevented ({mode}): taker {taker_order_id}, user {user_id}"
324 )
325 }
326 OrderBookError::RiskMaxOpenOrders {
327 account,
328 current,
329 limit,
330 } => {
331 write!(
332 f,
333 "risk: account {account} has {current} open orders (limit {limit})"
334 )
335 }
336 OrderBookError::RiskMaxNotional {
337 account,
338 current,
339 attempted,
340 limit,
341 } => {
342 write!(
343 f,
344 "risk: account {account} notional {current} + attempted {attempted} would exceed limit {limit}"
345 )
346 }
347 OrderBookError::RiskPriceBand {
348 submitted,
349 reference,
350 deviation_bps,
351 limit_bps,
352 } => {
353 write!(
354 f,
355 "risk: submitted price {submitted} deviates {deviation_bps} bps from reference {reference} (limit {limit_bps} bps)"
356 )
357 }
358 #[cfg(feature = "nats")]
359 OrderBookError::NatsPublishError { message } => {
360 write!(f, "nats publish error: {message}")
361 }
362 #[cfg(feature = "nats")]
363 OrderBookError::NatsSerializationError { message } => {
364 write!(f, "nats serialization error: {message}")
365 }
366 }
367 }
368}
369
370impl std::error::Error for OrderBookError {}
371
372impl From<PriceLevelError> for OrderBookError {
373 fn from(err: PriceLevelError) -> Self {
374 OrderBookError::PriceLevelError(err)
375 }
376}
377
378impl From<crate::orderbook::serialization::SerializationError> for OrderBookError {
379 fn from(err: crate::orderbook::serialization::SerializationError) -> Self {
385 OrderBookError::SerializationError {
386 message: err.to_string(),
387 }
388 }
389}
390
391impl Clone for OrderBookError {
392 fn clone(&self) -> Self {
393 match self {
394 OrderBookError::PriceLevelError(err) => {
395 let cloned_err = match err {
397 PriceLevelError::ParseError { message } => PriceLevelError::ParseError {
398 message: message.clone(),
399 },
400 PriceLevelError::InvalidFormat => PriceLevelError::InvalidFormat,
401 PriceLevelError::UnknownOrderType(s) => {
402 PriceLevelError::UnknownOrderType(s.clone())
403 }
404 PriceLevelError::MissingField(s) => PriceLevelError::MissingField(s.clone()),
405 PriceLevelError::InvalidFieldValue { field, value } => {
406 PriceLevelError::InvalidFieldValue {
407 field: field.clone(),
408 value: value.clone(),
409 }
410 }
411 PriceLevelError::InvalidOperation { message } => {
412 PriceLevelError::InvalidOperation {
413 message: message.clone(),
414 }
415 }
416 PriceLevelError::SerializationError { message } => {
417 PriceLevelError::SerializationError {
418 message: message.clone(),
419 }
420 }
421 PriceLevelError::DeserializationError { message } => {
422 PriceLevelError::DeserializationError {
423 message: message.clone(),
424 }
425 }
426 PriceLevelError::ChecksumMismatch { expected, actual } => {
427 PriceLevelError::ChecksumMismatch {
428 expected: expected.clone(),
429 actual: actual.clone(),
430 }
431 }
432 PriceLevelError::DuplicateOrderId(id) => {
433 PriceLevelError::DuplicateOrderId(id.clone())
434 }
435 };
436 OrderBookError::PriceLevelError(cloned_err)
437 }
438 OrderBookError::OrderNotFound(s) => OrderBookError::OrderNotFound(s.clone()),
439 OrderBookError::InvalidPriceLevel(p) => OrderBookError::InvalidPriceLevel(*p),
440 OrderBookError::PriceCrossing {
441 price,
442 side,
443 opposite_price,
444 } => OrderBookError::PriceCrossing {
445 price: *price,
446 side: *side,
447 opposite_price: *opposite_price,
448 },
449 OrderBookError::InsufficientLiquidity {
450 side,
451 requested,
452 available,
453 } => OrderBookError::InsufficientLiquidity {
454 side: *side,
455 requested: *requested,
456 available: *available,
457 },
458 OrderBookError::InsufficientLiquidityNotional {
459 side,
460 requested,
461 spent,
462 } => OrderBookError::InsufficientLiquidityNotional {
463 side: *side,
464 requested: *requested,
465 spent: *spent,
466 },
467 OrderBookError::InvalidOperation { message } => OrderBookError::InvalidOperation {
468 message: message.clone(),
469 },
470 OrderBookError::KillSwitchActive => OrderBookError::KillSwitchActive,
471 OrderBookError::SerializationError { message } => OrderBookError::SerializationError {
472 message: message.clone(),
473 },
474 OrderBookError::DeserializationError { message } => {
475 OrderBookError::DeserializationError {
476 message: message.clone(),
477 }
478 }
479 OrderBookError::ChecksumMismatch { expected, actual } => {
480 OrderBookError::ChecksumMismatch {
481 expected: expected.clone(),
482 actual: actual.clone(),
483 }
484 }
485 OrderBookError::InvalidTickSize { price, tick_size } => {
486 OrderBookError::InvalidTickSize {
487 price: *price,
488 tick_size: *tick_size,
489 }
490 }
491 OrderBookError::InvalidLotSize { quantity, lot_size } => {
492 OrderBookError::InvalidLotSize {
493 quantity: *quantity,
494 lot_size: *lot_size,
495 }
496 }
497 OrderBookError::OrderSizeOutOfRange { quantity, min, max } => {
498 OrderBookError::OrderSizeOutOfRange {
499 quantity: *quantity,
500 min: *min,
501 max: *max,
502 }
503 }
504 OrderBookError::DuplicateOrderId { order_id } => OrderBookError::DuplicateOrderId {
505 order_id: *order_id,
506 },
507 OrderBookError::MissingUserId { order_id } => OrderBookError::MissingUserId {
508 order_id: *order_id,
509 },
510 OrderBookError::QuantityOverflow { visible, hidden } => {
511 OrderBookError::QuantityOverflow {
512 visible: *visible,
513 hidden: *hidden,
514 }
515 }
516 OrderBookError::SelfTradePrevented {
517 mode,
518 taker_order_id,
519 user_id,
520 } => OrderBookError::SelfTradePrevented {
521 mode: *mode,
522 taker_order_id: *taker_order_id,
523 user_id: *user_id,
524 },
525 OrderBookError::RiskMaxOpenOrders {
526 account,
527 current,
528 limit,
529 } => OrderBookError::RiskMaxOpenOrders {
530 account: *account,
531 current: *current,
532 limit: *limit,
533 },
534 OrderBookError::RiskMaxNotional {
535 account,
536 current,
537 attempted,
538 limit,
539 } => OrderBookError::RiskMaxNotional {
540 account: *account,
541 current: *current,
542 attempted: *attempted,
543 limit: *limit,
544 },
545 OrderBookError::RiskPriceBand {
546 submitted,
547 reference,
548 deviation_bps,
549 limit_bps,
550 } => OrderBookError::RiskPriceBand {
551 submitted: *submitted,
552 reference: *reference,
553 deviation_bps: *deviation_bps,
554 limit_bps: *limit_bps,
555 },
556 #[cfg(feature = "nats")]
557 OrderBookError::NatsPublishError { message } => OrderBookError::NatsPublishError {
558 message: message.clone(),
559 },
560 #[cfg(feature = "nats")]
561 OrderBookError::NatsSerializationError { message } => {
562 OrderBookError::NatsSerializationError {
563 message: message.clone(),
564 }
565 }
566 }
567 }
568}
569
570#[derive(Debug, Clone)]
572#[non_exhaustive]
573pub enum ManagerError {
574 ProcessorAlreadyStarted,
576
577 BookAlreadyExists {
581 symbol: String,
583 },
584}
585
586impl fmt::Display for ManagerError {
587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588 match self {
589 ManagerError::ProcessorAlreadyStarted => {
590 write!(f, "trade processor already started")
591 }
592 ManagerError::BookAlreadyExists { symbol } => {
593 write!(f, "order book already exists for symbol: {symbol}")
594 }
595 }
596 }
597}
598
599impl std::error::Error for ManagerError {}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604 use pricelevel::{Hash32, Id};
605
606 #[test]
607 fn test_clone_order_not_found() {
608 let error = OrderBookError::OrderNotFound("order123".to_string());
609 let cloned = error.clone();
610 assert!(matches!(cloned, OrderBookError::OrderNotFound(ref s) if s == "order123"));
611 }
612
613 #[test]
614 fn test_clone_invalid_price_level() {
615 let error = OrderBookError::InvalidPriceLevel(12345);
616 let cloned = error.clone();
617 assert!(matches!(cloned, OrderBookError::InvalidPriceLevel(12345)));
618 }
619
620 #[test]
621 fn test_clone_price_crossing() {
622 let error = OrderBookError::PriceCrossing {
623 price: 100,
624 side: Side::Buy,
625 opposite_price: 99,
626 };
627 let cloned = error.clone();
628 assert!(matches!(
629 cloned,
630 OrderBookError::PriceCrossing {
631 price: 100,
632 side: Side::Buy,
633 opposite_price: 99
634 }
635 ));
636 }
637
638 #[test]
639 fn test_clone_insufficient_liquidity() {
640 let error = OrderBookError::InsufficientLiquidity {
641 side: Side::Sell,
642 requested: 1000,
643 available: 500,
644 };
645 let cloned = error.clone();
646 assert!(matches!(
647 cloned,
648 OrderBookError::InsufficientLiquidity {
649 side: Side::Sell,
650 requested: 1000,
651 available: 500
652 }
653 ));
654 }
655
656 #[test]
657 fn test_clone_insufficient_liquidity_notional() {
658 let error = OrderBookError::InsufficientLiquidityNotional {
659 side: Side::Buy,
660 requested: 1_000_000,
661 spent: 0,
662 };
663 let cloned = error.clone();
664 assert!(matches!(
665 cloned,
666 OrderBookError::InsufficientLiquidityNotional {
667 side: Side::Buy,
668 requested: 1_000_000,
669 spent: 0
670 }
671 ));
672 }
673
674 #[test]
675 fn test_display_insufficient_liquidity_notional() {
676 let error = OrderBookError::InsufficientLiquidityNotional {
677 side: Side::Sell,
678 requested: 42,
679 spent: 0,
680 };
681 let s = format!("{error}");
682 assert!(s.contains("Insufficient liquidity by notional"));
683 assert!(s.contains("42"));
684 }
685
686 #[test]
687 fn test_clone_invalid_operation() {
688 let error = OrderBookError::InvalidOperation {
689 message: "Cannot cancel filled order".to_string(),
690 };
691 let cloned = error.clone();
692 assert!(matches!(
693 cloned,
694 OrderBookError::InvalidOperation { ref message } if message == "Cannot cancel filled order"
695 ));
696 }
697
698 #[test]
699 fn test_clone_serialization_error() {
700 let error = OrderBookError::SerializationError {
701 message: "Failed to serialize".to_string(),
702 };
703 let cloned = error.clone();
704 assert!(matches!(
705 cloned,
706 OrderBookError::SerializationError { ref message } if message == "Failed to serialize"
707 ));
708 }
709
710 #[test]
711 fn test_clone_checksum_mismatch() {
712 let error = OrderBookError::ChecksumMismatch {
713 expected: "abc123".to_string(),
714 actual: "def456".to_string(),
715 };
716 let cloned = error.clone();
717 assert!(matches!(
718 cloned,
719 OrderBookError::ChecksumMismatch { ref expected, ref actual }
720 if expected == "abc123" && actual == "def456"
721 ));
722 }
723
724 #[test]
725 fn test_clone_invalid_tick_size() {
726 let error = OrderBookError::InvalidTickSize {
727 price: 10050,
728 tick_size: 100,
729 };
730 let cloned = error.clone();
731 assert!(matches!(
732 cloned,
733 OrderBookError::InvalidTickSize {
734 price: 10050,
735 tick_size: 100
736 }
737 ));
738 }
739
740 #[test]
741 fn test_clone_invalid_lot_size() {
742 let error = OrderBookError::InvalidLotSize {
743 quantity: 75,
744 lot_size: 10,
745 };
746 let cloned = error.clone();
747 assert!(matches!(
748 cloned,
749 OrderBookError::InvalidLotSize {
750 quantity: 75,
751 lot_size: 10
752 }
753 ));
754 }
755
756 #[test]
757 fn test_clone_order_size_out_of_range() {
758 let error = OrderBookError::OrderSizeOutOfRange {
759 quantity: 5,
760 min: Some(10),
761 max: Some(1000),
762 };
763 let cloned = error.clone();
764 assert!(matches!(
765 cloned,
766 OrderBookError::OrderSizeOutOfRange {
767 quantity: 5,
768 min: Some(10),
769 max: Some(1000)
770 }
771 ));
772 }
773
774 #[test]
775 fn test_clone_missing_user_id() {
776 let order_id = Id::new_uuid();
777 let error = OrderBookError::MissingUserId { order_id };
778 let cloned = error.clone();
779 assert!(matches!(
780 cloned,
781 OrderBookError::MissingUserId { order_id: id } if id == order_id
782 ));
783 }
784
785 #[test]
786 fn test_clone_self_trade_prevented() {
787 let taker_id = Id::new_uuid();
788 let user_id = Hash32::from([1u8; 32]);
789 let error = OrderBookError::SelfTradePrevented {
790 mode: crate::orderbook::stp::STPMode::CancelMaker,
791 taker_order_id: taker_id,
792 user_id,
793 };
794 let cloned = error.clone();
795 assert!(matches!(
796 cloned,
797 OrderBookError::SelfTradePrevented {
798 mode: crate::orderbook::stp::STPMode::CancelMaker,
799 taker_order_id: id,
800 user_id: uid
801 } if id == taker_id && uid == user_id
802 ));
803 }
804
805 #[test]
806 fn test_clone_price_level_error_parse_error() {
807 let price_level_err = PriceLevelError::ParseError {
808 message: "Parse failed".to_string(),
809 };
810 let error = OrderBookError::PriceLevelError(price_level_err);
811 let cloned = error.clone();
812 assert!(matches!(
813 cloned,
814 OrderBookError::PriceLevelError(PriceLevelError::ParseError { ref message })
815 if message == "Parse failed"
816 ));
817 }
818
819 #[test]
820 fn test_clone_price_level_error_invalid_format() {
821 let price_level_err = PriceLevelError::InvalidFormat;
822 let error = OrderBookError::PriceLevelError(price_level_err);
823 let cloned = error.clone();
824 assert!(matches!(
825 cloned,
826 OrderBookError::PriceLevelError(PriceLevelError::InvalidFormat)
827 ));
828 }
829
830 #[test]
831 fn test_clone_price_level_error_unknown_order_type() {
832 let price_level_err = PriceLevelError::UnknownOrderType("CUSTOM".to_string());
833 let error = OrderBookError::PriceLevelError(price_level_err);
834 let cloned = error.clone();
835 assert!(matches!(
836 cloned,
837 OrderBookError::PriceLevelError(PriceLevelError::UnknownOrderType(ref s))
838 if s == "CUSTOM"
839 ));
840 }
841
842 #[test]
843 fn test_clone_price_level_error_checksum_mismatch() {
844 let price_level_err = PriceLevelError::ChecksumMismatch {
845 expected: "hash1".to_string(),
846 actual: "hash2".to_string(),
847 };
848 let error = OrderBookError::PriceLevelError(price_level_err);
849 let cloned = error.clone();
850 assert!(matches!(
851 cloned,
852 OrderBookError::PriceLevelError(PriceLevelError::ChecksumMismatch {
853 ref expected,
854 ref actual
855 }) if expected == "hash1" && actual == "hash2"
856 ));
857 }
858}