Skip to main content

nautilus_binance/
factories.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//! Factory functions for creating Binance clients and components.
17
18use std::{cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21    cache::Cache,
22    clients::{DataClient, ExecutionClient},
23    clock::Clock,
24    factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28    enums::{AccountType, OmsType},
29    identifiers::ClientId,
30};
31
32use crate::{
33    common::{
34        consts::{BINANCE, BINANCE_VENUE},
35        enums::BinanceProductType,
36    },
37    config::{BinanceDataClientConfig, BinanceExecClientConfig},
38    futures::{data::BinanceFuturesDataClient, execution::BinanceFuturesExecutionClient},
39    spot::{data::BinanceSpotDataClient, execution::BinanceSpotExecutionClient},
40};
41
42/// Factory for creating Binance data clients.
43#[derive(Debug, Clone)]
44#[cfg_attr(
45    feature = "python",
46    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", from_py_object)
47)]
48#[cfg_attr(
49    feature = "python",
50    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.binance")
51)]
52pub struct BinanceDataClientFactory;
53
54impl BinanceDataClientFactory {
55    /// Creates a new [`BinanceDataClientFactory`] instance.
56    #[must_use]
57    pub const fn new() -> Self {
58        Self
59    }
60}
61
62impl Default for BinanceDataClientFactory {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl DataClientFactory for BinanceDataClientFactory {
69    fn create(
70        &self,
71        name: &str,
72        config: &dyn ClientConfig,
73        _cache: Rc<RefCell<Cache>>,
74        _clock: Rc<RefCell<dyn Clock>>,
75    ) -> anyhow::Result<Box<dyn DataClient>> {
76        let binance_config = config
77            .as_any()
78            .downcast_ref::<BinanceDataClientConfig>()
79            .ok_or_else(|| {
80                anyhow::anyhow!(
81                    "Invalid config type for BinanceDataClientFactory. Expected BinanceDataClientConfig, was {config:?}",
82                )
83            })?
84            .clone();
85
86        let client_id = ClientId::from(name);
87
88        let product_type = binance_config
89            .product_types
90            .first()
91            .copied()
92            .unwrap_or(BinanceProductType::Spot);
93
94        match product_type {
95            BinanceProductType::Spot => {
96                let client = BinanceSpotDataClient::new(client_id, binance_config)?;
97                Ok(Box::new(client))
98            }
99            BinanceProductType::UsdM | BinanceProductType::CoinM => {
100                let client =
101                    BinanceFuturesDataClient::new(client_id, binance_config, product_type)?;
102                Ok(Box::new(client))
103            }
104            _ => {
105                anyhow::bail!("Unsupported product type for Binance data client: {product_type:?}")
106            }
107        }
108    }
109
110    fn name(&self) -> &'static str {
111        BINANCE
112    }
113
114    fn config_type(&self) -> &'static str {
115        stringify!(BinanceDataClientConfig)
116    }
117}
118
119/// Factory for creating Binance Spot execution clients.
120#[derive(Debug, Clone)]
121#[cfg_attr(
122    feature = "python",
123    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance", from_py_object)
124)]
125#[cfg_attr(
126    feature = "python",
127    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.binance")
128)]
129pub struct BinanceExecutionClientFactory;
130
131impl BinanceExecutionClientFactory {
132    /// Creates a new [`BinanceExecutionClientFactory`] instance.
133    #[must_use]
134    pub const fn new() -> Self {
135        Self
136    }
137}
138
139impl Default for BinanceExecutionClientFactory {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145impl ExecutionClientFactory for BinanceExecutionClientFactory {
146    fn create(
147        &self,
148        name: &str,
149        config: &dyn ClientConfig,
150        cache: Rc<RefCell<Cache>>,
151    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
152        let binance_config = config
153            .as_any()
154            .downcast_ref::<BinanceExecClientConfig>()
155            .ok_or_else(|| {
156                anyhow::anyhow!(
157                    "Invalid config type for BinanceExecutionClientFactory. Expected BinanceExecClientConfig, was {config:?}",
158                )
159            })?
160            .clone();
161
162        let product_type = binance_config
163            .product_types
164            .first()
165            .copied()
166            .unwrap_or(BinanceProductType::Spot);
167
168        match product_type {
169            BinanceProductType::Spot => {
170                // Spot uses cash account type and hedging OMS
171                let account_type = AccountType::Cash;
172                let oms_type = OmsType::Hedging;
173
174                let core = ExecutionClientCore::new(
175                    binance_config.trader_id,
176                    ClientId::from(name),
177                    *BINANCE_VENUE,
178                    oms_type,
179                    binance_config.account_id,
180                    account_type,
181                    None, // base_currency
182                    cache,
183                );
184
185                let client = BinanceSpotExecutionClient::new(core, binance_config)?;
186                Ok(Box::new(client))
187            }
188            BinanceProductType::UsdM | BinanceProductType::CoinM => {
189                // Futures uses margin account type and netting OMS
190                let account_type = AccountType::Margin;
191                let oms_type = OmsType::Netting;
192
193                let core = ExecutionClientCore::new(
194                    binance_config.trader_id,
195                    ClientId::from(name),
196                    *BINANCE_VENUE,
197                    oms_type,
198                    binance_config.account_id,
199                    account_type,
200                    None, // base_currency
201                    cache,
202                );
203
204                let client = BinanceFuturesExecutionClient::new(core, binance_config)?;
205                Ok(Box::new(client))
206            }
207            _ => {
208                anyhow::bail!(
209                    "Unsupported product type for Binance execution client: {product_type:?}"
210                )
211            }
212        }
213    }
214
215    fn name(&self) -> &'static str {
216        BINANCE
217    }
218
219    fn config_type(&self) -> &'static str {
220        stringify!(BinanceExecClientConfig)
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use nautilus_common::factories::DataClientFactory;
227    use rstest::rstest;
228
229    use super::*;
230
231    #[rstest]
232    fn test_binance_data_client_factory_creation() {
233        let factory = BinanceDataClientFactory::new();
234        assert_eq!(factory.name(), "BINANCE");
235        assert_eq!(factory.config_type(), "BinanceDataClientConfig");
236    }
237
238    #[rstest]
239    fn test_binance_data_client_factory_default() {
240        let factory = BinanceDataClientFactory;
241        assert_eq!(factory.name(), "BINANCE");
242    }
243}