use super::{history::SortDirection, order::Side};
use rust_decimal::Decimal;
#[derive(
Debug,
strum::Display,
Clone,
Copy,
serde::Serialize,
serde::Deserialize,
strum::EnumString,
PartialEq,
Eq,
Hash,
)]
#[strum(serialize_all = "PascalCase")]
#[serde(rename_all = "PascalCase")]
pub enum FillType {
User,
BookLiquidation,
Adl,
Backstop,
Liquidation,
AllLiquidation,
CollateralConversion,
CollateralConversionAndSpotLiquidation,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Fill {
pub trade_id: Option<i64>,
pub client_id: Option<String>,
pub order_id: String,
pub symbol: String,
pub fee_symbol: String,
pub price: Decimal,
pub quantity: Decimal,
pub fee: Decimal,
pub side: Side,
pub timestamp: String,
pub is_maker: bool,
pub system_order_type: Option<String>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct FillsHistoryParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fill_type: Option<FillType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub market_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strategy_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_direction: Option<SortDirection>,
}
impl FillsHistoryParams {
pub fn with_symbol<S: Into<String>>(mut self, symbol: S) -> Self {
self.symbol = Some(symbol.into());
self
}
pub fn with_from(mut self, from: i64) -> Self {
self.from = Some(from);
self
}
pub fn with_to(mut self, to: i64) -> Self {
self.to = Some(to);
self
}
pub fn with_fill_type(mut self, fill_type: FillType) -> Self {
self.fill_type = Some(fill_type);
self
}
pub fn with_market_type(mut self, market_type: String) -> Self {
self.market_type = Some(market_type);
self
}
pub fn with_order_id<S: Into<String>>(mut self, order_id: S) -> Self {
self.order_id = Some(order_id.into());
self
}
pub fn with_strategy_id<S: Into<String>>(mut self, strategy_id: S) -> Self {
self.strategy_id = Some(strategy_id.into());
self
}
pub fn with_limit(mut self, limit: u64) -> Self {
self.limit = Some(limit);
self
}
pub fn with_offset(mut self, offset: u64) -> Self {
self.offset = Some(offset);
self
}
pub fn with_sort_direction(mut self, sort_direction: SortDirection) -> Self {
self.sort_direction = Some(sort_direction);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fills_history_params_omits_none_fields_in_query_string() {
let params = FillsHistoryParams::default()
.with_from(1000)
.with_to(2000)
.with_limit(1000)
.with_offset(0)
.with_sort_direction(SortDirection::Asc);
let query = serde_qs::to_string(¶ms).unwrap();
for segment in query.split('&') {
assert!(
segment.contains('='),
"bare query key emitted for unset option: {segment:?} in {query:?}"
);
}
let pairs: Vec<_> = query
.split('&')
.map(|segment| segment.split_once('=').unwrap())
.collect();
assert_eq!(
pairs,
[
("from", "1000"),
("to", "2000"),
("limit", "1000"),
("offset", "0"),
("sort_direction", "Asc"),
]
);
}
}