1use rust_decimal::{Decimal, serde::str_option::deserialize as option_decimal};
2use serde::{Deserialize, Serialize};
3use serde_aux::prelude::{
4 deserialize_number_from_string as number,
5 deserialize_option_number_from_string as option_number,
6};
7
8use crate::{
9 AdlRankIndicator, ExecType, OrderType, PositionIdx, PositionMode, PositionStatus, Side,
10 Timestamp, TpslMode, TradeMode, TriggerBy,
11 enums::{Category, StopOrderType},
12 serde::{empty_string_as_none, int_to_bool},
13 ws::PositionMsg,
14};
15
16#[derive(Clone, Debug, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct GetPositionInfoParams {
19 pub category: Category,
23 #[serde(skip_serializing_if = "Option::is_none")]
27 pub symbol: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub base_coin: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
34 pub settle_coin: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub limit: Option<u64>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub cursor: Option<String>,
41}
42
43impl GetPositionInfoParams {
44 pub fn new(category: Category) -> Self {
45 Self {
46 category,
47 symbol: None,
48 base_coin: None,
49 settle_coin: None,
50 limit: None,
51 cursor: None,
52 }
53 }
54
55 pub fn with_symbol(mut self, v: String) -> Self {
56 self.symbol = Some(v);
57 self
58 }
59 pub fn with_base_coin(mut self, v: String) -> Self {
60 self.base_coin = Some(v);
61 self
62 }
63 pub fn with_settle_coin(mut self, v: String) -> Self {
64 self.settle_coin = Some(v);
65 self
66 }
67 pub fn with_limit(mut self, v: u64) -> Self {
68 self.limit = Some(v);
69 self
70 }
71 pub fn with_cursor(mut self, v: String) -> Self {
72 self.cursor = Some(v);
73 self
74 }
75}
76
77#[derive(Debug, Deserialize, PartialEq)]
79#[serde(rename_all = "camelCase")]
80pub struct Position {
81 pub position_idx: PositionIdx,
86 pub risk_id: i64,
89 #[serde(default, deserialize_with = "option_decimal")]
92 pub risk_limit_value: Option<Decimal>,
93 pub symbol: String,
95 #[serde(default, deserialize_with = "empty_string_as_none")]
99 pub side: Option<Side>,
100 pub size: Decimal,
102 pub avg_price: Decimal,
105 #[serde(default, deserialize_with = "option_decimal")]
107 pub position_value: Option<Decimal>,
108 #[serde(deserialize_with = "int_to_bool")]
112 pub auto_add_margin: bool,
113 pub position_status: PositionStatus,
115 pub leverage: Decimal,
118 pub mark_price: Decimal,
120 #[serde(default, deserialize_with = "option_decimal")]
127 pub liq_price: Option<Decimal>,
128 #[serde(rename = "positionIM", default, deserialize_with = "option_decimal")]
132 pub position_im: Option<Decimal>,
133 #[serde(
137 rename = "positionIMByMp",
138 default,
139 deserialize_with = "option_decimal"
140 )]
141 pub position_im_by_mp: Option<Decimal>,
142 #[serde(rename = "positionMM", default, deserialize_with = "option_decimal")]
146 pub position_mm: Option<Decimal>,
147 #[serde(
151 rename = "positionMMByMp",
152 default,
153 deserialize_with = "option_decimal"
154 )]
155 pub position_mm_by_mp: Option<Decimal>,
156 #[serde(default, deserialize_with = "option_decimal")]
158 pub take_profit: Option<Decimal>,
159 #[serde(default, deserialize_with = "option_decimal")]
161 pub stop_loss: Option<Decimal>,
162 #[serde(default, deserialize_with = "option_decimal")]
164 pub trailing_stop: Option<Decimal>,
165 #[serde(default, deserialize_with = "option_decimal")]
167 pub session_avg_price: Option<Decimal>,
168 #[serde(default, deserialize_with = "empty_string_as_none")]
170 pub delta: Option<String>,
171 #[serde(default, deserialize_with = "empty_string_as_none")]
173 pub gamma: Option<String>,
174 #[serde(default, deserialize_with = "empty_string_as_none")]
176 pub vega: Option<String>,
177 #[serde(default, deserialize_with = "empty_string_as_none")]
179 pub theta: Option<String>,
180 #[serde(default, deserialize_with = "option_decimal")]
182 pub unrealised_pnl: Option<Decimal>,
183 pub cur_realised_pnl: Decimal,
185 pub cum_realised_pnl: Decimal,
189 pub adl_rank_indicator: AdlRankIndicator,
191 #[serde(deserialize_with = "number")]
193 pub created_time: Timestamp,
194 #[serde(deserialize_with = "number")]
196 pub updated_time: Timestamp,
197 pub seq: i64,
202 pub is_reduce_only: bool,
207 #[serde(deserialize_with = "option_number")]
214 pub mmr_sys_updated_time: Option<Timestamp>,
215 #[serde(deserialize_with = "option_number")]
222 pub leverage_sys_updated_time: Option<Timestamp>,
223}
224
225impl Position {
226 pub fn update(&mut self, msg: PositionMsg) {
229 *self = msg.into();
230 }
231}
232
233impl From<PositionMsg> for Position {
234 fn from(msg: PositionMsg) -> Self {
235 Self {
236 position_idx: msg.position_idx,
237 risk_id: msg.risk_id,
238 risk_limit_value: msg.risk_limit_value,
239 symbol: msg.symbol,
240 side: msg.side,
241 size: msg.size,
242 avg_price: msg.entry_price,
243 position_value: Some(msg.position_value),
244 auto_add_margin: msg.auto_add_margin,
245 position_status: msg.position_status,
246 leverage: msg.leverage,
247 mark_price: msg.mark_price,
248 liq_price: msg.liq_price,
249 position_im: msg.position_im,
250 position_im_by_mp: msg.position_im_by_mp,
251 position_mm: msg.position_mm,
252 position_mm_by_mp: msg.position_mm_by_mp,
253 take_profit: Some(msg.take_profit),
254 stop_loss: Some(msg.stop_loss),
255 trailing_stop: Some(msg.trailing_stop),
256 session_avg_price: msg.session_avg_price,
257 delta: msg.delta,
258 gamma: msg.gamma,
259 vega: msg.vega,
260 theta: msg.theta,
261 unrealised_pnl: Some(msg.unrealised_pnl),
262 cur_realised_pnl: msg.cur_realised_pnl,
263 cum_realised_pnl: msg.cum_realised_pnl,
264 adl_rank_indicator: msg.adl_rank_indicator,
265 created_time: msg.created_time,
266 updated_time: msg.updated_time,
267 seq: msg.seq,
268 is_reduce_only: msg.is_reduce_only,
269 mmr_sys_updated_time: msg.mmr_sys_updated_time,
270 leverage_sys_updated_time: msg.leverage_sys_updated_time,
271 }
272 }
273}
274
275#[derive(Debug, Serialize, Clone)]
278#[serde(rename_all = "camelCase")]
279pub struct SetLeverageRequest {
280 pub category: Category,
282 pub symbol: String,
283 pub buy_leverage: Decimal,
286 pub sell_leverage: Decimal,
287}
288
289impl SetLeverageRequest {
290 pub fn new(category: Category, symbol: String, leverage: Decimal) -> Self {
291 Self {
292 category,
293 symbol,
294 buy_leverage: leverage,
295 sell_leverage: leverage,
296 }
297 }
298
299 pub fn with_asymmetric(mut self, buy_leverage: Decimal, sell_leverage: Decimal) -> Self {
300 self.buy_leverage = buy_leverage;
301 self.sell_leverage = sell_leverage;
302 self
303 }
304}
305
306#[derive(Debug, Serialize, Clone)]
309#[serde(rename_all = "camelCase")]
310pub struct SetTradingStopRequest {
311 pub category: Category,
313 pub symbol: String,
314 pub position_idx: PositionIdx,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub take_profit: Option<Decimal>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 pub stop_loss: Option<Decimal>,
320 #[serde(skip_serializing_if = "Option::is_none")]
322 pub trailing_stop: Option<Decimal>,
323 #[serde(skip_serializing_if = "Option::is_none")]
324 pub tp_trigger_by: Option<TriggerBy>,
325 #[serde(skip_serializing_if = "Option::is_none")]
326 pub sl_trigger_by: Option<TriggerBy>,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub active_price: Option<Decimal>,
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub tp_size: Option<Decimal>,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 pub sl_size: Option<Decimal>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub tp_limit_price: Option<Decimal>,
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub sl_limit_price: Option<Decimal>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub tp_order_type: Option<OrderType>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub sl_order_type: Option<OrderType>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub tpsl_mode: Option<TpslMode>,
348}
349
350impl SetTradingStopRequest {
351 pub fn new(category: Category, symbol: String, position_idx: PositionIdx) -> Self {
352 Self {
353 category,
354 symbol,
355 position_idx,
356 take_profit: None,
357 stop_loss: None,
358 trailing_stop: None,
359 tp_trigger_by: None,
360 sl_trigger_by: None,
361 active_price: None,
362 tp_size: None,
363 sl_size: None,
364 tp_limit_price: None,
365 sl_limit_price: None,
366 tp_order_type: None,
367 sl_order_type: None,
368 tpsl_mode: None,
369 }
370 }
371
372 pub fn with_take_profit(mut self, v: Decimal) -> Self {
373 self.take_profit = Some(v);
374 self
375 }
376 pub fn with_stop_loss(mut self, v: Decimal) -> Self {
377 self.stop_loss = Some(v);
378 self
379 }
380 pub fn with_trailing_stop(mut self, v: Decimal) -> Self {
381 self.trailing_stop = Some(v);
382 self
383 }
384 pub fn with_tp_trigger_by(mut self, v: TriggerBy) -> Self {
385 self.tp_trigger_by = Some(v);
386 self
387 }
388 pub fn with_sl_trigger_by(mut self, v: TriggerBy) -> Self {
389 self.sl_trigger_by = Some(v);
390 self
391 }
392 pub fn with_active_price(mut self, v: Decimal) -> Self {
393 self.active_price = Some(v);
394 self
395 }
396 pub fn with_tp_size(mut self, v: Decimal) -> Self {
397 self.tp_size = Some(v);
398 self
399 }
400 pub fn with_sl_size(mut self, v: Decimal) -> Self {
401 self.sl_size = Some(v);
402 self
403 }
404 pub fn with_tp_limit_price(mut self, v: Decimal) -> Self {
405 self.tp_limit_price = Some(v);
406 self
407 }
408 pub fn with_sl_limit_price(mut self, v: Decimal) -> Self {
409 self.sl_limit_price = Some(v);
410 self
411 }
412 pub fn with_tp_order_type(mut self, v: OrderType) -> Self {
413 self.tp_order_type = Some(v);
414 self
415 }
416 pub fn with_sl_order_type(mut self, v: OrderType) -> Self {
417 self.sl_order_type = Some(v);
418 self
419 }
420 pub fn with_tpsl_mode(mut self, v: TpslMode) -> Self {
421 self.tpsl_mode = Some(v);
422 self
423 }
424}
425
426#[derive(Debug, Serialize, Clone)]
429#[serde(rename_all = "camelCase")]
430pub struct SwitchCrossIsolatedMarginRequest {
431 pub category: Category,
433 pub symbol: String,
434 pub trade_mode: TradeMode,
436 pub buy_leverage: Decimal,
437 pub sell_leverage: Decimal,
438}
439
440impl SwitchCrossIsolatedMarginRequest {
441 pub fn cross(category: Category, symbol: String, leverage: Decimal) -> Self {
442 Self {
443 category,
444 symbol,
445 trade_mode: TradeMode::CrossMargin,
446 buy_leverage: leverage,
447 sell_leverage: leverage,
448 }
449 }
450
451 pub fn isolated(
452 category: Category,
453 symbol: String,
454 buy_leverage: Decimal,
455 sell_leverage: Decimal,
456 ) -> Self {
457 Self {
458 category,
459 symbol,
460 trade_mode: TradeMode::IsolatedMargin,
461 buy_leverage,
462 sell_leverage,
463 }
464 }
465}
466
467#[derive(Debug, Serialize, Clone)]
470#[serde(rename_all = "camelCase")]
471pub struct SwitchPositionModeRequest {
472 pub category: Category,
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub symbol: Option<String>,
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub coin: Option<String>,
480 pub mode: PositionMode,
482}
483
484impl SwitchPositionModeRequest {
485 pub fn one_way(category: Category, symbol: String) -> Self {
486 Self {
487 category,
488 symbol: Some(symbol),
489 coin: None,
490 mode: PositionMode::OneWay,
491 }
492 }
493
494 pub fn hedge(category: Category, symbol: String) -> Self {
495 Self {
496 category,
497 symbol: Some(symbol),
498 coin: None,
499 mode: PositionMode::Hedge,
500 }
501 }
502
503 pub fn with_coin(mut self, v: String) -> Self {
504 self.symbol = None;
505 self.coin = Some(v);
506 self
507 }
508}
509
510#[derive(Debug, Serialize, Clone)]
513#[serde(rename_all = "camelCase")]
514pub struct SetAutoAddMarginRequest {
515 pub category: Category,
517 pub symbol: String,
518 pub auto_add_margin: i32,
520 #[serde(skip_serializing_if = "Option::is_none")]
521 pub position_idx: Option<PositionIdx>,
522}
523
524impl SetAutoAddMarginRequest {
525 pub fn new(category: Category, symbol: String, enabled: bool) -> Self {
526 Self {
527 category,
528 symbol,
529 auto_add_margin: if enabled { 1 } else { 0 },
530 position_idx: None,
531 }
532 }
533
534 pub fn with_position_idx(mut self, v: PositionIdx) -> Self {
535 self.position_idx = Some(v);
536 self
537 }
538}
539
540#[derive(Debug, Serialize, Clone)]
543#[serde(rename_all = "camelCase")]
544pub struct SetRiskLimitRequest {
545 pub category: Category,
547 pub symbol: String,
548 pub risk_id: i64,
549 #[serde(skip_serializing_if = "Option::is_none")]
550 pub position_idx: Option<PositionIdx>,
551}
552
553impl SetRiskLimitRequest {
554 pub fn new(category: Category, symbol: String, risk_id: i64) -> Self {
555 Self {
556 category,
557 symbol,
558 risk_id,
559 position_idx: None,
560 }
561 }
562
563 pub fn with_position_idx(mut self, v: PositionIdx) -> Self {
564 self.position_idx = Some(v);
565 self
566 }
567}
568
569#[derive(Debug, Deserialize, PartialEq)]
570#[serde(rename_all = "camelCase")]
571pub struct SetRiskLimitResponse {
572 pub risk_id: i64,
573 pub risk_limit_value: String,
574 pub category: String,
575 #[serde(default, deserialize_with = "empty_string_as_none")]
576 pub message: Option<String>,
577}
578
579#[derive(Clone, Debug, Serialize)]
582#[serde(rename_all = "camelCase")]
583pub struct GetClosedPnlParams {
584 pub category: Category,
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub symbol: Option<String>,
589 #[serde(skip_serializing_if = "Option::is_none")]
590 pub start_time: Option<Timestamp>,
591 #[serde(skip_serializing_if = "Option::is_none")]
592 pub end_time: Option<Timestamp>,
593 #[serde(skip_serializing_if = "Option::is_none")]
595 pub limit: Option<i32>,
596 #[serde(skip_serializing_if = "Option::is_none")]
597 pub cursor: Option<String>,
598}
599
600impl GetClosedPnlParams {
601 pub fn new(category: Category) -> Self {
602 Self {
603 category,
604 symbol: None,
605 start_time: None,
606 end_time: None,
607 limit: None,
608 cursor: None,
609 }
610 }
611
612 pub fn with_symbol(mut self, v: String) -> Self {
613 self.symbol = Some(v);
614 self
615 }
616 pub fn with_start_time(mut self, v: Timestamp) -> Self {
617 self.start_time = Some(v);
618 self
619 }
620 pub fn with_end_time(mut self, v: Timestamp) -> Self {
621 self.end_time = Some(v);
622 self
623 }
624 pub fn with_limit(mut self, v: i32) -> Self {
625 self.limit = Some(v);
626 self
627 }
628 pub fn with_cursor(mut self, v: String) -> Self {
629 self.cursor = Some(v);
630 self
631 }
632}
633
634#[derive(Debug, Deserialize, PartialEq)]
635#[serde(rename_all = "camelCase")]
636pub struct ClosedPnl {
637 pub symbol: String,
638 pub order_id: String,
639 #[serde(default, deserialize_with = "empty_string_as_none")]
640 pub order_link_id: Option<String>,
641 pub side: Side,
642 #[serde(deserialize_with = "number")]
643 pub qty: Decimal,
644 #[serde(deserialize_with = "number")]
645 pub order_price: Decimal,
646 pub order_type: OrderType,
647 pub exec_type: ExecType,
648 #[serde(deserialize_with = "number")]
649 pub closed_size: Decimal,
650 pub cum_entry_value: Decimal,
651 pub avg_entry_price: Decimal,
652 pub cum_exit_value: Decimal,
653 pub avg_exit_price: Decimal,
654 pub closed_pnl: Decimal,
655 #[serde(deserialize_with = "number")]
656 pub fill_count: i64,
657 pub leverage: Decimal,
658 #[serde(deserialize_with = "number")]
659 pub created_time: Timestamp,
660 #[serde(deserialize_with = "number")]
661 pub updated_time: Timestamp,
662}
663
664#[derive(Clone, Debug, Serialize)]
667#[serde(rename_all = "camelCase")]
668pub struct GetExecutionListParams {
669 pub category: Category,
670 #[serde(skip_serializing_if = "Option::is_none")]
671 pub symbol: Option<String>,
672 #[serde(skip_serializing_if = "Option::is_none")]
673 pub order_id: Option<String>,
674 #[serde(skip_serializing_if = "Option::is_none")]
675 pub order_link_id: Option<String>,
676 #[serde(skip_serializing_if = "Option::is_none")]
677 pub base_coin: Option<String>,
678 #[serde(skip_serializing_if = "Option::is_none")]
679 pub start_time: Option<Timestamp>,
680 #[serde(skip_serializing_if = "Option::is_none")]
681 pub end_time: Option<Timestamp>,
682 #[serde(skip_serializing_if = "Option::is_none")]
683 pub exec_type: Option<ExecType>,
684 #[serde(skip_serializing_if = "Option::is_none")]
686 pub limit: Option<i32>,
687 #[serde(skip_serializing_if = "Option::is_none")]
688 pub cursor: Option<String>,
689}
690
691impl GetExecutionListParams {
692 pub fn new(category: Category) -> Self {
693 Self {
694 category,
695 symbol: None,
696 order_id: None,
697 order_link_id: None,
698 base_coin: None,
699 start_time: None,
700 end_time: None,
701 exec_type: None,
702 limit: None,
703 cursor: None,
704 }
705 }
706
707 pub fn with_symbol(mut self, v: String) -> Self {
708 self.symbol = Some(v);
709 self
710 }
711 pub fn with_order_id(mut self, v: String) -> Self {
712 self.order_id = Some(v);
713 self
714 }
715 pub fn with_order_link_id(mut self, v: String) -> Self {
716 self.order_link_id = Some(v);
717 self
718 }
719 pub fn with_base_coin(mut self, v: String) -> Self {
720 self.base_coin = Some(v);
721 self
722 }
723 pub fn with_start_time(mut self, v: Timestamp) -> Self {
724 self.start_time = Some(v);
725 self
726 }
727 pub fn with_end_time(mut self, v: Timestamp) -> Self {
728 self.end_time = Some(v);
729 self
730 }
731 pub fn with_exec_type(mut self, v: ExecType) -> Self {
732 self.exec_type = Some(v);
733 self
734 }
735 pub fn with_limit(mut self, v: i32) -> Self {
736 self.limit = Some(v);
737 self
738 }
739 pub fn with_cursor(mut self, v: String) -> Self {
740 self.cursor = Some(v);
741 self
742 }
743}
744
745#[derive(Debug, Deserialize, PartialEq)]
746#[serde(rename_all = "camelCase")]
747pub struct ExecutionEntry {
748 pub symbol: String,
749 pub order_id: String,
750 #[serde(default, deserialize_with = "empty_string_as_none")]
751 pub order_link_id: Option<String>,
752 pub side: Side,
753 pub order_price: Decimal,
754 pub order_qty: Decimal,
755 pub order_type: OrderType,
756 #[serde(default, deserialize_with = "empty_string_as_none")]
757 pub stop_order_type: Option<StopOrderType>,
758 pub exec_fee: Decimal,
759 pub exec_id: String,
760 pub exec_price: Decimal,
761 pub exec_qty: Decimal,
762 pub exec_type: ExecType,
763 pub exec_value: Decimal,
764 #[serde(deserialize_with = "number")]
765 pub exec_time: Timestamp,
766 pub fee_rate: Decimal,
767 #[serde(default, deserialize_with = "option_decimal")]
768 pub trade_iv: Option<Decimal>,
769 #[serde(default, deserialize_with = "option_decimal")]
770 pub mark_iv: Option<Decimal>,
771 pub mark_price: Decimal,
772 #[serde(default, deserialize_with = "option_decimal")]
773 pub index_price: Option<Decimal>,
774 #[serde(default, deserialize_with = "option_decimal")]
775 pub underlying_price: Option<Decimal>,
776 #[serde(default, deserialize_with = "empty_string_as_none")]
777 pub block_trade_id: Option<String>,
778 pub closed_size: Decimal,
779 pub seq: i64,
780 pub is_maker: bool,
781}