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/// Converts a Nautilus order type to Binance Spot order type.
70///
71/// # Errors
72///
73/// Returns an error if the order type is not supported on Binance Spot.
74pub fn order_type_to_binance_spot(
75    order_type: OrderType,
76    post_only: bool,
77) -> anyhow::Result<BinanceSpotOrderType> {
78    match (order_type, post_only) {
79        (OrderType::Market, _) => Ok(BinanceSpotOrderType::Market),
80        (OrderType::Limit, true) => Ok(BinanceSpotOrderType::LimitMaker),
81        (OrderType::Limit, false) => Ok(BinanceSpotOrderType::Limit),
82        (OrderType::StopMarket, _) => Ok(BinanceSpotOrderType::StopLoss),
83        (OrderType::StopLimit, _) => Ok(BinanceSpotOrderType::StopLossLimit),
84        (OrderType::MarketIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfit),
85        (OrderType::LimitIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfitLimit),
86        _ => anyhow::bail!("Unsupported order type for Binance Spot: {order_type:?}"),
87    }
88}
89
90/// Converts a Nautilus time in force to Binance Spot time in force.
91///
92/// Binance Spot only supports GTC, IOC, and FOK. GTD and other TIF values
93/// are rejected.
94///
95/// # Errors
96///
97/// Returns an error if the time in force is not supported on Binance Spot.
98pub fn time_in_force_to_binance_spot(tif: TimeInForce) -> anyhow::Result<BinanceTimeInForce> {
99    match tif {
100        TimeInForce::Gtc => Ok(BinanceTimeInForce::Gtc),
101        TimeInForce::Ioc => Ok(BinanceTimeInForce::Ioc),
102        TimeInForce::Fok => Ok(BinanceTimeInForce::Fok),
103        _ => anyhow::bail!("Unsupported time in force for Binance Spot: {tif:?}"),
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use rstest::rstest;
110
111    use super::*;
112
113    #[rstest]
114    #[case(OrderType::Market, false, BinanceSpotOrderType::Market)]
115    #[case(OrderType::Limit, false, BinanceSpotOrderType::Limit)]
116    #[case(OrderType::Limit, true, BinanceSpotOrderType::LimitMaker)]
117    #[case(OrderType::StopMarket, false, BinanceSpotOrderType::StopLoss)]
118    #[case(OrderType::StopLimit, false, BinanceSpotOrderType::StopLossLimit)]
119    #[case(OrderType::MarketIfTouched, false, BinanceSpotOrderType::TakeProfit)]
120    #[case(
121        OrderType::LimitIfTouched,
122        false,
123        BinanceSpotOrderType::TakeProfitLimit
124    )]
125    fn test_order_type_to_binance_spot(
126        #[case] order_type: OrderType,
127        #[case] post_only: bool,
128        #[case] expected: BinanceSpotOrderType,
129    ) {
130        let result = order_type_to_binance_spot(order_type, post_only).unwrap();
131        assert_eq!(result, expected);
132    }
133
134    #[rstest]
135    #[case(OrderType::TrailingStopMarket)]
136    fn test_order_type_to_binance_spot_unsupported(#[case] order_type: OrderType) {
137        let result = order_type_to_binance_spot(order_type, false);
138        assert!(result.is_err());
139    }
140
141    #[rstest]
142    #[case(TimeInForce::Gtc, BinanceTimeInForce::Gtc)]
143    #[case(TimeInForce::Ioc, BinanceTimeInForce::Ioc)]
144    #[case(TimeInForce::Fok, BinanceTimeInForce::Fok)]
145    fn test_time_in_force_to_binance_spot(
146        #[case] tif: TimeInForce,
147        #[case] expected: BinanceTimeInForce,
148    ) {
149        let result = time_in_force_to_binance_spot(tif).unwrap();
150        assert_eq!(result, expected);
151    }
152
153    #[rstest]
154    #[case(TimeInForce::Gtd)]
155    fn test_time_in_force_to_binance_spot_rejects_gtd(#[case] tif: TimeInForce) {
156        let result = time_in_force_to_binance_spot(tif);
157        assert!(result.is_err());
158    }
159}