1use chrono::{DateTime, Utc};
19use derive_builder::Builder;
20use serde::{self, Deserialize, Serialize, Serializer};
21use serde_json::Value;
22
23fn serialize_json_as_string<S>(value: &Option<Value>, serializer: S) -> Result<S::Ok, S::Error>
25where
26 S: Serializer,
27{
28 match value {
29 Some(v) => serializer.serialize_str(&v.to_string()),
30 None => serializer.serialize_none(),
31 }
32}
33
34use crate::common::enums::{
35 BitmexContingencyType, BitmexExecInstruction, BitmexOrderType, BitmexPegPriceType, BitmexSide,
36 BitmexTimeInForce,
37};
38
39fn serialize_string_vec_as_json<S>(
40 values: &Option<Vec<String>>,
41 serializer: S,
42) -> Result<S::Ok, S::Error>
43where
44 S: serde::Serializer,
45{
46 match values {
47 Some(vec) => {
48 let json_array = serde_json::to_string(vec).map_err(serde::ser::Error::custom)?;
49 serializer.serialize_str(&json_array)
50 }
51 None => serializer.serialize_none(),
52 }
53}
54
55#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
57#[builder(default)]
58#[builder(setter(into, strip_option))]
59#[serde(rename_all = "camelCase")]
60pub struct GetTradeParams {
61 pub symbol: Option<String>,
63 #[serde(
65 skip_serializing_if = "Option::is_none",
66 serialize_with = "serialize_json_as_string"
67 )]
68 pub filter: Option<Value>,
69 #[serde(
71 skip_serializing_if = "Option::is_none",
72 serialize_with = "serialize_json_as_string"
73 )]
74 pub columns: Option<Value>,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub count: Option<i32>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub start: Option<i32>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub reverse: Option<bool>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub start_time: Option<DateTime<Utc>>,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub end_time: Option<DateTime<Utc>>,
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
94#[builder(default)]
95#[builder(setter(into, strip_option))]
96#[serde(rename_all = "camelCase")]
97pub struct GetTradeBucketedParams {
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub symbol: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub bin_size: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub partial: Option<bool>,
107 #[serde(
109 skip_serializing_if = "Option::is_none",
110 serialize_with = "serialize_json_as_string"
111 )]
112 pub filter: Option<Value>,
113 #[serde(
115 skip_serializing_if = "Option::is_none",
116 serialize_with = "serialize_json_as_string"
117 )]
118 pub columns: Option<Value>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub count: Option<i32>,
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub start: Option<i32>,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub reverse: Option<bool>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub start_time: Option<DateTime<Utc>>,
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub end_time: Option<DateTime<Utc>>,
134}
135
136#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
138#[builder(default)]
139#[builder(setter(into, strip_option))]
140#[serde(rename_all = "camelCase")]
141pub struct GetOrderBookL2Params {
142 pub symbol: String,
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub depth: Option<u32>,
147}
148
149#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
151#[builder(default)]
152#[builder(setter(into, strip_option))]
153#[serde(rename_all = "camelCase")]
154pub struct GetFundingParams {
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub symbol: Option<String>,
158 #[serde(
160 skip_serializing_if = "Option::is_none",
161 serialize_with = "serialize_json_as_string"
162 )]
163 pub filter: Option<Value>,
164 #[serde(
166 skip_serializing_if = "Option::is_none",
167 serialize_with = "serialize_json_as_string"
168 )]
169 pub columns: Option<Value>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub count: Option<i32>,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub start: Option<i32>,
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub reverse: Option<bool>,
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub start_time: Option<DateTime<Utc>>,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub end_time: Option<DateTime<Utc>>,
185}
186
187#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
189#[builder(default)]
190#[builder(setter(into, strip_option))]
191#[serde(rename_all = "camelCase")]
192pub struct GetOrderParams {
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub symbol: Option<String>,
196 #[serde(
198 skip_serializing_if = "Option::is_none",
199 serialize_with = "serialize_json_as_string"
200 )]
201 pub filter: Option<Value>,
202 #[serde(
204 skip_serializing_if = "Option::is_none",
205 serialize_with = "serialize_json_as_string"
206 )]
207 pub columns: Option<Value>,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub count: Option<i32>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub start: Option<i32>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub reverse: Option<bool>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub start_time: Option<DateTime<Utc>>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub end_time: Option<DateTime<Utc>>,
223}
224
225#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
227#[builder(default)]
228#[builder(setter(into, strip_option))]
229#[serde(rename_all = "camelCase")]
230pub struct PostOrderParams {
231 pub symbol: String,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub side: Option<BitmexSide>,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub order_qty: Option<u32>,
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub price: Option<f64>,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub display_qty: Option<u32>,
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub stop_px: Option<f64>,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 #[serde(rename = "clOrdID")]
251 pub cl_ord_id: Option<String>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 #[serde(rename = "clOrdLinkID")]
255 pub cl_ord_link_id: Option<String>,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub peg_offset_value: Option<f64>,
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub peg_price_type: Option<BitmexPegPriceType>,
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub ord_type: Option<BitmexOrderType>,
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub time_in_force: Option<BitmexTimeInForce>,
268 #[serde(
270 serialize_with = "serialize_exec_instructions_optional",
271 skip_serializing_if = "is_exec_inst_empty"
272 )]
273 pub exec_inst: Option<Vec<BitmexExecInstruction>>,
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub contingency_type: Option<BitmexContingencyType>,
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub text: Option<String>,
280}
281
282fn is_exec_inst_empty(exec_inst: &Option<Vec<BitmexExecInstruction>>) -> bool {
283 exec_inst.as_ref().is_none_or(Vec::is_empty)
284}
285
286fn serialize_exec_instructions_optional<S>(
287 instructions: &Option<Vec<BitmexExecInstruction>>,
288 serializer: S,
289) -> Result<S::Ok, S::Error>
290where
291 S: serde::Serializer,
292{
293 match instructions {
294 Some(inst) if !inst.is_empty() => {
295 let joined = inst
296 .iter()
297 .map(std::string::ToString::to_string)
298 .collect::<Vec<_>>()
299 .join(",");
300 serializer.serialize_some(&joined)
301 }
302 _ => serializer.serialize_none(),
303 }
304}
305
306#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
308#[builder(default)]
309#[builder(setter(into, strip_option))]
310#[serde(rename_all = "camelCase")]
311pub struct DeleteOrderParams {
312 #[serde(
314 skip_serializing_if = "Option::is_none",
315 serialize_with = "serialize_string_vec_as_json",
316 rename = "orderID"
317 )]
318 pub order_id: Option<Vec<String>>,
319 #[serde(
321 skip_serializing_if = "Option::is_none",
322 serialize_with = "serialize_string_vec_as_json",
323 rename = "clOrdID"
324 )]
325 pub cl_ord_id: Option<Vec<String>>,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 pub text: Option<String>,
329}
330
331impl DeleteOrderParamsBuilder {
332 pub fn build_validated(self) -> Result<DeleteOrderParams, String> {
338 let params = self.build().map_err(|e| format!("Failed to build: {e}"))?;
339
340 if params.order_id.is_some() && params.cl_ord_id.is_some() {
342 return Err("Cannot provide both order_id and cl_ord_id - use only one".to_string());
343 }
344
345 if params.order_id.is_none() && params.cl_ord_id.is_none() {
347 return Err("Must provide either order_id or cl_ord_id".to_string());
348 }
349
350 Ok(params)
351 }
352}
353
354#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
360#[builder(default)]
361#[builder(setter(into, strip_option))]
362#[serde(rename_all = "camelCase")]
363pub struct DeleteAllOrdersParams {
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub symbol: Option<String>,
367 #[serde(
369 skip_serializing_if = "Option::is_none",
370 serialize_with = "serialize_json_as_string"
371 )]
372 pub filter: Option<Value>,
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub text: Option<String>,
376}
377
378#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
380#[builder(default)]
381#[builder(setter(into, strip_option))]
382#[serde(rename_all = "camelCase")]
383pub struct PutOrderParams {
384 #[serde(rename = "orderID")]
386 pub order_id: Option<String>,
387 #[serde(rename = "origClOrdID")]
389 pub orig_cl_ord_id: Option<String>,
390 #[serde(rename = "clOrdID")]
392 pub cl_ord_id: Option<String>,
393 pub order_qty: Option<u32>,
395 pub leaves_qty: Option<u32>,
397 pub price: Option<f64>,
399 pub stop_px: Option<f64>,
401 pub peg_offset_value: Option<f64>,
403 pub text: Option<String>,
405}
406
407#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
409#[builder(default)]
410#[builder(setter(into, strip_option))]
411#[serde(rename_all = "camelCase")]
412pub struct GetExecutionParams {
413 #[serde(skip_serializing_if = "Option::is_none")]
415 pub symbol: Option<String>,
416 #[serde(
418 skip_serializing_if = "Option::is_none",
419 serialize_with = "serialize_json_as_string"
420 )]
421 pub filter: Option<Value>,
422 #[serde(
424 skip_serializing_if = "Option::is_none",
425 serialize_with = "serialize_json_as_string"
426 )]
427 pub columns: Option<Value>,
428 #[serde(skip_serializing_if = "Option::is_none")]
430 pub count: Option<i32>,
431 #[serde(skip_serializing_if = "Option::is_none")]
433 pub start: Option<i32>,
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub reverse: Option<bool>,
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub start_time: Option<DateTime<Utc>>,
440 #[serde(skip_serializing_if = "Option::is_none")]
442 pub end_time: Option<DateTime<Utc>>,
443}
444
445#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
447#[builder(default)]
448#[builder(setter(into, strip_option))]
449#[serde(rename_all = "camelCase")]
450pub struct PostPositionLeverageParams {
451 pub symbol: String,
453 pub leverage: f64,
455 #[serde(skip_serializing_if = "Option::is_none")]
457 pub target_account_id: Option<i64>,
458}
459
460#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
466#[builder(default)]
467#[builder(setter(into, strip_option))]
468pub struct PostCancelAllAfterParams {
469 pub timeout: u64,
471}
472
473#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
475#[builder(default)]
476#[builder(setter(into, strip_option))]
477#[serde(rename_all = "camelCase")]
478pub struct GetPositionParams {
479 #[serde(
481 skip_serializing_if = "Option::is_none",
482 serialize_with = "serialize_json_as_string"
483 )]
484 pub filter: Option<Value>,
485 #[serde(
487 skip_serializing_if = "Option::is_none",
488 serialize_with = "serialize_json_as_string"
489 )]
490 pub columns: Option<Value>,
491 #[serde(skip_serializing_if = "Option::is_none")]
493 pub count: Option<i32>,
494}
495
496#[cfg(test)]
497mod tests {
498 use rstest::rstest;
499
500 use super::*;
501
502 #[rstest]
503 fn test_cancel_all_after_params_serializes() {
504 let params = PostCancelAllAfterParams { timeout: 60_000 };
505 let encoded = serde_urlencoded::to_string(¶ms).unwrap();
506 assert_eq!(encoded, "timeout=60000");
507 }
508
509 #[rstest]
510 fn test_cancel_all_after_params_disarm_serializes() {
511 let params = PostCancelAllAfterParams { timeout: 0 };
512 let encoded = serde_urlencoded::to_string(¶ms).unwrap();
513 assert_eq!(encoded, "timeout=0");
514 }
515}