Skip to main content

bybit_client/types/
position.rs

1//! Position-related types for position management.
2
3use serde::{Deserialize, Serialize};
4
5use crate::types::{Category, OrderType, PositionIdx, TriggerBy};
6
7
8/// Parameters for getting position info.
9#[derive(Debug, Clone, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct GetPositionInfoParams {
12    /// Product category.
13    pub category: Category,
14    /// Trading symbol.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub symbol: Option<String>,
17    /// Base coin filter.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub base_coin: Option<String>,
20    /// Settle coin filter.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub settle_coin: Option<String>,
23    /// Limit.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub limit: Option<u32>,
26    /// Cursor for pagination.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub cursor: Option<String>,
29}
30
31impl GetPositionInfoParams {
32    /// Create new parameters.
33    pub fn new(category: Category) -> Self {
34        Self {
35            category,
36            symbol: None,
37            base_coin: None,
38            settle_coin: None,
39            limit: None,
40            cursor: None,
41        }
42    }
43
44    /// Set symbol filter.
45    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
46        self.symbol = Some(symbol.into());
47        self
48    }
49
50    /// Set base coin filter.
51    pub fn base_coin(mut self, coin: impl Into<String>) -> Self {
52        self.base_coin = Some(coin.into());
53        self
54    }
55
56    /// Set settle coin filter.
57    pub fn settle_coin(mut self, coin: impl Into<String>) -> Self {
58        self.settle_coin = Some(coin.into());
59        self
60    }
61
62    /// Set limit.
63    pub fn limit(mut self, limit: u32) -> Self {
64        self.limit = Some(limit);
65        self
66    }
67
68    /// Set cursor.
69    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
70        self.cursor = Some(cursor.into());
71        self
72    }
73}
74
75/// Parameters for setting leverage.
76#[derive(Debug, Clone, Serialize)]
77#[serde(rename_all = "camelCase")]
78pub struct SetLeverageParams {
79    /// Product category.
80    pub category: Category,
81    /// Trading symbol.
82    pub symbol: String,
83    /// Buy leverage (must be same as sell for one-way mode).
84    pub buy_leverage: String,
85    /// Sell leverage (must be same as buy for one-way mode).
86    pub sell_leverage: String,
87}
88
89impl SetLeverageParams {
90    /// Create new parameters.
91    pub fn new(
92        category: Category,
93        symbol: impl Into<String>,
94        buy_leverage: impl Into<String>,
95        sell_leverage: impl Into<String>,
96    ) -> Self {
97        Self {
98            category,
99            symbol: symbol.into(),
100            buy_leverage: buy_leverage.into(),
101            sell_leverage: sell_leverage.into(),
102        }
103    }
104
105    /// Create parameters with same leverage for buy and sell (one-way mode).
106    pub fn uniform(category: Category, symbol: impl Into<String>, leverage: impl Into<String>) -> Self {
107        let lev = leverage.into();
108        Self::new(category, symbol, lev.clone(), lev)
109    }
110}
111
112/// Parameters for switching isolated margin mode.
113#[derive(Debug, Clone, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct SwitchMarginModeParams {
116    /// Product category.
117    pub category: Category,
118    /// Trading symbol.
119    pub symbol: String,
120    /// Trade mode (0=cross margin, 1=isolated margin).
121    pub trade_mode: i32,
122    /// Buy leverage.
123    pub buy_leverage: String,
124    /// Sell leverage.
125    pub sell_leverage: String,
126}
127
128impl SwitchMarginModeParams {
129    /// Create parameters for cross margin mode.
130    pub fn cross_margin(
131        category: Category,
132        symbol: impl Into<String>,
133        buy_leverage: impl Into<String>,
134        sell_leverage: impl Into<String>,
135    ) -> Self {
136        Self {
137            category,
138            symbol: symbol.into(),
139            trade_mode: 0,
140            buy_leverage: buy_leverage.into(),
141            sell_leverage: sell_leverage.into(),
142        }
143    }
144
145    /// Create parameters for isolated margin mode.
146    pub fn isolated_margin(
147        category: Category,
148        symbol: impl Into<String>,
149        buy_leverage: impl Into<String>,
150        sell_leverage: impl Into<String>,
151    ) -> Self {
152        Self {
153            category,
154            symbol: symbol.into(),
155            trade_mode: 1,
156            buy_leverage: buy_leverage.into(),
157            sell_leverage: sell_leverage.into(),
158        }
159    }
160}
161
162/// Parameters for switching position mode.
163#[derive(Debug, Clone, Serialize)]
164#[serde(rename_all = "camelCase")]
165pub struct SwitchPositionModeParams {
166    /// Product category.
167    pub category: Category,
168    /// Trading symbol (for linear, required if coin is not set).
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub symbol: Option<String>,
171    /// Coin (for inverse, required if symbol is not set).
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub coin: Option<String>,
174    /// Position mode (0=one-way, 3=hedge).
175    pub mode: i32,
176}
177
178impl SwitchPositionModeParams {
179    /// Create parameters for one-way mode with symbol.
180    pub fn one_way_by_symbol(category: Category, symbol: impl Into<String>) -> Self {
181        Self {
182            category,
183            symbol: Some(symbol.into()),
184            coin: None,
185            mode: 0,
186        }
187    }
188
189    /// Create parameters for hedge mode with symbol.
190    pub fn hedge_by_symbol(category: Category, symbol: impl Into<String>) -> Self {
191        Self {
192            category,
193            symbol: Some(symbol.into()),
194            coin: None,
195            mode: 3,
196        }
197    }
198
199    /// Create parameters for one-way mode with coin.
200    pub fn one_way_by_coin(category: Category, coin: impl Into<String>) -> Self {
201        Self {
202            category,
203            symbol: None,
204            coin: Some(coin.into()),
205            mode: 0,
206        }
207    }
208
209    /// Create parameters for hedge mode with coin.
210    pub fn hedge_by_coin(category: Category, coin: impl Into<String>) -> Self {
211        Self {
212            category,
213            symbol: None,
214            coin: Some(coin.into()),
215            mode: 3,
216        }
217    }
218}
219
220/// Parameters for setting trading stop (TP/SL/trailing stop).
221#[derive(Debug, Clone, Serialize)]
222#[serde(rename_all = "camelCase")]
223pub struct SetTradingStopParams {
224    /// Product category.
225    pub category: Category,
226    /// Trading symbol.
227    pub symbol: String,
228    /// Position index.
229    pub position_idx: PositionIdx,
230    /// Take profit price.
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub take_profit: Option<String>,
233    /// Stop loss price.
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub stop_loss: Option<String>,
236    /// Trailing stop distance.
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub trailing_stop: Option<String>,
239    /// Take profit trigger type.
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub tp_trigger_by: Option<TriggerBy>,
242    /// Stop loss trigger type.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub sl_trigger_by: Option<TriggerBy>,
245    /// Active price for trailing stop.
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub active_price: Option<String>,
248    /// TP/SL mode (Full or Partial).
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub tpsl_mode: Option<String>,
251    /// TP size for partial mode.
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub tp_size: Option<String>,
254    /// SL size for partial mode.
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub sl_size: Option<String>,
257    /// TP limit price.
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub tp_limit_price: Option<String>,
260    /// SL limit price.
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub sl_limit_price: Option<String>,
263    /// TP order type.
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub tp_order_type: Option<OrderType>,
266    /// SL order type.
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub sl_order_type: Option<OrderType>,
269}
270
271impl SetTradingStopParams {
272    /// Create new parameters.
273    pub fn new(category: Category, symbol: impl Into<String>, position_idx: PositionIdx) -> Self {
274        Self {
275            category,
276            symbol: symbol.into(),
277            position_idx,
278            take_profit: None,
279            stop_loss: None,
280            trailing_stop: None,
281            tp_trigger_by: None,
282            sl_trigger_by: None,
283            active_price: None,
284            tpsl_mode: None,
285            tp_size: None,
286            sl_size: None,
287            tp_limit_price: None,
288            sl_limit_price: None,
289            tp_order_type: None,
290            sl_order_type: None,
291        }
292    }
293
294    /// Set take profit price.
295    pub fn take_profit(mut self, tp: impl Into<String>) -> Self {
296        self.take_profit = Some(tp.into());
297        self
298    }
299
300    /// Set stop loss price.
301    pub fn stop_loss(mut self, sl: impl Into<String>) -> Self {
302        self.stop_loss = Some(sl.into());
303        self
304    }
305
306    /// Set trailing stop distance.
307    pub fn trailing_stop(mut self, ts: impl Into<String>) -> Self {
308        self.trailing_stop = Some(ts.into());
309        self
310    }
311
312    /// Set take profit trigger type.
313    pub fn tp_trigger_by(mut self, by: TriggerBy) -> Self {
314        self.tp_trigger_by = Some(by);
315        self
316    }
317
318    /// Set stop loss trigger type.
319    pub fn sl_trigger_by(mut self, by: TriggerBy) -> Self {
320        self.sl_trigger_by = Some(by);
321        self
322    }
323
324    /// Set TP/SL mode.
325    pub fn tpsl_mode(mut self, mode: impl Into<String>) -> Self {
326        self.tpsl_mode = Some(mode.into());
327        self
328    }
329}
330
331/// Parameters for setting auto add margin.
332#[derive(Debug, Clone, Serialize)]
333#[serde(rename_all = "camelCase")]
334pub struct SetAutoAddMarginParams {
335    /// Product category (must be linear).
336    pub category: Category,
337    /// Trading symbol.
338    pub symbol: String,
339    /// Auto add margin (0=off, 1=on).
340    pub auto_add_margin: i32,
341    /// Position index.
342    #[serde(skip_serializing_if = "Option::is_none")]
343    pub position_idx: Option<PositionIdx>,
344}
345
346impl SetAutoAddMarginParams {
347    /// Create parameters to enable auto add margin.
348    pub fn enable(category: Category, symbol: impl Into<String>) -> Self {
349        Self {
350            category,
351            symbol: symbol.into(),
352            auto_add_margin: 1,
353            position_idx: None,
354        }
355    }
356
357    /// Create parameters to disable auto add margin.
358    pub fn disable(category: Category, symbol: impl Into<String>) -> Self {
359        Self {
360            category,
361            symbol: symbol.into(),
362            auto_add_margin: 0,
363            position_idx: None,
364        }
365    }
366
367    /// Set position index.
368    pub fn position_idx(mut self, idx: PositionIdx) -> Self {
369        self.position_idx = Some(idx);
370        self
371    }
372}
373
374/// Parameters for adding or reducing margin.
375#[derive(Debug, Clone, Serialize)]
376#[serde(rename_all = "camelCase")]
377pub struct AddReduceMarginParams {
378    /// Product category.
379    pub category: Category,
380    /// Trading symbol.
381    pub symbol: String,
382    /// Margin amount (positive to add, negative to reduce).
383    pub margin: String,
384    /// Position index.
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub position_idx: Option<PositionIdx>,
387}
388
389impl AddReduceMarginParams {
390    /// Create new parameters.
391    pub fn new(category: Category, symbol: impl Into<String>, margin: impl Into<String>) -> Self {
392        Self {
393            category,
394            symbol: symbol.into(),
395            margin: margin.into(),
396            position_idx: None,
397        }
398    }
399
400    /// Set position index.
401    pub fn position_idx(mut self, idx: PositionIdx) -> Self {
402        self.position_idx = Some(idx);
403        self
404    }
405}
406
407/// Parameters for getting closed PnL.
408#[derive(Debug, Clone, Serialize)]
409#[serde(rename_all = "camelCase")]
410pub struct GetClosedPnlParams {
411    /// Product category.
412    pub category: Category,
413    /// Trading symbol.
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub symbol: Option<String>,
416    /// Start time (ms).
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub start_time: Option<u64>,
419    /// End time (ms).
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub end_time: Option<u64>,
422    /// Limit.
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub limit: Option<u32>,
425    /// Cursor.
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub cursor: Option<String>,
428}
429
430impl GetClosedPnlParams {
431    /// Create new parameters.
432    pub fn new(category: Category) -> Self {
433        Self {
434            category,
435            symbol: None,
436            start_time: None,
437            end_time: None,
438            limit: None,
439            cursor: None,
440        }
441    }
442
443    /// Set symbol filter.
444    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
445        self.symbol = Some(symbol.into());
446        self
447    }
448
449    /// Set start time.
450    pub fn start_time(mut self, start: u64) -> Self {
451        self.start_time = Some(start);
452        self
453    }
454
455    /// Set end time.
456    pub fn end_time(mut self, end: u64) -> Self {
457        self.end_time = Some(end);
458        self
459    }
460
461    /// Set limit.
462    pub fn limit(mut self, limit: u32) -> Self {
463        self.limit = Some(limit);
464        self
465    }
466
467    /// Set cursor.
468    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
469        self.cursor = Some(cursor.into());
470        self
471    }
472}
473
474
475/// Position information.
476#[derive(Debug, Clone, Deserialize)]
477#[serde(rename_all = "camelCase")]
478pub struct PositionInfo {
479    /// Position index.
480    pub position_idx: i32,
481    /// Risk ID.
482    #[serde(default)]
483    pub risk_id: Option<i32>,
484    /// Risk limit value.
485    #[serde(default)]
486    pub risk_limit_value: Option<String>,
487    /// Trading symbol.
488    pub symbol: String,
489    /// Position side (Buy, Sell, None).
490    pub side: String,
491    /// Position size.
492    pub size: String,
493    /// Average entry price.
494    pub avg_price: String,
495    /// Position value.
496    pub position_value: String,
497    /// Trade mode (0=cross, 1=isolated).
498    pub trade_mode: i32,
499    /// Auto add margin flag.
500    #[serde(default)]
501    pub auto_add_margin: Option<i32>,
502    /// Position status.
503    #[serde(default)]
504    pub position_status: Option<String>,
505    /// Leverage.
506    pub leverage: String,
507    /// Mark price.
508    pub mark_price: String,
509    /// Liquidation price.
510    #[serde(default)]
511    pub liq_price: Option<String>,
512    /// Bankruptcy price.
513    #[serde(default)]
514    pub bust_price: Option<String>,
515    /// Position initial margin.
516    #[serde(default)]
517    pub position_i_m: Option<String>,
518    /// Position maintenance margin.
519    #[serde(default)]
520    pub position_m_m: Option<String>,
521    /// Position balance.
522    #[serde(default)]
523    pub position_balance: Option<String>,
524    /// TP/SL mode.
525    #[serde(default)]
526    pub tpsl_mode: Option<String>,
527    /// Take profit price.
528    #[serde(default)]
529    pub take_profit: Option<String>,
530    /// Stop loss price.
531    #[serde(default)]
532    pub stop_loss: Option<String>,
533    /// Trailing stop price.
534    #[serde(default)]
535    pub trailing_stop: Option<String>,
536    /// Session average price.
537    #[serde(default)]
538    pub session_avg_price: Option<String>,
539    /// Delta (options).
540    #[serde(default)]
541    pub delta: Option<String>,
542    /// Gamma (options).
543    #[serde(default)]
544    pub gamma: Option<String>,
545    /// Vega (options).
546    #[serde(default)]
547    pub vega: Option<String>,
548    /// Theta (options).
549    #[serde(default)]
550    pub theta: Option<String>,
551    /// Unrealised PnL.
552    pub unrealised_pnl: String,
553    /// Current session realised PnL.
554    #[serde(default)]
555    pub cur_realised_pnl: Option<String>,
556    /// Cumulative realised PnL.
557    pub cum_realised_pnl: String,
558    /// ADL rank indicator.
559    #[serde(default)]
560    pub adl_rank_indicator: Option<i32>,
561    /// Is reduce only.
562    #[serde(default)]
563    pub is_reduce_only: Option<bool>,
564    /// MMR system updated time.
565    #[serde(default)]
566    pub mmr_sys_updated_time: Option<String>,
567    /// Leverage system updated time.
568    #[serde(default)]
569    pub leverage_sys_updated_time: Option<String>,
570    /// Created time (ms).
571    pub created_time: String,
572    /// Updated time (ms).
573    pub updated_time: String,
574    /// Sequence number.
575    #[serde(default)]
576    pub seq: Option<i64>,
577}
578
579/// Position list result.
580#[derive(Debug, Clone, Deserialize)]
581#[serde(rename_all = "camelCase")]
582pub struct PositionListResult {
583    /// Product category.
584    pub category: Category,
585    /// List of positions.
586    pub list: Vec<PositionInfo>,
587    /// Cursor for next page.
588    #[serde(default)]
589    pub next_page_cursor: Option<String>,
590}
591
592/// Closed PnL record.
593#[derive(Debug, Clone, Deserialize)]
594#[serde(rename_all = "camelCase")]
595pub struct ClosedPnl {
596    /// Trading symbol.
597    pub symbol: String,
598    /// Order ID.
599    pub order_id: String,
600    /// Side.
601    pub side: String,
602    /// Quantity.
603    pub qty: String,
604    /// Order price.
605    pub order_price: String,
606    /// Order type.
607    pub order_type: String,
608    /// Execution type.
609    pub exec_type: String,
610    /// Closed size.
611    pub closed_size: String,
612    /// Opening fee.
613    #[serde(default)]
614    pub open_fee: Option<String>,
615    /// Closing fee.
616    #[serde(default)]
617    pub close_fee: Option<String>,
618    /// Cumulative entry value.
619    pub cum_entry_value: String,
620    /// Average entry price.
621    pub avg_entry_price: String,
622    /// Cumulative exit value.
623    pub cum_exit_value: String,
624    /// Average exit price.
625    pub avg_exit_price: String,
626    /// Closed PnL.
627    pub closed_pnl: String,
628    /// Fill count.
629    #[serde(default)]
630    pub fill_count: Option<String>,
631    /// Leverage.
632    pub leverage: String,
633    /// Created time (ms).
634    pub created_time: String,
635    /// Updated time (ms).
636    pub updated_time: String,
637}
638
639/// Closed PnL list result.
640#[derive(Debug, Clone, Deserialize)]
641#[serde(rename_all = "camelCase")]
642pub struct ClosedPnlListResult {
643    /// Product category.
644    pub category: Category,
645    /// List of closed PnL records.
646    pub list: Vec<ClosedPnl>,
647    /// Cursor for next page.
648    #[serde(default)]
649    pub next_page_cursor: Option<String>,
650}
651
652/// Margin operation result.
653#[derive(Debug, Clone, Deserialize)]
654#[serde(rename_all = "camelCase")]
655pub struct MarginOperationResult {
656    /// Product category.
657    pub category: Category,
658    /// Trading symbol.
659    pub symbol: String,
660    /// Position index.
661    pub position_idx: i32,
662    /// Risk ID.
663    #[serde(default)]
664    pub risk_id: Option<i32>,
665    /// Risk limit value.
666    #[serde(default)]
667    pub risk_limit_value: Option<String>,
668    /// Position size.
669    pub size: String,
670    /// Average price.
671    #[serde(default)]
672    pub avg_price: Option<String>,
673    /// Liquidation price.
674    #[serde(default)]
675    pub liq_price: Option<String>,
676    /// Bankruptcy price.
677    #[serde(default)]
678    pub bust_price: Option<String>,
679    /// Mark price.
680    #[serde(default)]
681    pub mark_price: Option<String>,
682    /// Position value.
683    #[serde(default)]
684    pub position_value: Option<String>,
685    /// Leverage.
686    pub leverage: String,
687    /// Auto add margin flag.
688    #[serde(default)]
689    pub auto_add_margin: Option<i32>,
690    /// Position status.
691    #[serde(default)]
692    pub position_status: Option<String>,
693    /// Position initial margin.
694    #[serde(default)]
695    pub position_i_m: Option<String>,
696    /// Position maintenance margin.
697    #[serde(default)]
698    pub position_m_m: Option<String>,
699    /// Take profit price.
700    #[serde(default)]
701    pub take_profit: Option<String>,
702    /// Stop loss price.
703    #[serde(default)]
704    pub stop_loss: Option<String>,
705    /// Trailing stop.
706    #[serde(default)]
707    pub trailing_stop: Option<String>,
708    /// Unrealised PnL.
709    #[serde(default)]
710    pub unrealised_pnl: Option<String>,
711    /// Cumulative realised PnL.
712    #[serde(default)]
713    pub cum_realised_pnl: Option<String>,
714    /// Created time.
715    #[serde(default)]
716    pub created_time: Option<String>,
717    /// Updated time.
718    #[serde(default)]
719    pub updated_time: Option<String>,
720}
721
722#[cfg(test)]
723mod tests {
724    use super::*;
725
726    #[test]
727    fn test_get_position_info_params() {
728        let params = GetPositionInfoParams::new(Category::Linear)
729            .symbol("BTCUSDT")
730            .limit(10);
731
732        assert_eq!(params.category, Category::Linear);
733        assert_eq!(params.symbol, Some("BTCUSDT".to_string()));
734        assert_eq!(params.limit, Some(10));
735    }
736
737    #[test]
738    fn test_set_leverage_params() {
739        let params = SetLeverageParams::uniform(Category::Linear, "BTCUSDT", "10");
740        assert_eq!(params.buy_leverage, "10");
741        assert_eq!(params.sell_leverage, "10");
742    }
743
744    #[test]
745    fn test_switch_position_mode_params() {
746        let params = SwitchPositionModeParams::hedge_by_symbol(Category::Linear, "BTCUSDT");
747        assert_eq!(params.mode, 3);
748        assert_eq!(params.symbol, Some("BTCUSDT".to_string()));
749    }
750
751    #[test]
752    fn test_trading_stop_params() {
753        let params = SetTradingStopParams::new(Category::Linear, "BTCUSDT", PositionIdx::OneWay)
754            .take_profit("55000")
755            .stop_loss("45000");
756
757        assert_eq!(params.take_profit, Some("55000".to_string()));
758        assert_eq!(params.stop_loss, Some("45000".to_string()));
759    }
760}