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