1mod account;
2mod client;
3mod common;
4mod market;
5mod orders;
6mod positions;
7mod user;
8
9pub use account::*;
10pub use client::*;
11pub use common::*;
12pub use market::*;
13pub use orders::*;
14pub use positions::*;
15pub use user::*;
16
17#[cfg(test)]
18mod tests {
19 use std::collections::HashMap;
20
21 use rust_decimal::dec;
22
23 use crate::{
24 AccountType, AdlRankIndicator, CancelType, CreateType, MarginMode, OrderStatus, OrderType,
25 PositionIdx, PositionStatus, RejectReason, Side, SmpType, SpotHedgingStatus, TimeInForce,
26 TpslMode, TriggerBy, TriggerDirection, UnifiedMarginStatus,
27 enums::{Category, StopOrderType},
28 serde::{Unique, deserialize_json},
29 };
30
31 use super::*;
32
33 #[test]
34 fn deserialize_response_kline_inverse() {
35 let json = r#"{
36 "retCode": 0,
37 "retMsg": "OK",
38 "result": {
39 "symbol": "BTCUSD",
40 "category": "inverse",
41 "list": [
42 [
43 "1670608800000",
44 "17071",
45 "17073",
46 "17027",
47 "17055.5",
48 "268611",
49 "15.74462667"
50 ],
51 [
52 "1670605200000",
53 "17071.5",
54 "17071.5",
55 "17061",
56 "17071",
57 "4177",
58 "0.24469757"
59 ],
60 [
61 "1670601600000",
62 "17086.5",
63 "17088",
64 "16978",
65 "17071.5",
66 "6356",
67 "0.37288112"
68 ]
69 ]
70 },
71 "retExtInfo": {},
72 "time": 1672025956592
73 }"#;
74 let expected = Resp {
75 ret_code: 0,
76 ret_msg: String::from("OK"),
77 result: KLine::Inverse {
78 symbol: String::from("BTCUSD"),
79 list: vec![
80 KLineRow {
81 start_time: 1670608800000,
82 open_price: dec!(17071),
83 high_price: dec!(17073),
84 low_price: dec!(17027),
85 close_price: dec!(17055.5),
86 volume: dec!(268611),
87 turnover: dec!(15.74462667),
88 },
89 KLineRow {
90 start_time: 1670605200000,
91 open_price: dec!(17071.5),
92 high_price: dec!(17071.5),
93 low_price: dec!(17061),
94 close_price: dec!(17071),
95 volume: dec!(4177),
96 turnover: dec!(0.24469757),
97 },
98 KLineRow {
99 start_time: 1670601600000,
100 open_price: dec!(17086.5),
101 high_price: dec!(17088),
102 low_price: dec!(16978),
103 close_price: dec!(17071.5),
104 volume: dec!(6356),
105 turnover: dec!(0.37288112),
106 },
107 ],
108 },
109 time: Some(1672025956592),
110 ret_ext_info: Some(RetExtInfo::default()),
111 };
112
113 let message = deserialize_json(json).unwrap();
114
115 assert_eq!(expected, message);
116 }
117
118 #[test]
119 fn deserialize_response_ticker_inverse() {
120 let json = r#"{
121 "retCode": 0,
122 "retMsg": "OK",
123 "result": {
124 "category": "inverse",
125 "list": [
126 {
127 "symbol": "BTCUSD",
128 "lastPrice": "16597.00",
129 "indexPrice": "16598.54",
130 "markPrice": "16596.00",
131 "prevPrice24h": "16464.50",
132 "price24hPcnt": "0.008047",
133 "highPrice24h": "30912.50",
134 "lowPrice24h": "15700.00",
135 "prevPrice1h": "16595.50",
136 "openInterest": "373504107",
137 "openInterestValue": "22505.67",
138 "turnover24h": "2352.94950046",
139 "volume24h": "49337318",
140 "fundingRate": "-0.001034",
141 "nextFundingTime": "1672387200000",
142 "predictedDeliveryPrice": "",
143 "basisRate": "",
144 "deliveryFeeRate": "",
145 "deliveryTime": "0",
146 "ask1Size": "1",
147 "bid1Price": "16596.00",
148 "ask1Price": "16597.50",
149 "bid1Size": "1",
150 "basis": ""
151 }
152 ]
153 },
154 "retExtInfo": {},
155 "time": 1672376496682
156 }"#;
157 let expected = Resp {
158 ret_code: 0,
159 ret_msg: String::from("OK"),
160 result: Ticker::Inverse {
161 list: vec![LinearInverseTicker {
162 symbol: String::from("BTCUSD"),
163 last_price: dec!(16597.00),
164 mark_price: dec!(16596.00),
165 index_price: dec!(16598.54),
166 prev_price24h: dec!(16464.50),
167 price24h_pcnt: dec!(0.008047),
168 high_price24h: dec!(30912.50),
169 low_price24h: dec!(15700.00),
170 prev_price1h: dec!(16595.50),
171 open_interest: dec!(373504107),
172 open_interest_value: dec!(22505.67),
173 turnover24h: dec!(2352.94950046),
174 volume24h: dec!(49337318),
175 funding_rate: Some(dec!(-0.001034)),
176 next_funding_time: 1672387200000,
177 predicted_delivery_price: None,
178 basis_rate: None,
179 basis: None,
180 delivery_fee_rate: None,
181 delivery_time: Some(0),
182 bid1_price: dec!(16596.00),
183 bid1_size: dec!(1),
184 ask1_price: dec!(16597.50),
185 ask1_size: dec!(1),
186 pre_open_price: None,
187 pre_qty: None,
188 cur_pre_listing_phase: None,
189 }],
190 },
191 time: Some(1672376496682),
192 ret_ext_info: Some(RetExtInfo::default()),
193 };
194
195 let message = deserialize_json(json).unwrap();
196
197 assert_eq!(expected, message);
198 }
199
200 #[test]
201 fn deserialize_response_trad_spot() {
202 let json = r#"{
203 "retCode": 0,
204 "retMsg": "OK",
205 "result": {
206 "category": "spot",
207 "list": [
208 {
209 "execId": "2100000000007764263",
210 "symbol": "BTCUSDT",
211 "price": "16618.49",
212 "size": "0.00012",
213 "side": "Buy",
214 "time": "1672052955758",
215 "isBlockTrade": false,
216 "isRPITrade": true
217 }
218 ]
219 },
220 "retExtInfo": {},
221 "time": 1672053054358
222 }"#;
223 let expected = Resp {
224 ret_code: 0,
225 ret_msg: String::from("OK"),
226 result: Trade::Spot {
227 list: vec![InverseLinearSpotTrade {
228 exec_id: String::from("2100000000007764263"),
229 symbol: String::from("BTCUSDT"),
230 price: dec!(16618.49),
231 size: dec!(0.00012),
232 side: Side::Buy,
233 time: 1672052955758,
234 is_block_trade: false,
235 is_rpi_trade: true,
236 }],
237 },
238 time: Some(1672053054358),
239 ret_ext_info: Some(RetExtInfo::default()),
240 };
241
242 let message = deserialize_json(json).unwrap();
243
244 assert_eq!(expected, message);
245 }
246
247 #[test]
248 fn deserialize_response_orderbook_linear() {
249 let json = r#"{
250 "retCode": 0,
251 "retMsg": "OK",
252 "result": {
253 "s": "BTCUSDT",
254 "b": [
255 ["30190.65", "0.165956"],
256 ["30190.10", "0.020000"]
257 ],
258 "a": [
259 ["30201.42", "1.038467"],
260 ["30201.50", "0.500000"]
261 ],
262 "ts": 1675418560614,
263 "u": 177400507,
264 "seq": 66544703342
265 },
266 "retExtInfo": {},
267 "time": 1675418560633
268 }"#;
269 let expected = Resp {
270 ret_code: 0,
271 ret_msg: String::from("OK"),
272 result: Orderbook {
273 symbol: String::from("BTCUSDT"),
274 bids: vec![
275 OrderbookLevel {
276 price: dec!(30190.65),
277 size: dec!(0.165956),
278 },
279 OrderbookLevel {
280 price: dec!(30190.10),
281 size: dec!(0.020000),
282 },
283 ],
284 asks: vec![
285 OrderbookLevel {
286 price: dec!(30201.42),
287 size: dec!(1.038467),
288 },
289 OrderbookLevel {
290 price: dec!(30201.50),
291 size: dec!(0.500000),
292 },
293 ],
294 ts: 1675418560614,
295 update_id: 177400507,
296 seq: 66544703342,
297 cts: None,
298 },
299 time: Some(1675418560633),
300 ret_ext_info: Some(RetExtInfo::default()),
301 };
302
303 let message = deserialize_json(json).unwrap();
304
305 assert_eq!(expected, message);
306 }
307
308 #[test]
309 fn deserialize_response_get_open_closed_orders_linear() {
310 let json = r#"{
311 "retCode": 0,
312 "retMsg": "OK",
313 "result": {
314 "list": [
315 {
316 "orderId": "fd4300ae-7847-404e-b947-b46980a4d140",
317 "orderLinkId": "test-000005",
318 "blockTradeId": "",
319 "symbol": "ETHUSDT",
320 "price": "1600.00",
321 "qty": "0.10",
322 "side": "Buy",
323 "isLeverage": "",
324 "positionIdx": 1,
325 "orderStatus": "New",
326 "cancelType": "UNKNOWN",
327 "rejectReason": "EC_NoError",
328 "avgPrice": "0",
329 "leavesQty": "0.10",
330 "leavesValue": "160",
331 "cumExecQty": "0.00",
332 "cumExecValue": "0",
333 "cumExecFee": "0",
334 "timeInForce": "GTC",
335 "orderType": "Limit",
336 "stopOrderType": "UNKNOWN",
337 "orderIv": "",
338 "triggerPrice": "0.00",
339 "takeProfit": "2500.00",
340 "stopLoss": "1500.00",
341 "tpTriggerBy": "LastPrice",
342 "slTriggerBy": "LastPrice",
343 "triggerDirection": 0,
344 "triggerBy": "UNKNOWN",
345 "lastPriceOnCreated": "",
346 "reduceOnly": false,
347 "closeOnTrigger": false,
348 "smpType": "None",
349 "smpGroup": 0,
350 "smpOrderId": "",
351 "tpslMode": "Full",
352 "tpLimitPrice": "",
353 "slLimitPrice": "",
354 "placeType": "",
355 "createdTime": "1684738540559",
356 "updatedTime": "1684738540561"
357 }
358 ],
359 "nextPageCursor": "page_args%3Dfd4300ae-7847-404e-b947-b46980a4d140%26symbol%3D6%26",
360 "category": "linear"
361 },
362 "retExtInfo": {},
363 "time": 1684765770483
364 }"#;
365 let expected = Resp {
366 ret_code: 0,
367 ret_msg: String::from("OK"),
368 result: CursorPagination {
369 category: Some(Category::Linear),
370 next_page_cursor: Some(String::from(
371 "page_args%3Dfd4300ae-7847-404e-b947-b46980a4d140%26symbol%3D6%26",
372 )),
373 list: vec![Order {
374 order_id: String::from("fd4300ae-7847-404e-b947-b46980a4d140"),
375 order_link_id: Some(String::from("test-000005")),
376 block_trade_id: None,
377 symbol: String::from("ETHUSDT"),
378 price: dec!(1600.00),
379 qty: dec!(0.10),
380 side: Side::Buy,
381 is_leverage: None,
382 position_idx: PositionIdx::Buy,
383 order_status: OrderStatus::New,
384 create_type: None,
385 cancel_type: CancelType::UNKNOWN,
386 reject_reason: RejectReason::EcNoError,
387 avg_price: Some(dec!(0.0)),
388 leaves_qty: dec!(0.10),
389 leaves_value: dec!(160),
390 cum_exec_qty: dec!(0.00),
391 cum_exec_value: dec!(0),
392 cum_exec_fee: dec!(0),
393 time_in_force: TimeInForce::GTC,
394 order_type: OrderType::Limit,
395 stop_order_type: Some(StopOrderType::UNKNOWN),
396 order_iv: None,
397 market_unit: None,
398 trigger_price: Some(dec!(0.00)),
399 take_profit: Some(dec!(2500.00)),
400 stop_loss: Some(dec!(1500.00)),
401 tpsl_mode: Some(TpslMode::Full),
402 oco_trigger_by: None,
403 tp_limit_price: None,
404 sl_limit_price: None,
405 tp_trigger_by: Some(TriggerBy::LastPrice),
406 sl_trigger_by: Some(TriggerBy::LastPrice),
407 trigger_direction: TriggerDirection::UNKNOWN,
408 trigger_by: Some(TriggerBy::UNKNOWN),
409 last_price_on_created: None,
410 base_price: None,
411 reduce_only: false,
412 close_on_trigger: false,
413 place_type: None,
414 smp_type: SmpType::None,
415 smp_group: 0,
416 smp_order_id: None,
417 created_time: 1684738540559,
418 updated_time: 1684738540561,
419 }],
420 },
421 time: Some(1684765770483),
422 ret_ext_info: Some(RetExtInfo::default()),
423 };
424
425 let message = deserialize_json(json).unwrap();
426
427 assert_eq!(expected, message);
428 }
429
430 #[test]
431 fn deserialize_response_get_open_closed_orders_linear2() {
432 let json = r#"{
433 "retCode":0,
434 "retMsg":"OK",
435 "result":{
436 "nextPageCursor":"aed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113%2Caed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113",
437 "category":"linear",
438 "list":[
439 {
440 "symbol":"BTCUSDT",
441 "orderType":"Limit",
442 "orderLinkId":"",
443 "slLimitPrice":"0",
444 "orderId":"aed77e97-492f-45be-8ada-4ff350ec07a5",
445 "cancelType":"UNKNOWN",
446 "avgPrice":"",
447 "stopOrderType":"",
448 "lastPriceOnCreated":"103550",
449 "orderStatus":"New",
450 "createType":"CreateByUser",
451 "takeProfit":"",
452 "cumExecValue":"0",
453 "tpslMode":"",
454 "smpType":"None",
455 "triggerDirection":0,
456 "blockTradeId":"",
457 "isLeverage":"",
458 "rejectReason":"EC_NoError",
459 "price":"103000",
460 "orderIv":"",
461 "createdTime":"1762701687113",
462 "tpTriggerBy":"",
463 "positionIdx":1,
464 "timeInForce":"GTC",
465 "leavesValue":"1030",
466 "updatedTime":"1762701687113",
467 "side":"Buy",
468 "smpGroup":0,
469 "triggerPrice":"",
470 "tpLimitPrice":"0",
471 "cumExecFee":"0",
472 "leavesQty":"0.01",
473 "slTriggerBy":"",
474 "closeOnTrigger":false,
475 "placeType":"",
476 "cumExecQty":"0",
477 "reduceOnly":false,
478 "qty":"0.01",
479 "stopLoss":"",
480 "marketUnit":"",
481 "smpOrderId":"",
482 "triggerBy":""
483 }
484 ]
485 },
486 "retExtInfo":{},
487 "time":1762711342768
488 }"#;
489 let expected = Resp {
490 ret_code: 0,
491 ret_msg: String::from("OK"),
492 result: CursorPagination {
493 category: Some(Category::Linear),
494 next_page_cursor: Some(String::from(
495 "aed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113%2Caed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113",
496 )),
497 list: vec![Order {
498 order_id: String::from("aed77e97-492f-45be-8ada-4ff350ec07a5"),
499 order_link_id: None,
500 block_trade_id: None,
501 symbol: String::from("BTCUSDT"),
502 price: dec!(103000),
503 qty: dec!(0.01),
504 side: Side::Buy,
505 is_leverage: None,
506 position_idx: PositionIdx::Buy,
507 order_status: OrderStatus::New,
508 create_type: Some(CreateType::CreateByUser),
509 cancel_type: CancelType::UNKNOWN,
510 reject_reason: RejectReason::EcNoError,
511 avg_price: None,
512 leaves_qty: dec!(0.01),
513 leaves_value: dec!(1030),
514 cum_exec_qty: dec!(0),
515 cum_exec_value: dec!(0),
516 cum_exec_fee: dec!(0),
517 time_in_force: TimeInForce::GTC,
518 order_type: OrderType::Limit,
519 stop_order_type: None,
520 order_iv: None,
521 market_unit: None,
522 trigger_price: None,
523 take_profit: None,
524 stop_loss: None,
525 tpsl_mode: None,
526 oco_trigger_by: None,
527 tp_limit_price: Some(dec!(0)),
528 sl_limit_price: Some(dec!(0)),
529 tp_trigger_by: None,
530 sl_trigger_by: None,
531 trigger_direction: TriggerDirection::UNKNOWN,
532 trigger_by: None,
533 last_price_on_created: Some(dec!(103550)),
534 base_price: None,
535 reduce_only: false,
536 close_on_trigger: false,
537 place_type: None,
538 smp_type: SmpType::None,
539 smp_group: 0,
540 smp_order_id: None,
541 created_time: 1762701687113,
542 updated_time: 1762701687113,
543 }],
544 },
545 time: Some(1762711342768),
546 ret_ext_info: Some(RetExtInfo::default()),
547 };
548
549 let message = deserialize_json(json).unwrap();
550
551 assert_eq!(expected, message);
552 }
553
554 #[test]
555 fn deserialize_response_get_position_info_inverse() {
556 let json = r#"{
557 "retCode": 0,
558 "retMsg": "OK",
559 "result": {
560 "list": [
561 {
562 "positionIdx": 0,
563 "riskId": 1,
564 "riskLimitValue": "150",
565 "symbol": "BTCUSD",
566 "side": "Sell",
567 "size": "300",
568 "avgPrice": "27464.50441675",
569 "positionValue": "0.01092319",
570 "tradeMode": 0,
571 "positionStatus": "Normal",
572 "autoAddMargin": 1,
573 "adlRankIndicator": 2,
574 "leverage": "10",
575 "positionBalance": "0.00139186",
576 "markPrice": "28224.50",
577 "liqPrice": "",
578 "bustPrice": "999999.00",
579 "positionMM": "0.0000015",
580 "positionMMByMp": "0.0000015",
581 "positionIM": "0.00010923",
582 "positionIMByMp": "0.00010923",
583 "tpslMode": "Full",
584 "takeProfit": "0.00",
585 "stopLoss": "0.00",
586 "trailingStop": "0.00",
587 "unrealisedPnl": "-0.00029413",
588 "curRealisedPnl": "0.00013123",
589 "cumRealisedPnl": "-0.00096902",
590 "seq": 5723621632,
591 "isReduceOnly": false,
592 "mmrSysUpdatedTime": "",
593 "leverageSysUpdatedTime": "",
594 "sessionAvgPrice": "",
595 "createdTime": "1676538056258",
596 "updatedTime": "1697673600012"
597 }
598 ],
599 "nextPageCursor": "",
600 "category": "inverse"
601 },
602 "retExtInfo": {},
603 "time": 1697684980172
604 }"#;
605 let expected = Resp {
606 ret_code: 0,
607 ret_msg: String::from("OK"),
608 result: CursorPagination {
609 category: Some(Category::Inverse),
610 next_page_cursor: None,
611 list: vec![Position {
612 position_idx: PositionIdx::OneWay,
613 risk_id: 1,
614 risk_limit_value: Some(dec!(150)),
615 symbol: String::from("BTCUSD"),
616 side: Some(Side::Sell),
617 size: dec!(300),
618 avg_price: dec!(27464.50441675),
619 position_value: Some(dec!(0.01092319)),
620 auto_add_margin: true,
621 position_status: PositionStatus::Normal,
622 leverage: dec!(10),
623 mark_price: dec!(28224.50),
624 liq_price: None,
625 position_im: Some(dec!(0.00010923)),
626 position_im_by_mp: Some(dec!(0.00010923)),
627 position_mm: Some(dec!(0.0000015)),
628 position_mm_by_mp: Some(dec!(0.0000015)),
629 take_profit: Some(dec!(0.00)),
630 stop_loss: Some(dec!(0.00)),
631 trailing_stop: Some(dec!(0.00)),
632 session_avg_price: None,
633 delta: None,
634 gamma: None,
635 vega: None,
636 theta: None,
637 unrealised_pnl: Some(dec!(-0.00029413)),
638 cur_realised_pnl: dec!(0.00013123),
639 cum_realised_pnl: dec!(-0.00096902),
640 adl_rank_indicator: AdlRankIndicator::Two,
641 created_time: 1676538056258,
642 updated_time: 1697673600012,
643 seq: 5723621632,
644 is_reduce_only: false,
645 mmr_sys_updated_time: None,
646 leverage_sys_updated_time: None,
647 }],
648 },
649 time: Some(1697684980172),
650 ret_ext_info: Some(RetExtInfo::default()),
651 };
652
653 let message: Resp<CursorPagination<Position>> = deserialize_json(json).unwrap();
654
655 assert_eq!(message, expected);
656 }
657
658 #[test]
659 fn deserialize_response_get_wallet_balance() {
660 let json = r#"{
661 "retCode": 0,
662 "retMsg": "OK",
663 "result": {
664 "list": [
665 {
666 "totalEquity": "3.31216591",
667 "accountIMRate": "0",
668 "accountIMRateByMp": "0",
669 "totalMarginBalance": "3.00326056",
670 "totalInitialMargin": "0",
671 "totalInitialMarginByMp": "0",
672 "accountType": "UNIFIED",
673 "totalAvailableBalance": "3.00326056",
674 "accountMMRate": "0",
675 "accountMMRateByMp": "0",
676 "totalPerpUPL": "0",
677 "totalWalletBalance": "3.00326056",
678 "accountLTV": "0",
679 "totalMaintenanceMargin": "0",
680 "totalMaintenanceMarginByMp": "0",
681 "coin": [
682 {
683 "availableToBorrow": "3",
684 "bonus": "0",
685 "accruedInterest": "0",
686 "availableToWithdraw": "0",
687 "totalOrderIM": "0",
688 "equity": "0",
689 "totalPositionMM": "0",
690 "usdValue": "0",
691 "spotHedgingQty": "0.01592413",
692 "unrealisedPnl": "0",
693 "collateralSwitch": true,
694 "borrowAmount": "0.0",
695 "totalPositionIM": "0",
696 "walletBalance": "0",
697 "cumRealisedPnl": "0",
698 "locked": "0",
699 "marginCollateral": true,
700 "coin": "BTC",
701 "spotBorrow": "0"
702 }
703 ]
704 }
705 ]
706 },
707 "retExtInfo": {},
708 "time": 1690872862481
709 }"#;
710 let coin = WalletCoin {
711 coin: String::from("BTC"),
712 equity: dec!(0),
713 usd_value: dec!(0),
714 wallet_balance: dec!(0),
715 locked: dec!(0),
716 spot_hedging_qty: dec!(0.01592413),
717 borrow_amount: dec!(0.0),
718 accrued_interest: dec!(0),
719 total_order_im: Some(dec!(0)),
720 total_position_im: Some(dec!(0)),
721 total_position_mm: Some(dec!(0)),
722 unrealised_pnl: dec!(0),
723 cum_realised_pnl: dec!(0),
724 bonus: dec!(0),
725 margin_collateral: true,
726 collateral_switch: true,
727 spot_borrow: Some(dec!(0)),
728 };
729 let coin = HashMap::from([(Unique::unique_key(&coin), coin)]);
730 let expected = Resp {
731 ret_code: 0,
732 ret_msg: String::from("OK"),
733 result: List {
734 list: vec![WalletBalance {
735 account_type: AccountType::UNIFIED,
736 account_im_rate: dec!(0),
737 account_im_rate_by_mp: dec!(0),
738 account_mm_rate: dec!(0),
739 account_mm_rate_by_mp: dec!(0),
740 total_equity: dec!(3.31216591),
741 total_wallet_balance: dec!(3.00326056),
742 total_margin_balance: dec!(3.00326056),
743 total_available_balance: dec!(3.00326056),
744 total_perp_upl: dec!(0),
745 total_initial_margin: dec!(0),
746 total_initial_margin_by_mp: dec!(0),
747 total_maintenance_margin: dec!(0),
748 total_maintenance_margin_by_mp: dec!(0),
749 coin,
750 }],
751 },
752 time: Some(1690872862481),
753 ret_ext_info: Some(RetExtInfo::default()),
754 };
755
756 let message = deserialize_json(json).unwrap();
757
758 assert_eq!(expected, message);
759 }
760
761 #[test]
762 fn deserialize_response_get_wallet_balance_2() {
763 let json = r#"{
764 "retCode":0,
765 "retMsg":"OK",
766 "result":{
767 "list":[
768 {
769 "totalEquity":"36.42053792",
770 "accountIMRate":"0",
771 "accountIMRateByMp":"0",
772 "totalMarginBalance":"36.42053792",
773 "totalInitialMargin":"0",
774 "totalInitialMarginByMp":"0",
775 "accountType":"UNIFIED",
776 "totalAvailableBalance":"36.42053792",
777 "accountMMRate":"0",
778 "accountMMRateByMp":"0",
779 "totalPerpUPL":"0",
780 "totalWalletBalance":"36.42053792",
781 "accountLTV":"0",
782 "totalMaintenanceMargin":"0",
783 "totalMaintenanceMarginByMp":"0",
784 "coin":[
785 {
786 "availableToBorrow":"",
787 "bonus":"0",
788 "accruedInterest":"0",
789 "availableToWithdraw":"",
790 "totalOrderIM":"0",
791 "equity":"36.4061211",
792 "totalPositionMM":"0",
793 "usdValue":"36.42053792",
794 "unrealisedPnl":"0",
795 "collateralSwitch":true,
796 "spotHedgingQty":"0",
797 "borrowAmount":"0.000000000000000000",
798 "totalPositionIM":"0",
799 "walletBalance":"36.4061211",
800 "cumRealisedPnl":"-2084.9938789",
801 "locked":"0",
802 "marginCollateral":true,
803 "coin":"USDT",
804 "spotBorrow": "0"
805 }
806 ]
807 }
808 ]
809 },
810 "retExtInfo":{},
811 "time":1751570498412
812 }"#;
813 let coin = WalletCoin {
814 coin: String::from("USDT"),
815 equity: dec!(36.4061211),
816 usd_value: dec!(36.42053792),
817 wallet_balance: dec!(36.4061211),
818 locked: dec!(0),
819 spot_hedging_qty: dec!(0),
820 borrow_amount: dec!(0.000000000000000000),
821 accrued_interest: dec!(0),
822 total_order_im: Some(dec!(0)),
823 total_position_im: Some(dec!(0)),
824 total_position_mm: Some(dec!(0)),
825 unrealised_pnl: dec!(0),
826 cum_realised_pnl: dec!(-2084.9938789),
827 bonus: dec!(0),
828 margin_collateral: true,
829 collateral_switch: true,
830 spot_borrow: Some(dec!(0)),
831 };
832 let coin = HashMap::from([(Unique::unique_key(&coin), coin)]);
833 let expected = Resp {
834 ret_code: 0,
835 ret_msg: String::from("OK"),
836 result: List {
837 list: vec![WalletBalance {
838 account_type: AccountType::UNIFIED,
839 account_im_rate: dec!(0),
840 account_im_rate_by_mp: dec!(0),
841 account_mm_rate: dec!(0),
842 account_mm_rate_by_mp: dec!(0),
843 total_equity: dec!(36.42053792),
844 total_wallet_balance: dec!(36.42053792),
845 total_margin_balance: dec!(36.42053792),
846 total_available_balance: dec!(36.42053792),
847 total_perp_upl: dec!(0),
848 total_initial_margin: dec!(0),
849 total_initial_margin_by_mp: dec!(0),
850 total_maintenance_margin: dec!(0),
851 total_maintenance_margin_by_mp: dec!(0),
852 coin,
853 }],
854 },
855 time: Some(1751570498412),
856 ret_ext_info: Some(RetExtInfo::default()),
857 };
858
859 let message = deserialize_json(json).unwrap();
860
861 assert_eq!(expected, message);
862 }
863
864 #[test]
865 fn deserialize_response_get_account_info() {
866 let json = r#"{
867 "retCode": 0,
868 "retMsg": "OK",
869 "result": {
870 "marginMode": "REGULAR_MARGIN",
871 "updatedTime": "1697078946000",
872 "unifiedMarginStatus": 4,
873 "dcpStatus": "OFF",
874 "timeWindow": 10,
875 "smpGroup": 0,
876 "isMasterTrader": false,
877 "spotHedgingStatus": "OFF"
878 }
879 }"#;
880 let expected = Resp {
881 ret_code: 0,
882 ret_msg: String::from("OK"),
883 result: AccountInfo {
884 unified_margin_status: UnifiedMarginStatus::UnifiedTradingAccount1Pro,
885 margin_mode: MarginMode::RegularMargin,
886 is_master_trader: false,
887 spot_hedging_status: SpotHedgingStatus::Off,
888 updated_time: 1697078946000,
889 },
890 time: None,
891 ret_ext_info: None,
892 };
893
894 let message = deserialize_json(json).unwrap();
895
896 assert_eq!(expected, message);
897 }
898}