Skip to main content

nautilus_binance/spot/
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 Spot-specific enumerations.
17
18use nautilus_model::enums::{OrderType, TimeInForce};
19use serde::{Deserialize, Serialize};
20
21use crate::common::enums::BinanceTimeInForce;
22
23/// Spot order type enumeration.
24#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
26pub enum BinanceSpotOrderType {
27    /// Limit order.
28    Limit,
29    /// Market order.
30    Market,
31    /// Stop loss (triggers market sell when price drops to stop price).
32    StopLoss,
33    /// Stop loss limit (triggers limit sell when price drops to stop price).
34    StopLossLimit,
35    /// Take profit (triggers market sell when price rises to stop price).
36    TakeProfit,
37    /// Take profit limit (triggers limit sell when price rises to stop price).
38    TakeProfitLimit,
39    /// Limit maker (post-only, rejected if would match immediately).
40    LimitMaker,
41    /// Unknown or undocumented value.
42    #[serde(other)]
43    Unknown,
44}
45
46/// Spot order response type.
47#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
48#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
49pub enum BinanceOrderResponseType {
50    /// Acknowledge only (fastest).
51    Ack,
52    /// Result with order details.
53    Result,
54    /// Full response with fills.
55    #[default]
56    Full,
57}
58
59/// Cancel/replace mode for order modification.
60#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
62pub enum BinanceCancelReplaceMode {
63    /// Stop if cancel fails.
64    StopOnFailure,
65    /// Continue with new order even if cancel fails.
66    AllowFailure,
67}
68
69/// Spot user data stream event types.
70///
71/// These are the `"e"` field values on JSON frames emitted by the Spot user
72/// data stream and the Spot WebSocket Trading API. The Spot wire format is
73/// camelCase throughout; this enum is kept separate from
74/// [`crate::common::enums::BinanceWsEventType`], which mixes camelCase
75/// market-data events with `SCREAMING_SNAKE_CASE` Futures user-data events.
76#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub enum BinanceSpotUserDataEventType {
79    /// Order execution report (`executionReport`).
80    ExecutionReport,
81    /// Account position update (`outboundAccountPosition`).
82    OutboundAccountPosition,
83    /// Balance update (`balanceUpdate`).
84    BalanceUpdate,
85    /// Server shutdown notice, sent ~10 minutes before disconnection.
86    ServerShutdown,
87    /// Listen key expired (legacy user data stream session).
88    ListenKeyExpired,
89    /// External lock update (`externalLockUpdate`).
90    ExternalLockUpdate,
91    /// Event stream terminated.
92    EventStreamTerminated,
93    /// Unknown or undocumented event type.
94    #[serde(other)]
95    Unknown,
96}
97
98/// Converts a Nautilus order type to Binance Spot order type.
99///
100/// # Errors
101///
102/// Returns an error if the order type is not supported on Binance Spot.
103pub fn order_type_to_binance_spot(
104    order_type: OrderType,
105    post_only: bool,
106) -> anyhow::Result<BinanceSpotOrderType> {
107    match (order_type, post_only) {
108        (OrderType::Market, _) => Ok(BinanceSpotOrderType::Market),
109        (OrderType::Limit, true) => Ok(BinanceSpotOrderType::LimitMaker),
110        (OrderType::Limit, false) => Ok(BinanceSpotOrderType::Limit),
111        (OrderType::StopMarket, _) => Ok(BinanceSpotOrderType::StopLoss),
112        (OrderType::StopLimit, _) => Ok(BinanceSpotOrderType::StopLossLimit),
113        (OrderType::MarketIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfit),
114        (OrderType::LimitIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfitLimit),
115        _ => anyhow::bail!("Unsupported order type for Binance Spot: {order_type:?}"),
116    }
117}
118
119/// Converts a Nautilus time in force to Binance Spot time in force.
120///
121/// Binance Spot only supports GTC, IOC, and FOK. GTD and other TIF values
122/// are rejected.
123///
124/// # Errors
125///
126/// Returns an error if the time in force is not supported on Binance Spot.
127pub fn time_in_force_to_binance_spot(tif: TimeInForce) -> anyhow::Result<BinanceTimeInForce> {
128    match tif {
129        TimeInForce::Gtc => Ok(BinanceTimeInForce::Gtc),
130        TimeInForce::Ioc => Ok(BinanceTimeInForce::Ioc),
131        TimeInForce::Fok => Ok(BinanceTimeInForce::Fok),
132        _ => anyhow::bail!("Unsupported time in force for Binance Spot: {tif:?}"),
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use rstest::rstest;
139
140    use super::*;
141
142    #[rstest]
143    #[case(OrderType::Market, false, BinanceSpotOrderType::Market)]
144    #[case(OrderType::Limit, false, BinanceSpotOrderType::Limit)]
145    #[case(OrderType::Limit, true, BinanceSpotOrderType::LimitMaker)]
146    #[case(OrderType::StopMarket, false, BinanceSpotOrderType::StopLoss)]
147    #[case(OrderType::StopLimit, false, BinanceSpotOrderType::StopLossLimit)]
148    #[case(OrderType::MarketIfTouched, false, BinanceSpotOrderType::TakeProfit)]
149    #[case(
150        OrderType::LimitIfTouched,
151        false,
152        BinanceSpotOrderType::TakeProfitLimit
153    )]
154    fn test_order_type_to_binance_spot(
155        #[case] order_type: OrderType,
156        #[case] post_only: bool,
157        #[case] expected: BinanceSpotOrderType,
158    ) {
159        let result = order_type_to_binance_spot(order_type, post_only).unwrap();
160        assert_eq!(result, expected);
161    }
162
163    #[rstest]
164    #[case(OrderType::TrailingStopMarket)]
165    fn test_order_type_to_binance_spot_unsupported(#[case] order_type: OrderType) {
166        let result = order_type_to_binance_spot(order_type, false);
167        result.unwrap_err();
168    }
169
170    #[rstest]
171    #[case(TimeInForce::Gtc, BinanceTimeInForce::Gtc)]
172    #[case(TimeInForce::Ioc, BinanceTimeInForce::Ioc)]
173    #[case(TimeInForce::Fok, BinanceTimeInForce::Fok)]
174    fn test_time_in_force_to_binance_spot(
175        #[case] tif: TimeInForce,
176        #[case] expected: BinanceTimeInForce,
177    ) {
178        let result = time_in_force_to_binance_spot(tif).unwrap();
179        assert_eq!(result, expected);
180    }
181
182    #[rstest]
183    #[case(TimeInForce::Gtd)]
184    fn test_time_in_force_to_binance_spot_rejects_gtd(#[case] tif: TimeInForce) {
185        let result = time_in_force_to_binance_spot(tif);
186        result.unwrap_err();
187    }
188}