1use std::ops::Deref;
5use std::ops::Not;
6
7use chrono::DateTime;
8use chrono::Utc;
9
10use http::Method;
11use http_endpoint::Bytes;
12
13use num_decimal::Num;
14
15use serde::de::IntoDeserializer;
16use serde::Deserialize;
17use serde::Deserializer;
18use serde::Serialize;
19use serde_json::from_slice as from_json;
20use serde_json::to_vec as to_json;
21use serde_urlencoded::to_string as to_query;
22
23use uuid::Uuid;
24
25use crate::api::v2::asset;
26use crate::util::vec_from_str;
27use crate::Str;
28
29
30#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
32pub struct Id(pub Uuid);
33
34impl Deref for Id {
35 type Target = Uuid;
36
37 #[inline]
38 fn deref(&self) -> &Self::Target {
39 &self.0
40 }
41}
42
43
44#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
46#[non_exhaustive]
47pub enum Status {
48 #[serde(rename = "new")]
51 New,
52 #[serde(rename = "replaced")]
54 Replaced,
55 #[serde(rename = "partially_filled")]
57 PartiallyFilled,
58 #[serde(rename = "filled")]
61 Filled,
62 #[serde(rename = "done_for_day")]
65 DoneForDay,
66 #[serde(rename = "canceled")]
71 Canceled,
72 #[serde(rename = "expired")]
75 Expired,
76 #[serde(rename = "accepted")]
79 Accepted,
80 #[serde(rename = "pending_new")]
84 PendingNew,
85 #[serde(rename = "accepted_for_bidding")]
88 AcceptedForBidding,
89 #[serde(rename = "pending_cancel")]
92 PendingCancel,
93 #[serde(rename = "pending_replace")]
95 PendingReplace,
96 #[serde(rename = "stopped")]
100 Stopped,
101 #[serde(rename = "rejected")]
105 Rejected,
106 #[serde(rename = "suspended")]
109 Suspended,
110 #[serde(rename = "calculated")]
114 Calculated,
115 #[serde(rename = "held")]
119 Held,
120 #[doc(hidden)]
124 #[serde(other, rename(serialize = "unknown"))]
125 Unknown,
126}
127
128impl Status {
129 #[inline]
132 pub fn is_terminal(self) -> bool {
133 matches!(
134 self,
135 Self::Replaced | Self::Filled | Self::Canceled | Self::Expired | Self::Rejected
136 )
137 }
138}
139
140
141#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
143pub enum Side {
144 #[serde(rename = "buy")]
146 Buy,
147 #[serde(rename = "sell")]
149 Sell,
150}
151
152impl Not for Side {
153 type Output = Self;
154
155 #[inline]
156 fn not(self) -> Self::Output {
157 match self {
158 Self::Buy => Self::Sell,
159 Self::Sell => Self::Buy,
160 }
161 }
162}
163
164
165#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
167#[non_exhaustive]
168pub enum Class {
169 #[serde(rename = "simple")]
172 Simple,
173 #[serde(rename = "bracket")]
177 Bracket,
178 #[serde(rename = "oco")]
183 OneCancelsOther,
184 #[serde(rename = "oto")]
188 OneTriggersOther,
189}
190
191impl Default for Class {
192 #[inline]
193 fn default() -> Self {
194 Self::Simple
195 }
196}
197
198
199#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
202#[non_exhaustive]
203pub enum Type {
204 #[serde(rename = "market")]
206 Market,
207 #[serde(rename = "limit")]
209 Limit,
210 #[serde(rename = "stop")]
212 Stop,
213 #[serde(rename = "stop_limit")]
215 StopLimit,
216 #[serde(rename = "trailing_stop")]
218 TrailingStop,
219}
220
221impl Default for Type {
222 #[inline]
223 fn default() -> Self {
224 Self::Market
225 }
226}
227
228
229#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
231#[non_exhaustive]
232pub enum TimeInForce {
233 #[serde(rename = "day")]
236 Day,
237 #[serde(rename = "fok")]
240 FillOrKill,
241 #[serde(rename = "ioc")]
244 ImmediateOrCancel,
245 #[serde(rename = "gtc")]
247 UntilCanceled,
248 #[serde(rename = "opg")]
251 UntilMarketOpen,
252 #[serde(rename = "cls")]
255 UntilMarketClose,
256}
257
258impl Default for TimeInForce {
259 #[inline]
260 fn default() -> Self {
261 Self::Day
262 }
263}
264
265
266#[derive(Debug, Deserialize, Serialize)]
267#[serde(rename = "take_profit")]
268struct TakeProfitSerde {
269 #[serde(rename = "limit_price")]
270 limit_price: Num,
271}
272
273
274#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
277#[serde(from = "TakeProfitSerde", into = "TakeProfitSerde")]
278#[non_exhaustive]
279pub enum TakeProfit {
280 Limit(Num),
282}
283
284impl From<TakeProfitSerde> for TakeProfit {
285 fn from(other: TakeProfitSerde) -> Self {
286 Self::Limit(other.limit_price)
287 }
288}
289
290impl From<TakeProfit> for TakeProfitSerde {
291 fn from(other: TakeProfit) -> Self {
292 match other {
293 TakeProfit::Limit(limit_price) => Self { limit_price },
294 }
295 }
296}
297
298
299#[derive(Debug, Deserialize, Serialize)]
300#[serde(rename = "stop_loss")]
301struct StopLossSerde {
302 #[serde(rename = "stop_price")]
303 stop_price: Num,
304 #[serde(rename = "limit_price", skip_serializing_if = "Option::is_none")]
305 limit_price: Option<Num>,
306}
307
308
309#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
312#[serde(from = "StopLossSerde", into = "StopLossSerde")]
313#[non_exhaustive]
314pub enum StopLoss {
315 Stop(Num),
317 StopLimit(Num, Num),
319}
320
321impl From<StopLossSerde> for StopLoss {
322 fn from(other: StopLossSerde) -> Self {
323 if let Some(limit_price) = other.limit_price {
324 Self::StopLimit(other.stop_price, limit_price)
325 } else {
326 Self::Stop(other.stop_price)
327 }
328 }
329}
330
331impl From<StopLoss> for StopLossSerde {
332 fn from(other: StopLoss) -> Self {
333 match other {
334 StopLoss::Stop(stop_price) => Self {
335 stop_price,
336 limit_price: None,
337 },
338 StopLoss::StopLimit(stop_price, limit_price) => Self {
339 stop_price,
340 limit_price: Some(limit_price),
341 },
342 }
343 }
344}
345
346
347#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
349#[serde(untagged)]
350pub enum Amount {
351 Quantity {
353 #[serde(rename = "qty")]
356 quantity: Num,
357 },
358 Notional {
360 #[serde(rename = "notional")]
363 notional: Num,
364 },
365}
366
367impl Amount {
368 #[inline]
370 pub fn quantity(amount: impl Into<Num>) -> Self {
371 Self::Quantity {
372 quantity: amount.into(),
373 }
374 }
375
376 #[inline]
378 pub fn notional(amount: impl Into<Num>) -> Self {
379 Self::Notional {
380 notional: amount.into(),
381 }
382 }
383}
384
385
386#[derive(Clone, Debug, Default, Eq, PartialEq)]
388pub struct CreateReqInit {
389 pub class: Class,
391 pub type_: Type,
393 pub time_in_force: TimeInForce,
395 pub limit_price: Option<Num>,
397 pub stop_price: Option<Num>,
399 pub trail_price: Option<Num>,
401 pub trail_percent: Option<Num>,
403 pub take_profit: Option<TakeProfit>,
405 pub stop_loss: Option<StopLoss>,
407 pub extended_hours: bool,
409 pub client_order_id: Option<String>,
411 #[doc(hidden)]
413 pub _non_exhaustive: (),
414}
415
416impl CreateReqInit {
417 pub fn init<S>(self, symbol: S, side: Side, amount: Amount) -> CreateReq
424 where
425 S: Into<String>,
426 {
427 CreateReq {
428 symbol: asset::Symbol::Sym(symbol.into()),
429 amount,
430 side,
431 class: self.class,
432 type_: self.type_,
433 time_in_force: self.time_in_force,
434 limit_price: self.limit_price,
435 stop_price: self.stop_price,
436 take_profit: self.take_profit,
437 stop_loss: self.stop_loss,
438 extended_hours: self.extended_hours,
439 client_order_id: self.client_order_id,
440 trail_price: self.trail_price,
441 trail_percent: self.trail_percent,
442 _non_exhaustive: (),
443 }
444 }
445}
446
447
448#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
450pub struct CreateReq {
451 #[serde(rename = "symbol")]
453 pub symbol: asset::Symbol,
454 #[serde(flatten)]
456 pub amount: Amount,
457 #[serde(rename = "side")]
459 pub side: Side,
460 #[serde(rename = "order_class")]
462 pub class: Class,
463 #[serde(rename = "type")]
465 pub type_: Type,
466 #[serde(rename = "time_in_force")]
468 pub time_in_force: TimeInForce,
469 #[serde(rename = "limit_price")]
471 pub limit_price: Option<Num>,
472 #[serde(rename = "stop_price")]
474 pub stop_price: Option<Num>,
475 #[serde(rename = "trail_price")]
477 pub trail_price: Option<Num>,
478 #[serde(rename = "trail_percent")]
480 pub trail_percent: Option<Num>,
481 #[serde(rename = "take_profit")]
483 pub take_profit: Option<TakeProfit>,
484 #[serde(rename = "stop_loss")]
486 pub stop_loss: Option<StopLoss>,
487 #[serde(rename = "extended_hours")]
492 pub extended_hours: bool,
493 #[serde(rename = "client_order_id")]
501 pub client_order_id: Option<String>,
502 #[doc(hidden)]
504 #[serde(skip)]
505 pub _non_exhaustive: (),
506}
507
508
509#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
511pub struct ChangeReq {
512 #[serde(rename = "qty")]
514 pub quantity: Option<Num>,
515 #[serde(rename = "time_in_force")]
517 pub time_in_force: Option<TimeInForce>,
518 #[serde(rename = "limit_price")]
520 pub limit_price: Option<Num>,
521 #[serde(rename = "stop_price")]
523 pub stop_price: Option<Num>,
524 #[serde(rename = "trail")]
526 pub trail: Option<Num>,
527 #[serde(rename = "client_order_id")]
529 pub client_order_id: Option<String>,
530 #[doc(hidden)]
532 #[serde(skip)]
533 pub _non_exhaustive: (),
534}
535
536
537fn empty_to_default<'de, D>(deserializer: D) -> Result<Class, D::Error>
542where
543 D: Deserializer<'de>,
544{
545 let class = <&str>::deserialize(deserializer)?;
546 if class.is_empty() {
547 Ok(Class::default())
548 } else {
549 Class::deserialize(class.into_deserializer())
550 }
551}
552
553
554#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
557pub struct Order {
558 #[serde(rename = "id")]
560 pub id: Id,
561 #[serde(rename = "client_order_id")]
563 pub client_order_id: String,
564 #[serde(rename = "status")]
566 pub status: Status,
567 #[serde(rename = "created_at")]
569 pub created_at: DateTime<Utc>,
570 #[serde(rename = "updated_at")]
572 pub updated_at: Option<DateTime<Utc>>,
573 #[serde(rename = "submitted_at")]
575 pub submitted_at: Option<DateTime<Utc>>,
576 #[serde(rename = "filled_at")]
578 pub filled_at: Option<DateTime<Utc>>,
579 #[serde(rename = "expired_at")]
581 pub expired_at: Option<DateTime<Utc>>,
582 #[serde(rename = "canceled_at")]
584 pub canceled_at: Option<DateTime<Utc>>,
585 #[serde(rename = "asset_class")]
587 pub asset_class: asset::Class,
588 #[serde(rename = "asset_id")]
590 pub asset_id: asset::Id,
591 #[serde(rename = "symbol")]
593 pub symbol: String,
594 #[serde(flatten)]
596 pub amount: Amount,
597 #[serde(rename = "filled_qty")]
599 pub filled_quantity: Num,
600 #[serde(rename = "type")]
602 pub type_: Type,
603 #[serde(rename = "order_class", deserialize_with = "empty_to_default")]
605 pub class: Class,
606 #[serde(rename = "side")]
608 pub side: Side,
609 #[serde(rename = "time_in_force")]
611 pub time_in_force: TimeInForce,
612 #[serde(rename = "limit_price")]
614 pub limit_price: Option<Num>,
615 #[serde(rename = "stop_price")]
617 pub stop_price: Option<Num>,
618 #[serde(rename = "trail_price")]
620 pub trail_price: Option<Num>,
621 #[serde(rename = "trail_percent")]
623 pub trail_percent: Option<Num>,
624 #[serde(rename = "filled_avg_price")]
626 pub average_fill_price: Option<Num>,
627 #[serde(rename = "extended_hours")]
630 pub extended_hours: bool,
631 #[serde(rename = "legs", deserialize_with = "vec_from_str")]
636 pub legs: Vec<Order>,
637 #[doc(hidden)]
639 #[serde(skip)]
640 pub _non_exhaustive: (),
641}
642
643
644Endpoint! {
645 pub Get(Id),
648 Ok => Order, [
649 OK,
651 ],
652 Err => GetError, [
653 NOT_FOUND => NotFound,
655 ]
656
657 fn path(input: &Self::Input) -> Str {
658 format!("/v2/orders/{}", input.as_simple()).into()
659 }
660}
661
662
663Endpoint! {
664 pub GetByClientId(String),
667 Ok => Order, [
668 OK,
670 ],
671 Err => GetByClientIdError, [
675 NOT_FOUND => NotFound,
677 ]
678
679 #[inline]
680 fn path(_input: &Self::Input) -> Str {
681 "/v2/orders:by_client_order_id".into()
682 }
683
684 fn query(input: &Self::Input) -> Result<Option<Str>, Self::ConversionError> {
685 #[derive(Serialize)]
686 struct ClientOrderId<'s> {
687 #[serde(rename = "client_order_id")]
688 order_id: &'s str,
689 }
690
691 let order_id = ClientOrderId {
692 order_id: input,
693 };
694 Ok(Some(to_query(order_id)?.into()))
695 }
696}
697
698
699Endpoint! {
700 pub Create(CreateReq),
702 Ok => Order, [
703 OK,
705 ],
706 Err => CreateError, [
707 UNPROCESSABLE_ENTITY => InvalidInput,
709 ]
710
711 #[inline]
712 fn method() -> Method {
713 Method::POST
714 }
715
716 #[inline]
717 fn path(_input: &Self::Input) -> Str {
718 "/v2/orders".into()
719 }
720
721 fn body(input: &Self::Input) -> Result<Option<Bytes>, Self::ConversionError> {
722 let json = to_json(input)?;
723 let bytes = Bytes::from(json);
724 Ok(Some(bytes))
725 }
726}
727
728
729Endpoint! {
730 pub Change((Id, ChangeReq)),
733 Ok => Order, [
734 OK,
736 ],
737 Err => ChangeError, [
738 NOT_FOUND => NotFound,
740 UNPROCESSABLE_ENTITY => InvalidInput,
742 ]
743
744 #[inline]
745 fn method() -> Method {
746 Method::PATCH
747 }
748
749 fn path(input: &Self::Input) -> Str {
750 let (id, _) = input;
751 format!("/v2/orders/{}", id.as_simple()).into()
752 }
753
754 fn body(input: &Self::Input) -> Result<Option<Bytes>, Self::ConversionError> {
755 let (_, request) = input;
756 let json = to_json(request)?;
757 let bytes = Bytes::from(json);
758 Ok(Some(bytes))
759 }
760}
761
762
763EndpointNoParse! {
764 pub Delete(Id),
767 Ok => (), [
768 NO_CONTENT,
770 ],
771 Err => DeleteError, [
772 NOT_FOUND => NotFound,
774 UNPROCESSABLE_ENTITY => NotCancelable,
776 ]
777
778 #[inline]
779 fn method() -> Method {
780 Method::DELETE
781 }
782
783 fn path(input: &Self::Input) -> Str {
784 format!("/v2/orders/{}", input.as_simple()).into()
785 }
786
787 #[inline]
788 fn parse(body: &[u8]) -> Result<Self::Output, Self::ConversionError> {
789 debug_assert_eq!(body, b"");
790 Ok(())
791 }
792
793 fn parse_err(body: &[u8]) -> Result<Self::ApiError, Vec<u8>> {
794 from_json::<Self::ApiError>(body).map_err(|_| body.to_vec())
795 }
796}
797
798
799#[cfg(test)]
800mod tests {
801 use super::*;
802
803 use std::str::FromStr as _;
804
805 use futures::TryFutureExt;
806
807 use serde_json::from_slice as from_json;
808
809 use test_log::test;
810
811 use uuid::Uuid;
812
813 use crate::api::v2::asset;
814 use crate::api::v2::asset::Exchange;
815 use crate::api::v2::asset::Symbol;
816 use crate::api::v2::order_util::order_aapl;
817 use crate::api_info::ApiInfo;
818 use crate::Client;
819 use crate::RequestError;
820
821
822 #[test]
824 fn emit_side() {
825 assert_eq!(to_json(&Side::Buy).unwrap(), br#""buy""#);
826 assert_eq!(to_json(&Side::Sell).unwrap(), br#""sell""#);
827 }
828
829 #[test]
831 fn negate_side() {
832 assert_eq!(!Side::Buy, Side::Sell);
833 assert_eq!(!Side::Sell, Side::Buy);
834 }
835
836 #[test]
838 fn emit_type() {
839 assert_eq!(to_json(&Type::Market).unwrap(), br#""market""#);
840 assert_eq!(to_json(&Type::Limit).unwrap(), br#""limit""#);
841 assert_eq!(to_json(&Type::Stop).unwrap(), br#""stop""#);
842 }
843
844 #[test]
846 fn serialize_deserialize_legs() {
847 let take_profit = TakeProfit::Limit(Num::new(3, 2));
848 let json = to_json(&take_profit).unwrap();
849 assert_eq!(json, br#"{"limit_price":"1.5"}"#);
850 assert_eq!(from_json::<TakeProfit>(&json).unwrap(), take_profit);
851
852 let stop_loss = StopLoss::Stop(Num::from(42));
853 let json = to_json(&stop_loss).unwrap();
854 assert_eq!(json, br#"{"stop_price":"42"}"#);
855 assert_eq!(from_json::<StopLoss>(&json).unwrap(), stop_loss);
856
857 let stop_loss = StopLoss::StopLimit(Num::from(13), Num::from(96));
858 let json = to_json(&stop_loss).unwrap();
859 let expected = br#"{"stop_price":"13","limit_price":"96"}"#;
860 assert_eq!(json, &expected[..]);
861 assert_eq!(from_json::<StopLoss>(&json).unwrap(), stop_loss);
862 }
863
864 #[test]
866 fn parse_quantity_amount() {
867 let serialized = br#"{
868 "qty": "15"
869}"#;
870 let amount = from_json::<Amount>(serialized).unwrap();
871 assert_eq!(amount, Amount::quantity(15));
872 }
873
874 #[test]
876 fn parse_notional_amount() {
877 let serialized = br#"{
878 "notional": "15.12"
879}"#;
880 let amount = from_json::<Amount>(serialized).unwrap();
881 assert_eq!(amount, Amount::notional(Num::from_str("15.12").unwrap()));
882 }
883
884 #[test]
886 fn deserialize_serialize_reference_order() {
887 let json = br#"{
888 "id": "904837e3-3b76-47ec-b432-046db621571b",
889 "client_order_id": "904837e3-3b76-47ec-b432-046db621571b",
890 "created_at": "2018-10-05T05:48:59Z",
891 "updated_at": "2018-10-05T05:48:59Z",
892 "submitted_at": "2018-10-05T05:48:59Z",
893 "filled_at": "2018-10-05T05:48:59Z",
894 "expired_at": "2018-10-05T05:48:59Z",
895 "canceled_at": "2018-10-05T05:48:59Z",
896 "failed_at": "2018-10-05T05:48:59Z",
897 "asset_id": "904837e3-3b76-47ec-b432-046db621571b",
898 "symbol": "AAPL",
899 "asset_class": "us_equity",
900 "qty": "15",
901 "filled_qty": "0",
902 "type": "market",
903 "order_class": "oto",
904 "side": "buy",
905 "time_in_force": "day",
906 "limit_price": "107.00",
907 "stop_price": "106.00",
908 "filled_avg_price": "106.25",
909 "status": "accepted",
910 "extended_hours": false,
911 "legs": null
912}"#;
913
914 let id = Id(Uuid::parse_str("904837e3-3b76-47ec-b432-046db621571b").unwrap());
915 let order = from_json::<Order>(&to_json(&from_json::<Order>(json).unwrap()).unwrap()).unwrap();
916 assert_eq!(order.id, id);
917 assert_eq!(
918 order.created_at,
919 DateTime::parse_from_rfc3339("2018-10-05T05:48:59Z").unwrap()
920 );
921 assert_eq!(order.symbol, "AAPL");
922 assert_eq!(order.amount, Amount::quantity(15));
923 assert_eq!(order.type_, Type::Market);
924 assert_eq!(order.class, Class::OneTriggersOther);
925 assert_eq!(order.time_in_force, TimeInForce::Day);
926 assert_eq!(order.limit_price, Some(Num::from(107)));
927 assert_eq!(order.stop_price, Some(Num::from(106)));
928 assert_eq!(order.average_fill_price, Some(Num::new(10625, 100)));
929 }
930
931 #[test]
936 fn deserialize_order_with_empty_order_class() {
937 let json = br#"{
938 "id": "904837e3-3b76-47ec-b432-046db621571b",
939 "client_order_id": "904837e3-3b76-47ec-b432-046db621571b",
940 "created_at": "2018-10-05T05:48:59Z",
941 "updated_at": "2018-10-05T05:48:59Z",
942 "submitted_at": "2018-10-05T05:48:59Z",
943 "filled_at": "2018-10-05T05:48:59Z",
944 "expired_at": "2018-10-05T05:48:59Z",
945 "canceled_at": "2018-10-05T05:48:59Z",
946 "failed_at": "2018-10-05T05:48:59Z",
947 "asset_id": "904837e3-3b76-47ec-b432-046db621571b",
948 "symbol": "AAPL",
949 "asset_class": "us_equity",
950 "qty": "15",
951 "filled_qty": "0",
952 "type": "market",
953 "order_class": "",
954 "side": "buy",
955 "time_in_force": "day",
956 "limit_price": "107.00",
957 "stop_price": "106.00",
958 "filled_avg_price": "106.25",
959 "status": "accepted",
960 "extended_hours": false,
961 "legs": null
962}"#;
963
964 let order = from_json::<Order>(json).unwrap();
965 assert_eq!(order.class, Class::Simple);
966 }
967
968 #[test]
970 fn serialize_deserialize_order_request() {
971 let request = CreateReqInit {
972 type_: Type::TrailingStop,
973 trail_price: Some(Num::from(50)),
974 ..Default::default()
975 }
976 .init("SPY", Side::Buy, Amount::quantity(1));
977
978 let json = to_json(&request).unwrap();
979 assert_eq!(from_json::<CreateReq>(&json).unwrap(), request);
980 }
981
982 #[test]
984 fn serialize_deserialize_change_request() {
985 let request = ChangeReq {
986 quantity: Some(Num::from(37)),
987 time_in_force: Some(TimeInForce::UntilCanceled),
988 trail: Some(Num::from(42)),
989 ..Default::default()
990 };
991
992 let json = to_json(&request).unwrap();
993 assert_eq!(from_json::<ChangeReq>(&json).unwrap(), request);
994 }
995
996 #[test(tokio::test)]
998 async fn submit_limit_order() {
999 async fn test(extended_hours: bool) -> Result<(), RequestError<CreateError>> {
1000 let mut request = CreateReqInit {
1001 type_: Type::Limit,
1002 limit_price: Some(Num::from(1)),
1003 extended_hours,
1004 ..Default::default()
1005 }
1006 .init("SPY", Side::Buy, Amount::quantity(1));
1007
1008 request.symbol =
1009 Symbol::SymExchgCls("SPY".to_string(), Exchange::Arca, asset::Class::UsEquity);
1010
1011 let api_info = ApiInfo::from_env().unwrap();
1012 let client = Client::new(api_info);
1013
1014 let order = client.issue::<Create>(&request).await?;
1015 client.issue::<Delete>(&order.id).await.unwrap();
1016
1017 assert_eq!(order.symbol, "SPY");
1018 assert_eq!(order.amount, Amount::quantity(1));
1019 assert_eq!(order.side, Side::Buy);
1020 assert_eq!(order.type_, Type::Limit);
1021 assert_eq!(order.class, Class::default());
1022 assert_eq!(order.time_in_force, TimeInForce::Day);
1023 assert_eq!(order.limit_price, Some(Num::from(1)));
1024 assert_eq!(order.stop_price, None);
1025 assert_eq!(order.extended_hours, extended_hours);
1026 Ok(())
1027 }
1028
1029 test(false).await.unwrap();
1030
1031 let result = test(true).await;
1037 match result {
1038 Ok(()) | Err(RequestError::Endpoint(CreateError::NotPermitted(..))) => (),
1039 err => panic!("unexpected error: {err:?}"),
1040 };
1041 }
1042
1043 #[test(tokio::test)]
1045 async fn submit_trailing_stop_price_order() {
1046 let request = CreateReqInit {
1047 type_: Type::TrailingStop,
1048 trail_price: Some(Num::from(50)),
1049 ..Default::default()
1050 }
1051 .init("SPY", Side::Buy, Amount::quantity(1));
1052
1053 let api_info = ApiInfo::from_env().unwrap();
1054 let client = Client::new(api_info);
1055
1056 let order = client.issue::<Create>(&request).await.unwrap();
1057 client.issue::<Delete>(&order.id).await.unwrap();
1058
1059 assert_eq!(order.symbol, "SPY");
1060 assert_eq!(order.amount, Amount::quantity(1));
1061 assert_eq!(order.side, Side::Buy);
1062 assert_eq!(order.type_, Type::TrailingStop);
1063 assert_eq!(order.time_in_force, TimeInForce::Day);
1064 assert_eq!(order.limit_price, None);
1065 assert_eq!(order.trail_price, Some(Num::from(50)));
1068 assert_eq!(order.trail_percent, None);
1069 }
1070
1071 #[test(tokio::test)]
1073 async fn submit_trailing_stop_percent_order() {
1074 let request = CreateReqInit {
1075 type_: Type::TrailingStop,
1076 trail_percent: Some(Num::from(10)),
1077 ..Default::default()
1078 }
1079 .init("SPY", Side::Buy, Amount::quantity(1));
1080
1081 let api_info = ApiInfo::from_env().unwrap();
1082 let client = Client::new(api_info);
1083
1084 let order = client.issue::<Create>(&request).await.unwrap();
1085 client.issue::<Delete>(&order.id).await.unwrap();
1086
1087 assert_eq!(order.symbol, "SPY");
1088 assert_eq!(order.amount, Amount::quantity(1));
1089 assert_eq!(order.side, Side::Buy);
1090 assert_eq!(order.type_, Type::TrailingStop);
1091 assert_eq!(order.time_in_force, TimeInForce::Day);
1092 assert_eq!(order.limit_price, None);
1093 assert_eq!(order.trail_price, None);
1096 assert_eq!(order.trail_percent, Some(Num::from(10)));
1097 }
1098
1099 #[test(tokio::test)]
1100 async fn submit_bracket_order() {
1101 let request = CreateReqInit {
1102 class: Class::Bracket,
1103 type_: Type::Limit,
1104 limit_price: Some(Num::from(2)),
1105 take_profit: Some(TakeProfit::Limit(Num::from(3))),
1106 stop_loss: Some(StopLoss::Stop(Num::from(1))),
1107 ..Default::default()
1108 }
1109 .init("SPY", Side::Buy, Amount::quantity(1));
1110
1111 let api_info = ApiInfo::from_env().unwrap();
1112 let client = Client::new(api_info);
1113
1114 let order = client.issue::<Create>(&request).await.unwrap();
1115 client.issue::<Delete>(&order.id).await.unwrap();
1116
1117 for leg in &order.legs {
1118 client.issue::<Delete>(&leg.id).await.unwrap();
1119 }
1120
1121 assert_eq!(order.symbol, "SPY");
1122 assert_eq!(order.amount, Amount::quantity(1));
1123 assert_eq!(order.side, Side::Buy);
1124 assert_eq!(order.type_, Type::Limit);
1125 assert_eq!(order.class, Class::Bracket);
1126 assert_eq!(order.time_in_force, TimeInForce::Day);
1127 assert_eq!(order.limit_price, Some(Num::from(2)));
1128 assert_eq!(order.stop_price, None);
1129 assert!(!order.extended_hours);
1130 assert_eq!(order.legs.len(), 2);
1131 assert_eq!(order.legs[0].status, Status::Held);
1132 assert_eq!(order.legs[1].status, Status::Held);
1133 }
1134
1135 #[test(tokio::test)]
1136 async fn submit_one_triggers_other_order() {
1137 let request = CreateReqInit {
1138 class: Class::OneTriggersOther,
1139 type_: Type::Limit,
1140 limit_price: Some(Num::from(2)),
1141 stop_loss: Some(StopLoss::Stop(Num::from(1))),
1142 ..Default::default()
1143 }
1144 .init("SPY", Side::Buy, Amount::quantity(1));
1145
1146 let api_info = ApiInfo::from_env().unwrap();
1147 let client = Client::new(api_info);
1148
1149 let order = client.issue::<Create>(&request).await.unwrap();
1150 client.issue::<Delete>(&order.id).await.unwrap();
1151
1152 for leg in &order.legs {
1153 client.issue::<Delete>(&leg.id).await.unwrap();
1154 }
1155
1156 assert_eq!(order.symbol, "SPY");
1157 assert_eq!(order.amount, Amount::quantity(1));
1158 assert_eq!(order.side, Side::Buy);
1159 assert_eq!(order.type_, Type::Limit);
1160 assert_eq!(order.class, Class::OneTriggersOther);
1161 assert_eq!(order.time_in_force, TimeInForce::Day);
1162 assert_eq!(order.limit_price, Some(Num::from(2)));
1163 assert_eq!(order.stop_price, None);
1164 assert!(!order.extended_hours);
1165 assert_eq!(order.legs.len(), 1);
1166 assert_eq!(order.legs[0].status, Status::Held);
1167 }
1168
1169 #[test(tokio::test)]
1171 async fn submit_other_order_types() {
1172 async fn test(time_in_force: TimeInForce) {
1173 let api_info = ApiInfo::from_env().unwrap();
1174 let client = Client::new(api_info);
1175
1176 let request = CreateReqInit {
1177 type_: Type::Limit,
1178 class: Class::Simple,
1179 time_in_force,
1180 limit_price: Some(Num::from(1)),
1181 ..Default::default()
1182 }
1183 .init("AAPL", Side::Buy, Amount::quantity(1));
1184
1185 match client.issue::<Create>(&request).await {
1186 Ok(order) => {
1187 client.issue::<Delete>(&order.id).await.unwrap();
1188
1189 assert_eq!(order.time_in_force, time_in_force);
1190 },
1191 Err(RequestError::Endpoint(CreateError::NotPermitted(..))) => (),
1194 Err(err) => panic!("Received unexpected error: {err:?}"),
1195 }
1196 }
1197
1198 test(TimeInForce::FillOrKill).await;
1199 test(TimeInForce::ImmediateOrCancel).await;
1200 test(TimeInForce::UntilMarketOpen).await;
1201 test(TimeInForce::UntilMarketClose).await;
1202 }
1203
1204 #[test(tokio::test)]
1207 async fn submit_unsatisfiable_order() {
1208 let api_info = ApiInfo::from_env().unwrap();
1209 let client = Client::new(api_info);
1210
1211 let request = CreateReqInit {
1212 type_: Type::Limit,
1213 limit_price: Some(Num::from(1000)),
1214 ..Default::default()
1215 }
1216 .init("AAPL", Side::Buy, Amount::quantity(100_000));
1217
1218 let result = client.issue::<Create>(&request).await;
1219 let err = result.unwrap_err();
1220
1221 match err {
1222 RequestError::Endpoint(CreateError::NotPermitted(..)) => (),
1223 _ => panic!("Received unexpected error: {err:?}"),
1224 };
1225 }
1226
1227 #[test(tokio::test)]
1229 async fn submit_unsatisfiable_notional_order() {
1230 let request =
1231 CreateReqInit::default().init("SPY", Side::Buy, Amount::notional(Num::from(10_000_000)));
1232
1233 let api_info = ApiInfo::from_env().unwrap();
1234 let client = Client::new(api_info);
1235
1236 let result = client.issue::<Create>(&request).await;
1237 let err = result.unwrap_err();
1238
1239 match err {
1240 RequestError::Endpoint(CreateError::NotPermitted(..)) => (),
1241 _ => panic!("Received unexpected error: {err:?}"),
1242 };
1243 }
1244
1245 #[test(tokio::test)]
1247 async fn submit_unsatisfiable_fractional_order() {
1248 let qty = Num::from(1_000_000) + Num::new(1, 2);
1249 let request = CreateReqInit::default().init("SPY", Side::Buy, Amount::quantity(qty));
1250
1251 let api_info = ApiInfo::from_env().unwrap();
1252 let client = Client::new(api_info);
1253
1254 let result = client.issue::<Create>(&request).await;
1255 let err = result.unwrap_err();
1256
1257 match err {
1258 RequestError::Endpoint(CreateError::NotPermitted(..)) => (),
1259 _ => panic!("Received unexpected error: {err:?}"),
1260 };
1261 }
1262
1263 #[test(tokio::test)]
1266 async fn cancel_invalid_order() {
1267 let id = Id(Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap());
1268 let api_info = ApiInfo::from_env().unwrap();
1269 let client = Client::new(api_info);
1270 let result = client.issue::<Delete>(&id).await;
1271 let err = result.unwrap_err();
1272
1273 match err {
1274 RequestError::Endpoint(DeleteError::NotFound(..)) => (),
1275 _ => panic!("Received unexpected error: {err:?}"),
1276 };
1277 }
1278
1279 #[test(tokio::test)]
1281 async fn retrieve_order_by_id() {
1282 let api_info = ApiInfo::from_env().unwrap();
1283 let client = Client::new(api_info);
1284 let submitted = order_aapl(&client).await.unwrap();
1285 let result = client.issue::<Get>(&submitted.id).await;
1286 client.issue::<Delete>(&submitted.id).await.unwrap();
1287 let gotten = result.unwrap();
1288
1289 assert_eq!(submitted.id, gotten.id);
1292 assert_eq!(submitted.asset_class, gotten.asset_class);
1293 assert_eq!(submitted.asset_id, gotten.asset_id);
1294 assert_eq!(submitted.symbol, gotten.symbol);
1295 assert_eq!(submitted.amount, gotten.amount);
1296 assert_eq!(submitted.type_, gotten.type_);
1297 assert_eq!(submitted.side, gotten.side);
1298 assert_eq!(submitted.time_in_force, gotten.time_in_force);
1299 }
1300
1301 #[test(tokio::test)]
1302 async fn retrieve_non_existent_order() {
1303 let id = Id(Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap());
1304 let api_info = ApiInfo::from_env().unwrap();
1305 let client = Client::new(api_info);
1306 let result = client.issue::<Get>(&id).await;
1307 let err = result.unwrap_err();
1308
1309 match err {
1310 RequestError::Endpoint(GetError::NotFound(..)) => (),
1311 _ => panic!("Received unexpected error: {err:?}"),
1312 };
1313 }
1314
1315 #[test(tokio::test)]
1316 async fn extended_hours_market_order() {
1317 let request = CreateReqInit {
1318 extended_hours: true,
1319 ..Default::default()
1320 }
1321 .init("SPY", Side::Buy, Amount::quantity(1));
1322
1323 let api_info = ApiInfo::from_env().unwrap();
1324 let client = Client::new(api_info);
1325
1326 let result = client.issue::<Create>(&request).await;
1329 let err = result.unwrap_err();
1330
1331 match err {
1332 RequestError::Endpoint(CreateError::InvalidInput(..)) => (),
1333 _ => panic!("Received unexpected error: {err:?}"),
1334 };
1335 }
1336
1337 #[test(tokio::test)]
1339 async fn change_order() {
1340 let request = CreateReqInit {
1341 type_: Type::Limit,
1342 limit_price: Some(Num::from(1)),
1343 ..Default::default()
1344 }
1345 .init("AAPL", Side::Buy, Amount::quantity(1));
1346
1347 let api_info = ApiInfo::from_env().unwrap();
1348 let client = Client::new(api_info);
1349 let order = client.issue::<Create>(&request).await.unwrap();
1350
1351 let request = ChangeReq {
1352 quantity: Some(Num::from(2)),
1353 time_in_force: Some(TimeInForce::UntilCanceled),
1354 limit_price: Some(Num::from(2)),
1355 ..Default::default()
1356 };
1357
1358 let result = client.issue::<Change>(&(order.id, request)).await;
1359 let id = if let Ok(replaced) = &result {
1360 replaced.id
1361 } else {
1362 order.id
1363 };
1364
1365 client.issue::<Delete>(&id).await.unwrap();
1366
1367 match result {
1368 Ok(order) => {
1369 assert_eq!(order.amount, Amount::quantity(2));
1370 assert_eq!(order.time_in_force, TimeInForce::UntilCanceled);
1371 assert_eq!(order.limit_price, Some(Num::from(2)));
1372 assert_eq!(order.stop_price, None);
1373 },
1374 Err(RequestError::Endpoint(ChangeError::InvalidInput(..))) => {
1375 },
1380 e => panic!("received unexpected error: {e:?}"),
1381 }
1382 }
1383
1384 #[test(tokio::test)]
1386 async fn change_trail_stop_order() {
1387 let request = CreateReqInit {
1388 type_: Type::TrailingStop,
1389 trail_price: Some(Num::from(20)),
1390 ..Default::default()
1391 }
1392 .init("SPY", Side::Buy, Amount::quantity(1));
1393
1394 let api_info = ApiInfo::from_env().unwrap();
1395 let client = Client::new(api_info);
1396 let order = client.issue::<Create>(&request).await.unwrap();
1397 assert_eq!(order.trail_price, Some(Num::from(20)));
1398
1399 let request = ChangeReq {
1400 trail: Some(Num::from(30)),
1401 ..Default::default()
1402 };
1403
1404 let result = client.issue::<Change>(&(order.id, request)).await;
1405 let id = if let Ok(replaced) = &result {
1406 replaced.id
1407 } else {
1408 order.id
1409 };
1410
1411 client.issue::<Delete>(&id).await.unwrap();
1412
1413 match result {
1414 Ok(order) => {
1415 assert_eq!(order.trail_price, Some(Num::from(30)));
1416 },
1417 Err(RequestError::Endpoint(ChangeError::InvalidInput(..))) => (),
1418 e => panic!("received unexpected error: {e:?}"),
1419 }
1420 }
1421
1422 #[test(tokio::test)]
1425 async fn submit_with_client_order_id() {
1426 let client_order_id = Uuid::new_v4().as_simple().to_string();
1430
1431 let request = CreateReqInit {
1432 type_: Type::Limit,
1433 limit_price: Some(Num::from(1)),
1434 client_order_id: Some(client_order_id.clone()),
1435 ..Default::default()
1436 }
1437 .init("SPY", Side::Buy, Amount::quantity(1));
1438
1439 let api_info = ApiInfo::from_env().unwrap();
1440 let client = Client::new(api_info);
1441
1442 let (issued, retrieved) = client
1443 .issue::<Create>(&request)
1444 .and_then(|order| async {
1445 let retrieved = client.issue::<GetByClientId>(&client_order_id).await;
1446 client.issue::<Delete>(&order.id).await.unwrap();
1447 Ok((order, retrieved.unwrap()))
1448 })
1449 .await
1450 .unwrap();
1451
1452 assert_eq!(issued.client_order_id, client_order_id);
1453 assert_eq!(retrieved.client_order_id, client_order_id);
1454 assert_eq!(retrieved.id, issued.id);
1455
1456 let err = client.issue::<Create>(&request).await.unwrap_err();
1459
1460 match err {
1461 RequestError::Endpoint(CreateError::InvalidInput(..)) => (),
1462 _ => panic!("Received unexpected error: {err:?}"),
1463 };
1464 }
1465
1466 #[test(tokio::test)]
1468 async fn change_client_order_id() {
1469 let request = CreateReqInit {
1470 type_: Type::Limit,
1471 limit_price: Some(Num::from(1)),
1472 ..Default::default()
1473 }
1474 .init("SPY", Side::Buy, Amount::quantity(1));
1475
1476 let api_info = ApiInfo::from_env().unwrap();
1477 let client = Client::new(api_info);
1478
1479 let order = client.issue::<Create>(&request).await.unwrap();
1480
1481 let client_order_id = Uuid::new_v4().as_simple().to_string();
1482 let request = ChangeReq {
1483 client_order_id: Some(client_order_id.clone()),
1484 ..Default::default()
1485 };
1486
1487 let change_result = client.issue::<Change>(&(order.id, request)).await;
1488 let id = if let Ok(replaced) = &change_result {
1489 replaced.id
1490 } else {
1491 order.id
1492 };
1493
1494 let get_result = client.issue::<GetByClientId>(&client_order_id).await;
1495 let () = client.issue::<Delete>(&id).await.unwrap();
1496
1497 match change_result {
1498 Ok(..) => {
1499 let order = get_result.unwrap();
1500 assert_eq!(order.symbol, "SPY");
1501 assert_eq!(order.type_, Type::Limit);
1502 assert_eq!(order.limit_price, Some(Num::from(1)));
1503 },
1504 Err(RequestError::Endpoint(ChangeError::InvalidInput(..))) => (),
1505 e => panic!("received unexpected error: {e:?}"),
1506 }
1507 }
1508}