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<Ustr>,
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<Ustr>,
260 pub start_timestamp_ns: i64,
262 pub end_timestamp_ns: i64,
264 pub account_id: Option<Ustr>,
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((
283 "transaction_types",
284 self.transaction_types
285 .iter()
286 .map(Ustr::as_str)
287 .collect::<Vec<_>>()
288 .join(","),
289 ));
290 }
291
292 query.push(("start_timestamp_ns", self.start_timestamp_ns.to_string()));
293 query.push(("end_timestamp_ns", self.end_timestamp_ns.to_string()));
294
295 if let Some(account_id) = &self.account_id {
296 query.push(("account_id", account_id.to_string()));
297 }
298
299 if let Some(cursor) = &self.cursor {
300 query.push(("cursor", cursor.clone()));
301 }
302
303 if let Some(limit) = self.limit {
304 query.push(("limit", limit.to_string()));
305 }
306
307 if let Some(sort_ts) = &self.sort_ts {
308 query.push(("sort_ts", sort_ts.clone()));
309 }
310
311 query.serialize(serializer)
312 }
313}
314
315impl GetTransactionsParams {
316 #[must_use]
318 pub fn new(
319 transaction_types: Vec<Ustr>,
320 start_timestamp_ns: i64,
321 end_timestamp_ns: i64,
322 ) -> Self {
323 Self {
324 transaction_types,
325 start_timestamp_ns,
326 end_timestamp_ns,
327 account_id: None,
328 cursor: None,
329 limit: None,
330 sort_ts: None,
331 }
332 }
333}
334
335#[derive(Clone, Debug, Deserialize, Serialize)]
340pub struct GetTradesParams {
341 pub symbol: Ustr,
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub limit: Option<i32>,
346}
347
348impl GetTradesParams {
349 #[must_use]
351 pub fn new(symbol: Ustr, limit: Option<i32>) -> Self {
352 Self { symbol, limit }
353 }
354}
355
356#[derive(Clone, Debug, Deserialize, Serialize)]
361pub struct GetBookParams {
362 pub symbol: Ustr,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub level: Option<i32>,
367}
368
369impl GetBookParams {
370 #[must_use]
372 pub fn new(symbol: Ustr, level: Option<i32>) -> Self {
373 Self { symbol, level }
374 }
375}
376
377#[derive(Clone, Debug, Deserialize, Serialize)]
384pub struct GetOrderStatusParams {
385 #[serde(rename = "oid", skip_serializing_if = "Option::is_none")]
387 pub order_id: Option<String>,
388 #[serde(rename = "cid", skip_serializing_if = "Option::is_none")]
390 pub client_order_id: Option<u64>,
391}
392
393impl GetOrderStatusParams {
394 #[must_use]
396 pub fn by_order_id(order_id: impl Into<String>) -> Self {
397 Self {
398 order_id: Some(order_id.into()),
399 client_order_id: None,
400 }
401 }
402
403 #[must_use]
405 pub fn by_client_order_id(cid: u64) -> Self {
406 Self {
407 order_id: None,
408 client_order_id: Some(cid),
409 }
410 }
411}
412
413#[derive(Clone, Debug, Default, Deserialize, Serialize)]
418pub struct GetOpenOrdersParams {
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub account_id: Option<Ustr>,
422 #[serde(skip_serializing_if = "Option::is_none")]
424 pub limit: Option<i32>,
425 #[serde(skip_serializing_if = "Option::is_none")]
427 pub offset: Option<i32>,
428 #[serde(skip_serializing_if = "Option::is_none")]
430 pub sort_ts: Option<String>,
431}
432
433impl GetOpenOrdersParams {
434 #[must_use]
436 pub fn new() -> Self {
437 Self::default()
438 }
439}
440
441#[derive(Clone, Debug, Default, Deserialize)]
446pub struct GetOrdersParams {
447 pub symbol: Option<Ustr>,
449 pub start_time: Option<String>,
451 pub end_time: Option<String>,
453 pub start_timestamp_ns: Option<i64>,
455 pub end_timestamp_ns: Option<i64>,
457 pub limit: Option<i32>,
459 pub offset: Option<i32>,
461 pub order_state: Option<AxOrderStatus>,
463 pub order_id: Option<String>,
465 pub order_ids: Vec<String>,
467 pub account_id: Option<Ustr>,
469 pub cursor: Option<String>,
471}
472
473impl Serialize for GetOrdersParams {
474 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
475 where
476 S: Serializer,
477 {
478 let mut query = Vec::new();
479
480 if let Some(symbol) = self.symbol {
481 query.push(("symbol", symbol.to_string()));
482 }
483
484 if let Some(start_time) = &self.start_time {
485 query.push(("start_time", start_time.clone()));
486 }
487
488 if let Some(end_time) = &self.end_time {
489 query.push(("end_time", end_time.clone()));
490 }
491
492 if let Some(start_timestamp_ns) = self.start_timestamp_ns {
493 query.push(("start_timestamp_ns", start_timestamp_ns.to_string()));
494 }
495
496 if let Some(end_timestamp_ns) = self.end_timestamp_ns {
497 query.push(("end_timestamp_ns", end_timestamp_ns.to_string()));
498 }
499
500 if let Some(limit) = self.limit {
501 query.push(("limit", limit.to_string()));
502 }
503
504 if let Some(offset) = self.offset {
505 query.push(("offset", offset.to_string()));
506 }
507
508 if let Some(order_state) = self.order_state {
509 query.push(("order_states", order_state.to_string()));
510 }
511
512 if let Some(order_id) = &self.order_id {
513 query.push(("order_id", order_id.clone()));
514 }
515
516 if !self.order_ids.is_empty() {
517 query.push(("order_ids", self.order_ids.join(",")));
518 }
519
520 if let Some(account_id) = &self.account_id {
521 query.push(("account_id", account_id.to_string()));
522 }
523
524 if let Some(cursor) = &self.cursor {
525 query.push(("cursor", cursor.clone()));
526 }
527
528 query.serialize(serializer)
529 }
530}
531
532impl GetOrdersParams {
533 #[must_use]
535 pub fn new() -> Self {
536 Self::default()
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use rstest::rstest;
543 use ustr::Ustr;
544
545 use super::*;
546
547 #[rstest]
548 fn test_get_ticker_params_serialization() {
549 let params = GetTickerParams::new(Ustr::from("GBPUSD-PERP"));
550 let qs = serde_urlencoded::to_string(¶ms).unwrap();
551 assert_eq!(qs, "symbol=GBPUSD-PERP");
552 }
553
554 #[rstest]
555 fn test_get_tickers_params_serialization() {
556 let params = GetTickersParams {
557 limit: Some(50),
558 offset: Some(10),
559 sort: Some("symbol".to_string()),
560 };
561 let qs = serde_urlencoded::to_string(¶ms).unwrap();
562
563 assert!(qs.contains("limit=50"));
564 assert!(qs.contains("offset=10"));
565 assert!(qs.contains("sort=symbol"));
566 }
567
568 #[rstest]
569 fn test_get_instrument_params_serialization() {
570 let params = GetInstrumentParams::new(Ustr::from("EURUSD-PERP"));
571 let qs = serde_urlencoded::to_string(¶ms).unwrap();
572 assert_eq!(qs, "symbol=EURUSD-PERP");
573 }
574
575 #[rstest]
576 fn test_get_candles_params_serialization() {
577 let params = GetCandlesParams::new(
578 Ustr::from("GBPUSD-PERP"),
579 1000000000,
580 2000000000,
581 AxCandleWidth::Minutes1,
582 );
583 let qs = serde_urlencoded::to_string(¶ms).unwrap();
584 assert!(qs.contains("symbol=GBPUSD-PERP"));
585 assert!(qs.contains("start_timestamp_ns=1000000000"));
586 assert!(qs.contains("end_timestamp_ns=2000000000"));
587 assert!(qs.contains("candle_width=1m"));
588 }
589
590 #[rstest]
591 fn test_get_candle_params_serialization() {
592 let params = GetCandleParams::new(Ustr::from("GBPUSD-PERP"), AxCandleWidth::Hours1);
593 let qs = serde_urlencoded::to_string(¶ms).unwrap();
594 assert!(qs.contains("symbol=GBPUSD-PERP"));
595 assert!(qs.contains("candle_width=1h"));
596 }
597
598 #[rstest]
599 fn test_get_funding_rates_params_serialization() {
600 let mut params =
601 GetFundingRatesParams::new(Ustr::from("GBPUSD-PERP"), 1000000000, 2000000000);
602 params.cursor = Some("opaque+/=".to_string());
603 params.limit = Some(100);
604 params.sort_ts = Some("desc".to_string());
605 let qs = serde_urlencoded::to_string(¶ms).unwrap();
606 assert!(qs.contains("symbol=GBPUSD-PERP"));
607 assert!(qs.contains("start_timestamp_ns=1000000000"));
608 assert!(qs.contains("end_timestamp_ns=2000000000"));
609 assert!(qs.contains("cursor=opaque%2B%2F%3D"));
610 assert!(qs.contains("limit=100"));
611 assert!(qs.contains("sort_ts=desc"));
612 }
613
614 #[rstest]
615 fn test_get_funding_slots_params_serialization() {
616 let params = GetFundingSlotsParams::new(Ustr::from("GBPUSD-PERP"));
617 let qs = serde_urlencoded::to_string(¶ms).unwrap();
618 assert_eq!(qs, "symbol=GBPUSD-PERP");
619 }
620
621 #[rstest]
622 fn test_get_funding_slots_params_serialization_with_date() {
623 let mut params = GetFundingSlotsParams::new(Ustr::from("GBPUSD-PERP"));
624 params.date = Some("2026-07-06".to_string());
625 let qs = serde_urlencoded::to_string(¶ms).unwrap();
626 assert!(qs.contains("symbol=GBPUSD-PERP"));
627 assert!(qs.contains("date=2026-07-06"));
628 }
629
630 #[rstest]
631 fn test_get_fills_params_serialization() {
632 let mut params = GetFillsParams::new(1000000000, 2000000000);
633 params.account_id = Some(Ustr::from("account-1"));
634 params.symbol = Some(Ustr::from("GBPUSD-PERP"));
635 params.cursor = Some("opaque+/=".to_string());
636 params.limit = Some(100);
637 params.sort_ts = Some("desc".to_string());
638 let qs = serde_urlencoded::to_string(¶ms).unwrap();
639 assert!(qs.contains("start_timestamp_ns=1000000000"));
640 assert!(qs.contains("end_timestamp_ns=2000000000"));
641 assert!(qs.contains("account_id=account-1"));
642 assert!(qs.contains("symbol=GBPUSD-PERP"));
643 assert!(qs.contains("cursor=opaque%2B%2F%3D"));
644 assert!(qs.contains("limit=100"));
645 assert!(qs.contains("sort_ts=desc"));
646 }
647
648 #[rstest]
649 fn test_get_transactions_params_serialization() {
650 let mut params = GetTransactionsParams::new(
651 vec![Ustr::from("FUNDING"), Ustr::from("TRADE")],
652 1000000000,
653 2000000000,
654 );
655 params.account_id = Some(Ustr::from("account-1"));
656 params.cursor = Some("opaque+/=".to_string());
657 params.limit = Some(100);
658 params.sort_ts = Some("desc".to_string());
659 let qs = serde_urlencoded::to_string(¶ms).unwrap();
660
661 assert!(qs.contains("transaction_types=FUNDING%2CTRADE"));
662 assert!(qs.contains("start_timestamp_ns=1000000000"));
663 assert!(qs.contains("end_timestamp_ns=2000000000"));
664 assert!(qs.contains("account_id=account-1"));
665 assert!(qs.contains("cursor=opaque%2B%2F%3D"));
666 assert!(qs.contains("limit=100"));
667 assert!(qs.contains("sort_ts=desc"));
668 }
669
670 #[rstest]
671 fn test_get_trades_params_serialization() {
672 let params = GetTradesParams::new(Ustr::from("BTC-PERP"), Some(50));
673 let qs = serde_urlencoded::to_string(¶ms).unwrap();
674 assert!(qs.contains("symbol=BTC-PERP"));
675 assert!(qs.contains("limit=50"));
676 }
677
678 #[rstest]
679 fn test_get_trades_params_serialization_no_limit() {
680 let params = GetTradesParams::new(Ustr::from("BTC-PERP"), None);
681 let qs = serde_urlencoded::to_string(¶ms).unwrap();
682 assert_eq!(qs, "symbol=BTC-PERP");
683 }
684
685 #[rstest]
686 fn test_get_open_orders_params_serialization() {
687 let params = GetOpenOrdersParams {
688 account_id: Some(Ustr::from("account-1")),
689 limit: Some(100),
690 offset: Some(200),
691 sort_ts: Some("desc".to_string()),
692 };
693 let qs = serde_urlencoded::to_string(¶ms).unwrap();
694
695 assert!(qs.contains("account_id=account-1"));
696 assert!(qs.contains("limit=100"));
697 assert!(qs.contains("offset=200"));
698 assert!(qs.contains("sort_ts=desc"));
699 }
700
701 #[rstest]
702 fn test_get_book_params_serialization() {
703 let params = GetBookParams::new(Ustr::from("EURUSD-PERP"), Some(3));
704 let qs = serde_urlencoded::to_string(¶ms).unwrap();
705 assert!(qs.contains("symbol=EURUSD-PERP"));
706 assert!(qs.contains("level=3"));
707 }
708
709 #[rstest]
710 fn test_get_order_status_by_order_id_serialization() {
711 let params = GetOrderStatusParams::by_order_id("O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
712 let qs = serde_urlencoded::to_string(¶ms).unwrap();
713 assert_eq!(qs, "oid=O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
714 }
715
716 #[rstest]
717 fn test_get_order_status_by_client_order_id_serialization() {
718 let params = GetOrderStatusParams::by_client_order_id(12345);
719 let qs = serde_urlencoded::to_string(¶ms).unwrap();
720 assert_eq!(qs, "cid=12345");
721 }
722
723 #[rstest]
724 fn test_get_orders_params_serialization() {
725 let params = GetOrdersParams {
726 symbol: Some(Ustr::from("EURUSD-PERP")),
727 order_state: Some(AxOrderStatus::Filled),
728 order_id: Some("ORD-1".to_string()),
729 order_ids: vec!["ORD-2".to_string(), "ORD-3".to_string()],
730 account_id: Some(Ustr::from("account-1")),
731 cursor: Some("next".to_string()),
732 start_timestamp_ns: Some(1000000000),
733 end_timestamp_ns: Some(2000000000),
734 limit: Some(100),
735 ..Default::default()
736 };
737 let qs = serde_urlencoded::to_string(¶ms).unwrap();
738
739 assert!(qs.contains("symbol=EURUSD-PERP"));
740 assert!(qs.contains("order_states=FILLED"));
741 assert!(qs.contains("order_id=ORD-1"));
742 assert!(qs.contains("order_ids=ORD-2%2CORD-3"));
743 assert!(qs.contains("account_id=account-1"));
744 assert!(qs.contains("cursor=next"));
745 assert!(qs.contains("start_timestamp_ns=1000000000"));
746 assert!(qs.contains("end_timestamp_ns=2000000000"));
747 assert!(qs.contains("limit=100"));
748 }
749}