Skip to main content

bybit/http/
mod.rs

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_get_open_closed_orders_linear() {
249        let json = r#"{
250            "retCode": 0,
251            "retMsg": "OK",
252            "result": {
253                "list": [
254                    {
255                        "orderId": "fd4300ae-7847-404e-b947-b46980a4d140",
256                        "orderLinkId": "test-000005",
257                        "blockTradeId": "",
258                        "symbol": "ETHUSDT",
259                        "price": "1600.00",
260                        "qty": "0.10",
261                        "side": "Buy",
262                        "isLeverage": "",
263                        "positionIdx": 1,
264                        "orderStatus": "New",
265                        "cancelType": "UNKNOWN",
266                        "rejectReason": "EC_NoError",
267                        "avgPrice": "0",
268                        "leavesQty": "0.10",
269                        "leavesValue": "160",
270                        "cumExecQty": "0.00",
271                        "cumExecValue": "0",
272                        "cumExecFee": "0",
273                        "timeInForce": "GTC",
274                        "orderType": "Limit",
275                        "stopOrderType": "UNKNOWN",
276                        "orderIv": "",
277                        "triggerPrice": "0.00",
278                        "takeProfit": "2500.00",
279                        "stopLoss": "1500.00",
280                        "tpTriggerBy": "LastPrice",
281                        "slTriggerBy": "LastPrice",
282                        "triggerDirection": 0,
283                        "triggerBy": "UNKNOWN",
284                        "lastPriceOnCreated": "",
285                        "reduceOnly": false,
286                        "closeOnTrigger": false,
287                        "smpType": "None",
288                        "smpGroup": 0,
289                        "smpOrderId": "",
290                        "tpslMode": "Full",
291                        "tpLimitPrice": "",
292                        "slLimitPrice": "",
293                        "placeType": "",
294                        "createdTime": "1684738540559",
295                        "updatedTime": "1684738540561"
296                    }
297                ],
298                "nextPageCursor": "page_args%3Dfd4300ae-7847-404e-b947-b46980a4d140%26symbol%3D6%26",
299                "category": "linear"
300            },
301            "retExtInfo": {},
302            "time": 1684765770483
303        }"#;
304        let expected = Resp {
305            ret_code: 0,
306            ret_msg: String::from("OK"),
307            result: CursorPagination {
308                category: Some(Category::Linear),
309                next_page_cursor: Some(String::from(
310                    "page_args%3Dfd4300ae-7847-404e-b947-b46980a4d140%26symbol%3D6%26",
311                )),
312                list: vec![Order {
313                    order_id: String::from("fd4300ae-7847-404e-b947-b46980a4d140"),
314                    order_link_id: Some(String::from("test-000005")),
315                    block_trade_id: None,
316                    symbol: String::from("ETHUSDT"),
317                    price: dec!(1600.00),
318                    qty: dec!(0.10),
319                    side: Side::Buy,
320                    is_leverage: None,
321                    position_idx: PositionIdx::Buy,
322                    order_status: OrderStatus::New,
323                    create_type: None,
324                    cancel_type: CancelType::UNKNOWN,
325                    reject_reason: RejectReason::EcNoError,
326                    avg_price: Some(dec!(0.0)),
327                    leaves_qty: dec!(0.10),
328                    leaves_value: dec!(160),
329                    cum_exec_qty: dec!(0.00),
330                    cum_exec_value: dec!(0),
331                    cum_exec_fee: dec!(0),
332                    time_in_force: TimeInForce::GTC,
333                    order_type: OrderType::Limit,
334                    stop_order_type: Some(StopOrderType::UNKNOWN),
335                    order_iv: None,
336                    market_unit: None,
337                    trigger_price: Some(dec!(0.00)),
338                    take_profit: Some(dec!(2500.00)),
339                    stop_loss: Some(dec!(1500.00)),
340                    tpsl_mode: Some(TpslMode::Full),
341                    oco_trigger_by: None,
342                    tp_limit_price: None,
343                    sl_limit_price: None,
344                    tp_trigger_by: Some(TriggerBy::LastPrice),
345                    sl_trigger_by: Some(TriggerBy::LastPrice),
346                    trigger_direction: TriggerDirection::UNKNOWN,
347                    trigger_by: Some(TriggerBy::UNKNOWN),
348                    last_price_on_created: None,
349                    base_price: None,
350                    reduce_only: false,
351                    close_on_trigger: false,
352                    place_type: None,
353                    smp_type: SmpType::None,
354                    smp_group: 0,
355                    smp_order_id: None,
356                    created_time: 1684738540559,
357                    updated_time: 1684738540561,
358                }],
359            },
360            time: Some(1684765770483),
361            ret_ext_info: Some(RetExtInfo::default()),
362        };
363
364        let message = deserialize_json(json).unwrap();
365
366        assert_eq!(expected, message);
367    }
368
369    #[test]
370    fn deserialize_response_get_open_closed_orders_linear2() {
371        let json = r#"{
372            "retCode":0,
373            "retMsg":"OK",
374            "result":{
375                "nextPageCursor":"aed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113%2Caed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113",
376                "category":"linear",
377                "list":[
378                    {
379                        "symbol":"BTCUSDT",
380                        "orderType":"Limit",
381                        "orderLinkId":"",
382                        "slLimitPrice":"0",
383                        "orderId":"aed77e97-492f-45be-8ada-4ff350ec07a5",
384                        "cancelType":"UNKNOWN",
385                        "avgPrice":"",
386                        "stopOrderType":"",
387                        "lastPriceOnCreated":"103550",
388                        "orderStatus":"New",
389                        "createType":"CreateByUser",
390                        "takeProfit":"",
391                        "cumExecValue":"0",
392                        "tpslMode":"",
393                        "smpType":"None",
394                        "triggerDirection":0,
395                        "blockTradeId":"",
396                        "isLeverage":"",
397                        "rejectReason":"EC_NoError",
398                        "price":"103000",
399                        "orderIv":"",
400                        "createdTime":"1762701687113",
401                        "tpTriggerBy":"",
402                        "positionIdx":1,
403                        "timeInForce":"GTC",
404                        "leavesValue":"1030",
405                        "updatedTime":"1762701687113",
406                        "side":"Buy",
407                        "smpGroup":0,
408                        "triggerPrice":"",
409                        "tpLimitPrice":"0",
410                        "cumExecFee":"0",
411                        "leavesQty":"0.01",
412                        "slTriggerBy":"",
413                        "closeOnTrigger":false,
414                        "placeType":"",
415                        "cumExecQty":"0",
416                        "reduceOnly":false,
417                        "qty":"0.01",
418                        "stopLoss":"",
419                        "marketUnit":"",
420                        "smpOrderId":"",
421                        "triggerBy":""
422                    }
423                ]
424            },
425            "retExtInfo":{},
426            "time":1762711342768
427        }"#;
428        let expected = Resp {
429            ret_code: 0,
430            ret_msg: String::from("OK"),
431            result: CursorPagination {
432                category: Some(Category::Linear),
433                next_page_cursor: Some(String::from(
434                    "aed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113%2Caed77e97-492f-45be-8ada-4ff350ec07a5%3A1762701687113",
435                )),
436                list: vec![Order {
437                    order_id: String::from("aed77e97-492f-45be-8ada-4ff350ec07a5"),
438                    order_link_id: None,
439                    block_trade_id: None,
440                    symbol: String::from("BTCUSDT"),
441                    price: dec!(103000),
442                    qty: dec!(0.01),
443                    side: Side::Buy,
444                    is_leverage: None,
445                    position_idx: PositionIdx::Buy,
446                    order_status: OrderStatus::New,
447                    create_type: Some(CreateType::CreateByUser),
448                    cancel_type: CancelType::UNKNOWN,
449                    reject_reason: RejectReason::EcNoError,
450                    avg_price: None,
451                    leaves_qty: dec!(0.01),
452                    leaves_value: dec!(1030),
453                    cum_exec_qty: dec!(0),
454                    cum_exec_value: dec!(0),
455                    cum_exec_fee: dec!(0),
456                    time_in_force: TimeInForce::GTC,
457                    order_type: OrderType::Limit,
458                    stop_order_type: None,
459                    order_iv: None,
460                    market_unit: None,
461                    trigger_price: None,
462                    take_profit: None,
463                    stop_loss: None,
464                    tpsl_mode: None,
465                    oco_trigger_by: None,
466                    tp_limit_price: Some(dec!(0)),
467                    sl_limit_price: Some(dec!(0)),
468                    tp_trigger_by: None,
469                    sl_trigger_by: None,
470                    trigger_direction: TriggerDirection::UNKNOWN,
471                    trigger_by: None,
472                    last_price_on_created: Some(dec!(103550)),
473                    base_price: None,
474                    reduce_only: false,
475                    close_on_trigger: false,
476                    place_type: None,
477                    smp_type: SmpType::None,
478                    smp_group: 0,
479                    smp_order_id: None,
480                    created_time: 1762701687113,
481                    updated_time: 1762701687113,
482                }],
483            },
484            time: Some(1762711342768),
485            ret_ext_info: Some(RetExtInfo::default()),
486        };
487
488        let message = deserialize_json(json).unwrap();
489
490        assert_eq!(expected, message);
491    }
492
493    #[test]
494    fn deserialize_response_get_position_info_inverse() {
495        let json = r#"{
496            "retCode": 0,
497            "retMsg": "OK",
498            "result": {
499                "list": [
500                    {
501                        "positionIdx": 0,
502                        "riskId": 1,
503                        "riskLimitValue": "150",
504                        "symbol": "BTCUSD",
505                        "side": "Sell",
506                        "size": "300",
507                        "avgPrice": "27464.50441675",
508                        "positionValue": "0.01092319",
509                        "tradeMode": 0,
510                        "positionStatus": "Normal",
511                        "autoAddMargin": 1,
512                        "adlRankIndicator": 2,
513                        "leverage": "10",
514                        "positionBalance": "0.00139186",
515                        "markPrice": "28224.50",
516                        "liqPrice": "",
517                        "bustPrice": "999999.00",
518                        "positionMM": "0.0000015",
519                        "positionMMByMp": "0.0000015",
520                        "positionIM": "0.00010923",
521                        "positionIMByMp": "0.00010923",
522                        "tpslMode": "Full",
523                        "takeProfit": "0.00",
524                        "stopLoss": "0.00",
525                        "trailingStop": "0.00",
526                        "unrealisedPnl": "-0.00029413",
527                        "curRealisedPnl": "0.00013123",
528                        "cumRealisedPnl": "-0.00096902",
529                        "seq": 5723621632,
530                        "isReduceOnly": false,
531                        "mmrSysUpdatedTime": "",
532                        "leverageSysUpdatedTime": "",
533                        "sessionAvgPrice": "",
534                        "createdTime": "1676538056258",
535                        "updatedTime": "1697673600012"
536                    }
537                ],
538                "nextPageCursor": "",
539                "category": "inverse"
540            },
541            "retExtInfo": {},
542            "time": 1697684980172
543        }"#;
544        let expected = Resp {
545            ret_code: 0,
546            ret_msg: String::from("OK"),
547            result: CursorPagination {
548                category: Some(Category::Inverse),
549                next_page_cursor: None,
550                list: vec![Position {
551                    position_idx: PositionIdx::OneWay,
552                    risk_id: 1,
553                    risk_limit_value: Some(dec!(150)),
554                    symbol: String::from("BTCUSD"),
555                    side: Some(Side::Sell),
556                    size: dec!(300),
557                    avg_price: dec!(27464.50441675),
558                    position_value: Some(dec!(0.01092319)),
559                    auto_add_margin: true,
560                    position_status: PositionStatus::Normal,
561                    leverage: dec!(10),
562                    mark_price: dec!(28224.50),
563                    liq_price: None,
564                    position_im: Some(dec!(0.00010923)),
565                    position_im_by_mp: Some(dec!(0.00010923)),
566                    position_mm: Some(dec!(0.0000015)),
567                    position_mm_by_mp: Some(dec!(0.0000015)),
568                    take_profit: Some(dec!(0.00)),
569                    stop_loss: Some(dec!(0.00)),
570                    trailing_stop: Some(dec!(0.00)),
571                    session_avg_price: None,
572                    delta: None,
573                    gamma: None,
574                    vega: None,
575                    theta: None,
576                    unrealised_pnl: Some(dec!(-0.00029413)),
577                    cur_realised_pnl: dec!(0.00013123),
578                    cum_realised_pnl: dec!(-0.00096902),
579                    adl_rank_indicator: AdlRankIndicator::Two,
580                    created_time: 1676538056258,
581                    updated_time: 1697673600012,
582                    seq: 5723621632,
583                    is_reduce_only: false,
584                    mmr_sys_updated_time: None,
585                    leverage_sys_updated_time: None,
586                }],
587            },
588            time: Some(1697684980172),
589            ret_ext_info: Some(RetExtInfo::default()),
590        };
591
592        let message: Resp<CursorPagination<Position>> = deserialize_json(json).unwrap();
593
594        assert_eq!(message, expected);
595    }
596
597    #[test]
598    fn deserialize_response_get_wallet_balance() {
599        let json = r#"{
600            "retCode": 0,
601            "retMsg": "OK",
602            "result": {
603                "list": [
604                    {
605                        "totalEquity": "3.31216591",
606                        "accountIMRate": "0",
607                        "accountIMRateByMp": "0",
608                        "totalMarginBalance": "3.00326056",
609                        "totalInitialMargin": "0",
610                        "totalInitialMarginByMp": "0",
611                        "accountType": "UNIFIED",
612                        "totalAvailableBalance": "3.00326056",
613                        "accountMMRate": "0",
614                        "accountMMRateByMp": "0",
615                        "totalPerpUPL": "0",
616                        "totalWalletBalance": "3.00326056",
617                        "accountLTV": "0",
618                        "totalMaintenanceMargin": "0",
619                        "totalMaintenanceMarginByMp": "0",
620                        "coin": [
621                            {
622                                "availableToBorrow": "3",
623                                "bonus": "0",
624                                "accruedInterest": "0",
625                                "availableToWithdraw": "0",
626                                "totalOrderIM": "0",
627                                "equity": "0",
628                                "totalPositionMM": "0",
629                                "usdValue": "0",
630                                "spotHedgingQty": "0.01592413",
631                                "unrealisedPnl": "0",
632                                "collateralSwitch": true,
633                                "borrowAmount": "0.0",
634                                "totalPositionIM": "0",
635                                "walletBalance": "0",
636                                "cumRealisedPnl": "0",
637                                "locked": "0",
638                                "marginCollateral": true,
639                                "coin": "BTC",
640                                "spotBorrow": "0"
641                            }
642                        ]
643                    }
644                ]
645            },
646            "retExtInfo": {},
647            "time": 1690872862481
648        }"#;
649        let coin = WalletCoin {
650            coin: String::from("BTC"),
651            equity: dec!(0),
652            usd_value: dec!(0),
653            wallet_balance: dec!(0),
654            locked: dec!(0),
655            spot_hedging_qty: dec!(0.01592413),
656            borrow_amount: dec!(0.0),
657            accrued_interest: dec!(0),
658            total_order_im: Some(dec!(0)),
659            total_position_im: Some(dec!(0)),
660            total_position_mm: Some(dec!(0)),
661            unrealised_pnl: dec!(0),
662            cum_realised_pnl: dec!(0),
663            bonus: dec!(0),
664            margin_collateral: true,
665            collateral_switch: true,
666            spot_borrow: Some(dec!(0)),
667        };
668        let coin = HashMap::from([(Unique::unique_key(&coin), coin)]);
669        let expected = Resp {
670            ret_code: 0,
671            ret_msg: String::from("OK"),
672            result: List {
673                list: vec![WalletBalance {
674                    account_type: AccountType::UNIFIED,
675                    account_im_rate: dec!(0),
676                    account_im_rate_by_mp: dec!(0),
677                    account_mm_rate: dec!(0),
678                    account_mm_rate_by_mp: dec!(0),
679                    total_equity: dec!(3.31216591),
680                    total_wallet_balance: dec!(3.00326056),
681                    total_margin_balance: dec!(3.00326056),
682                    total_available_balance: dec!(3.00326056),
683                    total_perp_upl: dec!(0),
684                    total_initial_margin: dec!(0),
685                    total_initial_margin_by_mp: dec!(0),
686                    total_maintenance_margin: dec!(0),
687                    total_maintenance_margin_by_mp: dec!(0),
688                    coin,
689                }],
690            },
691            time: Some(1690872862481),
692            ret_ext_info: Some(RetExtInfo::default()),
693        };
694
695        let message = deserialize_json(json).unwrap();
696
697        assert_eq!(expected, message);
698    }
699
700    #[test]
701    fn deserialize_response_get_wallet_balance_2() {
702        let json = r#"{
703            "retCode":0,
704            "retMsg":"OK",
705            "result":{
706                "list":[
707                    {
708                        "totalEquity":"36.42053792",
709                        "accountIMRate":"0",
710                        "accountIMRateByMp":"0",
711                        "totalMarginBalance":"36.42053792",
712                        "totalInitialMargin":"0",
713                        "totalInitialMarginByMp":"0",
714                        "accountType":"UNIFIED",
715                        "totalAvailableBalance":"36.42053792",
716                        "accountMMRate":"0",
717                        "accountMMRateByMp":"0",
718                        "totalPerpUPL":"0",
719                        "totalWalletBalance":"36.42053792",
720                        "accountLTV":"0",
721                        "totalMaintenanceMargin":"0",
722                        "totalMaintenanceMarginByMp":"0",
723                        "coin":[
724                            {
725                                "availableToBorrow":"",
726                                "bonus":"0",
727                                "accruedInterest":"0",
728                                "availableToWithdraw":"",
729                                "totalOrderIM":"0",
730                                "equity":"36.4061211",
731                                "totalPositionMM":"0",
732                                "usdValue":"36.42053792",
733                                "unrealisedPnl":"0",
734                                "collateralSwitch":true,
735                                "spotHedgingQty":"0",
736                                "borrowAmount":"0.000000000000000000",
737                                "totalPositionIM":"0",
738                                "walletBalance":"36.4061211",
739                                "cumRealisedPnl":"-2084.9938789",
740                                "locked":"0",
741                                "marginCollateral":true,
742                                "coin":"USDT",
743                                "spotBorrow": "0"
744                            }
745                        ]
746                    }
747                ]
748            },
749            "retExtInfo":{},
750            "time":1751570498412
751        }"#;
752        let coin = WalletCoin {
753            coin: String::from("USDT"),
754            equity: dec!(36.4061211),
755            usd_value: dec!(36.42053792),
756            wallet_balance: dec!(36.4061211),
757            locked: dec!(0),
758            spot_hedging_qty: dec!(0),
759            borrow_amount: dec!(0.000000000000000000),
760            accrued_interest: dec!(0),
761            total_order_im: Some(dec!(0)),
762            total_position_im: Some(dec!(0)),
763            total_position_mm: Some(dec!(0)),
764            unrealised_pnl: dec!(0),
765            cum_realised_pnl: dec!(-2084.9938789),
766            bonus: dec!(0),
767            margin_collateral: true,
768            collateral_switch: true,
769            spot_borrow: Some(dec!(0)),
770        };
771        let coin = HashMap::from([(Unique::unique_key(&coin), coin)]);
772        let expected = Resp {
773            ret_code: 0,
774            ret_msg: String::from("OK"),
775            result: List {
776                list: vec![WalletBalance {
777                    account_type: AccountType::UNIFIED,
778                    account_im_rate: dec!(0),
779                    account_im_rate_by_mp: dec!(0),
780                    account_mm_rate: dec!(0),
781                    account_mm_rate_by_mp: dec!(0),
782                    total_equity: dec!(36.42053792),
783                    total_wallet_balance: dec!(36.42053792),
784                    total_margin_balance: dec!(36.42053792),
785                    total_available_balance: dec!(36.42053792),
786                    total_perp_upl: dec!(0),
787                    total_initial_margin: dec!(0),
788                    total_initial_margin_by_mp: dec!(0),
789                    total_maintenance_margin: dec!(0),
790                    total_maintenance_margin_by_mp: dec!(0),
791                    coin,
792                }],
793            },
794            time: Some(1751570498412),
795            ret_ext_info: Some(RetExtInfo::default()),
796        };
797
798        let message = deserialize_json(json).unwrap();
799
800        assert_eq!(expected, message);
801    }
802
803    #[test]
804    fn deserialize_response_get_account_info() {
805        let json = r#"{
806            "retCode": 0,
807            "retMsg": "OK",
808            "result": {
809                "marginMode": "REGULAR_MARGIN",
810                "updatedTime": "1697078946000",
811                "unifiedMarginStatus": 4,
812                "dcpStatus": "OFF",
813                "timeWindow": 10,
814                "smpGroup": 0,
815                "isMasterTrader": false,
816                "spotHedgingStatus": "OFF"
817            }
818        }"#;
819        let expected = Resp {
820            ret_code: 0,
821            ret_msg: String::from("OK"),
822            result: AccountInfo {
823                unified_margin_status: UnifiedMarginStatus::UnifiedTradingAccount1Pro,
824                margin_mode: MarginMode::RegularMargin,
825                is_master_trader: false,
826                spot_hedging_status: SpotHedgingStatus::Off,
827                updated_time: 1697078946000,
828            },
829            time: None,
830            ret_ext_info: None,
831        };
832
833        let message = deserialize_json(json).unwrap();
834
835        assert_eq!(expected, message);
836    }
837}