ccxt_exchanges/binance/parser/
position.rs1use super::{parse_decimal, parse_f64};
2use ccxt_core::{
3 Result,
4 error::{Error, ParseError},
5 types::{Leverage, LeverageTier, MarginType, Market, Position},
6};
7use rust_decimal::Decimal;
8use serde_json::Value;
9
10pub fn parse_position(data: &Value, market: Option<&Market>) -> Result<Position> {
12 let symbol = if let Some(m) = market {
13 m.symbol.clone()
14 } else {
15 data["symbol"]
16 .as_str()
17 .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
18 .to_string()
19 };
20
21 let position_side = data["positionSide"].as_str().unwrap_or("BOTH");
22
23 let side = match position_side {
24 "LONG" => Some("long".to_string()),
25 "SHORT" => Some("short".to_string()),
26 "BOTH" => {
27 let position_amt = parse_f64(data, "positionAmt").unwrap_or(0.0);
28 if position_amt > 0.0 {
29 Some("long".to_string())
30 } else if position_amt < 0.0 {
31 Some("short".to_string())
32 } else {
33 None
34 }
35 }
36 _ => None,
37 };
38
39 let contracts = parse_f64(data, "positionAmt").map(f64::abs);
40 let contract_size = Some(1.0);
41 let entry_price = parse_f64(data, "entryPrice");
42 let mark_price = parse_f64(data, "markPrice");
43 let notional = parse_f64(data, "notional").map(f64::abs);
44 let leverage = parse_f64(data, "leverage");
45 let collateral =
46 parse_f64(data, "isolatedWallet").or_else(|| parse_f64(data, "positionInitialMargin"));
47 let initial_margin =
48 parse_f64(data, "initialMargin").or_else(|| parse_f64(data, "positionInitialMargin"));
49 let maintenance_margin =
50 parse_f64(data, "maintMargin").or_else(|| parse_f64(data, "positionMaintMargin"));
51 let unrealized_pnl =
52 parse_f64(data, "unrealizedProfit").or_else(|| parse_f64(data, "unRealizedProfit"));
53 let liquidation_price = parse_f64(data, "liquidationPrice");
54 let margin_ratio = parse_f64(data, "marginRatio");
55 let margin_mode = data["marginType"]
56 .as_str()
57 .or_else(|| data["marginMode"].as_str())
58 .map(str::to_lowercase);
59 let hedged = position_side != "BOTH";
60 let percentage = match (unrealized_pnl, collateral) {
61 (Some(pnl), Some(col)) if col > 0.0 => Some((pnl / col) * 100.0),
62 _ => None,
63 };
64 let initial_margin_percentage = parse_f64(data, "initialMarginPercentage");
65 let maintenance_margin_percentage = parse_f64(data, "maintMarginPercentage");
66 let update_time = data["updateTime"].as_i64();
67
68 Ok(Position {
69 info: data.clone(),
70 id: None,
71 symbol,
72 side,
73 contracts,
74 contract_size,
75 entry_price,
76 mark_price,
77 notional,
78 leverage,
79 collateral,
80 initial_margin,
81 initial_margin_percentage,
82 maintenance_margin,
83 maintenance_margin_percentage,
84 unrealized_pnl,
85 realized_pnl: None,
86 liquidation_price,
87 margin_ratio,
88 margin_mode,
89 hedged: Some(hedged),
90 percentage,
91 position_side: None,
92 dual_side_position: None,
93 timestamp: update_time,
94 datetime: update_time.map(|t| {
95 chrono::DateTime::from_timestamp(t / 1000, 0)
96 .map(|dt| dt.to_rfc3339())
97 .unwrap_or_default()
98 }),
99 })
100}
101
102pub fn parse_leverage(data: &Value, _market: Option<&Market>) -> Result<Leverage> {
104 let market_id = data
105 .get("symbol")
106 .and_then(serde_json::Value::as_str)
107 .unwrap_or("");
108
109 let margin_mode =
110 if let Some(isolated) = data.get("isolated").and_then(serde_json::Value::as_bool) {
111 Some(if isolated {
112 MarginType::Isolated
113 } else {
114 MarginType::Cross
115 })
116 } else {
117 data.get("marginType")
118 .and_then(serde_json::Value::as_str)
119 .map(|margin_type| {
120 if margin_type == "crossed" {
121 MarginType::Cross
122 } else {
123 MarginType::Isolated
124 }
125 })
126 };
127
128 let side = data
129 .get("positionSide")
130 .and_then(serde_json::Value::as_str)
131 .map(str::to_lowercase);
132
133 let leverage_value = data.get("leverage").and_then(serde_json::Value::as_i64);
134
135 let (long_leverage, short_leverage) = match side.as_deref() {
136 None | Some("both") => (leverage_value, leverage_value),
137 Some("long") => (leverage_value, None),
138 Some("short") => (None, leverage_value),
139 _ => (None, None),
140 };
141
142 Ok(Leverage {
143 info: data.clone(),
144 symbol: market_id.to_string(),
145 margin_mode,
146 long_leverage,
147 short_leverage,
148 timestamp: None,
149 datetime: None,
150 })
151}
152
153pub fn parse_leverage_tier(data: &Value, market: &Market) -> Result<LeverageTier> {
155 let tier = data["bracket"]
156 .as_i64()
157 .or_else(|| data["tier"].as_i64())
158 .unwrap_or(0) as i32;
159
160 let min_notional = parse_decimal(data, "notionalFloor")
161 .or_else(|| parse_decimal(data, "minNotional"))
162 .unwrap_or(Decimal::ZERO);
163
164 let max_notional = parse_decimal(data, "notionalCap")
165 .or_else(|| parse_decimal(data, "maxNotional"))
166 .unwrap_or(Decimal::MAX);
167
168 let maintenance_margin_rate = parse_decimal(data, "maintMarginRatio")
169 .or_else(|| parse_decimal(data, "maintenanceMarginRate"))
170 .unwrap_or(Decimal::ZERO);
171
172 let max_leverage = data["initialLeverage"]
173 .as_i64()
174 .or_else(|| data["maxLeverage"].as_i64())
175 .unwrap_or(1) as i32;
176
177 Ok(LeverageTier {
178 info: data.clone(),
179 tier,
180 symbol: market.symbol.clone(),
181 currency: market.quote.clone(),
182 min_notional,
183 max_notional,
184 maintenance_margin_rate,
185 max_leverage,
186 })
187}