ccxt_exchanges/bitget/rest/futures/
positions.rs1use super::super::super::{Bitget, parser, symbol::BitgetSymbolConverter};
4use ccxt_core::{Error, ParseError, Result, types::Position};
5use tracing::warn;
6
7impl Bitget {
8 pub async fn fetch_position_impl(&self, symbol: &str) -> Result<Position> {
12 let exchange_id = BitgetSymbolConverter::unified_to_exchange(symbol);
13 let product_type = BitgetSymbolConverter::product_type_from_symbol(symbol);
14
15 let data = self
16 .signed_request("/api/v2/mix/position/single-position")
17 .param("symbol", &exchange_id)
18 .param("productType", product_type)
19 .param("marginCoin", Self::margin_coin_from_symbol(symbol))
20 .execute()
21 .await?;
22
23 let positions_array = data["data"].as_array().ok_or_else(|| {
24 Error::from(ParseError::invalid_format("data", "Expected data array"))
25 })?;
26
27 if positions_array.is_empty() {
28 return Err(Error::from(ParseError::missing_field_owned(format!(
29 "No position found for symbol: {}",
30 symbol
31 ))));
32 }
33
34 parser::parse_position(&positions_array[0], symbol)
35 }
36
37 pub async fn fetch_positions_impl(&self, symbols: &[&str]) -> Result<Vec<Position>> {
41 let product_type = if symbols.is_empty() {
42 self.options().effective_product_type()
43 } else {
44 BitgetSymbolConverter::product_type_from_symbol(symbols[0])
45 };
46
47 let margin_coin = if symbols.is_empty() {
48 "USDT".to_string()
49 } else {
50 Self::margin_coin_from_symbol(symbols[0]).to_string()
51 };
52
53 let data = self
54 .signed_request("/api/v2/mix/position/all-position")
55 .param("productType", product_type)
56 .param("marginCoin", &margin_coin)
57 .execute()
58 .await?;
59
60 let positions_array = data["data"].as_array().ok_or_else(|| {
61 Error::from(ParseError::invalid_format("data", "Expected data array"))
62 })?;
63
64 let mut positions = Vec::new();
65 for position_data in positions_array {
66 let bitget_symbol = position_data["symbol"].as_str().unwrap_or_default();
67 let symbol_hint =
68 BitgetSymbolConverter::exchange_to_unified_hint(bitget_symbol, product_type);
69
70 match parser::parse_position(position_data, &symbol_hint) {
71 Ok(position) => {
72 if symbols.is_empty() || symbols.contains(&position.symbol.as_str()) {
73 positions.push(position);
74 }
75 }
76 Err(e) => {
77 warn!(error = %e, symbol = %bitget_symbol, "Failed to parse Bitget position");
78 }
79 }
80 }
81
82 Ok(positions)
83 }
84
85 pub(crate) fn margin_coin_from_symbol(symbol: &str) -> &str {
91 if let Some(pos) = symbol.find(':') {
92 let settle_part = &symbol[pos + 1..];
93 if let Some(dash_pos) = settle_part.find('-') {
95 &settle_part[..dash_pos]
96 } else {
97 settle_part
98 }
99 } else if let Some(pos) = symbol.find('/') {
100 &symbol[pos + 1..]
101 } else {
102 "USDT"
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_margin_coin_from_symbol() {
113 assert_eq!(Bitget::margin_coin_from_symbol("BTC/USDT:USDT"), "USDT");
114 assert_eq!(Bitget::margin_coin_from_symbol("BTC/USD:BTC"), "BTC");
115 assert_eq!(Bitget::margin_coin_from_symbol("BTC/USDT"), "USDT");
116 assert_eq!(
117 Bitget::margin_coin_from_symbol("BTC/USDT:USDT-241231"),
118 "USDT"
119 );
120 }
121}