apca/api/v2/
order.rs

1// Copyright (C) 2019-2024 The apca Developers
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use 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/// An ID uniquely identifying an order.
31#[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/// The status an order can have.
45#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
46#[non_exhaustive]
47pub enum Status {
48  /// The order has been received by Alpaca, and routed to exchanges for
49  /// execution. This is the usual initial state of an order.
50  #[serde(rename = "new")]
51  New,
52  /// The order has changed.
53  #[serde(rename = "replaced")]
54  Replaced,
55  /// The order has been partially filled.
56  #[serde(rename = "partially_filled")]
57  PartiallyFilled,
58  /// The order has been filled, and no further updates will occur for
59  /// the order.
60  #[serde(rename = "filled")]
61  Filled,
62  /// The order is done executing for the day, and will not receive
63  /// further updates until the next trading day.
64  #[serde(rename = "done_for_day")]
65  DoneForDay,
66  /// The order has been canceled, and no further updates will occur for
67  /// the order. This can be either due to a cancel request by the user,
68  /// or the order has been canceled by the exchanges due to its
69  /// time-in-force.
70  #[serde(rename = "canceled")]
71  Canceled,
72  /// The order has expired, and no further updates will occur for the
73  /// order.
74  #[serde(rename = "expired")]
75  Expired,
76  /// The order has been received by Alpaca, but hasn't yet been routed
77  /// to the execution venue. This state only occurs on rare occasions.
78  #[serde(rename = "accepted")]
79  Accepted,
80  /// The order has been received by Alpaca, and routed to the
81  /// exchanges, but has not yet been accepted for execution. This state
82  /// only occurs on rare occasions.
83  #[serde(rename = "pending_new")]
84  PendingNew,
85  /// The order has been received by exchanges, and is evaluated for
86  /// pricing. This state only occurs on rare occasions.
87  #[serde(rename = "accepted_for_bidding")]
88  AcceptedForBidding,
89  /// The order is waiting to be canceled. This state only occurs on
90  /// rare occasions.
91  #[serde(rename = "pending_cancel")]
92  PendingCancel,
93  /// The order is awaiting replacement.
94  #[serde(rename = "pending_replace")]
95  PendingReplace,
96  /// The order has been stopped, and a trade is guaranteed for the
97  /// order, usually at a stated price or better, but has not yet
98  /// occurred. This state only occurs on rare occasions.
99  #[serde(rename = "stopped")]
100  Stopped,
101  /// The order has been rejected, and no further updates will occur for
102  /// the order. This state occurs on rare occasions and may occur based
103  /// on various conditions decided by the exchanges.
104  #[serde(rename = "rejected")]
105  Rejected,
106  /// The order has been suspended, and is not eligible for trading.
107  /// This state only occurs on rare occasions.
108  #[serde(rename = "suspended")]
109  Suspended,
110  /// The order has been completed for the day (either filled or done
111  /// for day), but remaining settlement calculations are still pending.
112  /// This state only occurs on rare occasions.
113  #[serde(rename = "calculated")]
114  Calculated,
115  /// The order is still being held. This may be the case for legs of
116  /// bracket-style orders that are not active yet because the primary
117  /// order has not filled yet.
118  #[serde(rename = "held")]
119  Held,
120  /// Any other status that we have not accounted for.
121  ///
122  /// Note that having any such status should be considered a bug.
123  #[doc(hidden)]
124  #[serde(other, rename(serialize = "unknown"))]
125  Unknown,
126}
127
128impl Status {
129  /// Check whether the status is terminal, i.e., no more changes will
130  /// occur to the associated order.
131  #[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/// The side an order is on.
142#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
143pub enum Side {
144  /// Buy an asset.
145  #[serde(rename = "buy")]
146  Buy,
147  /// Sell an asset.
148  #[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/// The class an order belongs to.
166#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
167#[non_exhaustive]
168pub enum Class {
169  /// Any non-bracket order (i.e., regular market, limit, or stop loss
170  /// orders).
171  #[serde(rename = "simple")]
172  Simple,
173  /// A bracket order is a chain of three orders that can be used to manage your
174  /// position entry and exit. It is a common use case of an
175  /// one-triggers & one-cancels-other order.
176  #[serde(rename = "bracket")]
177  Bracket,
178  /// A One-cancels-other is a set of two orders with the same side
179  /// (buy/buy or sell/sell) and currently only exit order is supported.
180  /// Such an order can be used to add two legs to an already filled
181  /// order.
182  #[serde(rename = "oco")]
183  OneCancelsOther,
184  /// A one-triggers-other order that can either have a take-profit or
185  /// stop-loss leg set. It essentially attached a single leg to an
186  /// entry order.
187  #[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/// The type of an order.
200// Note that we currently do not support `stop_limit` orders.
201#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
202#[non_exhaustive]
203pub enum Type {
204  /// A market order.
205  #[serde(rename = "market")]
206  Market,
207  /// A limit order.
208  #[serde(rename = "limit")]
209  Limit,
210  /// A stop on quote order.
211  #[serde(rename = "stop")]
212  Stop,
213  /// A stop limit order.
214  #[serde(rename = "stop_limit")]
215  StopLimit,
216  /// A trailing stop order.
217  #[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/// A description of the time for which an order is valid.
230#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
231#[non_exhaustive]
232pub enum TimeInForce {
233  /// The order is good for the day, and it will be canceled
234  /// automatically at the end of Regular Trading Hours if unfilled.
235  #[serde(rename = "day")]
236  Day,
237  /// The order is only executed if the entire order quantity can
238  /// be filled, otherwise the order is canceled.
239  #[serde(rename = "fok")]
240  FillOrKill,
241  /// The order requires all or part of the order to be executed
242  /// immediately. Any unfilled portion of the order is canceled.
243  #[serde(rename = "ioc")]
244  ImmediateOrCancel,
245  /// The order is good until canceled.
246  #[serde(rename = "gtc")]
247  UntilCanceled,
248  /// This order is eligible to execute only in the market opening
249  /// auction. Any unfilled orders after the open will be canceled.
250  #[serde(rename = "opg")]
251  UntilMarketOpen,
252  /// This order is eligible to execute only in the market closing
253  /// auction. Any unfilled orders after the close will be canceled.
254  #[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/// The take profit part of a bracket, one-cancels-other, or
275/// one-triggers-other order.
276#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
277#[serde(from = "TakeProfitSerde", into = "TakeProfitSerde")]
278#[non_exhaustive]
279pub enum TakeProfit {
280  /// The limit price to use.
281  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/// The stop loss part of a bracket, one-cancels-other, or
310/// one-triggers-other order.
311#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
312#[serde(from = "StopLossSerde", into = "StopLossSerde")]
313#[non_exhaustive]
314pub enum StopLoss {
315  /// The stop loss price to use.
316  Stop(Num),
317  /// The stop loss and stop limit price to use.
318  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/// An abstraction to be able to handle orders in both notional and quantity units.
348#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
349#[serde(untagged)]
350pub enum Amount {
351  /// Wrapper for the quantity field.
352  Quantity {
353    /// A number of shares to order. This can be a fractional number if
354    /// trading fractionals or a whole number if not.
355    #[serde(rename = "qty")]
356    quantity: Num,
357  },
358  /// Wrapper for the notional field.
359  Notional {
360    /// A dollar amount to use for the order. This can result in
361    /// fractional quantities.
362    #[serde(rename = "notional")]
363    notional: Num,
364  },
365}
366
367impl Amount {
368  /// Helper method to initialize a quantity.
369  #[inline]
370  pub fn quantity(amount: impl Into<Num>) -> Self {
371    Self::Quantity {
372      quantity: amount.into(),
373    }
374  }
375
376  /// Helper method to initialize a notional.
377  #[inline]
378  pub fn notional(amount: impl Into<Num>) -> Self {
379    Self::Notional {
380      notional: amount.into(),
381    }
382  }
383}
384
385
386/// A helper for initializing `CreateReq` objects.
387#[derive(Clone, Debug, Default, Eq, PartialEq)]
388pub struct CreateReqInit {
389  /// See `CreateReq::class`.
390  pub class: Class,
391  /// See `CreateReq::type_`.
392  pub type_: Type,
393  /// See `CreateReq::time_in_force`.
394  pub time_in_force: TimeInForce,
395  /// See `CreateReq::limit_price`.
396  pub limit_price: Option<Num>,
397  /// See `CreateReq::stop_price`.
398  pub stop_price: Option<Num>,
399  /// See `CreateReq::trail_price`.
400  pub trail_price: Option<Num>,
401  /// See `CreateReq::trail_percent`.
402  pub trail_percent: Option<Num>,
403  /// See `CreateReq::take_profit`.
404  pub take_profit: Option<TakeProfit>,
405  /// See `CreateReq::stop_loss`.
406  pub stop_loss: Option<StopLoss>,
407  /// See `CreateReq::extended_hours`.
408  pub extended_hours: bool,
409  /// See `CreateReq::client_order_id`.
410  pub client_order_id: Option<String>,
411  /// The type is non-exhaustive and open to extension.
412  #[doc(hidden)]
413  pub _non_exhaustive: (),
414}
415
416impl CreateReqInit {
417  /// Create a `CreateReq` from a `CreateReqInit`.
418  ///
419  /// The provided symbol is assumed to be a "simple" symbol and not any
420  /// of the composite forms of the [`Symbol`][asset::Symbol] enum. That
421  /// is, it is not being parsed but directly treated as the
422  /// [`Sym`][asset::Symbol::Sym] variant.
423  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/// A POST request to be made to the /v2/orders endpoint.
449#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
450pub struct CreateReq {
451  /// Symbol or asset ID to identify the asset to trade.
452  #[serde(rename = "symbol")]
453  pub symbol: asset::Symbol,
454  /// Amount of shares to trade.
455  #[serde(flatten)]
456  pub amount: Amount,
457  /// The side the order is on.
458  #[serde(rename = "side")]
459  pub side: Side,
460  /// The order class.
461  #[serde(rename = "order_class")]
462  pub class: Class,
463  /// The type of the order.
464  #[serde(rename = "type")]
465  pub type_: Type,
466  /// How long the order will be valid.
467  #[serde(rename = "time_in_force")]
468  pub time_in_force: TimeInForce,
469  /// The limit price.
470  #[serde(rename = "limit_price")]
471  pub limit_price: Option<Num>,
472  /// The stop price.
473  #[serde(rename = "stop_price")]
474  pub stop_price: Option<Num>,
475  /// The dollar value away from the high water mark.
476  #[serde(rename = "trail_price")]
477  pub trail_price: Option<Num>,
478  /// The percent value away from the high water mark.
479  #[serde(rename = "trail_percent")]
480  pub trail_percent: Option<Num>,
481  /// Take profit information for bracket-style orders.
482  #[serde(rename = "take_profit")]
483  pub take_profit: Option<TakeProfit>,
484  /// Stop loss information for bracket-style orders.
485  #[serde(rename = "stop_loss")]
486  pub stop_loss: Option<StopLoss>,
487  /// Whether or not the order is eligible to execute during
488  /// pre-market/after hours. Note that a value of `true` can only be
489  /// combined with limit orders that are good for the day (i.e.,
490  /// `TimeInForce::Day`).
491  #[serde(rename = "extended_hours")]
492  pub extended_hours: bool,
493  /// Client unique order ID (free form string).
494  ///
495  /// This ID is entirely under control of the client, but kept and
496  /// passed along by Alpaca. It can be used for associating additional
497  /// information with an order, from the client.
498  ///
499  /// The documented maximum length is 48 characters.
500  #[serde(rename = "client_order_id")]
501  pub client_order_id: Option<String>,
502  /// The type is non-exhaustive and open to extension.
503  #[doc(hidden)]
504  #[serde(skip)]
505  pub _non_exhaustive: (),
506}
507
508
509/// A PATCH request to be made to the /v2/orders/{order-id} endpoint.
510#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
511pub struct ChangeReq {
512  /// Number of shares to trade.
513  #[serde(rename = "qty")]
514  pub quantity: Option<Num>,
515  /// How long the order will be valid.
516  #[serde(rename = "time_in_force")]
517  pub time_in_force: Option<TimeInForce>,
518  /// The limit price.
519  #[serde(rename = "limit_price")]
520  pub limit_price: Option<Num>,
521  /// The stop price.
522  #[serde(rename = "stop_price")]
523  pub stop_price: Option<Num>,
524  /// The new value of the `trail_price` or `trail_percent` value.
525  #[serde(rename = "trail")]
526  pub trail: Option<Num>,
527  /// Client unique order ID (free form string).
528  #[serde(rename = "client_order_id")]
529  pub client_order_id: Option<String>,
530  /// The type is non-exhaustive and open to extension.
531  #[doc(hidden)]
532  #[serde(skip)]
533  pub _non_exhaustive: (),
534}
535
536
537/// A deserialization function for order classes that may be an empty
538/// string.
539///
540/// If the order class is empty, the default one will be used.
541fn 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/// A single order as returned by the /v2/orders endpoint on a GET
555/// request.
556#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
557pub struct Order {
558  /// The order's ID.
559  #[serde(rename = "id")]
560  pub id: Id,
561  /// Client unique order ID.
562  #[serde(rename = "client_order_id")]
563  pub client_order_id: String,
564  /// The status of the order.
565  #[serde(rename = "status")]
566  pub status: Status,
567  /// Timestamp this order was created at.
568  #[serde(rename = "created_at")]
569  pub created_at: DateTime<Utc>,
570  /// Timestamp this order was updated at last.
571  #[serde(rename = "updated_at")]
572  pub updated_at: Option<DateTime<Utc>>,
573  /// Timestamp this order was submitted at.
574  #[serde(rename = "submitted_at")]
575  pub submitted_at: Option<DateTime<Utc>>,
576  /// Timestamp this order was filled at.
577  #[serde(rename = "filled_at")]
578  pub filled_at: Option<DateTime<Utc>>,
579  /// Timestamp this order expired at.
580  #[serde(rename = "expired_at")]
581  pub expired_at: Option<DateTime<Utc>>,
582  /// Timestamp this order expired at.
583  #[serde(rename = "canceled_at")]
584  pub canceled_at: Option<DateTime<Utc>>,
585  /// The order's asset class.
586  #[serde(rename = "asset_class")]
587  pub asset_class: asset::Class,
588  /// The ID of the asset represented by the order.
589  #[serde(rename = "asset_id")]
590  pub asset_id: asset::Id,
591  /// The symbol of the asset being traded.
592  #[serde(rename = "symbol")]
593  pub symbol: String,
594  /// The amount being requested.
595  #[serde(flatten)]
596  pub amount: Amount,
597  /// The quantity that was filled.
598  #[serde(rename = "filled_qty")]
599  pub filled_quantity: Num,
600  /// The type of order.
601  #[serde(rename = "type")]
602  pub type_: Type,
603  /// The order class.
604  #[serde(rename = "order_class", deserialize_with = "empty_to_default")]
605  pub class: Class,
606  /// The side the order is on.
607  #[serde(rename = "side")]
608  pub side: Side,
609  /// A representation of how long the order will be valid.
610  #[serde(rename = "time_in_force")]
611  pub time_in_force: TimeInForce,
612  /// The limit price.
613  #[serde(rename = "limit_price")]
614  pub limit_price: Option<Num>,
615  /// The stop price.
616  #[serde(rename = "stop_price")]
617  pub stop_price: Option<Num>,
618  /// The dollar value away from the high water mark.
619  #[serde(rename = "trail_price")]
620  pub trail_price: Option<Num>,
621  /// The percent value away from the high water mark.
622  #[serde(rename = "trail_percent")]
623  pub trail_percent: Option<Num>,
624  /// The average price at which the order was filled.
625  #[serde(rename = "filled_avg_price")]
626  pub average_fill_price: Option<Num>,
627  /// If true, the order is eligible for execution outside regular
628  /// trading hours.
629  #[serde(rename = "extended_hours")]
630  pub extended_hours: bool,
631  /// Additional legs of the order.
632  ///
633  /// Such an additional leg could be, for example, the order for the
634  /// take profit part of a bracket-style order.
635  #[serde(rename = "legs", deserialize_with = "vec_from_str")]
636  pub legs: Vec<Order>,
637  /// The type is non-exhaustive and open to extension.
638  #[doc(hidden)]
639  #[serde(skip)]
640  pub _non_exhaustive: (),
641}
642
643
644Endpoint! {
645  /// The representation of a GET request to the /v2/orders/{order-id}
646  /// endpoint.
647  pub Get(Id),
648  Ok => Order, [
649    /// The order object for the given ID was retrieved successfully.
650    /* 200 */ OK,
651  ],
652  Err => GetError, [
653    /// No order was found with the given ID.
654    /* 404 */ 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  /// The representation of a GET request to the
665  /// /v2/orders:by_client_order_id endpoint.
666  pub GetByClientId(String),
667  Ok => Order, [
668    /// The order object for the given ID was retrieved successfully.
669    /* 200 */ OK,
670  ],
671  // TODO: We really should reuse `GetError` as it is defined for the
672  //       `Get` endpoint here, but that requires significant changes to
673  //       the `http-endpoint` crate.
674  Err => GetByClientIdError, [
675    /// No order was found with the given client ID.
676    /* 404 */ 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  /// The representation of a POST request to the /v2/orders endpoint.
701  pub Create(CreateReq),
702  Ok => Order, [
703    /// The order was submitted successfully.
704    /* 200 */ OK,
705  ],
706  Err => CreateError, [
707    /// Some data in the request was invalid.
708    /* 422 */ 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  /// The representation of a PATCH request to the /v2/orders/{order-id}
731  /// endpoint.
732  pub Change((Id, ChangeReq)),
733  Ok => Order, [
734    /// The order object for the given ID was changed successfully.
735    /* 200 */ OK,
736  ],
737  Err => ChangeError, [
738    /// No order was found with the given ID.
739    /* 404 */ NOT_FOUND => NotFound,
740    /// Some data in the request was invalid.
741    /* 422 */ 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  /// The representation of a DELETE request to the /v2/orders/{order-id}
765  /// endpoint.
766  pub Delete(Id),
767  Ok => (), [
768    /// The order was canceled successfully.
769    /* 204 */ NO_CONTENT,
770  ],
771  Err => DeleteError, [
772    /// No order was found with the given ID.
773    /* 404 */ NOT_FOUND => NotFound,
774    /// The order can no longer be canceled.
775    /* 422 */ 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  /// Check that we can serialize a [`Side`] object.
823  #[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  /// Check that we can properly negate a [`Side`] object.
830  #[test]
831  fn negate_side() {
832    assert_eq!(!Side::Buy, Side::Sell);
833    assert_eq!(!Side::Sell, Side::Buy);
834  }
835
836  /// Check that we can serialize a [`Type`] object.
837  #[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  /// Make sure that we can serialize and deserialize order legs.
845  #[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  /// Check that we can parse the `Amount::quantity` variant properly.
865  #[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  /// Check that we can parse the `Amount::notional` variant properly.
875  #[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  /// Verify that we can deserialize and serialize a reference order.
885  #[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  /// Verify that we can deserialize an order with an empty order class.
932  ///
933  /// Unfortunately, the Alpaca API may return such an empty class for
934  /// requests that don't explicitly set the class.
935  #[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  /// Check that we can serialize and deserialize a [`CreateReq`].
969  #[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  /// Check that we can serialize and deserialize a [`ChangeReq`].
983  #[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  /// Verify that we can submit a limit order.
997  #[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    // When an extended hours order is submitted between 6pm and 8pm,
1032    // the Alpaca API reports an error:
1033    // > {"message":"extended hours orders between 6:00pm and 8:00pm is not supported"}
1034    //
1035    // So we need to treat this case specially.
1036    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  /// Check that we can properly submit a trailing stop price order.
1044  #[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    // We don't check the stop price here. It may be set to a value that
1066    // we can't know in advance.
1067    assert_eq!(order.trail_price, Some(Num::from(50)));
1068    assert_eq!(order.trail_percent, None);
1069  }
1070
1071  /// Check that we can properly submit a trailing stop percent order.
1072  #[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    // We don't check the stop price here. It may be set to a value that
1094    // we can't know in advance.
1095    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 submission of orders of various time in force types.
1170  #[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        // Submission of those orders may fail at certain times of the
1192        // day as per the Alpaca documentation. So ignore those errors.
1193        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  /// Check that we see the expected error being reported when
1205  /// attempting to submit an unsatisfiable order.
1206  #[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 that we can submit an order with a notional amount.
1228  #[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 that we can submit an order with a fractional quantity.
1246  #[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  /// Check that we get back the expected error when attempting to
1264  /// cancel an invalid (non-existent) order.
1265  #[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  /// Check that we can retrieve an order given its ID.
1280  #[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    // We can't simply compare the two orders for equality, because some
1290    // time stamps as well as the status may differ.
1291    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    // We are submitting a market order with extended_hours, that is
1327    // invalid as per the Alpaca documentation.
1328    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  /// Check that we can change an existing order.
1338  #[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        // When the market is closed a change request will never succeed
1376        // and always report an error along the lines of:
1377        // "unable to replace order, order isn't sent to exchange yet".
1378        // We can't do much more than accept this behavior.
1379      },
1380      e => panic!("received unexpected error: {e:?}"),
1381    }
1382  }
1383
1384  /// Test changing of a trailing stop order.
1385  #[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  /// Check that we can submit an order with a custom client order ID
1423  /// and then retrieve the order object back via this identifier.
1424  #[test(tokio::test)]
1425  async fn submit_with_client_order_id() {
1426    // We need a truly random identifier here, because Alpaca will never
1427    // forget any client order ID and any ID previously used one cannot
1428    // be reused again.
1429    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    // We should not be able to submit another order with the same
1457    // client ID.
1458    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 that we can change the client order ID of an order.
1467  #[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}