Skip to main content

nautilus_binance/
config.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 adapter configuration structures.
17
18use std::{any::Any, collections::HashMap};
19
20use nautilus_common::factories::ClientConfig;
21use nautilus_model::{
22    identifiers::{AccountId, TraderId},
23    types::Currency,
24};
25use nautilus_network::websocket::TransportBackend;
26use rust_decimal::Decimal;
27use serde::{Deserialize, Serialize};
28
29use crate::common::enums::{BinanceEnvironment, BinanceMarginType, BinanceProductType};
30
31/// Spot market-data transport mode.
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
33#[cfg_attr(
34    feature = "python",
35    pyo3::pyclass(
36        module = "nautilus_trader.core.nautilus_pyo3.binance",
37        eq,
38        from_py_object
39    )
40)]
41#[cfg_attr(
42    feature = "python",
43    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.binance")
44)]
45pub enum BinanceSpotMarketDataMode {
46    #[default]
47    /// Spot SBE streams (requires Ed25519 credentials).
48    Sbe,
49    /// Force Spot public JSON streams (does not require credentials).
50    Json,
51}
52
53/// Configuration for Binance data client.
54///
55/// Ed25519 API keys are required for SBE WebSocket streams.
56#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
57#[serde(default, deny_unknown_fields)]
58#[cfg_attr(
59    feature = "python",
60    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", from_py_object)
61)]
62#[cfg_attr(
63    feature = "python",
64    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
65)]
66pub struct BinanceDataClientConfig {
67    /// Product type to subscribe to.
68    #[builder(default = BinanceProductType::Spot)]
69    pub product_type: BinanceProductType,
70    /// Environment (live, testnet, or demo).
71    #[builder(default = BinanceEnvironment::Live)]
72    pub environment: BinanceEnvironment,
73    /// Optional base URL override for HTTP API.
74    pub base_url_http: Option<String>,
75    /// Optional base URL override for WebSocket.
76    ///
77    /// Live USD-M Futures data overrides are normalized onto the matching
78    /// `/market/ws` and `/public/ws` routes.
79    pub base_url_ws: Option<String>,
80    /// API key (Ed25519).
81    pub api_key: Option<String>,
82    /// API secret (Ed25519 base64-encoded or PEM).
83    pub api_secret: Option<String>,
84    /// Spot market-data transport mode.
85    ///
86    /// - `Sbe` uses SBE streams and requires Ed25519 credentials.
87    /// - `Json` forces public JSON streams with no credentials.
88    #[builder(default)]
89    pub spot_market_data_mode: BinanceSpotMarketDataMode,
90    /// Interval in seconds for polling exchange info to detect instrument status
91    /// changes (e.g. Trading -> Halt). Set to 0 to disable. Defaults to 3600 (60 minutes).
92    #[builder(default = 3600)]
93    pub instrument_status_poll_secs: u64,
94    /// WebSocket transport backend (defaults to `Tungstenite`).
95    #[builder(default)]
96    pub transport_backend: TransportBackend,
97}
98
99impl Default for BinanceDataClientConfig {
100    fn default() -> Self {
101        Self::builder().build()
102    }
103}
104
105impl ClientConfig for BinanceDataClientConfig {
106    fn as_any(&self) -> &dyn Any {
107        self
108    }
109}
110
111/// Configuration for Binance execution client.
112///
113/// Ed25519 API keys are required for execution clients. Binance deprecated
114/// listenKey-based user data streams in favor of WebSocket API authentication,
115/// which only supports Ed25519.
116#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
117#[serde(default, deny_unknown_fields)]
118#[cfg_attr(
119    feature = "python",
120    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", from_py_object)
121)]
122#[cfg_attr(
123    feature = "python",
124    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
125)]
126pub struct BinanceExecClientConfig {
127    /// Trader ID for the client.
128    #[builder(default = TraderId::from("TRADER-001"))]
129    pub trader_id: TraderId,
130    /// Account ID for the client.
131    #[builder(default = AccountId::from("BINANCE-001"))]
132    pub account_id: AccountId,
133    /// Product type to trade.
134    #[builder(default = BinanceProductType::Spot)]
135    pub product_type: BinanceProductType,
136    /// Environment (live, testnet, or demo).
137    #[builder(default = BinanceEnvironment::Live)]
138    pub environment: BinanceEnvironment,
139    /// Optional base URL override for HTTP API.
140    pub base_url_http: Option<String>,
141    /// Optional base URL override for WebSocket user data stream.
142    ///
143    /// Live USD-M Futures stream overrides are normalized onto the `/private/ws` route.
144    pub base_url_ws: Option<String>,
145    /// Optional base URL override for WebSocket trading API (Spot and USD-M Futures).
146    pub base_url_ws_trading: Option<String>,
147    /// Whether to use the WebSocket trading API for order operations (Spot and USD-M Futures).
148    #[builder(default = true)]
149    pub use_ws_trading: bool,
150    /// Whether to use Binance Futures hedging position IDs.
151    ///
152    /// When true, fill reports include a `venue_position_id` derived from
153    /// the instrument and position side (e.g. `ETHUSDT-PERP.BINANCE-LONG`).
154    /// When false, `venue_position_id` is None, allowing virtual positions
155    /// with `OmsType::Hedging`.
156    #[builder(default = true)]
157    pub use_position_ids: bool,
158    /// Default taker fee rate for commission estimation.
159    ///
160    /// Used as a fallback when the venue omits commission fields in
161    /// exchange-generated fills (liquidation, ADL, settlement).
162    /// Standard Binance Futures taker fee is 0.0004 (0.04%).
163    #[builder(default = Decimal::new(4, 4))]
164    pub default_taker_fee: Decimal,
165    /// API key (Ed25519 required, uses env var if not provided).
166    pub api_key: Option<String>,
167    /// API secret (Ed25519 base64-encoded, required, uses env var if not provided).
168    pub api_secret: Option<String>,
169    /// Initial leverage per Binance symbol (e.g. BTCUSDT -> 20), applied during connect.
170    pub futures_leverages: Option<HashMap<String, u32>>,
171    /// Margin type per Binance symbol (e.g. BTCUSDT -> Cross), applied during connect.
172    pub futures_margin_types: Option<HashMap<String, BinanceMarginType>>,
173    /// Currency that Binance Futures Credits (`BNFCR`) balances and fees resolve to (defaults to USDT).
174    #[builder(default = Currency::USDT())]
175    pub bnfcr_currency: Currency,
176    /// If true, the EXPIRED execution type emits `OrderCanceled` instead of `OrderExpired`.
177    ///
178    /// Binance uses EXPIRED for certain cancel scenarios depending on order type
179    /// and time-in-force combination.
180    #[builder(default = false)]
181    pub treat_expired_as_canceled: bool,
182    /// If true, drive fills from the lower-latency `TRADE_LITE` user data event
183    /// and dedup the matching fill portion of `ORDER_TRADE_UPDATE`. If false,
184    /// `TRADE_LITE` events are ignored and fills come from `ORDER_TRADE_UPDATE`.
185    #[builder(default = false)]
186    pub use_trade_lite: bool,
187    /// WebSocket transport backend (defaults to `Tungstenite`).
188    #[builder(default)]
189    pub transport_backend: TransportBackend,
190}
191
192impl Default for BinanceExecClientConfig {
193    fn default() -> Self {
194        Self::builder().build()
195    }
196}
197
198impl ClientConfig for BinanceExecClientConfig {
199    fn as_any(&self) -> &dyn Any {
200        self
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use rstest::rstest;
207
208    use super::*;
209
210    #[rstest]
211    fn test_data_config_toml_minimal() {
212        let config: BinanceDataClientConfig = toml::from_str(
213            r#"
214environment = "Testnet"
215product_type = "USD_M"
216instrument_status_poll_secs = 600
217"#,
218        )
219        .unwrap();
220
221        assert_eq!(config.environment, BinanceEnvironment::Testnet);
222        assert_eq!(config.product_type, BinanceProductType::UsdM);
223        assert_eq!(config.spot_market_data_mode, BinanceSpotMarketDataMode::Sbe);
224        assert_eq!(config.instrument_status_poll_secs, 600);
225    }
226
227    #[rstest]
228    fn test_data_config_toml_spot_market_data_mode_override() {
229        let config: BinanceDataClientConfig = toml::from_str(
230            r#"
231spot_market_data_mode = "Json"
232"#,
233        )
234        .unwrap();
235
236        assert_eq!(
237            config.spot_market_data_mode,
238            BinanceSpotMarketDataMode::Json
239        );
240    }
241
242    #[rstest]
243    fn test_data_config_toml_rejects_plural_product_types() {
244        let result = toml::from_str::<BinanceDataClientConfig>(
245            r#"
246product_types = ["SPOT", "USD_M"]
247"#,
248        );
249
250        let message = result.unwrap_err().to_string();
251        assert!(message.contains("unknown field `product_types`"));
252    }
253
254    #[rstest]
255    fn test_exec_config_toml_empty_uses_defaults() {
256        let config: BinanceExecClientConfig = toml::from_str("").unwrap();
257        let expected = BinanceExecClientConfig::default();
258
259        assert_eq!(config.environment, expected.environment);
260        assert_eq!(config.product_type, expected.product_type);
261        assert_eq!(config.use_ws_trading, expected.use_ws_trading);
262        assert_eq!(config.use_position_ids, expected.use_position_ids);
263        assert_eq!(config.default_taker_fee, expected.default_taker_fee);
264        assert_eq!(
265            config.treat_expired_as_canceled,
266            expected.treat_expired_as_canceled,
267        );
268        assert_eq!(config.use_trade_lite, expected.use_trade_lite);
269        assert_eq!(config.transport_backend, expected.transport_backend);
270    }
271}