Skip to main content

ccxt_exchanges/binance/parser/
position.rs

1use 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
10/// Parse position data from Binance futures position risk.
11pub 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
102/// Parse leverage information from Binance futures API.
103pub 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
153/// Parse leverage tier from Binance API response.
154pub 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}