Skip to main content

bpx_api_types/
fill.rs

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    /// Filter by symbol
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub symbol: Option<String>,
51    /// From timestamp in milliseconds
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub from: Option<i64>,
54    /// To timestamp in milliseconds
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub to: Option<i64>,
57    /// Filter by fill type
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub fill_type: Option<FillType>,
60    /// Filter by market type
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub market_type: Option<String>,
63    /// Filter by order ID
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub order_id: Option<String>,
66    /// Filter by strategy ID
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub strategy_id: Option<String>,
69    /// Maximum number of results to return
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub limit: Option<u64>,
72    /// Offset for pagination - default to 0
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub offset: Option<u64>,
75    /// Sort direction
76    #[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(&params).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}