1use std::collections::BTreeMap;
2
3use serde_json::{json, Value};
4
5use crate::api::{Position, API};
6use crate::client::Client;
7use crate::errors::BybitError;
8use crate::model::{
9 AddMarginRequest, AddMarginResponse, AddReduceMarginRequest, AddReduceMarginResponse,
10 ChangeMarginRequest, ChangeMarginResponse, ClosedPnlRequest,
11 ClosedPnlResponse, InfoResponse, LeverageRequest, LeverageResponse,
12 MarginModeRequest, MarginModeResponse, MoveHistoryRequest, MoveHistoryResponse,
13 MovePositionRequest, MovePositionResponse, PositionRequest, SetRiskLimit, SetRiskLimitResponse, TradingStopRequest,
14 TradingStopResponse,
15};
16use crate::util::{build_json_request, build_request, date_to_milliseconds};
17
18#[derive(Clone)]
19pub struct PositionManager {
20 pub client: Client,
21 pub recv_window: u16,
22}
23
24impl PositionManager {
25 pub async fn get_info<'b>(&self, req: PositionRequest<'_>) -> Result<InfoResponse, BybitError> {
52 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
53 parameters.insert("category".into(), req.category.as_str().into());
54 if let Some(v) = req.symbol {
55 parameters.insert("symbol".into(), v.into());
56 }
57 if let Some(v) = req.base_coin {
58 parameters.insert("baseCoin".into(), v.into());
59 }
60 if let Some(v) = req.settle_coin {
61 parameters.insert("settleCoin".into(), v.into());
62 }
63 if let Some(v) = req.limit {
64 parameters.insert("limit".into(), v.to_string());
65 }
66 let request = build_request(¶meters);
67 let response: InfoResponse = self
68 .client
69 .get_signed(
70 API::Position(Position::Information),
71 self.recv_window.into(),
72 Some(request),
73 )
74 .await?;
75 Ok(response)
76 }
77
78 pub async fn set_leverage<'b>(
88 &self,
89 req: LeverageRequest<'_>,
90 ) -> Result<LeverageResponse, BybitError> {
91 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
92 parameters.insert("category".into(), req.category.as_str().into());
93 parameters.insert("symbol".into(), req.symbol.into());
94 parameters.insert("buyLeverage".into(), req.leverage.to_string());
95 parameters.insert("sellLeverage".into(), req.leverage.to_string());
96 let request = build_json_request(¶meters);
97 let response: LeverageResponse = self
98 .client
99 .post_signed(
100 API::Position(Position::SetLeverage),
101 self.recv_window.into(),
102 Some(request),
103 )
104 .await?;
105 Ok(response)
106 }
107
108 pub async fn set_margin_mode<'b>(
118 &self,
119 req: ChangeMarginRequest<'_>,
120 ) -> Result<ChangeMarginResponse, BybitError> {
121 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
122 parameters.insert("category".into(), req.category.as_str().into());
123 parameters.insert("symbol".into(), req.symbol.into());
124 parameters.insert("tradeMode".into(), req.trade_mode.into());
125 let request = build_json_request(¶meters);
126 let response: ChangeMarginResponse = self
127 .client
128 .post_signed(
129 API::Position(Position::SwitchIsolated),
130 self.recv_window.into(),
131 Some(request),
132 )
133 .await?;
134 Ok(response)
135 }
136
137 pub async fn set_position_mode<'b>(
147 &self,
148 req: MarginModeRequest<'_>,
149 ) -> Result<MarginModeResponse, BybitError> {
150 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
151 parameters.insert("category".into(), req.category.as_str().into());
152 if let Some(v) = req.symbol {
153 parameters.insert("symbol".into(), v.into());
154 }
155 if let Some(v) = req.coin {
156 parameters.insert("coin".into(), v.into());
157 }
158 parameters.insert("mode".into(), req.mode.into());
159 let request = build_json_request(¶meters);
160 let response: MarginModeResponse = self
161 .client
162 .post_signed(
163 API::Position(Position::SwitchMode),
164 self.recv_window.into(),
165 Some(request),
166 )
167 .await?;
168 Ok(response)
169 }
170
171 pub async fn set_risk_limit<'b>(
181 &self,
182 req: SetRiskLimit<'_>,
183 ) -> Result<SetRiskLimitResponse, BybitError> {
184 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
185 parameters.insert("category".into(), req.category.as_str().into());
186 parameters.insert("symbol".into(), req.symbol.into());
187 parameters.insert("riskId".into(), req.risk_id.into());
188 if let Some(v) = req.position_idx {
189 parameters.insert("positionIdx".into(), v.into());
190 }
191 let request = build_json_request(¶meters);
192 let response: SetRiskLimitResponse = self
193 .client
194 .post_signed(
195 API::Position(Position::SetRiskLimit),
196 self.recv_window.into(),
197 Some(request),
198 )
199 .await?;
200 Ok(response)
201 }
202
203 pub async fn set_trading_stop<'b>(
213 &self,
214 req: TradingStopRequest<'_>,
215 ) -> Result<TradingStopResponse, BybitError> {
216 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
217 parameters.insert("category".into(), req.category.as_str().into());
218 parameters.insert("symbol".into(), req.symbol.into());
219 if let Some(v) = req.take_profit {
220 parameters.insert("takeProfit".into(), v.into());
221 }
222 if let Some(v) = req.stop_loss {
223 parameters.insert("stopLoss".into(), v.into());
224 }
225 if let Some(v) = req.tp_trigger_by {
226 parameters.insert("tpTriggerBy".into(), v.into());
227 }
228 if let Some(v) = req.sl_trigger_by {
229 parameters.insert("slTriggerBy".into(), v.into());
230 }
231 if let Some(v) = req.tpsl_mode {
232 parameters.insert("tpslMode".into(), v.into());
233 }
234 if let Some(v) = req.tp_order_type {
235 parameters.insert("tpOrderType".into(), v.as_str().into());
236 }
237 if let Some(v) = req.sl_order_type {
238 parameters.insert("slOrderType".into(), v.as_str().into());
239 }
240 if let Some(v) = req.tp_size {
241 parameters.insert("tpSize".into(), v.into());
242 }
243 if let Some(v) = req.sl_size {
244 parameters.insert("slSize".into(), v.into());
245 }
246 if let Some(v) = req.tp_limit_price {
247 parameters.insert("tpLimitPrice".into(), v.into());
248 }
249 if let Some(v) = req.sl_limit_price {
250 parameters.insert("slLimitPrice".into(), v.into());
251 }
252 parameters.insert("positionIdx".into(), req.position_idx.into());
253 let request = build_json_request(¶meters);
254 let response: TradingStopResponse = self
255 .client
256 .post_signed(
257 API::Position(Position::SetTradingStop),
258 self.recv_window.into(),
259 Some(request),
260 )
261 .await?;
262 Ok(response)
263 }
264
265 pub async fn set_add_margin<'b>(
275 &self,
276 req: AddMarginRequest<'_>,
277 ) -> Result<AddMarginResponse, BybitError> {
278 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
280
281 parameters.insert("category".into(), req.category.as_str().into());
283 parameters.insert("symbol".into(), req.symbol.into());
284
285 if req.auto_add {
287 parameters.insert("autoAddMargin".into(), 1.into());
288 } else {
289 parameters.insert("autoAddMargin".into(), 0.into());
290 }
291
292 if let Some(v) = req.position_idx {
294 parameters.insert("positionIdx".into(), v.into());
295 }
296
297 let request = build_json_request(¶meters);
299
300 let response: AddMarginResponse = self
302 .client
303 .post_signed(
304 API::Position(Position::SetAutoaddMargin),
305 self.recv_window.into(),
306 Some(request),
307 )
308 .await?;
309
310 Ok(response)
312 }
313
314 pub async fn add_or_reduce_margin<'b>(
324 &self,
325 req: AddReduceMarginRequest<'_>,
326 ) -> Result<AddReduceMarginResponse, BybitError> {
327 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
329
330 parameters.insert("category".into(), req.category.as_str().into());
332 parameters.insert("symbol".into(), req.symbol.into());
333
334 parameters.insert("margin".into(), req.margin.into());
336
337 if let Some(v) = req.position_idx {
339 parameters.insert("positionIdx".into(), v.into());
340 }
341
342 let request = build_json_request(¶meters);
344
345 let response: AddReduceMarginResponse = self
347 .client
348 .post_signed(
349 API::Position(Position::AddorReduceMargin),
350 self.recv_window.into(),
351 Some(request),
352 )
353 .await?;
354
355 Ok(response)
357 }
358
359 pub async fn get_closed_pnl<'b>(
360 &self,
361 req: ClosedPnlRequest<'_>,
362 ) -> Result<ClosedPnlResponse, BybitError> {
363 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
364 parameters.insert("category".into(), req.category.as_str().into());
365 if let Some(v) = req.symbol {
366 parameters.insert("symbol".into(), v.into());
367 }
368
369 if let Some(start_str) = req.start_time.as_ref().map(|s| s.as_ref()) {
370 let start_millis = date_to_milliseconds(start_str);
371 parameters
372 .entry("end".to_owned())
373 .or_insert_with(|| start_millis.to_string().into());
374 }
375 if let Some(end_str) = req.end_time.as_ref().map(|s| s.as_ref()) {
376 let end_millis = date_to_milliseconds(end_str);
377 parameters
378 .entry("end".to_owned())
379 .or_insert_with(|| end_millis.to_string().into());
380 }
381 if let Some(v) = req.limit {
382 parameters.insert("limit".into(), v.into());
383 }
384 let request = build_request(¶meters);
385 let response: ClosedPnlResponse = self
386 .client
387 .get_signed(
388 API::Position(Position::ClosedPnl),
389 self.recv_window.into(),
390 Some(request),
391 )
392 .await?;
393 Ok(response)
394 }
395
396 pub async fn move_position<'b>(
406 &self,
407 req: MovePositionRequest<'_>,
408 ) -> Result<MovePositionResponse, BybitError> {
409 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
410 parameters.insert("fromUid".into(), req.from_uid.into());
412 parameters.insert("toUid".into(), req.to_uid.into());
414 parameters.insert("list".into(), json!(req.list));
416 let request = build_json_request(¶meters);
417 let response: MovePositionResponse = self
418 .client
419 .post_signed(
420 API::Position(Position::MovePosition),
421 self.recv_window.into(),
422 Some(request),
423 )
424 .await?;
425 Ok(response)
426 }
427
428 pub async fn move_position_history<'b>(
438 &self,
439 req: MoveHistoryRequest<'_>,
440 ) -> Result<MoveHistoryResponse, BybitError> {
441 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
443
444 if let Some(category) = req.category {
446 parameters.insert("category".into(), category.as_str().into());
447 }
448
449 if let Some(symbol) = req.symbol {
451 parameters.insert("symbol".into(), symbol.into());
452 }
453
454 if let Some(start_str) = req.start_time.as_ref().map(|s| s.as_ref()) {
456 let start_millis = date_to_milliseconds(start_str);
457 parameters
458 .entry("end".to_owned())
459 .or_insert_with(|| start_millis.to_string().into());
460 }
461
462 if let Some(end_str) = req.end_time.as_ref().map(|s| s.as_ref()) {
464 let end_millis = date_to_milliseconds(end_str);
465 parameters
466 .entry("end".to_owned())
467 .or_insert_with(|| end_millis.to_string().into());
468 }
469
470 if let Some(status) = req.status {
472 parameters.insert("status".into(), status.into());
473 }
474
475 if let Some(block_trade_id) = req.block_trade_id {
477 parameters.insert("blockTradeId".into(), block_trade_id.into());
478 }
479
480 if let Some(limit) = req.limit {
482 parameters.insert("limit".into(), limit.into());
483 }
484
485 let request = build_request(¶meters);
487
488 let response: MoveHistoryResponse = self
490 .client
491 .get_signed(
492 API::Position(Position::MovePositionHistory),
493 self.recv_window.into(),
494 Some(request),
495 )
496 .await?;
497
498 Ok(response)
500 }
501}