use std::{any::Any, collections::HashMap};
use nautilus_common::factories::ClientConfig;
use nautilus_model::identifiers::{AccountId, TraderId};
use nautilus_network::websocket::TransportBackend;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::common::enums::{BinanceEnvironment, BinanceMarginType, BinanceProductType};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.binance",
eq,
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.binance")
)]
pub enum BinanceSpotMarketDataMode {
#[default]
Sbe,
Json,
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default, deny_unknown_fields)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
)]
pub struct BinanceDataClientConfig {
#[builder(default = BinanceProductType::Spot)]
pub product_type: BinanceProductType,
#[builder(default = BinanceEnvironment::Live)]
pub environment: BinanceEnvironment,
pub base_url_http: Option<String>,
pub base_url_ws: Option<String>,
pub api_key: Option<String>,
pub api_secret: Option<String>,
#[builder(default)]
pub spot_market_data_mode: BinanceSpotMarketDataMode,
#[builder(default = 3600)]
pub instrument_status_poll_secs: u64,
#[builder(default)]
pub transport_backend: TransportBackend,
}
impl Default for BinanceDataClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl ClientConfig for BinanceDataClientConfig {
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default, deny_unknown_fields)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
)]
pub struct BinanceExecClientConfig {
#[builder(default = TraderId::from("TRADER-001"))]
pub trader_id: TraderId,
#[builder(default = AccountId::from("BINANCE-001"))]
pub account_id: AccountId,
#[builder(default = BinanceProductType::Spot)]
pub product_type: BinanceProductType,
#[builder(default = BinanceEnvironment::Live)]
pub environment: BinanceEnvironment,
pub base_url_http: Option<String>,
pub base_url_ws: Option<String>,
pub base_url_ws_trading: Option<String>,
#[builder(default = true)]
pub use_ws_trading: bool,
#[builder(default = true)]
pub use_position_ids: bool,
#[builder(default = Decimal::new(4, 4))]
pub default_taker_fee: Decimal,
pub api_key: Option<String>,
pub api_secret: Option<String>,
pub futures_leverages: Option<HashMap<String, u32>>,
pub futures_margin_types: Option<HashMap<String, BinanceMarginType>>,
#[builder(default = false)]
pub treat_expired_as_canceled: bool,
#[builder(default = false)]
pub use_trade_lite: bool,
#[builder(default)]
pub transport_backend: TransportBackend,
}
impl Default for BinanceExecClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl ClientConfig for BinanceExecClientConfig {
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_data_config_toml_minimal() {
let config: BinanceDataClientConfig = toml::from_str(
r#"
environment = "Testnet"
product_type = "USD_M"
instrument_status_poll_secs = 600
"#,
)
.unwrap();
assert_eq!(config.environment, BinanceEnvironment::Testnet);
assert_eq!(config.product_type, BinanceProductType::UsdM);
assert_eq!(config.spot_market_data_mode, BinanceSpotMarketDataMode::Sbe);
assert_eq!(config.instrument_status_poll_secs, 600);
}
#[rstest]
fn test_data_config_toml_spot_market_data_mode_override() {
let config: BinanceDataClientConfig = toml::from_str(
r#"
spot_market_data_mode = "Json"
"#,
)
.unwrap();
assert_eq!(
config.spot_market_data_mode,
BinanceSpotMarketDataMode::Json
);
}
#[rstest]
fn test_data_config_toml_rejects_plural_product_types() {
let result = toml::from_str::<BinanceDataClientConfig>(
r#"
product_types = ["SPOT", "USD_M"]
"#,
);
let message = result.unwrap_err().to_string();
assert!(message.contains("unknown field `product_types`"));
}
#[rstest]
fn test_exec_config_toml_empty_uses_defaults() {
let config: BinanceExecClientConfig = toml::from_str("").unwrap();
let expected = BinanceExecClientConfig::default();
assert_eq!(config.environment, expected.environment);
assert_eq!(config.product_type, expected.product_type);
assert_eq!(config.use_ws_trading, expected.use_ws_trading);
assert_eq!(config.use_position_ids, expected.use_position_ids);
assert_eq!(config.default_taker_fee, expected.default_taker_fee);
assert_eq!(
config.treat_expired_as_canceled,
expected.treat_expired_as_canceled,
);
assert_eq!(config.use_trade_lite, expected.use_trade_lite);
assert_eq!(config.transport_backend, expected.transport_backend);
}
}