use serde::Deserialize;
use serde::Serialize;
use serde_urlencoded::to_string as to_query;
use crate::api::v2::order::Order;
use crate::util::string_slice_to_str;
use crate::util::vec_from_comma_separated_str;
use crate::Str;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum Status {
#[serde(rename = "open")]
Open,
#[serde(rename = "closed")]
Closed,
#[serde(rename = "all")]
All,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct OrdersReq {
#[serde(
rename = "symbols",
default,
deserialize_with = "vec_from_comma_separated_str",
serialize_with = "string_slice_to_str"
)]
pub symbols: Vec<String>,
#[serde(rename = "status")]
pub status: Status,
#[serde(rename = "limit")]
pub limit: Option<usize>,
#[serde(rename = "nested")]
pub nested: bool,
}
impl Default for OrdersReq {
fn default() -> Self {
Self {
symbols: Vec::new(),
status: Status::Open,
limit: None,
nested: true,
}
}
}
Endpoint! {
pub Get(OrdersReq),
Ok => Vec<Order>, [
OK,
],
Err => GetError, []
#[inline]
fn path(_input: &Self::Input) -> Str {
"/v2/orders".into()
}
fn query(input: &Self::Input) -> Result<Option<Str>, Self::ConversionError> {
Ok(Some(to_query(input)?.into()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::future::ok;
use futures::pin_mut;
use futures::StreamExt;
use futures::TryStreamExt;
use num_decimal::Num;
use serde_json::from_slice as from_json;
use serde_json::to_vec as to_json;
use serde_urlencoded::from_str as from_query;
use serde_urlencoded::to_string as to_query;
use test_log::test;
use crate::api::v2::order;
use crate::api::v2::order_util::order_aapl;
use crate::api::v2::order_util::order_stock;
use crate::api::v2::updates;
use crate::api_info::ApiInfo;
use crate::Client;
#[test]
fn serialize_deserialize_request() {
let mut request = OrdersReq {
symbols: vec!["ABC".into()],
status: Status::Closed,
limit: Some(42),
nested: true,
};
let json = to_json(&request).unwrap();
assert_eq!(from_json::<OrdersReq>(&json).unwrap(), request);
request.symbols.clear();
let json = to_json(&request).unwrap();
assert_eq!(from_json::<OrdersReq>(&json).unwrap(), request);
}
#[test]
fn serialize_deserialize_query_request() {
let mut request = OrdersReq {
symbols: vec!["ABC".into()],
status: Status::Closed,
limit: Some(42),
nested: true,
};
let query = to_query(&request).unwrap();
assert_eq!(from_query::<OrdersReq>(&query).unwrap(), request);
request.symbols.clear();
let query = to_query(&request).unwrap();
assert_eq!(from_query::<OrdersReq>(&query).unwrap(), request);
}
async fn cancel_order(client: &Client, id: order::Id) {
let (stream, _subscription) = client.subscribe::<updates::OrderUpdates>().await.unwrap();
pin_mut!(stream);
client.issue::<order::Delete>(&id).await.unwrap();
let _update = stream
.try_filter_map(|res| {
let update = res.unwrap();
ok(Some(update))
})
.try_skip_while(|update| {
ok(update.order.id != id || !matches!(update.event, updates::OrderStatus::Canceled))
})
.next()
.await
.unwrap()
.unwrap();
}
#[test(tokio::test)]
#[ignore]
async fn list_orders() {
async fn test(status: Status) {
let api_info = ApiInfo::from_env().unwrap();
let client = Client::new(api_info);
let request = OrdersReq {
status,
..Default::default()
};
let order = order_aapl(&client).await.unwrap();
let result = client.issue::<Get>(&request).await;
cancel_order(&client, order.id).await;
let before = result.unwrap();
let after = client.issue::<Get>(&request).await.unwrap();
match status {
Status::Open => {
assert!(before.into_iter().any(|x| x.id == order.id));
assert!(!after.into_iter().any(|x| x.id == order.id));
},
Status::Closed => {
assert!(!before.into_iter().any(|x| x.id == order.id));
assert!(after.into_iter().any(|x| x.id == order.id));
},
Status::All => {
assert!(before.into_iter().any(|x| x.id == order.id));
assert!(after.into_iter().any(|x| x.id == order.id));
},
}
}
test(Status::Open).await;
test(Status::Closed).await;
test(Status::All).await;
}
#[test(tokio::test)]
#[ignore]
async fn list_nested_order() {
let request = order::OrderReqInit {
class: order::Class::OneTriggersOther,
type_: order::Type::Limit,
limit_price: Some(Num::from(2)),
take_profit: Some(order::TakeProfit::Limit(Num::from(3))),
..Default::default()
}
.init("SPY", order::Side::Buy, order::Amount::quantity(1));
let api_info = ApiInfo::from_env().unwrap();
let client = Client::new(api_info);
let order = client.issue::<order::Post>(&request).await.unwrap();
assert_eq!(order.legs.len(), 1);
let request = OrdersReq {
status: Status::Open,
..Default::default()
};
let list = client.issue::<Get>(&request).await.unwrap();
client.issue::<order::Delete>(&order.id).await.unwrap();
let mut filtered = list.into_iter().filter(|o| o.id == order.id);
let listed = filtered.next().unwrap();
assert_eq!(listed.legs.len(), 1);
assert_eq!(filtered.next(), None);
}
#[test(tokio::test)]
#[ignore]
async fn symbol_filter_orders() {
let api_info = ApiInfo::from_env().unwrap();
let client = Client::new(api_info);
let request = OrdersReq::default();
let orders = client.issue::<Get>(&request).await.unwrap();
let num_goog = orders.iter().filter(|x| x.symbol == "GOOG").count();
let num_ibm = orders.iter().filter(|x| x.symbol == "IBM").count();
let buy_order = order_stock(&client, "GOOG")
.await
.expect("Failed to create GOOG order");
let request = OrdersReq {
symbols: vec!["IBM".to_string()],
..Default::default()
};
let ibm_orders = client.issue::<Get>(&request).await;
let request = OrdersReq {
symbols: vec!["GOOG".to_string()],
..Default::default()
};
let goog_orders = client.issue::<Get>(&request).await;
cancel_order(&client, buy_order.id).await;
assert_eq!(ibm_orders.unwrap().len(), num_ibm);
assert_eq!(goog_orders.unwrap().len(), num_goog + 1);
}
}