1use anyhow::anyhow;
4use anyhow::Result;
5use bigdecimal::{BigDecimal, FromPrimitive};
6use serde_derive::{Deserialize, Serialize};
7use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
8
9use crate::products::ProductType;
10use crate::products::Side; use crate::DateTime;
12
13#[derive(Deserialize, Serialize, Debug, Eq, PartialEq)]
18pub struct OrderConfiguration {
19 pub market_market_ioc: Option<Market>,
20 pub limit_limit_gtc: Option<Limit>,
21 pub limit_limit_gtd: Option<Limit>,
22 pub stop_limit_stop_limit_gtc: Option<StopLimit>,
23 pub stop_limit_stop_limit_gtd: Option<StopLimit>,
24}
25
26#[derive(Deserialize, Serialize, Debug, Eq, PartialEq)]
28pub struct Market {
29 pub quote_size: Option<BigDecimal>,
31 pub base_size: Option<BigDecimal>,
33}
34
35#[derive(Deserialize, Serialize, Debug, Eq, PartialEq)]
39pub struct Limit {
40 pub base_size: BigDecimal,
42 pub limit_price: BigDecimal,
44 pub end_time: Option<DateTime>,
46 pub post_only: Option<bool>,
48}
49
50#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
52#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
53pub enum StopDirection {
54 UnknownStopDirection,
55 StopDirectionStopUp,
56 StopDirectionStopDown,
57}
58
59#[derive(Deserialize, Serialize, Debug, Eq, PartialEq)]
63pub struct StopLimit {
64 pub base_size: BigDecimal,
66 pub limit_price: BigDecimal,
68 pub stop_price: BigDecimal,
72 pub stop_direction: StopDirection,
73 pub end_time: Option<DateTime>,
74}
75
76#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
78#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
79pub enum Status {
80 Open,
81 Filled,
82 Cancelled,
83 Expired,
84 Failed,
85 UnknownOrderStatus,
86}
87
88#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
90#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
91pub enum TimeInForce {
92 UnknownTimeInForce,
93 GoodUntilDateTime,
94 GoodUntilCancelled,
95 ImmediateOrCancel,
96 FillOrKill,
97}
98
99#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
101#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
102pub enum TriggerStatus {
103 UnknownTriggerStatus,
104 InvalidOrderType,
105 StopPending,
106 StopTriggered,
107}
108
109#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
111#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
112pub enum OrderType {
113 UnknownOrderType,
114 Market,
115 Limit,
116 Stop,
117 #[serde(rename = "STOP_LIMIT")]
118 StopLimitOrderType,
119}
120
121#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
123#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
124pub enum RejectReason {
125 RejectReasonUnspecified,
126}
127
128#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
130#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
131pub enum OrderPlacementSource {
132 RetailSimple,
133 RetailAdvanced,
134}
135
136#[derive(Deserialize, Debug, Eq, PartialEq)]
138pub struct Order {
139 pub order_id: String,
141 pub product_id: String,
143 pub user_id: String,
145 pub order_configuration: OrderConfiguration,
146 pub side: Side,
148 pub client_order_id: String,
150 pub status: Status,
152 pub time_in_force: TimeInForce,
154 pub created_time: DateTime,
156 pub completion_percentage: String,
158 pub filled_size: String,
160 pub average_filled_price: String,
162 pub fee: String,
164 pub number_of_fills: String,
166 pub filled_value: String,
168 pub pending_cancel: bool,
170 pub size_in_quote: bool,
172 pub total_fees: String,
174 pub size_inclusive_of_fees: bool,
176 pub total_value_after_fees: String,
178 pub trigger_status: TriggerStatus,
180 pub order_type: OrderType,
182 pub reject_reason: RejectReason,
184 pub settled: bool,
186 pub product_type: ProductType,
188 pub reject_message: Option<String>,
190 pub cancel_message: Option<String>,
192 pub order_placement_source: OrderPlacementSource,
194 pub outstanding_hold_amount: String,
196 pub is_liquidation: bool,
198}
199
200#[doc(hidden)]
201#[derive(Deserialize, Debug)]
203pub struct OrderResponse {
204 pub order: Order,
205}
206
207pub type OrderSide = crate::products::Side;
208pub type TradeType = crate::products::TradeType;
209
210#[doc(hidden)]
211#[derive(Deserialize, Debug)]
213pub struct OrdersResponse {
214 pub orders: Vec<Order>,
215 pub sequence: String,
216 pub has_next: bool,
217 pub cursor: String,
218}
219
220#[derive(Deserialize, Debug)]
222pub struct Fill {
223 pub entry_id: String,
225 pub trade_id: String,
227 pub order_id: String,
229 pub trade_time: DateTime,
231 pub trade_type: TradeType,
233 pub price: String,
235 pub size: String,
237 pub commission: String,
239 pub product_id: String,
241 pub sequence_timestamp: DateTime,
243 pub liquidity_indicator: LiquidityIndicator,
245 pub size_in_quote: bool,
247 pub user_id: String,
249 pub side: OrderSide,
251}
252
253#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
255#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
256pub enum LiquidityIndicator {
257 UnknownLiquidityIndicator,
258 Maker,
259 Taker,
260}
261
262#[doc(hidden)]
263#[derive(Deserialize, Debug)]
264pub struct FillsResponse {
265 pub fills: Vec<Fill>,
266 pub cursor: String,
267 }
269
270#[derive(Serialize, Debug)]
272pub struct OrderToSend {
273 client_order_id: String,
275 product_id: String,
277 side: OrderSide,
279 order_configuration: OrderConfiguration,
280}
281
282#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
284#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
285pub enum CreateOrderFailureReason {
286 UnknownFailureReason,
287 UnsupportedOrderConfiguration,
288 InvalidSide,
289 InvalidProductId,
290 InvalidSizePrecision,
291 InvalidPricePrecision,
292 InsufficientFund,
293 InvalidLedgerBalance,
294 OrderEntryDisabled,
295 IneligiblePair,
296 InvalidLimitPricePostOnly,
297 InvalidLimitPrice,
298 InvalidNoLiquidity,
299 InvalidRequest,
300 CommanderRejectedNewOrder,
301 InsufficientFunds,
302}
303
304#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
306#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
307pub enum PreviewCreateOrderFailureReason {
308 UnknownPreviewFailureReason,
309 PreviewMissingCommissionRate,
310 PreviewInvalidSide,
311 PreviewInvalidOrderConfig,
312 PreviewInvalidProductId,
313 PreviewInvalidSizePrecision,
314 PreviewInvalidPricePrecision,
315 PreviewMissingProductPriceBook,
316 PreviewInvalidLedgerBalance,
317 PreviewInsufficientLedgerBalance,
318 PreviewInvalidLimitPricePostOnly,
319 PreviewInvalidLimitPrice,
320 PreviewInvalidNoLiquidity,
321 PreviewInsufficientFund,
322 PreviewInvalidCommissionConfiguration,
323 PreviewInvalidStopPrice,
324 PreviewInvalidBaseSizeTooLarge,
325 PreviewInvalidBaseSizeTooSmall,
326 PreviewInvalidQuoteSizePrecision,
327 PreviewInvalidQuoteSizeTooLarge,
328 PreviewInvalidPriceTooLarge,
329 PreviewInvalidQuoteSizeTooSmall,
330 PreviewInsufficientFundsForFutures,
331 PreviewBreachedPriceLimit,
332 PreviewBreachedAccountPositionLimit,
333 PreviewBreachedCompanyPositionLimit,
334 PreviewInvalidMarginHealth,
335 PreviewRiskProxyFailure,
336 PreviewUntradableFcmAccountStatus,
337}
338
339#[doc(hidden)]
340#[derive(Deserialize, Debug)]
341pub struct OrderSuccessResponse {
342 pub order_id: String,
343 pub product_id: String,
344 pub side: OrderSide,
345 pub client_order_id: String,
346}
347
348#[doc(hidden)]
349#[derive(Deserialize, Debug)]
350pub struct OrderErrorResponse {
351 pub error: CreateOrderFailureReason,
352 pub message: String,
353 pub error_details: String,
354 pub preview_failure_reason: PreviewCreateOrderFailureReason,
355 pub new_order_failure_reason: CreateOrderFailureReason,
356}
357
358#[derive(Deserialize, Debug)]
360pub struct CreateOrderResponse {
361 pub success: bool,
363 pub failure_reason: CreateOrderFailureReason,
364 pub order_id: String,
366 pub success_response: Option<OrderSuccessResponse>,
367 pub error_response: Option<OrderErrorResponse>,
368 pub order_configuration: OrderConfiguration,
369}
370
371#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
373#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
374pub enum CancelOrderFailureReason {
375 UnknownCancelFailureReason,
376 InvalidCancelRequest,
377 UnknownCancelOrder,
378 CommanderRejectedCancelOrder,
379 DuplicateCancelRequest,
380}
381
382#[derive(Deserialize, Debug)]
384pub struct CancelOrderResponse {
385 pub success: bool,
387 pub failure_reason: Option<CancelOrderFailureReason>,
388 pub order_id: String,
390}
391
392#[doc(hidden)]
393#[derive(Deserialize, Debug)]
394pub struct CancelOrdersResponse {
395 pub results: Vec<CancelOrderResponse>,
396}
397
398pub fn create_market_order(
406 product_id: &str,
407 side: OrderSide,
408 order_size: f64,
409) -> Result<OrderToSend> {
410 let client_order_id = uuid::Uuid::new_v4().to_string();
411
412 let mut base_size = None;
413 let mut quote_size = None;
414
415 let order_size = f64_to_valid_bigdecimal(order_size)?;
416
417 match side {
418 OrderSide::Buy => quote_size = Some(order_size),
419 OrderSide::Sell => base_size = Some(order_size),
420 _ => {
421 return Err(anyhow!(
422 "Orders' side should be Buy or Sell . Got: {:?}",
423 side
424 ));
425 }
426 }
427
428 let order = OrderToSend {
429 client_order_id,
430 product_id: product_id.to_string(),
431 side,
432 order_configuration: OrderConfiguration {
433 market_market_ioc: Some(Market {
434 base_size,
435 quote_size,
436 }),
437 limit_limit_gtc: None,
438 limit_limit_gtd: None,
439 stop_limit_stop_limit_gtc: None,
440 stop_limit_stop_limit_gtd: None,
441 },
442 };
443 Ok(order)
444}
445
446pub fn create_limit_order_good_til_canceled(
452 product_id: &str,
453 side: OrderSide,
454 base_size: f64,
455 limit_price: f64,
456 post_only: bool,
457) -> Result<OrderToSend> {
458 let client_order_id = uuid::Uuid::new_v4().to_string();
459 anyhow::ensure!(
460 side == OrderSide::Buy || side == OrderSide::Sell,
461 "Orders' side should be Buy or Sell . Got: {:?}",
462 side
463 );
464 let base_size = f64_to_valid_bigdecimal(base_size)?;
465 let limit_price = f64_to_valid_bigdecimal(limit_price)?;
466
467 let order = OrderToSend {
468 client_order_id,
469 product_id: product_id.to_string(),
470 side,
471 order_configuration: OrderConfiguration {
472 market_market_ioc: None,
473 limit_limit_gtc: Some(Limit {
474 base_size,
475 limit_price,
476 end_time: None,
477 post_only: Some(post_only),
478 }),
479 limit_limit_gtd: None,
480 stop_limit_stop_limit_gtc: None,
481 stop_limit_stop_limit_gtd: None,
482 },
483 };
484 Ok(order)
485}
486
487pub fn create_limit_order_good_til_date(
493 product_id: &str,
494 side: OrderSide,
495 base_size: f64,
496 limit_price: f64,
497 end_time: DateTime,
498 post_only: bool,
499) -> Result<OrderToSend> {
500 let client_order_id = uuid::Uuid::new_v4().to_string();
501 anyhow::ensure!(
502 side == OrderSide::Buy || side == OrderSide::Sell,
503 "Orders' side should be Buy or Sell . Got: {:?}",
504 side
505 );
506 let size = f64_to_valid_bigdecimal(base_size)?;
507 let price = f64_to_valid_bigdecimal(limit_price)?;
508
509 let order = OrderToSend {
510 client_order_id,
511 product_id: product_id.to_string(),
512 side,
513 order_configuration: OrderConfiguration {
514 market_market_ioc: None,
515 limit_limit_gtc: None,
516 limit_limit_gtd: Some(Limit {
517 base_size: size,
518 limit_price: price,
519 end_time: Some(end_time),
520 post_only: Some(post_only),
521 }),
522 stop_limit_stop_limit_gtc: None,
523 stop_limit_stop_limit_gtd: None,
524 },
525 };
526 Ok(order)
527}
528
529pub fn create_stop_limit_order_good_til_canceled(
535 product_id: &str,
536 side: OrderSide,
537 base_size: f64,
538 limit_price: f64,
539 stop_price: f64,
540 stop_direction: StopDirection,
541) -> Result<OrderToSend> {
542 let client_order_id = uuid::Uuid::new_v4().to_string();
543 anyhow::ensure!(
544 side == OrderSide::Buy || side == OrderSide::Sell,
545 "Orders' side should be Buy or Sell . Got: {:?}",
546 side
547 );
548 let base_size = f64_to_valid_bigdecimal(base_size)?;
549 let limit_price = f64_to_valid_bigdecimal(limit_price)?;
550 let stop_price = f64_to_valid_bigdecimal(stop_price)?;
551
552 let order = OrderToSend {
553 client_order_id,
554 product_id: product_id.to_string(),
555 side,
556 order_configuration: OrderConfiguration {
557 market_market_ioc: None,
558 limit_limit_gtc: None,
559 limit_limit_gtd: None,
560 stop_limit_stop_limit_gtc: Some(StopLimit {
561 base_size,
562 limit_price,
563 stop_price,
564 end_time: None,
565 stop_direction,
566 }),
567 stop_limit_stop_limit_gtd: None,
568 },
569 };
570 Ok(order)
571}
572
573pub fn create_stop_limit_order_good_til_date(
579 product_id: &str,
580 side: OrderSide,
581 base_size: f64,
582 limit_price: f64,
583 stop_price: f64,
584 end_time: DateTime,
585 stop_direction: StopDirection,
586) -> Result<OrderToSend> {
587 let client_order_id = uuid::Uuid::new_v4().to_string();
588 anyhow::ensure!(
589 side == OrderSide::Buy || side == OrderSide::Sell,
590 "Orders' side should be Buy or Sell . Got: {:?}",
591 side
592 );
593 let base_size = f64_to_valid_bigdecimal(base_size)?;
594 let limit_price = f64_to_valid_bigdecimal(limit_price)?;
595 let stop_price = f64_to_valid_bigdecimal(stop_price)?;
596
597 let order = OrderToSend {
598 client_order_id,
599 product_id: product_id.to_string(),
600 side,
601 order_configuration: OrderConfiguration {
602 market_market_ioc: None,
603 limit_limit_gtc: None,
604 limit_limit_gtd: None,
605 stop_limit_stop_limit_gtc: None,
606 stop_limit_stop_limit_gtd: Some(StopLimit {
607 base_size,
608 limit_price,
609 stop_price,
610 end_time: Some(end_time),
611 stop_direction,
612 }),
613 },
614 };
615 Ok(order)
616}
617
618fn f64_to_valid_bigdecimal(x: f64) -> Result<BigDecimal> {
622 FromPrimitive::from_f64(x).ok_or(anyhow!("Could not convert {} to BigDecimal", x))
623}
624
625#[cfg(test)]
628mod tests {
629 use super::*;
630 #[test]
631 fn test_order_deserialize() {
632 let input = r##"{
633 "order": {
634 "order_id": "0000-000000-000000",
635 "product_id": "BTC-USD",
636 "user_id": "2222-000000-000000",
637 "order_configuration": {
638 "market_market_ioc": {
639 "quote_size": "10.00",
640 "base_size": "0.001"
641 },
642 "limit_limit_gtc": {
643 "base_size": "0.001",
644 "limit_price": "10000.00",
645 "post_only": false
646 },
647 "limit_limit_gtd": {
648 "base_size": "0.001",
649 "limit_price": "10000.00",
650 "end_time": "2021-05-31T09:59:59Z",
651 "post_only": false
652 },
653 "stop_limit_stop_limit_gtc": {
654 "base_size": "0.001",
655 "limit_price": "10000.00",
656 "stop_price": "20000.00",
657 "stop_direction": "UNKNOWN_STOP_DIRECTION"
658 },
659 "stop_limit_stop_limit_gtd": {
660 "base_size": "0.001",
661 "limit_price": "10000.00",
662 "stop_price": "20000.00",
663 "end_time": "2021-05-31T09:59:59Z",
664 "stop_direction": "UNKNOWN_STOP_DIRECTION"
665 }
666 },
667 "side": "UNKNOWN_ORDER_SIDE",
668 "client_order_id": "11111-000000-000000",
669 "status": "OPEN",
670 "time_in_force": "UNKNOWN_TIME_IN_FORCE",
671 "created_time": "2021-05-31T09:59:59Z",
672 "completion_percentage": "50",
673 "filled_size": "0.001",
674 "average_filled_price": "50",
675 "fee": "string",
676 "number_of_fills": "2",
677 "filled_value": "10000",
678 "pending_cancel": true,
679 "size_in_quote": false,
680 "total_fees": "5.00",
681 "size_inclusive_of_fees": false,
682 "total_value_after_fees": "string",
683 "trigger_status": "UNKNOWN_TRIGGER_STATUS",
684 "order_type": "UNKNOWN_ORDER_TYPE",
685 "reject_reason": "REJECT_REASON_UNSPECIFIED",
686 "settled": false,
687 "product_type": "SPOT",
688 "reject_message": "string",
689 "cancel_message": "string",
690 "order_placement_source": "RETAIL_ADVANCED",
691 "outstanding_hold_amount": "string",
692 "is_liquidation": false
693 }
694 }"##;
695 let result: OrderResponse = serde_json::from_slice(input.as_bytes()).unwrap();
696 let order = result.order;
697 assert_eq!(order.product_id, "BTC-USD".to_string());
698 assert!(!order
699 .order_configuration
700 .limit_limit_gtc
701 .unwrap()
702 .post_only
703 .unwrap());
704 }
705
706 #[test]
707 fn test_stop_direction_deserialize() {
708 let input = r##""UNKNOWN_STOP_DIRECTION""##;
709 let result: StopDirection = serde_json::from_slice(input.as_bytes()).unwrap();
710 assert_eq!(result, StopDirection::UnknownStopDirection);
711
712 let input = r##""STOP_DIRECTION_STOP_UP""##;
713 let result: StopDirection = serde_json::from_slice(input.as_bytes()).unwrap();
714 assert_eq!(result, StopDirection::StopDirectionStopUp);
715
716 let input = r##""STOP_DIRECTION_STOP_DOWN""##;
717 let result: StopDirection = serde_json::from_slice(input.as_bytes()).unwrap();
718 assert_eq!(result, StopDirection::StopDirectionStopDown);
719 }
720
721 #[test]
722 fn test_stop_direction_serialize() {
723 let expected = r##""STOP_DIRECTION_STOP_DOWN""##;
724 assert_eq!(
725 expected,
726 serde_json::to_string(&StopDirection::StopDirectionStopDown).unwrap()
727 );
728 }
729
730 #[test]
731 fn test_status_deserialize() {
732 let input = r##""OPEN""##;
733 let result: Status = serde_json::from_slice(input.as_bytes()).unwrap();
734 assert_eq!(result, Status::Open);
735
736 let input = r##""FILLED""##;
737 let result: Status = serde_json::from_slice(input.as_bytes()).unwrap();
738 assert_eq!(result, Status::Filled);
739
740 let input = r##""CANCELLED""##;
741 let result: Status = serde_json::from_slice(input.as_bytes()).unwrap();
742 assert_eq!(result, Status::Cancelled);
743
744 let input = r##""EXPIRED""##;
745 let result: Status = serde_json::from_slice(input.as_bytes()).unwrap();
746 assert_eq!(result, Status::Expired);
747
748 let input = r##""FAILED""##;
749 let result: Status = serde_json::from_slice(input.as_bytes()).unwrap();
750 assert_eq!(result, Status::Failed);
751
752 let input = r##""UNKNOWN_ORDER_STATUS""##;
753 let result: Status = serde_json::from_slice(input.as_bytes()).unwrap();
754 assert_eq!(result, Status::UnknownOrderStatus);
755 }
756
757 #[test]
758 fn test_status_serialize() {
759 let expected = r##""FILLED""##;
760 assert_eq!(expected, serde_json::to_string(&Status::Filled).unwrap());
761 }
762
763 #[test]
764 fn test_time_in_force_deserialize() {
765 let input = r##""UNKNOWN_TIME_IN_FORCE""##;
766 let result: TimeInForce = serde_json::from_slice(input.as_bytes()).unwrap();
767 assert_eq!(result, TimeInForce::UnknownTimeInForce);
768
769 let input = r##""GOOD_UNTIL_DATE_TIME""##;
770 let result: TimeInForce = serde_json::from_slice(input.as_bytes()).unwrap();
771 assert_eq!(result, TimeInForce::GoodUntilDateTime);
772
773 let input = r##""GOOD_UNTIL_CANCELLED""##;
774 let result: TimeInForce = serde_json::from_slice(input.as_bytes()).unwrap();
775 assert_eq!(result, TimeInForce::GoodUntilCancelled);
776
777 let input = r##""IMMEDIATE_OR_CANCEL""##;
778 let result: TimeInForce = serde_json::from_slice(input.as_bytes()).unwrap();
779 assert_eq!(result, TimeInForce::ImmediateOrCancel);
780
781 let input = r##""FILL_OR_KILL""##;
782 let result: TimeInForce = serde_json::from_slice(input.as_bytes()).unwrap();
783 assert_eq!(result, TimeInForce::FillOrKill);
784 }
785
786 #[test]
787 fn test_time_in_force_serialize() {
788 let expected = r##""GOOD_UNTIL_CANCELLED""##;
789 assert_eq!(
790 expected,
791 serde_json::to_string(&TimeInForce::GoodUntilCancelled).unwrap()
792 );
793 }
794
795 #[test]
796 fn test_trigger_status_deserialize() {
797 let input = r##""UNKNOWN_TRIGGER_STATUS""##;
798 let result: TriggerStatus = serde_json::from_slice(input.as_bytes()).unwrap();
799 assert_eq!(result, TriggerStatus::UnknownTriggerStatus);
800
801 let input = r##""INVALID_ORDER_TYPE""##;
802 let result: TriggerStatus = serde_json::from_slice(input.as_bytes()).unwrap();
803 assert_eq!(result, TriggerStatus::InvalidOrderType);
804
805 let input = r##""STOP_PENDING""##;
806 let result: TriggerStatus = serde_json::from_slice(input.as_bytes()).unwrap();
807 assert_eq!(result, TriggerStatus::StopPending);
808
809 let input = r##""STOP_TRIGGERED""##;
810 let result: TriggerStatus = serde_json::from_slice(input.as_bytes()).unwrap();
811 assert_eq!(result, TriggerStatus::StopTriggered);
812 }
813
814 #[test]
815 fn test_trigger_status_serialize() {
816 let expected = r##""INVALID_ORDER_TYPE""##;
817 assert_eq!(
818 expected,
819 serde_json::to_string(&TriggerStatus::InvalidOrderType).unwrap()
820 );
821 }
822
823 #[test]
824 fn test_order_type_deserialize() {
825 let input = r##""UNKNOWN_ORDER_TYPE""##;
826 let result: OrderType = serde_json::from_slice(input.as_bytes()).unwrap();
827 assert_eq!(result, OrderType::UnknownOrderType);
828
829 let input = r##""MARKET""##;
830 let result: OrderType = serde_json::from_slice(input.as_bytes()).unwrap();
831 assert_eq!(result, OrderType::Market);
832
833 let input = r##""LIMIT""##;
834 let result: OrderType = serde_json::from_slice(input.as_bytes()).unwrap();
835 assert_eq!(result, OrderType::Limit);
836
837 let input = r##""STOP""##;
838 let result: OrderType = serde_json::from_slice(input.as_bytes()).unwrap();
839 assert_eq!(result, OrderType::Stop);
840
841 let input = r##""STOP_LIMIT""##;
842 let result: OrderType = serde_json::from_slice(input.as_bytes()).unwrap();
843 assert_eq!(result, OrderType::StopLimitOrderType);
844 }
845
846 #[test]
847 fn test_order_type_serialize() {
848 let expected = r##""MARKET""##;
849 assert_eq!(expected, serde_json::to_string(&OrderType::Market).unwrap());
850 }
851
852 #[test]
853 fn test_reject_reason_deserialize() {
854 let input = r##""REJECT_REASON_UNSPECIFIED""##;
855 let result: RejectReason = serde_json::from_slice(input.as_bytes()).unwrap();
856 assert_eq!(result, RejectReason::RejectReasonUnspecified);
857 }
858
859 #[test]
860 fn test_reject_reason_serialize() {
861 let expected = r##""REJECT_REASON_UNSPECIFIED""##;
862 assert_eq!(
863 expected,
864 serde_json::to_string(&RejectReason::RejectReasonUnspecified).unwrap()
865 );
866 }
867
868 #[test]
869 fn test_order_placement_source_deserialize() {
870 let input = r##""RETAIL_SIMPLE""##;
871 let result: OrderPlacementSource = serde_json::from_slice(input.as_bytes()).unwrap();
872 assert_eq!(result, OrderPlacementSource::RetailSimple);
873
874 let input = r##""RETAIL_ADVANCED""##;
875 let result: OrderPlacementSource = serde_json::from_slice(input.as_bytes()).unwrap();
876 assert_eq!(result, OrderPlacementSource::RetailAdvanced);
877 }
878
879 #[test]
880 fn test_order_placement_source_serialize() {
881 let expected = r##""RETAIL_SIMPLE""##;
882 assert_eq!(
883 expected,
884 serde_json::to_string(&OrderPlacementSource::RetailSimple).unwrap()
885 );
886 }
887
888 #[test]
889 fn test_create_market_order_serialize() {
890 let product_id = "BTC-USD";
891 let side = OrderSide::Buy;
892 let order_size = 0.00001;
893 let order = create_market_order(product_id, side, order_size).unwrap();
894 let json = serde_json::to_string(&order);
895 assert!(json.is_ok());
896 }
897
898 #[test]
899 fn test_create_limit_order_good_til_canceled_serialize() {
900 let product_id = "BTC-USD";
901 let side = OrderSide::Buy;
902 let base_size = 0.00001;
903 let limit_price = 5000.0;
904 let post_only = false;
905 let order = create_limit_order_good_til_canceled(
906 product_id,
907 side,
908 base_size,
909 limit_price,
910 post_only,
911 )
912 .unwrap();
913 let json = serde_json::to_string(&order);
914 assert!(json.is_ok());
915 }
916
917 #[test]
918 fn test_create_limit_order_good_til_date_serialize() {
919 let product_id = "BTC-USD";
920 let side = OrderSide::Buy;
921 let base_size = 0.00001;
922 let limit_price = 5000.0;
923 let end_time = chrono::offset::Utc::now(); let post_only = false;
925 let order = create_limit_order_good_til_date(
926 product_id,
927 side,
928 base_size,
929 limit_price,
930 end_time,
931 post_only,
932 )
933 .unwrap();
934 let json = serde_json::to_string(&order);
935 assert!(json.is_ok());
936 }
937
938 #[test]
939 fn test_create_stop_limit_order_good_til_canceled_serialize() {
940 let product_id = "BTC-USD";
941 let side = OrderSide::Buy;
942 let base_size = 0.00001;
943 let limit_price = 5000.0;
944 let stop_price = 4000.0;
945 let stop_direction = StopDirection::StopDirectionStopUp;
946 let order = create_stop_limit_order_good_til_canceled(
947 product_id,
948 side,
949 base_size,
950 limit_price,
951 stop_price,
952 stop_direction,
953 )
954 .unwrap();
955 let json = serde_json::to_string(&order);
956 assert!(json.is_ok());
957 }
958
959 #[test]
960 fn test_create_stop_limit_order_good_til_date_serialize() {
961 let product_id = "BTC-USD";
962 let side = OrderSide::Buy;
963 let base_size = 0.00001;
964 let limit_price = 5000.0;
965 let stop_price = 4000.0;
966 let end_time = chrono::offset::Utc::now(); let stop_direction = StopDirection::StopDirectionStopUp;
968 let order = create_stop_limit_order_good_til_date(
969 product_id,
970 side,
971 base_size,
972 limit_price,
973 stop_price,
974 end_time,
975 stop_direction,
976 )
977 .unwrap();
978 let json = serde_json::to_string(&order);
979 assert!(json.is_ok());
980 }
981
982 #[test]
983 fn test_order_response_serde() {
984 let input = r##"{
985 "success": true,
986 "failure_reason": "INVALID_SIDE",
987 "order_id": "string",
988 "success_response": {
989 "order_id": "11111-00000-000000",
990 "product_id": "BTC-USD",
991 "side": "UNKNOWN_ORDER_SIDE",
992 "client_order_id": "0000-00000-000000"
993 },
994 "error_response": {
995 "error": "UNKNOWN_FAILURE_REASON",
996 "message": "The order configuration was invalid",
997 "error_details": "Market orders cannot be placed with empty order sizes",
998 "preview_failure_reason": "UNKNOWN_PREVIEW_FAILURE_REASON",
999 "new_order_failure_reason": "UNKNOWN_FAILURE_REASON"
1000 },
1001 "order_configuration": {
1002 "market_market_ioc": {
1003 "quote_size": "10.00",
1004 "base_size": "0.001"
1005 },
1006 "limit_limit_gtc": {
1007 "base_size": "0.001",
1008 "limit_price": "10000.00",
1009 "post_only": false
1010 },
1011 "limit_limit_gtd": {
1012 "base_size": "0.001",
1013 "limit_price": "10000.00",
1014 "end_time": "2021-05-31T09:59:59Z",
1015 "post_only": false
1016 },
1017 "stop_limit_stop_limit_gtc": {
1018 "base_size": "0.001",
1019 "limit_price": "10000.00",
1020 "stop_price": "20000.00",
1021 "stop_direction": "UNKNOWN_STOP_DIRECTION"
1022 },
1023 "stop_limit_stop_limit_gtd": {
1024 "base_size": 0.001,
1025 "limit_price": "10000.00",
1026 "stop_price": "20000.00",
1027 "end_time": "2021-05-31T09:59:59Z",
1028 "stop_direction": "UNKNOWN_STOP_DIRECTION"
1029 }
1030 }
1031 }"##;
1032 let result: CreateOrderResponse = serde_json::from_slice(input.as_bytes()).unwrap();
1033 assert!(result.success);
1034 assert!(!result
1035 .order_configuration
1036 .limit_limit_gtc
1037 .unwrap()
1038 .post_only
1039 .unwrap());
1040 }
1041
1042 #[test]
1043 fn test_order_response_success_serde() {
1044 let input = r##"{
1045 "success": true,
1046 "failure_reason": "UNKNOWN_FAILURE_REASON",
1047 "order_id": "11111111-4c82-40e2-980a-222222222222",
1048 "success_response": {
1049 "order_id": "11111111-4c82-40e2-980a-222222222222",
1050 "product_id": "BTC-USDT",
1051 "side": "BUY",
1052 "client_order_id": "33333333-74f5-4508-8b9a-222222222222"
1053 },
1054 "order_configuration": {
1055 "limit_limit_gtd": {
1056 "base_size": "1.000000000000000",
1057 "limit_price": "0.01000000000000000",
1058 "end_time": "2023-08-17T04:59:45.166512756Z",
1059 "post_only": false
1060 }
1061 }
1062 }"##;
1063 let result: CreateOrderResponse = serde_json::from_slice(input.as_bytes()).unwrap();
1064 assert!(result.success);
1065 assert!(!result
1066 .order_configuration
1067 .limit_limit_gtd
1068 .unwrap()
1069 .post_only
1070 .unwrap());
1071 }
1072
1073 #[test]
1074 fn test_cancel_orders_response_serde() {
1075 let input = r##"{
1076 "results": [
1077 {
1078 "success":false,
1079 "failure_reason": "UNKNOWN_CANCEL_ORDER",
1080 "order_id": "foo"
1081 }
1082 ]
1083 }"##;
1084 let results: CancelOrdersResponse = serde_json::from_slice(input.as_bytes()).unwrap();
1085 let result = &results.results[0];
1086 assert!(!result.success);
1087 }
1088}