1use super::{history::SortDirection, order::Side};
2use rust_decimal::Decimal;
3
4#[derive(
5 Debug,
6 strum::Display,
7 Clone,
8 Copy,
9 serde::Serialize,
10 serde::Deserialize,
11 strum::EnumString,
12 PartialEq,
13 Eq,
14 Hash,
15)]
16#[strum(serialize_all = "PascalCase")]
17#[serde(rename_all = "PascalCase")]
18pub enum FillType {
19 User,
20 BookLiquidation,
21 Adl,
22 Backstop,
23 Liquidation,
24 AllLiquidation,
25 CollateralConversion,
26 CollateralConversionAndSpotLiquidation,
27}
28
29#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct Fill {
32 pub trade_id: Option<i64>,
33 pub client_id: Option<String>,
34 pub order_id: String,
35 pub symbol: String,
36 pub fee_symbol: String,
37 pub price: Decimal,
38 pub quantity: Decimal,
39 pub fee: Decimal,
40 pub side: Side,
41 pub timestamp: String,
42 pub is_maker: bool,
43 pub system_order_type: Option<String>,
44}
45
46#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
47pub struct FillsHistoryParams {
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub symbol: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub from: Option<i64>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub to: Option<i64>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub fill_type: Option<FillType>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub market_type: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub order_id: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub strategy_id: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub limit: Option<u64>,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub offset: Option<u64>,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub sort_direction: Option<SortDirection>,
78}
79
80impl FillsHistoryParams {
81 pub fn with_symbol<S: Into<String>>(mut self, symbol: S) -> Self {
82 self.symbol = Some(symbol.into());
83 self
84 }
85
86 pub fn with_from(mut self, from: i64) -> Self {
87 self.from = Some(from);
88 self
89 }
90
91 pub fn with_to(mut self, to: i64) -> Self {
92 self.to = Some(to);
93 self
94 }
95
96 pub fn with_fill_type(mut self, fill_type: FillType) -> Self {
97 self.fill_type = Some(fill_type);
98 self
99 }
100
101 pub fn with_market_type(mut self, market_type: String) -> Self {
102 self.market_type = Some(market_type);
103 self
104 }
105
106 pub fn with_order_id<S: Into<String>>(mut self, order_id: S) -> Self {
107 self.order_id = Some(order_id.into());
108 self
109 }
110
111 pub fn with_strategy_id<S: Into<String>>(mut self, strategy_id: S) -> Self {
112 self.strategy_id = Some(strategy_id.into());
113 self
114 }
115
116 pub fn with_limit(mut self, limit: u64) -> Self {
117 self.limit = Some(limit);
118 self
119 }
120
121 pub fn with_offset(mut self, offset: u64) -> Self {
122 self.offset = Some(offset);
123 self
124 }
125
126 pub fn with_sort_direction(mut self, sort_direction: SortDirection) -> Self {
127 self.sort_direction = Some(sort_direction);
128 self
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn fills_history_params_omits_none_fields_in_query_string() {
138 let params = FillsHistoryParams::default()
139 .with_from(1000)
140 .with_to(2000)
141 .with_limit(1000)
142 .with_offset(0)
143 .with_sort_direction(SortDirection::Asc);
144
145 let query = serde_qs::to_string(¶ms).unwrap();
146
147 for segment in query.split('&') {
148 assert!(
149 segment.contains('='),
150 "bare query key emitted for unset option: {segment:?} in {query:?}"
151 );
152 }
153
154 let pairs: Vec<_> = query
155 .split('&')
156 .map(|segment| segment.split_once('=').unwrap())
157 .collect();
158 assert_eq!(
159 pairs,
160 [
161 ("from", "1000"),
162 ("to", "2000"),
163 ("limit", "1000"),
164 ("offset", "0"),
165 ("sort_direction", "Asc"),
166 ]
167 );
168 }
169}