Skip to main content

bybit/http/
mod.rs

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}