nautilus_binance/common/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Binance enumeration types for product types and environments.
17
18use std::fmt::Display;
19
20use nautilus_model::enums::{OrderSide, TimeInForce};
21use serde::{Deserialize, Serialize};
22
23/// Binance product type identifier.
24///
25/// Each product type corresponds to a different Binance API domain and
26/// has distinct trading rules and instrument specifications.
27#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
29#[cfg_attr(
30    feature = "python",
31    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", eq)
32)]
33pub enum BinanceProductType {
34    /// Spot trading (api.binance.com).
35    #[default]
36    Spot,
37    /// Spot Margin trading (uses Spot API with margin endpoints).
38    Margin,
39    /// USD-M Futures - linear perpetuals and delivery futures (fapi.binance.com).
40    UsdM,
41    /// COIN-M Futures - inverse perpetuals and delivery futures (dapi.binance.com).
42    CoinM,
43    /// European Options (eapi.binance.com).
44    Options,
45}
46
47impl BinanceProductType {
48    /// Returns the string representation used in API requests.
49    #[must_use]
50    pub const fn as_str(self) -> &'static str {
51        match self {
52            Self::Spot => "SPOT",
53            Self::Margin => "MARGIN",
54            Self::UsdM => "USD_M",
55            Self::CoinM => "COIN_M",
56            Self::Options => "OPTIONS",
57        }
58    }
59
60    /// Returns the instrument ID suffix for this product type.
61    #[must_use]
62    pub const fn suffix(self) -> &'static str {
63        match self {
64            Self::Spot => "-SPOT",
65            Self::Margin => "-MARGIN",
66            Self::UsdM => "-LINEAR",
67            Self::CoinM => "-INVERSE",
68            Self::Options => "-OPTION",
69        }
70    }
71
72    /// Returns true if this is a spot product (Spot or Margin).
73    #[must_use]
74    pub const fn is_spot(self) -> bool {
75        matches!(self, Self::Spot | Self::Margin)
76    }
77
78    /// Returns true if this is a futures product (USD-M or COIN-M).
79    #[must_use]
80    pub const fn is_futures(self) -> bool {
81        matches!(self, Self::UsdM | Self::CoinM)
82    }
83
84    /// Returns true if this is a linear product (Spot, Margin, or USD-M).
85    #[must_use]
86    pub const fn is_linear(self) -> bool {
87        matches!(self, Self::Spot | Self::Margin | Self::UsdM)
88    }
89
90    /// Returns true if this is an inverse product (COIN-M).
91    #[must_use]
92    pub const fn is_inverse(self) -> bool {
93        matches!(self, Self::CoinM)
94    }
95
96    /// Returns true if this is an options product.
97    #[must_use]
98    pub const fn is_options(self) -> bool {
99        matches!(self, Self::Options)
100    }
101}
102
103impl Display for BinanceProductType {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        write!(f, "{}", self.as_str())
106    }
107}
108
109/// Binance environment type.
110#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
111#[cfg_attr(
112    feature = "python",
113    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", eq)
114)]
115pub enum BinanceEnvironment {
116    /// Production/mainnet environment.
117    #[default]
118    Mainnet,
119    /// Testnet environment.
120    Testnet,
121}
122
123impl BinanceEnvironment {
124    /// Returns true if this is the testnet environment.
125    #[must_use]
126    pub const fn is_testnet(self) -> bool {
127        matches!(self, Self::Testnet)
128    }
129}
130
131/// Order side for Binance orders and trades.
132#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
133#[serde(rename_all = "UPPERCASE")]
134pub enum BinanceSide {
135    /// Buy side.
136    Buy,
137    /// Sell side.
138    Sell,
139}
140
141impl TryFrom<OrderSide> for BinanceSide {
142    type Error = anyhow::Error;
143
144    fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
145        match value {
146            OrderSide::Buy => Ok(Self::Buy),
147            OrderSide::Sell => Ok(Self::Sell),
148            _ => anyhow::bail!("Unsupported `OrderSide` for Binance: {value:?}"),
149        }
150    }
151}
152
153/// Position side for dual-side position mode.
154#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
155#[serde(rename_all = "UPPERCASE")]
156pub enum BinancePositionSide {
157    /// Single position mode (both).
158    Both,
159    /// Long position.
160    Long,
161    /// Short position.
162    Short,
163    /// Unknown or undocumented value.
164    #[serde(other)]
165    Unknown,
166}
167
168/// Margin type applied to a position.
169#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
170#[serde(rename_all = "lowercase")]
171pub enum BinanceMarginType {
172    /// Cross margin.
173    Cross,
174    /// Isolated margin.
175    Isolated,
176    /// Unknown or undocumented value.
177    #[serde(other)]
178    Unknown,
179}
180
181/// Working type for trigger price evaluation.
182#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
183#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
184pub enum BinanceWorkingType {
185    /// Use the contract price.
186    ContractPrice,
187    /// Use the mark price.
188    MarkPrice,
189    /// Unknown or undocumented value.
190    #[serde(other)]
191    Unknown,
192}
193
194/// Order status lifecycle values.
195#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
196#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
197pub enum BinanceOrderStatus {
198    /// Order accepted and working.
199    New,
200    /// Partially filled.
201    PartiallyFilled,
202    /// Fully filled.
203    Filled,
204    /// Canceled by user or system.
205    Canceled,
206    /// Pending cancel (not commonly used).
207    PendingCancel,
208    /// Rejected by exchange.
209    Rejected,
210    /// Expired.
211    Expired,
212    /// Expired in match (IOC/FOK not executed).
213    ExpiredInMatch,
214    /// Unknown or undocumented value.
215    #[serde(other)]
216    Unknown,
217}
218
219/// Futures order type enumeration.
220#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
221#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
222pub enum BinanceFuturesOrderType {
223    /// Limit order.
224    Limit,
225    /// Market order.
226    Market,
227    /// Stop (stop-limit) order.
228    Stop,
229    /// Stop market order.
230    StopMarket,
231    /// Take profit (limit) order.
232    TakeProfit,
233    /// Take profit market order.
234    TakeProfitMarket,
235    /// Trailing stop market order.
236    TrailingStopMarket,
237    /// Liquidation order created by exchange.
238    Liquidation,
239    /// Auto-deleveraging order created by exchange.
240    Adl,
241    /// Unknown or undocumented value.
242    #[serde(other)]
243    Unknown,
244}
245
246/// Time in force options.
247#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
248#[serde(rename_all = "UPPERCASE")]
249pub enum BinanceTimeInForce {
250    /// Good till canceled.
251    Gtc,
252    /// Immediate or cancel.
253    Ioc,
254    /// Fill or kill.
255    Fok,
256    /// Good till crossing (post-only).
257    Gtx,
258    /// Good till date.
259    Gtd,
260    /// Unknown or undocumented value.
261    #[serde(other)]
262    Unknown,
263}
264
265impl TryFrom<TimeInForce> for BinanceTimeInForce {
266    type Error = anyhow::Error;
267
268    fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
269        match value {
270            TimeInForce::Gtc => Ok(Self::Gtc),
271            TimeInForce::Ioc => Ok(Self::Ioc),
272            TimeInForce::Fok => Ok(Self::Fok),
273            TimeInForce::Gtd => Ok(Self::Gtd),
274            _ => anyhow::bail!("Unsupported `TimeInForce` for Binance: {value:?}"),
275        }
276    }
277}
278
279/// Income type for account income history.
280#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
281#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
282pub enum BinanceIncomeType {
283    /// Internal transfers.
284    Transfer,
285    /// Welcome bonus.
286    WelcomeBonus,
287    /// Realized profit and loss.
288    RealizedPnl,
289    /// Funding fee payments/receipts.
290    FundingFee,
291    /// Trading commission.
292    Commission,
293    /// Insurance clear.
294    InsuranceClear,
295    /// Referral kickback.
296    ReferralKickback,
297    /// Unknown or undocumented value.
298    #[serde(other)]
299    Unknown,
300}
301
302/// Price match mode for futures maker orders.
303#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
304#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
305pub enum BinancePriceMatch {
306    /// Match opposing side (default).
307    Opponent,
308    /// Match opposing side with 5 tick offset.
309    Opponent5,
310    /// Match opposing side with 10 tick offset.
311    Opponent10,
312    /// Match opposing side with 20 tick offset.
313    Opponent20,
314    /// Join current queue on same side.
315    Queue,
316    /// Join queue with 5 tick offset.
317    Queue5,
318    /// Join queue with 10 tick offset.
319    Queue10,
320    /// Join queue with 20 tick offset.
321    Queue20,
322    /// Unknown or undocumented value.
323    #[serde(other)]
324    Unknown,
325}
326
327/// Self-trade prevention mode.
328#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
329#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
330pub enum BinanceSelfTradePreventionMode {
331    /// Expire maker orders on self-trade.
332    ExpireMaker,
333    /// Expire taker orders on self-trade.
334    ExpireTaker,
335    /// Expire both sides on self-trade.
336    ExpireBoth,
337    /// Unknown or undocumented value.
338    #[serde(other)]
339    Unknown,
340}
341
342/// Trading status for symbols.
343#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
344#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
345pub enum BinanceTradingStatus {
346    /// Trading is active.
347    Trading,
348    /// Pending activation.
349    PendingTrading,
350    /// Pre-trading session.
351    PreTrading,
352    /// Post-trading session.
353    PostTrading,
354    /// End of day.
355    EndOfDay,
356    /// Trading halted.
357    Halt,
358    /// Auction match.
359    AuctionMatch,
360    /// Break period.
361    Break,
362    /// Unknown or undocumented value.
363    #[serde(other)]
364    Unknown,
365}
366
367/// Contract status for coin-margined futures.
368#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
369#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
370pub enum BinanceContractStatus {
371    /// Trading is active.
372    Trading,
373    /// Pending trading.
374    PendingTrading,
375    /// Pre-delivering.
376    PreDelivering,
377    /// Delivering.
378    Delivering,
379    /// Delivered.
380    Delivered,
381    /// Pre-delist.
382    PreDelisting,
383    /// Delisting in progress.
384    Delisting,
385    /// Contract down.
386    Down,
387    /// Unknown or undocumented value.
388    #[serde(other)]
389    Unknown,
390}
391
392/// WebSocket stream event types.
393///
394/// These are the "e" field values in WebSocket JSON messages.
395#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub enum BinanceWsEventType {
398    /// Aggregate trade event.
399    AggTrade,
400    /// Individual trade event.
401    Trade,
402    /// Book ticker (best bid/ask) event.
403    BookTicker,
404    /// Depth update (order book delta) event.
405    DepthUpdate,
406    /// Mark price update event.
407    MarkPriceUpdate,
408    /// Kline/candlestick event.
409    Kline,
410    /// Forced liquidation order event.
411    ForceOrder,
412    /// 24-hour rolling ticker event.
413    #[serde(rename = "24hrTicker")]
414    Ticker24Hr,
415    /// 24-hour rolling mini ticker event.
416    #[serde(rename = "24hrMiniTicker")]
417    MiniTicker24Hr,
418    /// Unknown or undocumented event type.
419    #[serde(other)]
420    Unknown,
421}
422
423impl BinanceWsEventType {
424    /// Returns the wire format string for this event type.
425    #[must_use]
426    pub const fn as_str(self) -> &'static str {
427        match self {
428            Self::AggTrade => "aggTrade",
429            Self::Trade => "trade",
430            Self::BookTicker => "bookTicker",
431            Self::DepthUpdate => "depthUpdate",
432            Self::MarkPriceUpdate => "markPriceUpdate",
433            Self::Kline => "kline",
434            Self::ForceOrder => "forceOrder",
435            Self::Ticker24Hr => "24hrTicker",
436            Self::MiniTicker24Hr => "24hrMiniTicker",
437            Self::Unknown => "unknown",
438        }
439    }
440}
441
442impl Display for BinanceWsEventType {
443    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444        write!(f, "{}", self.as_str())
445    }
446}
447
448/// WebSocket request method (operation type).
449///
450/// Used for subscription requests on both Spot and Futures WebSocket APIs.
451#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
452#[serde(rename_all = "UPPERCASE")]
453pub enum BinanceWsMethod {
454    /// Subscribe to streams.
455    Subscribe,
456    /// Unsubscribe from streams.
457    Unsubscribe,
458}
459
460/// Filter type identifiers returned in exchange info.
461#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
462#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
463pub enum BinanceFilterType {
464    /// Price filter.
465    PriceFilter,
466    /// Percent price filter.
467    PercentPrice,
468    /// Lot size filter.
469    LotSize,
470    /// Market lot size filter.
471    MarketLotSize,
472    /// Notional filter (spot).
473    Notional,
474    /// Min notional filter (futures).
475    MinNotional,
476    /// Maximum number of orders filter.
477    MaxNumOrders,
478    /// Maximum number of algo orders filter.
479    MaxNumAlgoOrders,
480    /// Unknown or undocumented value.
481    #[serde(other)]
482    Unknown,
483}
484
485impl Display for BinanceEnvironment {
486    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487        match self {
488            Self::Mainnet => write!(f, "Mainnet"),
489            Self::Testnet => write!(f, "Testnet"),
490        }
491    }
492}
493
494/// Rate limit type for API request quotas.
495#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
496pub enum BinanceRateLimitType {
497    RequestWeight,
498    Orders,
499}
500
501/// Rate limit time interval.
502#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
503pub enum BinanceRateLimitInterval {
504    Second,
505    Minute,
506    Day,
507}
508
509/// Kline (candlestick) interval.
510///
511/// # References
512/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
513#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
514pub enum BinanceKlineInterval {
515    /// 1 second (only for spot).
516    #[serde(rename = "1s")]
517    Second1,
518    /// 1 minute.
519    #[default]
520    #[serde(rename = "1m")]
521    Minute1,
522    /// 3 minutes.
523    #[serde(rename = "3m")]
524    Minute3,
525    /// 5 minutes.
526    #[serde(rename = "5m")]
527    Minute5,
528    /// 15 minutes.
529    #[serde(rename = "15m")]
530    Minute15,
531    /// 30 minutes.
532    #[serde(rename = "30m")]
533    Minute30,
534    /// 1 hour.
535    #[serde(rename = "1h")]
536    Hour1,
537    /// 2 hours.
538    #[serde(rename = "2h")]
539    Hour2,
540    /// 4 hours.
541    #[serde(rename = "4h")]
542    Hour4,
543    /// 6 hours.
544    #[serde(rename = "6h")]
545    Hour6,
546    /// 8 hours.
547    #[serde(rename = "8h")]
548    Hour8,
549    /// 12 hours.
550    #[serde(rename = "12h")]
551    Hour12,
552    /// 1 day.
553    #[serde(rename = "1d")]
554    Day1,
555    /// 3 days.
556    #[serde(rename = "3d")]
557    Day3,
558    /// 1 week.
559    #[serde(rename = "1w")]
560    Week1,
561    /// 1 month.
562    #[serde(rename = "1M")]
563    Month1,
564}
565
566impl BinanceKlineInterval {
567    /// Returns the string representation used by Binance API.
568    #[must_use]
569    pub const fn as_str(&self) -> &'static str {
570        match self {
571            Self::Second1 => "1s",
572            Self::Minute1 => "1m",
573            Self::Minute3 => "3m",
574            Self::Minute5 => "5m",
575            Self::Minute15 => "15m",
576            Self::Minute30 => "30m",
577            Self::Hour1 => "1h",
578            Self::Hour2 => "2h",
579            Self::Hour4 => "4h",
580            Self::Hour6 => "6h",
581            Self::Hour8 => "8h",
582            Self::Hour12 => "12h",
583            Self::Day1 => "1d",
584            Self::Day3 => "3d",
585            Self::Week1 => "1w",
586            Self::Month1 => "1M",
587        }
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use rstest::rstest;
594
595    use super::*;
596
597    #[rstest]
598    fn test_product_type_as_str() {
599        assert_eq!(BinanceProductType::Spot.as_str(), "SPOT");
600        assert_eq!(BinanceProductType::Margin.as_str(), "MARGIN");
601        assert_eq!(BinanceProductType::UsdM.as_str(), "USD_M");
602        assert_eq!(BinanceProductType::CoinM.as_str(), "COIN_M");
603        assert_eq!(BinanceProductType::Options.as_str(), "OPTIONS");
604    }
605
606    #[rstest]
607    fn test_product_type_suffix() {
608        assert_eq!(BinanceProductType::Spot.suffix(), "-SPOT");
609        assert_eq!(BinanceProductType::Margin.suffix(), "-MARGIN");
610        assert_eq!(BinanceProductType::UsdM.suffix(), "-LINEAR");
611        assert_eq!(BinanceProductType::CoinM.suffix(), "-INVERSE");
612        assert_eq!(BinanceProductType::Options.suffix(), "-OPTION");
613    }
614
615    #[rstest]
616    fn test_product_type_predicates() {
617        assert!(BinanceProductType::Spot.is_spot());
618        assert!(BinanceProductType::Margin.is_spot());
619        assert!(!BinanceProductType::UsdM.is_spot());
620
621        assert!(BinanceProductType::UsdM.is_futures());
622        assert!(BinanceProductType::CoinM.is_futures());
623        assert!(!BinanceProductType::Spot.is_futures());
624
625        assert!(BinanceProductType::CoinM.is_inverse());
626        assert!(!BinanceProductType::UsdM.is_inverse());
627
628        assert!(BinanceProductType::Options.is_options());
629        assert!(!BinanceProductType::Spot.is_options());
630    }
631}