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::CacheView,
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, TraderId},
30};
31
32use crate::{
33    common::{
34        consts::{BINANCE, BINANCE_VENUE},
35        enums::BinanceProductType,
36    },
37    config::{BinanceDataClientConfig, BinanceExecutionClientConfig},
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.adapters.binance", from_py_object)
47)]
48#[cfg_attr(
49    feature = "python",
50    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.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: CacheView,
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        binance_config.validate()?;
89
90        let product_type = binance_config.product_type;
91
92        match product_type {
93            BinanceProductType::Spot => {
94                let client = BinanceSpotDataClient::new(client_id, binance_config)?;
95                Ok(Box::new(client))
96            }
97            BinanceProductType::UsdM | BinanceProductType::CoinM => {
98                let client =
99                    BinanceFuturesDataClient::new(client_id, binance_config, product_type)?;
100                Ok(Box::new(client))
101            }
102            _ => {
103                anyhow::bail!("Unsupported product type for Binance data client: {product_type:?}")
104            }
105        }
106    }
107
108    fn name(&self) -> &'static str {
109        BINANCE
110    }
111
112    fn config_type(&self) -> &'static str {
113        stringify!(BinanceDataClientConfig)
114    }
115}
116
117/// Factory for creating Binance Spot execution clients.
118#[derive(Debug, Clone)]
119#[cfg_attr(
120    feature = "python",
121    pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
122)]
123#[cfg_attr(
124    feature = "python",
125    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
126)]
127pub struct BinanceExecutionClientFactory;
128
129impl BinanceExecutionClientFactory {
130    /// Creates a new [`BinanceExecutionClientFactory`] instance.
131    #[must_use]
132    pub const fn new() -> Self {
133        Self
134    }
135}
136
137impl Default for BinanceExecutionClientFactory {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl ExecutionClientFactory for BinanceExecutionClientFactory {
144    fn create(
145        &self,
146        trader_id: TraderId,
147        name: &str,
148        config: &dyn ClientConfig,
149        cache: CacheView,
150    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
151        let binance_config = config
152            .as_any()
153            .downcast_ref::<BinanceExecutionClientConfig>()
154            .ok_or_else(|| {
155                anyhow::anyhow!(
156                    "Invalid config type for BinanceExecutionClientFactory. Expected BinanceExecutionClientConfig, was {config:?}",
157                )
158            })?
159            .clone();
160
161        let product_type = binance_config.product_type;
162
163        binance_config.validate()?;
164
165        match product_type {
166            BinanceProductType::Spot => {
167                // Spot uses cash account type and hedging OMS
168                let account_type = AccountType::Cash;
169                let oms_type = OmsType::Hedging;
170
171                let core = ExecutionClientCore::new(
172                    trader_id,
173                    ClientId::from(name),
174                    *BINANCE_VENUE,
175                    oms_type,
176                    binance_config.account_id,
177                    account_type,
178                    None, // base_currency
179                    cache,
180                );
181
182                let client = BinanceSpotExecutionClient::new(core, binance_config)?;
183                Ok(Box::new(client))
184            }
185            BinanceProductType::UsdM | BinanceProductType::CoinM => {
186                let account_type = AccountType::Margin;
187                let oms_type = binance_config.oms_type.unwrap_or(OmsType::Netting);
188
189                let core = ExecutionClientCore::new(
190                    trader_id,
191                    ClientId::from(name),
192                    *BINANCE_VENUE,
193                    oms_type,
194                    binance_config.account_id,
195                    account_type,
196                    None, // base_currency
197                    cache,
198                );
199
200                let client = BinanceFuturesExecutionClient::new(core, binance_config)?;
201                Ok(Box::new(client))
202            }
203            _ => {
204                anyhow::bail!(
205                    "Unsupported product type for Binance execution client: {product_type:?}"
206                )
207            }
208        }
209    }
210
211    fn name(&self) -> &'static str {
212        BINANCE
213    }
214
215    fn config_type(&self) -> &'static str {
216        stringify!(BinanceExecutionClientConfig)
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use std::{cell::RefCell, rc::Rc};
223
224    use nautilus_common::{
225        cache::Cache,
226        factories::{DataClientFactory, ExecutionClientFactory},
227    };
228    use rstest::rstest;
229
230    use super::*;
231
232    #[rstest]
233    fn test_binance_data_client_factory_creation() {
234        let factory = BinanceDataClientFactory::new();
235        assert_eq!(factory.name(), BINANCE);
236        assert_eq!(factory.config_type(), "BinanceDataClientConfig");
237    }
238
239    #[rstest]
240    fn test_binance_data_client_factory_default() {
241        let factory = BinanceDataClientFactory;
242        assert_eq!(factory.name(), BINANCE);
243    }
244
245    #[rstest]
246    #[case(BinanceProductType::Spot, Some(OmsType::Netting), OmsType::Hedging)]
247    #[case(BinanceProductType::UsdM, None, OmsType::Netting)]
248    #[case(BinanceProductType::UsdM, Some(OmsType::Hedging), OmsType::Hedging)]
249    fn test_binance_execution_client_factory_selects_oms_type(
250        #[case] product_type: BinanceProductType,
251        #[case] oms_type: Option<OmsType>,
252        #[case] expected: OmsType,
253    ) {
254        let factory = BinanceExecutionClientFactory::new();
255        let config = BinanceExecutionClientConfig {
256            product_type,
257            use_ws_trading: false,
258            oms_type,
259            api_key: Some("test_key".to_string()),
260            api_secret: Some("test_secret".to_string()),
261            ..Default::default()
262        };
263        let cache = Rc::new(RefCell::new(Cache::default()));
264
265        let client = factory
266            .create(
267                TraderId::from("TRADER-001"),
268                "BINANCE-TEST",
269                &config,
270                cache.into(),
271            )
272            .unwrap();
273
274        assert_eq!(client.oms_type(), expected);
275    }
276}