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