Skip to main content

bybit/
enums.rs

1//! Enums Definitions
2//!
3//! Ref: https://bybit-exchange.github.io/docs/v5/enum
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_repr::*;
7use std::fmt;
8
9#[derive(Debug, Deserialize, Serialize)]
10pub enum Locale {
11    #[serde(rename = "de-DE")]
12    DeDe,
13    #[serde(rename = "en-US")]
14    EnUs,
15    #[serde(rename = "es-AR")]
16    EsAr,
17    #[serde(rename = "es-ES")]
18    EsEs,
19    #[serde(rename = "es-MX")]
20    EsMx,
21    #[serde(rename = "fr-FR")]
22    FrFr,
23    #[serde(rename = "kk-KZ")]
24    KkKz,
25    #[serde(rename = "id-ID")]
26    IdId,
27    #[serde(rename = "uk-UA")]
28    UkUa,
29    #[serde(rename = "ja-JP")]
30    JaJp,
31    #[serde(rename = "ru-RU")]
32    RuRu,
33    #[serde(rename = "th-TH")]
34    ThTh,
35    #[serde(rename = "pt-BR")]
36    PtBr,
37    #[serde(rename = "tr-TR")]
38    TrTr,
39    #[serde(rename = "vi-VN")]
40    ViVn,
41    #[serde(rename = "zh-TW")]
42    ZhTw,
43    #[serde(rename = "ar-SA")]
44    ArSa,
45    #[serde(rename = "hi-IN")]
46    HiIn,
47    #[serde(rename = "fil-PH")]
48    FilPh,
49}
50
51#[derive(Debug, Deserialize, Serialize)]
52pub enum AnnouncementType {
53    #[serde(rename = "new_crypto")]
54    NewCrypto,
55    #[serde(rename = "latest_bybit_news")]
56    LatestBybitNews,
57    #[serde(rename = "delistings")]
58    Delistings,
59    #[serde(rename = "latest_activities")]
60    LatestActivities,
61    #[serde(rename = "product_updates")]
62    ProductUpdates,
63    #[serde(rename = "maintenance_updates")]
64    MaintenanceUpdates,
65    #[serde(rename = "new_fiat_listings")]
66    NewFiatListings,
67    #[serde(rename = "other")]
68    Other,
69}
70
71/// Unified Account: spot | linear | inverse | option
72/// Classic Account: linear | inverse | spot
73#[derive(PartialEq, Debug, Deserialize, Serialize, Clone, Copy)]
74#[serde(rename_all = "lowercase")]
75pub enum Category {
76    /// Inverse contract, including Inverse perp, Inverse futures.
77    Inverse,
78    /// USDT perpetual, and USDC contract, including USDC perp, USDC futures.
79    Linear,
80    Option,
81    Spot,
82}
83
84impl fmt::Display for Category {
85    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86        let value = match self {
87            Self::Inverse => "inverse",
88            Self::Linear => "linear",
89            Self::Option => "option",
90            Self::Spot => "spot",
91        };
92        write!(f, "{value}")
93    }
94}
95
96#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
97pub enum OrderStatus {
98    // open status
99    /// order has been placed successfully
100    New,
101    PartiallyFilled,
102    /// Conditional orders are created
103    Untriggered,
104    // closed status
105    Rejected,
106    /// Only spot has this order status
107    PartiallyFilledCanceled,
108    Filled,
109    /// In derivatives, orders with this status may have an executed qty
110    Cancelled,
111    /// instantaneous state for conditional orders from Untriggered to New
112    Triggered,
113    /// UTA: Spot tp/sl order, conditional order, OCO order are cancelled before they are triggered
114    Deactivated,
115}
116
117impl OrderStatus {
118    pub fn is_open(&self) -> bool {
119        matches!(self, Self::New | Self::PartiallyFilled | Self::Untriggered)
120    }
121    pub fn is_closed(&self) -> bool {
122        matches!(
123            self,
124            Self::Rejected
125                | Self::PartiallyFilledCanceled
126                | Self::Filled
127                | Self::Cancelled
128                | Self::Triggered
129                | Self::Deactivated
130        )
131    }
132}
133
134#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
135pub enum TimeInForce {
136    /// GoodTillCancel
137    GTC,
138    /// ImmediateOrCancel
139    IOC,
140    /// FillOrKill
141    FOK,
142    PostOnly,
143    /// features:
144    /// Exclusive Matching: Only match non-algorithmic users; no execution against orders from Open API.
145    /// Post-Only Mechanism: Act as maker orders, adding liquidity
146    /// Lower Priority: Execute after non-RPI orders at the same price level.
147    /// Limited Access: Initially for select market makers across multiple pairs.
148    /// Order Book Updates: Excluded from API but displayed on the GUI.
149    RPI,
150}
151
152#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
153pub enum CreateType {
154    CreateByUser,
155    /// Spread order
156    CreateByFutureSpread,
157    CreateByAdminClosing,
158    /// USDC Futures delivery; position closed as a result of the delisting of a contract. This is recorded as a trade but not an order.
159    CreateBySettle,
160    /// Futures conditional order
161    CreateByStopOrder,
162    /// Futures take profit order
163    CreateByTakeProfit,
164    /// Futures partial take profit order
165    CreateByPartialTakeProfit,
166    /// Futures stop loss order
167    CreateByStopLoss,
168    /// Futures partial stop loss order
169    CreateByPartialStopLoss,
170    /// Futures trailing stop order
171    CreateByTrailingStop,
172    /// Laddered liquidation to reduce the required maintenance margin
173    CreateByLiq,
174    /// If the position is still subject to liquidation (i.e., does not meet the required maintenance margin level), the position shall be taken over by the liquidation engine and closed at the bankruptcy price.
175    #[serde(rename = "CreateByTakeOver_PassThrough")]
176    CreateByTakeOverPassThrough,
177    /// Auto-Deleveraging(ADL)
178    #[serde(rename = "CreateByAdl_PassThrough")]
179    CreateByAdlPassThrough,
180    /// Order placed via Paradigm
181    #[serde(rename = "CreateByBlock_PassThrough")]
182    CreateByBlockPassThrough,
183    /// Order created by move position
184    #[serde(rename = "CreateByBlockTradeMovePosition_PassThrough")]
185    CreateByBlockTradeMovePositionPassThrough,
186    /// The close order placed via web or app position area - web/app
187    CreateByClosing,
188    /// Order created via grid bot - web/app
189    CreateByFGridBot,
190    /// Order closed via grid bot - web/app
191    CloseByFGridBot,
192    /// Order created by TWAP - web/app
193    CreateByTWAP,
194    /// Order created by TV webhook - web/app
195    CreateByTVSignal,
196    /// Order created by Mm rate close function - web/app
197    CreateByMmRateClose,
198    /// Order created by Martingale bot - web/app
199    CreateByMartingaleBot,
200    /// Order closed by Martingale bot - web/app
201    CloseByMartingaleBot,
202    /// Order created by Ice berg strategy - web/app
203    CreateByIceBerg,
204    /// Order created by arbitrage - web/app
205    CreateByArbitrage,
206    /// Option dynamic delta hedge order - web/app
207    CreateByDdh,
208}
209
210#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
211pub enum ExecType {
212    Trade,
213    /// Auto-Deleveraging
214    AdlTrade,
215    /// Funding fee
216    Funding,
217    /// Takeover liquidation
218    BustTrade,
219    /// USDC futures delivery; Position closed by contract delisted
220    Delivery,
221    /// Inverse futures settlement; Position closed due to delisting
222    Settle,
223    BlockTrade,
224    MovePosition,
225    /// Spread leg execution
226    FutureSpread,
227    /// May be returned by a classic account. Cannot query by this type
228    UNKNOWN,
229}
230
231#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
232pub enum OrderType {
233    Market,
234    Limit,
235    /// is not a valid request parameter value. Is only used in some responses. Mainly, it is used when execType is Funding.
236    UNKNOWN,
237}
238
239#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
240pub enum StopOrderType {
241    TakeProfit,
242    StopLoss,
243    TrailingStop,
244    Stop,
245    PartialTakeProfit,
246    PartialStopLoss,
247    /// spot TP/SL order
248    #[serde(rename = "tpslOrder")]
249    TpslOrder,
250    /// spot Oco order
251    OcoOrder,
252    /// On web or app can set MMR to close position
253    MmRateClose,
254    /// Spot bidirectional tpsl order
255    BidirectionalTpslOrder,
256    UNKNOWN,
257}
258
259#[derive(Debug, PartialEq, Deserialize, Clone, Copy)]
260pub enum TickDirection {
261    /// price rise
262    PlusTick,
263    /// trade occurs at the same price as the previous trade, which occurred at a price higher than that for the trade preceding it
264    ZeroPlusTick,
265    /// price drop
266    MinusTick,
267    /// trade occurs at the same price as the previous trade, which occurred at a price lower than that for the trade preceding it
268    ZeroMinusTick,
269}
270
271#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
272pub enum Interval {
273    #[serde(rename = "1")]
274    Minute1,
275    #[serde(rename = "3")]
276    Minute3,
277    #[serde(rename = "5")]
278    Minute5,
279    #[serde(rename = "15")]
280    Minute15,
281    #[serde(rename = "30")]
282    Minute30,
283    #[serde(rename = "60")]
284    Hour1,
285    #[serde(rename = "120")]
286    Hour2,
287    #[serde(rename = "240")]
288    Hour4,
289    #[serde(rename = "360")]
290    Hour6,
291    #[serde(rename = "720")]
292    Hour12,
293    #[serde(rename = "D")]
294    Day1,
295    #[serde(rename = "W")]
296    Week1,
297    #[serde(rename = "M")]
298    Month1,
299}
300
301impl fmt::Display for Interval {
302    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
303        let value = match self {
304            Self::Minute1 => "1",
305            Self::Minute3 => "3",
306            Self::Minute5 => "5",
307            Self::Minute15 => "15",
308            Self::Minute30 => "30",
309            Self::Hour1 => "60",
310            Self::Hour2 => "120",
311            Self::Hour4 => "240",
312            Self::Hour6 => "360",
313            Self::Hour12 => "720",
314            Self::Day1 => "D",
315            Self::Week1 => "W",
316            Self::Month1 => "M",
317        };
318        write!(f, "{value}")
319    }
320}
321
322#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
323pub enum IntervalTime {
324    #[serde(rename = "5min")]
325    Minute5,
326    #[serde(rename = "15min")]
327    Minute15,
328    #[serde(rename = "30min")]
329    Minute30,
330    #[serde(rename = "1h")]
331    Hour1,
332    #[serde(rename = "4h")]
333    Hour4,
334    #[serde(rename = "1d")]
335    Day1,
336}
337
338#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
339#[repr(u8)]
340pub enum PositionIdx {
341    /// 0:one-way mode position
342    OneWay = 0,
343    /// 1:Buy side of hedge-mode position
344    Buy = 1,
345    /// 2:Sell side of hedge-mode position
346    Sell = 2,
347}
348
349#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
350#[repr(u8)]
351pub enum PositionMode {
352    /// 0: Merged Single (one-way)
353    OneWay = 0,
354    /// 3: Both Sides (hedge)
355    Hedge = 3,
356}
357
358#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
359pub enum PositionStatus {
360    Normal,
361    /// in the liquidation progress
362    Liq,
363    /// in the auto-deleverage progress
364    Adl,
365}
366
367#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
368pub enum RejectReason {
369    #[serde(rename = "EC_NoError")]
370    EcNoError,
371    #[serde(rename = "EC_Others")]
372    EcOthers,
373    #[serde(rename = "EC_UnknownMessageType")]
374    EcUnknownMessageType,
375    #[serde(rename = "EC_MissingClOrdID")]
376    EcMissingClOrdId,
377    #[serde(rename = "EC_MissingOrigClOrdID")]
378    EcMissingOrigClOrdId,
379    #[serde(rename = "EC_ClOrdIDOrigClOrdIDAreTheSame")]
380    EcClOrdIdorigClOrdIdareTheSame,
381    #[serde(rename = "EC_DuplicatedClOrdID")]
382    EcDuplicatedClOrdId,
383    #[serde(rename = "EC_OrigClOrdIDDoesNotExist")]
384    EcOrigClOrdIddoesNotExist,
385    #[serde(rename = "EC_TooLateToCancel")]
386    EcTooLateToCancel,
387    #[serde(rename = "EC_UnknownOrderType")]
388    EcUnknownOrderType,
389    #[serde(rename = "EC_UnknownSide")]
390    EcUnknownSide,
391    #[serde(rename = "EC_UnknownTimeInForce")]
392    EcUnknownTimeInForce,
393    #[serde(rename = "EC_WronglyRouted")]
394    EcWronglyRouted,
395    #[serde(rename = "EC_MarketOrderPriceIsNotZero")]
396    EcMarketOrderPriceIsNotZero,
397    #[serde(rename = "EC_LimitOrderInvalidPrice")]
398    EcLimitOrderInvalidPrice,
399    #[serde(rename = "EC_NoEnoughQtyToFill")]
400    EcNoEnoughQtyToFill,
401    #[serde(rename = "EC_NoImmediateQtyToFill")]
402    EcNoImmediateQtyToFill,
403    #[serde(rename = "EC_PerCancelRequest")]
404    EcPerCancelRequest,
405    #[serde(rename = "EC_MarketOrderCannotBePostOnly")]
406    EcMarketOrderCannotBePostOnly,
407    #[serde(rename = "EC_PostOnlyWillTakeLiquidity")]
408    EcPostOnlyWillTakeLiquidity,
409    #[serde(rename = "EC_CancelReplaceOrder")]
410    EcCancelReplaceOrder,
411    #[serde(rename = "EC_InvalidSymbolStatus")]
412    EcInvalidSymbolStatus,
413    #[serde(rename = "EC_CancelForNoFullFill")]
414    EcCancelForNoFullFill,
415    #[serde(rename = "EC_BySelfMatch")]
416    EcBySelfMatch,
417    /// used for pre-market order operation, e.g., during 2nd phase of call auction, cancel order is not allowed, when the cancel request is failed to be rejected by trading server, the request will be rejected by matching box finally
418    #[serde(rename = "EC_InCallAuctionStatus")]
419    EcInCallAuctionStatus,
420    #[serde(rename = "EC_QtyCannotBeZero")]
421    EcQtyCannotBeZero,
422    #[serde(rename = "EC_MarketOrderNoSupportTIF")]
423    EcMarketOrderNoSupportTif,
424    #[serde(rename = "EC_ReachMaxTradeNum")]
425    EcReachMaxTradeNum,
426    #[serde(rename = "EC_InvalidPriceScale")]
427    EcInvalidPriceScale,
428    #[serde(rename = "EC_BitIndexInvalid")]
429    EcBitIndexInvalid,
430    #[serde(rename = "EC_StopBySelfMatch")]
431    EcStopBySelfMatch,
432    #[serde(rename = "EC_InvalidSmpType")]
433    EcInvalidSmpType,
434    #[serde(rename = "EC_CancelByMMP")]
435    EcCancelByMmp,
436    #[serde(rename = "EC_InvalidUserType")]
437    EcInvalidUserType,
438    #[serde(rename = "EC_InvalidMirrorOid")]
439    EcInvalidMirrorOid,
440    #[serde(rename = "EC_InvalidMirrorUid")]
441    EcInvalidMirrorUid,
442    #[serde(rename = "EC_EcInvalidQty")]
443    EcEcInvalidQty,
444    #[serde(rename = "EC_InvalidAmount")]
445    EcInvalidAmount,
446    #[serde(rename = "EC_LoadOrderCancel")]
447    EcLoadOrderCancel,
448    #[serde(rename = "EC_MarketQuoteNoSuppSell")]
449    EcMarketQuoteNoSuppSell,
450    #[serde(rename = "EC_DisorderOrderID")]
451    EcDisorderOrderId,
452    #[serde(rename = "EC_InvalidBaseValue")]
453    EcInvalidBaseValue,
454    #[serde(rename = "EC_LoadOrderCanMatch")]
455    EcLoadOrderCanMatch,
456    #[serde(rename = "EC_SecurityStatusFail")]
457    EcSecurityStatusFail,
458    #[serde(rename = "EC_ReachRiskPriceLimit")]
459    EcReachRiskPriceLimit,
460    #[serde(rename = "EC_OrderNotExist")]
461    EcOrderNotExist,
462    #[serde(rename = "EC_CancelByOrderValueZero")]
463    EcCancelByOrderValueZero,
464    #[serde(rename = "EC_CancelByMatchValueZero")]
465    EcCancelByMatchValueZero,
466    #[serde(rename = "EC_ReachMarketPriceLimit")]
467    EcReachMarketPriceLimit,
468}
469
470#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
471pub enum AccountType {
472    /// Inverse Derivatives Account | Derivatives Account
473    CONTRACT,
474    /// Unified Trading Account
475    UNIFIED,
476    /// Funding Account
477    FUND,
478    /// Spot Account
479    SPOT,
480}
481
482impl AccountType {
483    pub fn is_uta_2(&self) -> bool {
484        matches!(self, Self::UNIFIED | Self::FUND)
485    }
486    pub fn is_uta_1(&self) -> bool {
487        matches!(self, Self::CONTRACT | Self::UNIFIED | Self::FUND)
488    }
489    pub fn is_classic(&self) -> bool {
490        matches!(self, Self::SPOT | Self::CONTRACT | Self::FUND)
491    }
492}
493
494#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
495pub enum TransferStatus {
496    SUCCESS,
497    PENDING,
498    FAILED,
499}
500
501#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
502pub enum DepositStatus {
503    #[serde(rename = "0")]
504    Unknown,
505    #[serde(rename = "1")]
506    ToBeConfirmed,
507    #[serde(rename = "2")]
508    Processing,
509    /// (finalised status of a success deposit)
510    #[serde(rename = "3")]
511    Success,
512    #[serde(rename = "4")]
513    DepositFailed,
514    #[serde(rename = "10011")]
515    PendingToBeCreditedToFundingPool,
516    #[serde(rename = "10012")]
517    CreditedToFundingPoolSuccessfully,
518}
519
520#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
521pub enum WithdrawStatus {
522    SecurityCheck,
523    Pending,
524    #[serde(rename = "success")]
525    Success,
526    CancelByUser,
527    Reject,
528    Fail,
529    BlockchainConfirmed,
530    MoreInformationRequired,
531    /// a rare status
532    Unknown,
533}
534
535#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
536pub enum TriggerBy {
537    LastPrice,
538    IndexPrice,
539    MarkPrice,
540    UNKNOWN,
541}
542
543#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
544pub enum CancelType {
545    CancelByUser,
546    /// cancelled by reduceOnly
547    CancelByReduceOnly,
548    /// cancelled in order to attempt liquidation prevention by freeing up margin
549    CancelByPrepareLiq,
550    /// cancelled in order to attempt liquidation prevention by freeing up margin
551    CancelAllBeforeLiq,
552    /// cancelled due to ADL
553    CancelByPrepareAdl,
554    /// cancelled due to ADL
555    CancelAllBeforeAdl,
556    CancelByAdmin,
557    /// cancelled due to delisting contract
558    CancelBySettle,
559    /// TP/SL order cancelled when the position is cleared
560    CancelByTpSlTsClear,
561    /// cancelled by SMP
562    CancelBySmp,
563    /// cancelled by DCP triggering
564    CancelByDCP,
565    /// Spread trading: the order price of a single leg order is outside the limit price range.
566    CancelByRebalance,
567
568    // Options:
569    CancelByCannotAffordOrderCost,
570    CancelByPmTrialMmOverEquity,
571    CancelByAccountBlocking,
572    CancelByDelivery,
573    CancelByMmpTriggered,
574    CancelByCrossSelfMuch,
575    CancelByCrossReachMaxTradeNum,
576
577    /// Not documented
578    UNKNOWN,
579}
580
581#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
582pub enum OptionPeriod {
583    #[serde(rename = "7")]
584    Day7,
585    #[serde(rename = "14")]
586    Day14,
587    #[serde(rename = "21")]
588    Day21,
589    #[serde(rename = "30")]
590    Day30,
591    #[serde(rename = "60")]
592    Day60,
593    #[serde(rename = "90")]
594    Day90,
595    #[serde(rename = "180")]
596    Day180,
597    #[serde(rename = "270")]
598    Day270,
599}
600
601#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
602pub enum DataRecordingPeriod {
603    #[serde(rename = "5min")]
604    Minute5,
605    #[serde(rename = "15min")]
606    Minute15,
607    #[serde(rename = "30min")]
608    Minute30,
609    #[serde(rename = "1h")]
610    Hour1,
611    #[serde(rename = "4h")]
612    Hour4,
613    #[serde(rename = "4d")]
614    Day4,
615}
616
617#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
618pub enum ContractType {
619    InversePerpetual,
620    LinearPerpetual,
621    /// USDT/USDC Futures
622    LinearFutures,
623    InverseFutures,
624}
625
626#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
627pub enum Status {
628    PreLaunch,
629    Trading,
630    Delivering,
631    Closed,
632}
633
634#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
635#[serde(rename_all = "camelCase")]
636pub enum MarginTrading {
637    /// Regardless of normal account or UTA account, this trading pair does not support margin trading
638    None,
639    /// For both normal account and UTA account, this trading pair supports margin trading
640    Both,
641    /// Only for UTA account,this trading pair supports margin trading
642    UtaOnly,
643    /// Only for normal account, this trading pair supports margin trading
644    NormalSpotOnly,
645}
646
647#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
648#[serde(rename_all = "camelCase")]
649pub enum CopyTrading {
650    /// Regardless of normal account or UTA account, this trading pair does not support copy trading
651    None,
652    /// For both normal account and UTA account, this trading pair supports copy trading
653    Both,
654    /// Only for UTA account,this trading pair supports copy trading
655    UtaOnly,
656    /// Only for normal account, this trading pair supports copy trading
657    NormalOnly,
658}
659
660#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
661#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
662pub enum Type {
663    /// Assets that transferred into Unified | (inverse) derivatives wallet
664    TransferIn,
665    /// Assets that transferred out from Unified | (inverse) derivatives wallet
666    TransferOut,
667    Trade,
668    /// USDT Perp funding settlement, and USDC Perp funding settlement + USDC 8-hour session settlement
669    /// USDT / Inverse Perp funding settlement
670    Settlement,
671    /// USDC Futures, Option delivery
672    Delivery,
673    /// Inverse Futures delivery
674    Liquidation,
675    /// Auto-Deleveraging
676    ADL,
677    Airdrop,
678    /// Bonus claimed
679    Bonus,
680    /// Bonus expired
681    BonusRecollect,
682    /// Trading fee refunded
683    FeeRefund,
684    /// Interest occurred due to borrowing
685    Interest,
686    /// Currency convert, and the liquidation for borrowing asset(UTA loan)
687    CurrencyBuy,
688    /// Currency convert, and the liquidation for borrowing asset(UTA loan)
689    CurrencySell,
690    BorrowedAmountInsLoan,
691    PrincipleRepaymentInsLoan,
692    InterestRepaymentInsLoan,
693    /// the liquidation for borrowing asset(INS loan)
694    AutoSoldCollateralInsLoan,
695    /// the liquidation for borrowing asset(INS loan)
696    AutoBuyLiabilityInsLoan,
697    AutoPrincipleRepaymentInsLoan,
698    AutoInterestRepaymentInsLoan,
699    /// Transfer In when in the liquidation of OTC loan
700    TransferInInsLoan,
701    /// Transfer Out when in the liquidation of OTC loan
702    TransferOutInsLoan,
703    /// One-click repayment currency sell
704    SpotRepaymentSell,
705    /// One-click repayment currency buy
706    SpotRepaymentBuy,
707    /// Spot leverage token subscription
708    TokensSubscription,
709    /// Spot leverage token redemption
710    TokensRedemption,
711    /// Asset auto deducted by system (roll back)
712    AutoDeduction,
713    /// Byfi flexible stake subscription
714    FlexibleStakingSubscription,
715    /// Byfi flexible stake redemption
716    FlexibleStakingRedemption,
717    /// Byfi fixed stake subscription
718    FixedStakingSubscription,
719    PremarketTransferOut,
720    PremarketDeliverySellNewCoin,
721    PremarketDeliveryBuyNewCoin,
722    PremarketDeliveryPledgePaySeller,
723    PremarketDeliveryPledgeBack,
724    PremarketRollbackPledgeBack,
725    PremarketRollbackPledgePenaltyToBuyer,
726    /// fireblocks business
727    CustodyNetworkFee,
728    /// fireblocks business
729    CustodySettleFee,
730    /// fireblocks / copper business
731    CustodyLock,
732    /// fireblocks business
733    CustodyUnlock,
734    /// fireblocks business
735    CustodyUnlockRefund,
736    /// crypto loan
737    LoansBorrowFunds,
738    /// crypto loan repayment
739    LoansPledgeAsset,
740    BonusTransferIn,
741    BonusTransferOut,
742    PefTransferIn,
743    PefTransferOut,
744    PefProfitShare,
745    #[serde(rename = "Others")]
746    Others,
747}
748
749impl Type {
750    pub fn is_uta(&self) -> bool {
751        matches!(
752            self,
753            Self::TransferIn
754                | Self::TransferOut
755                | Self::Trade
756                | Self::Settlement
757                | Self::Delivery
758                | Self::Liquidation
759                | Self::ADL
760                | Self::Airdrop
761                | Self::Bonus
762                | Self::BonusRecollect
763                | Self::FeeRefund
764                | Self::Interest
765                | Self::CurrencyBuy
766                | Self::CurrencySell
767                | Self::BorrowedAmountInsLoan
768                | Self::PrincipleRepaymentInsLoan
769                | Self::InterestRepaymentInsLoan
770                | Self::AutoSoldCollateralInsLoan
771                | Self::AutoBuyLiabilityInsLoan
772                | Self::AutoPrincipleRepaymentInsLoan
773                | Self::AutoInterestRepaymentInsLoan
774                | Self::TransferInInsLoan
775                | Self::TransferOutInsLoan
776                | Self::SpotRepaymentSell
777                | Self::SpotRepaymentBuy
778                | Self::TokensSubscription
779                | Self::TokensRedemption
780                | Self::AutoDeduction
781                | Self::FlexibleStakingSubscription
782                | Self::FlexibleStakingRedemption
783                | Self::FixedStakingSubscription
784                | Self::PremarketTransferOut
785                | Self::PremarketDeliverySellNewCoin
786                | Self::PremarketDeliveryBuyNewCoin
787                | Self::PremarketDeliveryPledgePaySeller
788                | Self::PremarketDeliveryPledgeBack
789                | Self::PremarketRollbackPledgeBack
790                | Self::PremarketRollbackPledgePenaltyToBuyer
791                | Self::CustodyNetworkFee
792                | Self::CustodySettleFee
793                | Self::CustodyLock
794                | Self::CustodyUnlock
795                | Self::CustodyUnlockRefund
796                | Self::LoansBorrowFunds
797                | Self::LoansPledgeAsset
798                | Self::BonusTransferIn
799                | Self::BonusTransferOut
800                | Self::PefTransferIn
801                | Self::PefTransferOut
802                | Self::PefProfitShare
803        )
804    }
805    pub fn is_contract(&self) -> bool {
806        matches!(
807            self,
808            Self::TransferIn
809                | Self::TransferOut
810                | Self::Trade
811                | Self::Settlement
812                | Self::Delivery
813                | Self::Liquidation
814                | Self::ADL
815                | Self::Airdrop
816                | Self::Bonus
817                | Self::BonusRecollect
818                | Self::FeeRefund
819                | Self::CurrencyBuy
820                | Self::CurrencySell
821                | Self::AutoDeduction
822                | Self::Others
823        )
824    }
825}
826
827#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
828#[repr(u8)]
829pub enum UnifiedMarginStatus {
830    ClassicAccount = 1,
831    /// 1.0
832    UnifiedTradingAccount1 = 3,
833    /// 1.0 (pro version)
834    UnifiedTradingAccount1Pro = 4,
835    /// 2.0
836    UnifiedTradingAccount2 = 5,
837    /// 2.0 (pro version)
838    UnifiedTradingAccount2Pro = 6,
839}
840
841#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
842#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
843pub enum MarginMode {
844    IsolatedMargin,
845    RegularMargin,
846    PortfolioMargin,
847}
848
849#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
850#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
851pub enum SpotHedgingStatus {
852    On,
853    Off,
854}
855
856#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
857pub enum LtStatus {
858    /// LT can be purchased and redeemed
859    #[serde(rename = "1")]
860    CanBePurchasedAndRedeemed,
861    /// LT can be purchased, but not redeemed
862    #[serde(rename = "2")]
863    CanBePurchasedButNotRedeemed,
864    /// LT can be redeemed, but not purchased
865    #[serde(rename = "3")]
866    CanBeRedeemedButNotPurchased,
867    /// LT cannot be purchased nor redeemed
868    #[serde(rename = "4")]
869    CannotBePurchasedNorRedeemed,
870    /// Adjusting position
871    #[serde(rename = "5")]
872    AdjustingPosition,
873}
874
875#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
876pub enum ConvertAccountType {
877    /// Unified Trading Account
878    #[serde(rename = "eb_convert_uta")]
879    Uta,
880    /// Funding Account
881    #[serde(rename = "eb_convert_funding")]
882    Funding,
883    /// Inverse Derivatives Account (no USDT in this wallet))
884    #[serde(rename = "eb_convert_inverse")]
885    Inverse,
886    /// Spot Account
887    #[serde(rename = "eb_convert_spot")]
888    Spot,
889    /// Derivatives Account (contain USDT in this wallet)
890    #[serde(rename = "eb_convert_contract")]
891    Contract,
892}
893
894#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
895pub enum VipLevel {
896    #[serde(rename = "No VIP")]
897    NoVIP,
898    #[serde(rename = "VIP-1")]
899    VIP1,
900    #[serde(rename = "VIP-2")]
901    VIP2,
902    #[serde(rename = "VIP-3")]
903    VIP3,
904    #[serde(rename = "VIP-4")]
905    VIP4,
906    #[serde(rename = "VIP-5")]
907    VIP5,
908    #[serde(rename = "VIP-Supreme")]
909    VIPSupreme,
910    #[serde(rename = "PRO-1")]
911    PRO1,
912    #[serde(rename = "PRO-2")]
913    PRO2,
914    #[serde(rename = "PRO-3")]
915    PRO3,
916    #[serde(rename = "PRO-4")]
917    PRO4,
918    #[serde(rename = "PRO-5")]
919    PRO5,
920}
921
922#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
923#[repr(u8)]
924pub enum AdlRankIndicator {
925    /// default value of empty position
926    Zero = 0,
927    One = 1,
928    Two = 2,
929    Three = 3,
930    Four = 4,
931    Five = 5,
932}
933
934#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
935pub enum SmpType {
936    /// default
937    None,
938    CancelMaker,
939    CancelTaker,
940    CancelBoth,
941}
942
943#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
944#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
945pub enum ExtraFeeType {
946    Unknown,
947    /// Government tax. Only for Indonesian site
948    Tax,
949    /// Indonesian foreign exchange tax. Only for Indonesian site
950    Cfx,
951    /// EU withholding tax. Only for EU site
952    Wht,
953    /// Indian GST tax. Only for kyc=Indian users
954    Gst,
955    /// ARE VAT tax. Only for kyc=ARE users
956    Vat,
957}
958
959#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
960#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
961pub enum ExtraSubFeeType {
962    Unknown,
963    /// Tax fee, fiat currency to digital currency. Only for Indonesian site
964    TaxPnn,
965    /// Tax fee, digital currency to fiat currency. Only for Indonesian site
966    TaxPph,
967    /// CFX fee, fiat currency to digital currency. Only for Indonesian site
968    CfxFiee,
969    /// EU site withholding tax. Only for EU site
970    AutWithholdingTax,
971    /// Indian GST tax. Only for kyc=Indian users
972    IndGst,
973    /// ARE VAT tax. Only for kyc=ARE users
974    AreVat,
975}
976
977#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
978#[serde(rename_all = "lowercase")]
979pub enum State {
980    Scheduled,
981    Ongoing,
982    Completed,
983    Canceled,
984}
985
986#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
987#[repr(u8)]
988pub enum ServiceTypes {
989    /// Trading service
990    TradingService = 1,
991    /// Trading service via http request
992    TradingServiceViaHttpRequest = 2,
993    /// Trading service via websocket
994    TradingServiceViaWebsocket = 3,
995    /// Private websocket stream
996    PrivateWebsocketStream = 4,
997    /// Market data service
998    MarketDataService = 5,
999}
1000
1001#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
1002#[repr(u8)]
1003pub enum Product {
1004    Futures = 1,
1005    Spot = 2,
1006    Option = 3,
1007    Spread = 4,
1008}
1009
1010#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
1011#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1012pub enum DCPProduct {
1013    Spot,
1014    Derivatives,
1015    Option,
1016}
1017
1018#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
1019#[repr(u8)]
1020pub enum MaintainType {
1021    PlannedMaintenance = 1,
1022    TemporaryMaintenance = 2,
1023    Incident = 3,
1024}
1025
1026#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
1027#[repr(u8)]
1028pub enum Env {
1029    Product = 1,
1030    ProductDemoService = 2,
1031}
1032
1033#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
1034pub enum TpslMode {
1035    Full,
1036    Partial,
1037    UNKNOWN,
1038}
1039
1040#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
1041pub enum OcoTriggerBy {
1042    #[serde(rename = "OcoTriggerByUnknown")]
1043    Unknown,
1044    #[serde(rename = "OcoTriggerByTp")]
1045    Tp,
1046    #[serde(rename = "OcoTriggerByBySl")]
1047    BySl,
1048}
1049
1050#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
1051#[repr(u8)]
1052pub enum TriggerDirection {
1053    UNKNOWN = 0,
1054    Rise = 1,
1055    Fall = 2,
1056}
1057
1058#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
1059pub enum CurAuctionPhase {
1060    /// Pre-market trading is not started
1061    NotStarted,
1062    /// Pre-market trading is finished
1063    /// After the auction, if the pre-market contract fails to enter continues trading phase, it will be delisted and phase="Finished"
1064    /// After the continuous trading, if the pre-market contract fails to be converted to official contract, it will be delisted and phase="Finished"
1065    Finished,
1066    /// Auction phase of pre-market trading
1067    /// only timeInForce=GTC, orderType=Limit order is allowed to submit
1068    /// TP/SL are not supported; Conditional orders are not supported
1069    /// cannot modify the order at this stage
1070    /// order price range: [preOpenPrice x 0.5, maxPrice]
1071    CallAuction,
1072    /// Auction no cancel phase of pre-market trading
1073    /// only timeInForce=GTC, orderType=Limit order is allowed to submit
1074    /// TP/SL are not supported; Conditional orders are not supported
1075    /// cannot modify and cancel the order at this stage
1076    /// order price range: Buy [lastPrice x 0.5, markPrice x 1.1], Sell [markPrice x 0.9, maxPrice]
1077    CallAuctionNoCancel,
1078    /// cross matching phase
1079    /// cannot create, modify and cancel the order at this stage
1080    /// Candle data is released from this stage
1081    CrossMatching,
1082    /// Continuous trading phase
1083    /// There is no restriction to create, amend, cancel orders
1084    /// orderbook, public trade data is released from this stage
1085    ContinuousTrading,
1086}
1087
1088#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
1089pub enum PlaceType {
1090    #[serde(rename = "option")]
1091    Option,
1092    #[serde(rename = "iv")]
1093    Iv,
1094    #[serde(rename = "price")]
1095    Price,
1096}
1097
1098#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
1099pub enum Side {
1100    Buy,
1101    Sell,
1102}
1103impl Side {
1104    pub fn reverse(&self) -> Self {
1105        match self {
1106            Side::Buy => Self::Sell,
1107            Side::Sell => Self::Buy,
1108        }
1109    }
1110}
1111
1112#[derive(Debug, PartialEq, Clone, Copy)]
1113pub enum Pair {
1114    // example of BTCUSDT
1115    Base,  // BTC
1116    Quote, // USDT
1117}
1118
1119#[derive(Debug, Deserialize, PartialEq, Clone, Copy)]
1120pub enum SlippageToleranceType {
1121    TickSize,
1122    Percent,
1123    /// default
1124    UNKNOWN,
1125}
1126
1127#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
1128#[repr(u8)]
1129pub enum TradeMode {
1130    CrossMargin = 0,
1131    IsolatedMargin = 1,
1132}
1133
1134#[derive(Debug, Clone, PartialEq)]
1135pub enum Topic {
1136    Orderbook {
1137        symbol: String,
1138        depth: DepthLevel,
1139    },
1140    Trade(String),
1141    Ticker(String),
1142    Kline {
1143        symbol: String,
1144        interval: Interval,
1145    },
1146    AllLiquidation(String),
1147    Position(Category),
1148    PositionAllCategory,
1149    Execution(Category),
1150    ExecutionAllCategory,
1151    FastExecution(Category),
1152    FastExecutionAllCategory,
1153    Order(Category),
1154    OrderAllCategory,
1155    Wallet,
1156    /// option only.
1157    Greek,
1158    Dcp(DcpFunction),
1159}
1160
1161impl fmt::Display for Topic {
1162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1163        let value = match self {
1164            Self::Orderbook { symbol, depth } => format!("orderbook.{depth}.{symbol}"),
1165            Self::Trade(symbol) => format!("publicTrade.{symbol}"),
1166            Self::Ticker(symbol) => format!("tickers.{symbol}"),
1167            Self::Kline { symbol, interval } => format!("kline.{interval}.{symbol}"),
1168            Self::AllLiquidation(symbol) => format!("allLiquidation.{symbol}"),
1169            Self::Position(category) => format!("position.{category}"),
1170            Self::PositionAllCategory => "position".to_string(),
1171            Self::Execution(category) => format!("execution.{category}"),
1172            Self::ExecutionAllCategory => "execution".to_string(),
1173            Self::FastExecution(category) => format!("execution.fast.{category}"),
1174            Self::FastExecutionAllCategory => "execution.fast".to_string(),
1175            Self::Order(category) => format!("order.{category}"),
1176            Self::OrderAllCategory => "order".to_string(),
1177            Self::Wallet => "wallet".to_string(),
1178            Self::Greek => "greek".to_string(),
1179            Self::Dcp(function) => format!("dcp.{function}"),
1180        };
1181        write!(f, "{value}")
1182    }
1183}
1184
1185impl Serialize for Topic {
1186    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1187    where
1188        S: Serializer,
1189    {
1190        let s = match self {
1191            Self::Orderbook { symbol, depth } => format!("orderbook.{depth}.{symbol}"),
1192            Self::Trade(symbol) => format!("publicTrade.{symbol}"),
1193            Self::Ticker(symbol) => format!("tickers.{symbol}"),
1194            Self::Kline { symbol, interval } => format!("kline.{interval}.{symbol}"),
1195            Self::AllLiquidation(symbol) => format!("allLiquidation.{symbol}"),
1196            Self::Position(category) => format!("position.{category}"),
1197            Self::PositionAllCategory => "position".to_string(),
1198            Self::Execution(category) => format!("execution.{category}"),
1199            Self::ExecutionAllCategory => "execution".to_string(),
1200            Self::FastExecution(category) => format!("execution.fast.{category}"),
1201            Self::FastExecutionAllCategory => "execution.fast".to_string(),
1202            Self::Order(category) => format!("order.{category}"),
1203            Self::OrderAllCategory => "order".to_string(),
1204            Self::Wallet => "wallet".to_string(),
1205            Self::Greek => "greek".to_string(),
1206            Self::Dcp(function) => format!("dcp.{function}"),
1207        };
1208        serializer.serialize_str(&s)
1209    }
1210}
1211
1212impl<'de> Deserialize<'de> for Topic {
1213    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1214    where
1215        D: Deserializer<'de>,
1216    {
1217        let s: &str = Deserialize::deserialize(deserializer)?;
1218        if let Some((kind, args)) = s.split_once('.') {
1219            match kind {
1220                "orderbook" => {
1221                    if let Some((depth, symbol)) = args.split_once('.') {
1222                        let depth =
1223                            serde_json::from_str(&format!("\"{depth}\"")).map_err(|err| {
1224                                serde::de::Error::custom(format!(
1225                                    "DepthLevel: {depth}, error: {err}"
1226                                ))
1227                            })?;
1228                        Ok(Self::Orderbook {
1229                            depth,
1230                            symbol: symbol.to_owned(),
1231                        })
1232                    } else {
1233                        Err(serde::de::Error::custom("invalid stream format"))
1234                    }
1235                }
1236                "publicTrade" => Ok(Self::Trade(args.to_owned())),
1237                "tickers" => Ok(Self::Ticker(args.to_owned())),
1238                "kline" => {
1239                    if let Some((interval, symbol)) = args.split_once('.') {
1240                        let interval = serde_json::from_str(&format!("\"{interval}\""))
1241                            .map_err(|err| serde::de::Error::custom(format!("Interval: {err}")))?;
1242                        Ok(Self::Kline {
1243                            symbol: symbol.to_owned(),
1244                            interval,
1245                        })
1246                    } else {
1247                        Err(serde::de::Error::custom("invalid stream format"))
1248                    }
1249                }
1250                "allLiquidation" => Ok(Self::AllLiquidation(args.to_owned())),
1251                "position" => {
1252                    let category = serde_json::from_str(&format!("\"{args}\""))
1253                        .map_err(|err| serde::de::Error::custom(format!("category: {err}")))?;
1254                    Ok(Self::Position(category))
1255                }
1256                "execution" => {
1257                    if args == "fast" {
1258                        Ok(Self::FastExecutionAllCategory)
1259                    } else if let Some((fast, category)) = args.split_once('.') {
1260                        let category = serde_json::from_str(&format!("\"{category}\""))
1261                            .map_err(|err| serde::de::Error::custom(format!("category: {err}")))?;
1262                        if fast == "fast" {
1263                            Ok(Self::FastExecution(category))
1264                        } else {
1265                            Ok(Self::Execution(category))
1266                        }
1267                    } else {
1268                        let category = serde_json::from_str(&format!("\"{args}\""))
1269                            .map_err(|err| serde::de::Error::custom(format!("category: {err}")))?;
1270                        Ok(Self::Execution(category))
1271                    }
1272                }
1273                "order" => {
1274                    let category = serde_json::from_str(&format!("\"{args}\""))
1275                        .map_err(|err| serde::de::Error::custom(format!("category: {err}")))?;
1276                    Ok(Self::Order(category))
1277                }
1278                "dcp" => {
1279                    let function = serde_json::from_str(&format!("\"{args}\""))
1280                        .map_err(|err| serde::de::Error::custom(format!("DcpFunction: {err}")))?;
1281                    Ok(Self::Dcp(function))
1282                }
1283                _ => Err(serde::de::Error::custom("invalid stream format")),
1284            }
1285        } else {
1286            match s {
1287                "position" => Ok(Self::PositionAllCategory),
1288                "execution" => Ok(Self::ExecutionAllCategory),
1289                "order" => Ok(Self::OrderAllCategory),
1290                "wallet" => Ok(Self::Wallet),
1291                "greek" => Ok(Self::Greek),
1292                _ => {
1293                    let msg = String::from("invalid stream format");
1294                    Err(serde::de::Error::custom(msg))
1295                }
1296            }
1297        }
1298    }
1299}
1300
1301#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
1302#[serde(rename_all = "lowercase")]
1303pub enum DcpFunction {
1304    Future,
1305    Option,
1306    Spot,
1307}
1308
1309impl fmt::Display for DcpFunction {
1310    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1311        let value = match self {
1312            Self::Future => "future",
1313            Self::Option => "option",
1314            Self::Spot => "spot",
1315        };
1316        write!(f, "{value}")
1317    }
1318}
1319
1320#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
1321pub enum DepthLevel {
1322    #[serde(rename = "1")]
1323    Level1,
1324    #[serde(rename = "25")]
1325    Level25,
1326    #[serde(rename = "50")]
1327    Level50,
1328    #[serde(rename = "100")]
1329    Level100,
1330    #[serde(rename = "200")]
1331    Level200,
1332    #[serde(rename = "500")]
1333    Level500,
1334}
1335
1336impl fmt::Display for DepthLevel {
1337    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1338        let value = match self {
1339            Self::Level1 => "1",
1340            Self::Level25 => "25",
1341            Self::Level50 => "50",
1342            Self::Level100 => "100",
1343            Self::Level200 => "200",
1344            Self::Level500 => "500",
1345        };
1346        write!(f, "{value}")
1347    }
1348}
1349
1350pub fn spot_fee_currency(side: Side, is_maker_order: bool, maker_fee_rate: f64) -> Pair {
1351    if maker_fee_rate >= 0.0 {
1352        match side {
1353            Side::Buy => Pair::Base,
1354            Side::Sell => Pair::Quote,
1355        }
1356    } else if is_maker_order {
1357        match side {
1358            Side::Buy => Pair::Quote,
1359            Side::Sell => Pair::Base,
1360        }
1361    } else {
1362        match side {
1363            Side::Buy => Pair::Base,
1364            Side::Sell => Pair::Quote,
1365        }
1366    }
1367}
1368
1369#[cfg(test)]
1370mod tests {
1371    use crate::serde::deserialize_json;
1372
1373    use super::*;
1374
1375    #[test]
1376    fn serialize_category() {
1377        let cases = vec![
1378            (Category::Inverse, r#""inverse""#),
1379            (Category::Linear, r#""linear""#),
1380            (Category::Option, r#""option""#),
1381            (Category::Spot, r#""spot""#),
1382        ];
1383        cases.iter().for_each(|(category, expected)| {
1384            let json = serde_json::to_string(category).unwrap();
1385            assert_eq!(json, *expected);
1386        });
1387    }
1388
1389    #[test]
1390    fn deserialize_category() {
1391        let cases = vec![
1392            (r#""inverse""#, Category::Inverse),
1393            (r#""linear""#, Category::Linear),
1394            (r#""option""#, Category::Option),
1395            (r#""spot""#, Category::Spot),
1396        ];
1397        cases.iter().for_each(|(json, expected)| {
1398            let message: Category = deserialize_json(json).unwrap();
1399            assert_eq!(message, *expected);
1400        });
1401    }
1402
1403    #[test]
1404    fn serialize_interval() {
1405        let cases = vec![
1406            (Interval::Minute1, r#""1""#),
1407            (Interval::Minute3, r#""3""#),
1408            (Interval::Minute5, r#""5""#),
1409            (Interval::Minute15, r#""15""#),
1410            (Interval::Minute30, r#""30""#),
1411            (Interval::Hour1, r#""60""#),
1412            (Interval::Hour2, r#""120""#),
1413            (Interval::Hour4, r#""240""#),
1414            (Interval::Hour6, r#""360""#),
1415            (Interval::Hour12, r#""720""#),
1416            (Interval::Day1, r#""D""#),
1417            (Interval::Week1, r#""W""#),
1418            (Interval::Month1, r#""M""#),
1419        ];
1420        cases.iter().for_each(|(category, expected)| {
1421            let json = serde_json::to_string(category).unwrap();
1422            assert_eq!(json, *expected);
1423        });
1424    }
1425
1426    #[test]
1427    fn deserialize_interval() {
1428        let cases = vec![
1429            (r#""1""#, Interval::Minute1),
1430            (r#""3""#, Interval::Minute3),
1431            (r#""5""#, Interval::Minute5),
1432            (r#""15""#, Interval::Minute15),
1433            (r#""30""#, Interval::Minute30),
1434            (r#""60""#, Interval::Hour1),
1435            (r#""120""#, Interval::Hour2),
1436            (r#""240""#, Interval::Hour4),
1437            (r#""360""#, Interval::Hour6),
1438            (r#""720""#, Interval::Hour12),
1439            (r#""D""#, Interval::Day1),
1440            (r#""W""#, Interval::Week1),
1441            (r#""M""#, Interval::Month1),
1442        ];
1443        cases.iter().for_each(|(json, expected)| {
1444            let message: Interval = deserialize_json(json).unwrap();
1445            assert_eq!(message, *expected);
1446        });
1447    }
1448
1449    #[test]
1450    fn serialize_topic() {
1451        let symbol = String::from("BTCUSDT");
1452        let cases = vec![
1453            (
1454                Topic::Orderbook {
1455                    symbol: symbol.clone(),
1456                    depth: DepthLevel::Level1,
1457                },
1458                r#""orderbook.1.BTCUSDT""#,
1459            ),
1460            (Topic::Trade(symbol.clone()), r#""publicTrade.BTCUSDT""#),
1461            (Topic::Ticker(symbol.clone()), r#""tickers.BTCUSDT""#),
1462            (
1463                Topic::Kline {
1464                    symbol: symbol.clone(),
1465                    interval: Interval::Minute1,
1466                },
1467                r#""kline.1.BTCUSDT""#,
1468            ),
1469            (
1470                Topic::AllLiquidation(symbol.clone()),
1471                r#""allLiquidation.BTCUSDT""#,
1472            ),
1473            (Topic::Position(Category::Linear), r#""position.linear""#),
1474            (Topic::PositionAllCategory, r#""position""#),
1475            (Topic::Execution(Category::Linear), r#""execution.linear""#),
1476            (Topic::ExecutionAllCategory, r#""execution""#),
1477            (
1478                Topic::FastExecution(Category::Linear),
1479                r#""execution.fast.linear""#,
1480            ),
1481            (Topic::FastExecutionAllCategory, r#""execution.fast""#),
1482            (Topic::Order(Category::Linear), r#""order.linear""#),
1483            (Topic::OrderAllCategory, r#""order""#),
1484            (Topic::Wallet, r#""wallet""#),
1485            (Topic::Greek, r#""greek""#),
1486            (Topic::Dcp(DcpFunction::Future), r#""dcp.future""#),
1487        ];
1488        cases.iter().for_each(|(category, expected)| {
1489            let json = serde_json::to_string(category).unwrap();
1490            assert_eq!(*expected, json);
1491        });
1492    }
1493
1494    #[test]
1495    fn deserialize_topic() {
1496        let symbol = String::from("BTCUSDT");
1497        let cases = vec![
1498            (
1499                r#""orderbook.1.BTCUSDT""#,
1500                Topic::Orderbook {
1501                    symbol: symbol.clone(),
1502                    depth: DepthLevel::Level1,
1503                },
1504            ),
1505            (r#""publicTrade.BTCUSDT""#, Topic::Trade(symbol.clone())),
1506            (r#""tickers.BTCUSDT""#, Topic::Ticker(symbol.clone())),
1507            (
1508                r#""kline.1.BTCUSDT""#,
1509                Topic::Kline {
1510                    symbol: symbol.clone(),
1511                    interval: Interval::Minute1,
1512                },
1513            ),
1514            (
1515                r#""allLiquidation.BTCUSDT""#,
1516                Topic::AllLiquidation(symbol.clone()),
1517            ),
1518            (r#""position.linear""#, Topic::Position(Category::Linear)),
1519            (r#""position""#, Topic::PositionAllCategory),
1520            (r#""execution.linear""#, Topic::Execution(Category::Linear)),
1521            (r#""execution""#, Topic::ExecutionAllCategory),
1522            (
1523                r#""execution.fast.linear""#,
1524                Topic::FastExecution(Category::Linear),
1525            ),
1526            (r#""execution.fast""#, Topic::FastExecutionAllCategory),
1527            (r#""order.linear""#, Topic::Order(Category::Linear)),
1528            (r#""order""#, Topic::OrderAllCategory),
1529            (r#""wallet""#, Topic::Wallet),
1530            (r#""greek""#, Topic::Greek),
1531            (r#""dcp.future""#, Topic::Dcp(DcpFunction::Future)),
1532        ];
1533        cases.iter().for_each(|(json, expected)| {
1534            let message = deserialize_json(json).unwrap();
1535            assert_eq!(*expected, message);
1536        });
1537    }
1538}