use serde::Deserialize;
use crate::account::history::orders::Order;
use crate::client::{ApiError, Call, Transport, read};
use crate::utils::lenient::list;
use crate::utils::time::seconds_to_ms;
use crate::utils::{AccountIndex, ClientOrderIndex, Dec, MarketId, MarketKind, OrderKind, Side};
pub const MOST_CLIENT_INDEXES: usize = 20;
pub const MOST_INACTIVE: u8 = 100;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct OrderPage {
#[serde(default, deserialize_with = "list")]
pub orders: Vec<Order>,
#[serde(default)]
pub next_cursor: Option<String>,
}
impl OrderPage {
#[must_use]
pub fn next(&self) -> Option<&str> {
self.next_cursor.as_deref().filter(|cursor| !cursor.is_empty())
}
}
#[must_use]
pub fn kinds_filter(kinds: &[MarketKind]) -> Option<String> {
(!kinds.is_empty()).then(|| kinds.iter().map(|kind| kind.filter()).collect::<Vec<_>>().join(","))
}
#[must_use]
pub fn active_orders_call(account: AccountIndex, market: Option<MarketId>, kinds: &[MarketKind]) -> Call {
Call::get("accountActiveOrders")
.field("account_index", account)
.maybe("market_id", market)
.maybe("market_type", kinds_filter(kinds))
.authed()
}
pub async fn active_orders(
transport: &impl Transport,
account: AccountIndex,
market: Option<MarketId>,
kinds: &[MarketKind],
) -> Result<Vec<Order>, ApiError> {
Ok(read::<OrderPage>(transport, active_orders_call(account, market, kinds)).await?.orders)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InactiveQuery {
pub account: AccountIndex,
pub market: Option<MarketId>,
pub kinds: Vec<MarketKind>,
pub side: Option<Side>,
pub between_timestamps: Option<String>,
pub cursor: Option<String>,
pub limit: u8,
}
impl InactiveQuery {
#[must_use]
pub const fn new(account: AccountIndex) -> InactiveQuery {
InactiveQuery {
account,
market: None,
kinds: Vec::new(),
side: None,
between_timestamps: None,
cursor: None,
limit: MOST_INACTIVE,
}
}
#[must_use]
pub fn after(&self, page: &OrderPage) -> Option<InactiveQuery> {
let cursor = page.next()?;
Some(InactiveQuery { cursor: Some(cursor.to_owned()), ..self.clone() })
}
}
#[must_use]
pub fn inactive_orders_call(query: &InactiveQuery) -> Call {
Call::get("accountInactiveOrders")
.field("account_index", query.account)
.maybe("market_id", query.market)
.maybe("ask_filter", query.side.map(|side| u8::from(side.is_ask())))
.maybe("between_timestamps", query.between_timestamps.as_deref())
.maybe("cursor", query.cursor.as_deref())
.field("limit", query.limit.clamp(1, MOST_INACTIVE))
.maybe("market_type", kinds_filter(&query.kinds))
.authed()
}
pub async fn inactive_orders(transport: &impl Transport, query: &InactiveQuery) -> Result<OrderPage, ApiError> {
read(transport, inactive_orders_call(query)).await
}
#[must_use]
pub fn orders_by_client_index_call(account: Option<AccountIndex>, indexes: &[ClientOrderIndex]) -> Call {
let listed: Vec<String> = indexes.iter().map(ToString::to_string).collect();
Call::get("accountOrders").maybe("account_index", account).field("client_order_indexes", listed.join(",")).authed()
}
pub async fn orders_by_client_index(
transport: &impl Transport,
account: Option<AccountIndex>,
indexes: &[ClientOrderIndex],
) -> Result<Vec<Order>, ApiError> {
let mut orders = Vec::new();
for chunk in indexes.chunks(MOST_CLIENT_INDEXES) {
orders.extend(read::<OrderPage>(transport, orders_by_client_index_call(account, chunk)).await?.orders);
}
Ok(orders)
}
impl Order {
#[must_use]
pub const fn is_trigger(&self) -> bool {
self.kind.is_trigger()
}
#[must_use]
pub fn limit_price(&self) -> Option<Dec> {
match self.kind {
OrderKind::Market | OrderKind::StopLoss | OrderKind::TakeProfit => None,
_ => Some(self.price).filter(|price| price.is_positive()),
}
}
#[must_use]
pub fn trigger(&self) -> Option<Dec> {
Some(self.trigger_price).filter(|price| self.is_trigger() && price.is_positive())
}
#[must_use]
pub const fn placed_ms(&self) -> u64 {
match self.created_at {
0 => seconds_to_ms(self.timestamp),
created => created,
}
}
#[must_use]
pub const fn remaining(&self) -> Dec {
self.remaining_base_amount
}
}
#[cfg(test)]
#[path = "tests/order_reads.rs"]
mod tests;