1use serde::{Deserialize, Serialize, Serializer};
26use ustr::Ustr;
27
28use crate::common::enums::{AxCandleWidth, AxOrderStatus};
29
30#[derive(Clone, Debug, Default, Deserialize, Serialize)]
35pub struct GetTickersParams {
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub limit: Option<i32>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub offset: Option<i32>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub sort: Option<String>,
45}
46
47impl GetTickersParams {
48 #[must_use]
50 pub fn new() -> Self {
51 Self::default()
52 }
53}
54
55#[derive(Clone, Debug, Deserialize, Serialize)]
60pub struct GetTickerParams {
61 pub symbol: Ustr,
63}
64
65impl GetTickerParams {
66 #[must_use]
68 pub fn new(symbol: Ustr) -> Self {
69 Self { symbol }
70 }
71}
72
73#[derive(Clone, Debug, Deserialize, Serialize)]
78pub struct GetInstrumentParams {
79 pub symbol: Ustr,
81}
82
83impl GetInstrumentParams {
84 #[must_use]
86 pub fn new(symbol: Ustr) -> Self {
87 Self { symbol }
88 }
89}
90
91#[derive(Clone, Debug, Deserialize, Serialize)]
96pub struct GetCandlesParams {
97 pub symbol: Ustr,
99 pub start_timestamp_ns: i64,
101 pub end_timestamp_ns: i64,
103 pub candle_width: AxCandleWidth,
105}
106
107impl GetCandlesParams {
108 #[must_use]
110 pub fn new(
111 symbol: Ustr,
112 start_timestamp_ns: i64,
113 end_timestamp_ns: i64,
114 candle_width: AxCandleWidth,
115 ) -> Self {
116 Self {
117 symbol,
118 start_timestamp_ns,
119 end_timestamp_ns,
120 candle_width,
121 }
122 }
123}
124
125#[derive(Clone, Debug, Deserialize, Serialize)]
131pub struct GetCandleParams {
132 pub symbol: Ustr,
134 pub candle_width: AxCandleWidth,
136}
137
138impl GetCandleParams {
139 #[must_use]
141 pub fn new(symbol: Ustr, candle_width: AxCandleWidth) -> Self {
142 Self {
143 symbol,
144 candle_width,
145 }
146 }
147}
148
149#[derive(Clone, Debug, Deserialize, Serialize)]
154pub struct GetFundingRatesParams {
155 pub symbol: Ustr,
157 pub start_timestamp_ns: i64,
159 pub end_timestamp_ns: i64,
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub cursor: Option<String>,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub limit: Option<i32>,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub sort_ts: Option<String>,
170}
171
172impl GetFundingRatesParams {
173 #[must_use]
175 pub fn new(symbol: Ustr, start_timestamp_ns: i64, end_timestamp_ns: i64) -> Self {
176 Self {
177 symbol,
178 start_timestamp_ns,
179 end_timestamp_ns,
180 cursor: None,
181 limit: None,
182 sort_ts: None,
183 }
184 }
185}
186
187#[derive(Clone, Debug, Deserialize, Serialize)]
192pub struct GetFundingSlotsParams {
193 pub symbol: Ustr,
195 #[serde(skip_serializing_if = "Option::is_none")]
198 pub date: Option<String>,
199}
200
201impl GetFundingSlotsParams {
202 #[must_use]
204 pub fn new(symbol: Ustr) -> Self {
205 Self { symbol, date: None }
206 }
207}
208
209#[derive(Clone, Debug, Deserialize, Serialize)]
214pub struct GetFillsParams {
215 pub start_timestamp_ns: i64,
217 pub end_timestamp_ns: i64,
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub account_id: Option<String>,
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub symbol: Option<Ustr>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub cursor: Option<String>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub limit: Option<i32>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub sort_ts: Option<String>,
234}
235
236impl GetFillsParams {
237 #[must_use]
239 pub fn new(start_timestamp_ns: i64, end_timestamp_ns: i64) -> Self {
240 Self {
241 start_timestamp_ns,
242 end_timestamp_ns,
243 account_id: None,
244 symbol: None,
245 cursor: None,
246 limit: None,
247 sort_ts: None,
248 }
249 }
250}
251
252#[derive(Clone, Debug, Deserialize)]
257pub struct GetTransactionsParams {
258 pub transaction_types: Vec<String>,
260 pub start_timestamp_ns: i64,
262 pub end_timestamp_ns: i64,
264 pub account_id: Option<String>,
266 pub cursor: Option<String>,
268 pub limit: Option<i32>,
270 pub sort_ts: Option<String>,
272}
273
274impl Serialize for GetTransactionsParams {
275 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
276 where
277 S: Serializer,
278 {
279 let mut query = Vec::with_capacity(7);
280
281 if !self.transaction_types.is_empty() {
282 query.push(("transaction_types", self.transaction_types.join(",")));
283 }
284
285 query.push(("start_timestamp_ns", self.start_timestamp_ns.to_string()));
286 query.push(("end_timestamp_ns", self.end_timestamp_ns.to_string()));
287
288 if let Some(account_id) = &self.account_id {
289 query.push(("account_id", account_id.clone()));
290 }
291
292 if let Some(cursor) = &self.cursor {
293 query.push(("cursor", cursor.clone()));
294 }
295
296 if let Some(limit) = self.limit {
297 query.push(("limit", limit.to_string()));
298 }
299
300 if let Some(sort_ts) = &self.sort_ts {
301 query.push(("sort_ts", sort_ts.clone()));
302 }
303
304 query.serialize(serializer)
305 }
306}
307
308impl GetTransactionsParams {
309 #[must_use]
311 pub fn new(
312 transaction_types: Vec<String>,
313 start_timestamp_ns: i64,
314 end_timestamp_ns: i64,
315 ) -> Self {
316 Self {
317 transaction_types,
318 start_timestamp_ns,
319 end_timestamp_ns,
320 account_id: None,
321 cursor: None,
322 limit: None,
323 sort_ts: None,
324 }
325 }
326}
327
328#[derive(Clone, Debug, Deserialize, Serialize)]
333pub struct GetTradesParams {
334 pub symbol: Ustr,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub limit: Option<i32>,
339}
340
341impl GetTradesParams {
342 #[must_use]
344 pub fn new(symbol: Ustr, limit: Option<i32>) -> Self {
345 Self { symbol, limit }
346 }
347}
348
349#[derive(Clone, Debug, Deserialize, Serialize)]
354pub struct GetBookParams {
355 pub symbol: Ustr,
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub level: Option<i32>,
360}
361
362impl GetBookParams {
363 #[must_use]
365 pub fn new(symbol: Ustr, level: Option<i32>) -> Self {
366 Self { symbol, level }
367 }
368}
369
370#[derive(Clone, Debug, Deserialize, Serialize)]
377pub struct GetOrderStatusParams {
378 #[serde(rename = "oid", skip_serializing_if = "Option::is_none")]
380 pub order_id: Option<String>,
381 #[serde(rename = "cid", skip_serializing_if = "Option::is_none")]
383 pub client_order_id: Option<u64>,
384}
385
386impl GetOrderStatusParams {
387 #[must_use]
389 pub fn by_order_id(order_id: impl Into<String>) -> Self {
390 Self {
391 order_id: Some(order_id.into()),
392 client_order_id: None,
393 }
394 }
395
396 #[must_use]
398 pub fn by_client_order_id(cid: u64) -> Self {
399 Self {
400 order_id: None,
401 client_order_id: Some(cid),
402 }
403 }
404}
405
406#[derive(Clone, Debug, Default, Deserialize, Serialize)]
411pub struct GetOpenOrdersParams {
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub account_id: Option<String>,
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub limit: Option<i32>,
418 #[serde(skip_serializing_if = "Option::is_none")]
420 pub offset: Option<i32>,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub sort_ts: Option<String>,
424}
425
426impl GetOpenOrdersParams {
427 #[must_use]
429 pub fn new() -> Self {
430 Self::default()
431 }
432}
433
434#[derive(Clone, Debug, Default, Deserialize)]
439pub struct GetOrdersParams {
440 pub symbol: Option<Ustr>,
442 pub start_time: Option<String>,
444 pub end_time: Option<String>,
446 pub start_timestamp_ns: Option<i64>,
448 pub end_timestamp_ns: Option<i64>,
450 pub limit: Option<i32>,
452 pub offset: Option<i32>,
454 pub order_state: Option<AxOrderStatus>,
456 pub order_id: Option<String>,
458 pub order_ids: Vec<String>,
460 pub account_id: Option<String>,
462 pub cursor: Option<String>,
464}
465
466impl Serialize for GetOrdersParams {
467 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
468 where
469 S: Serializer,
470 {
471 let mut query = Vec::new();
472
473 if let Some(symbol) = self.symbol {
474 query.push(("symbol", symbol.to_string()));
475 }
476
477 if let Some(start_time) = &self.start_time {
478 query.push(("start_time", start_time.clone()));
479 }
480
481 if let Some(end_time) = &self.end_time {
482 query.push(("end_time", end_time.clone()));
483 }
484
485 if let Some(start_timestamp_ns) = self.start_timestamp_ns {
486 query.push(("start_timestamp_ns", start_timestamp_ns.to_string()));
487 }
488
489 if let Some(end_timestamp_ns) = self.end_timestamp_ns {
490 query.push(("end_timestamp_ns", end_timestamp_ns.to_string()));
491 }
492
493 if let Some(limit) = self.limit {
494 query.push(("limit", limit.to_string()));
495 }
496
497 if let Some(offset) = self.offset {
498 query.push(("offset", offset.to_string()));
499 }
500
501 if let Some(order_state) = self.order_state {
502 query.push(("order_states", order_state.to_string()));
503 }
504
505 if let Some(order_id) = &self.order_id {
506 query.push(("order_id", order_id.clone()));
507 }
508
509 if !self.order_ids.is_empty() {
510 query.push(("order_ids", self.order_ids.join(",")));
511 }
512
513 if let Some(account_id) = &self.account_id {
514 query.push(("account_id", account_id.clone()));
515 }
516
517 if let Some(cursor) = &self.cursor {
518 query.push(("cursor", cursor.clone()));
519 }
520
521 query.serialize(serializer)
522 }
523}
524
525impl GetOrdersParams {
526 #[must_use]
528 pub fn new() -> Self {
529 Self::default()
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use rstest::rstest;
536 use ustr::Ustr;
537
538 use super::*;
539
540 #[rstest]
541 fn test_get_ticker_params_serialization() {
542 let params = GetTickerParams::new(Ustr::from("GBPUSD-PERP"));
543 let qs = serde_urlencoded::to_string(¶ms).unwrap();
544 assert_eq!(qs, "symbol=GBPUSD-PERP");
545 }
546
547 #[rstest]
548 fn test_get_tickers_params_serialization() {
549 let params = GetTickersParams {
550 limit: Some(50),
551 offset: Some(10),
552 sort: Some("symbol".to_string()),
553 };
554 let qs = serde_urlencoded::to_string(¶ms).unwrap();
555
556 assert!(qs.contains("limit=50"));
557 assert!(qs.contains("offset=10"));
558 assert!(qs.contains("sort=symbol"));
559 }
560
561 #[rstest]
562 fn test_get_instrument_params_serialization() {
563 let params = GetInstrumentParams::new(Ustr::from("EURUSD-PERP"));
564 let qs = serde_urlencoded::to_string(¶ms).unwrap();
565 assert_eq!(qs, "symbol=EURUSD-PERP");
566 }
567
568 #[rstest]
569 fn test_get_candles_params_serialization() {
570 let params = GetCandlesParams::new(
571 Ustr::from("GBPUSD-PERP"),
572 1000000000,
573 2000000000,
574 AxCandleWidth::Minutes1,
575 );
576 let qs = serde_urlencoded::to_string(¶ms).unwrap();
577 assert!(qs.contains("symbol=GBPUSD-PERP"));
578 assert!(qs.contains("start_timestamp_ns=1000000000"));
579 assert!(qs.contains("end_timestamp_ns=2000000000"));
580 assert!(qs.contains("candle_width=1m"));
581 }
582
583 #[rstest]
584 fn test_get_candle_params_serialization() {
585 let params = GetCandleParams::new(Ustr::from("GBPUSD-PERP"), AxCandleWidth::Hours1);
586 let qs = serde_urlencoded::to_string(¶ms).unwrap();
587 assert!(qs.contains("symbol=GBPUSD-PERP"));
588 assert!(qs.contains("candle_width=1h"));
589 }
590
591 #[rstest]
592 fn test_get_funding_rates_params_serialization() {
593 let mut params =
594 GetFundingRatesParams::new(Ustr::from("GBPUSD-PERP"), 1000000000, 2000000000);
595 params.cursor = Some("opaque+/=".to_string());
596 params.limit = Some(100);
597 params.sort_ts = Some("desc".to_string());
598 let qs = serde_urlencoded::to_string(¶ms).unwrap();
599 assert!(qs.contains("symbol=GBPUSD-PERP"));
600 assert!(qs.contains("start_timestamp_ns=1000000000"));
601 assert!(qs.contains("end_timestamp_ns=2000000000"));
602 assert!(qs.contains("cursor=opaque%2B%2F%3D"));
603 assert!(qs.contains("limit=100"));
604 assert!(qs.contains("sort_ts=desc"));
605 }
606
607 #[rstest]
608 fn test_get_funding_slots_params_serialization() {
609 let params = GetFundingSlotsParams::new(Ustr::from("GBPUSD-PERP"));
610 let qs = serde_urlencoded::to_string(¶ms).unwrap();
611 assert_eq!(qs, "symbol=GBPUSD-PERP");
612 }
613
614 #[rstest]
615 fn test_get_funding_slots_params_serialization_with_date() {
616 let mut params = GetFundingSlotsParams::new(Ustr::from("GBPUSD-PERP"));
617 params.date = Some("2026-07-06".to_string());
618 let qs = serde_urlencoded::to_string(¶ms).unwrap();
619 assert!(qs.contains("symbol=GBPUSD-PERP"));
620 assert!(qs.contains("date=2026-07-06"));
621 }
622
623 #[rstest]
624 fn test_get_fills_params_serialization() {
625 let mut params = GetFillsParams::new(1000000000, 2000000000);
626 params.account_id = Some("account-1".to_string());
627 params.symbol = Some(Ustr::from("GBPUSD-PERP"));
628 params.cursor = Some("opaque+/=".to_string());
629 params.limit = Some(100);
630 params.sort_ts = Some("desc".to_string());
631 let qs = serde_urlencoded::to_string(¶ms).unwrap();
632 assert!(qs.contains("start_timestamp_ns=1000000000"));
633 assert!(qs.contains("end_timestamp_ns=2000000000"));
634 assert!(qs.contains("account_id=account-1"));
635 assert!(qs.contains("symbol=GBPUSD-PERP"));
636 assert!(qs.contains("cursor=opaque%2B%2F%3D"));
637 assert!(qs.contains("limit=100"));
638 assert!(qs.contains("sort_ts=desc"));
639 }
640
641 #[rstest]
642 fn test_get_transactions_params_serialization() {
643 let mut params = GetTransactionsParams::new(
644 vec!["FUNDING".to_string(), "TRADE".to_string()],
645 1000000000,
646 2000000000,
647 );
648 params.account_id = Some("account-1".to_string());
649 params.cursor = Some("opaque+/=".to_string());
650 params.limit = Some(100);
651 params.sort_ts = Some("desc".to_string());
652 let qs = serde_urlencoded::to_string(¶ms).unwrap();
653
654 assert!(qs.contains("transaction_types=FUNDING%2CTRADE"));
655 assert!(qs.contains("start_timestamp_ns=1000000000"));
656 assert!(qs.contains("end_timestamp_ns=2000000000"));
657 assert!(qs.contains("account_id=account-1"));
658 assert!(qs.contains("cursor=opaque%2B%2F%3D"));
659 assert!(qs.contains("limit=100"));
660 assert!(qs.contains("sort_ts=desc"));
661 }
662
663 #[rstest]
664 fn test_get_trades_params_serialization() {
665 let params = GetTradesParams::new(Ustr::from("BTC-PERP"), Some(50));
666 let qs = serde_urlencoded::to_string(¶ms).unwrap();
667 assert!(qs.contains("symbol=BTC-PERP"));
668 assert!(qs.contains("limit=50"));
669 }
670
671 #[rstest]
672 fn test_get_trades_params_serialization_no_limit() {
673 let params = GetTradesParams::new(Ustr::from("BTC-PERP"), None);
674 let qs = serde_urlencoded::to_string(¶ms).unwrap();
675 assert_eq!(qs, "symbol=BTC-PERP");
676 }
677
678 #[rstest]
679 fn test_get_open_orders_params_serialization() {
680 let params = GetOpenOrdersParams {
681 account_id: Some("account-1".to_string()),
682 limit: Some(100),
683 offset: Some(200),
684 sort_ts: Some("desc".to_string()),
685 };
686 let qs = serde_urlencoded::to_string(¶ms).unwrap();
687
688 assert!(qs.contains("account_id=account-1"));
689 assert!(qs.contains("limit=100"));
690 assert!(qs.contains("offset=200"));
691 assert!(qs.contains("sort_ts=desc"));
692 }
693
694 #[rstest]
695 fn test_get_book_params_serialization() {
696 let params = GetBookParams::new(Ustr::from("EURUSD-PERP"), Some(3));
697 let qs = serde_urlencoded::to_string(¶ms).unwrap();
698 assert!(qs.contains("symbol=EURUSD-PERP"));
699 assert!(qs.contains("level=3"));
700 }
701
702 #[rstest]
703 fn test_get_order_status_by_order_id_serialization() {
704 let params = GetOrderStatusParams::by_order_id("O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
705 let qs = serde_urlencoded::to_string(¶ms).unwrap();
706 assert_eq!(qs, "oid=O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
707 }
708
709 #[rstest]
710 fn test_get_order_status_by_client_order_id_serialization() {
711 let params = GetOrderStatusParams::by_client_order_id(12345);
712 let qs = serde_urlencoded::to_string(¶ms).unwrap();
713 assert_eq!(qs, "cid=12345");
714 }
715
716 #[rstest]
717 fn test_get_orders_params_serialization() {
718 let params = GetOrdersParams {
719 symbol: Some(Ustr::from("EURUSD-PERP")),
720 order_state: Some(AxOrderStatus::Filled),
721 order_id: Some("ORD-1".to_string()),
722 order_ids: vec!["ORD-2".to_string(), "ORD-3".to_string()],
723 account_id: Some("account-1".to_string()),
724 cursor: Some("next".to_string()),
725 start_timestamp_ns: Some(1000000000),
726 end_timestamp_ns: Some(2000000000),
727 limit: Some(100),
728 ..Default::default()
729 };
730 let qs = serde_urlencoded::to_string(¶ms).unwrap();
731
732 assert!(qs.contains("symbol=EURUSD-PERP"));
733 assert!(qs.contains("order_states=FILLED"));
734 assert!(qs.contains("order_id=ORD-1"));
735 assert!(qs.contains("order_ids=ORD-2%2CORD-3"));
736 assert!(qs.contains("account_id=account-1"));
737 assert!(qs.contains("cursor=next"));
738 assert!(qs.contains("start_timestamp_ns=1000000000"));
739 assert!(qs.contains("end_timestamp_ns=2000000000"));
740 assert!(qs.contains("limit=100"));
741 }
742}