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
20#[cfg(test)]
21use nautilus_common::clock::VirtualClock;
22use nautilus_common::{
23    cache::CacheView,
24    clients::{DataClient, ExecutionClient},
25    clock::Clock,
26    factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
27};
28use nautilus_live::ExecutionClientCore;
29use nautilus_model::{
30    enums::{AccountType, OmsType},
31    identifiers::{ClientId, TraderId},
32};
33
34use crate::{
35    common::{
36        consts::{BINANCE, BINANCE_VENUE},
37        enums::BinanceProductType,
38    },
39    config::{BinanceDataClientConfig, BinanceExecutionClientConfig},
40    futures::{data::BinanceFuturesDataClient, execution::BinanceFuturesExecutionClient},
41    spot::{data::BinanceSpotDataClient, execution::BinanceSpotExecutionClient},
42};
43
44/// Factory for creating Binance data clients.
45#[derive(Debug, Clone)]
46#[cfg_attr(
47    feature = "python",
48    pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
49)]
50#[cfg_attr(
51    feature = "python",
52    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
53)]
54pub struct BinanceDataClientFactory;
55
56impl BinanceDataClientFactory {
57    /// Creates a new [`BinanceDataClientFactory`] instance.
58    #[must_use]
59    pub const fn new() -> Self {
60        Self
61    }
62}
63
64impl Default for BinanceDataClientFactory {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl DataClientFactory for BinanceDataClientFactory {
71    fn create(
72        &self,
73        name: &str,
74        config: &dyn ClientConfig,
75        _cache: CacheView,
76        _clock: Rc<RefCell<dyn Clock>>,
77    ) -> anyhow::Result<Box<dyn DataClient>> {
78        let binance_config = config
79            .as_any()
80            .downcast_ref::<BinanceDataClientConfig>()
81            .ok_or_else(|| {
82                anyhow::anyhow!(
83                    "Invalid config type for BinanceDataClientFactory. Expected BinanceDataClientConfig, was {config:?}",
84                )
85            })?
86            .clone();
87
88        let client_id = ClientId::from(name);
89
90        binance_config.validate()?;
91
92        let product_type = binance_config.product_type;
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.adapters.binance", from_py_object)
124)]
125#[cfg_attr(
126    feature = "python",
127    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.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        trader_id: TraderId,
149        name: &str,
150        config: &dyn ClientConfig,
151        cache: CacheView,
152        _clock: Rc<RefCell<dyn Clock>>,
153    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
154        let binance_config = config
155            .as_any()
156            .downcast_ref::<BinanceExecutionClientConfig>()
157            .ok_or_else(|| {
158                anyhow::anyhow!(
159                    "Invalid config type for BinanceExecutionClientFactory. Expected BinanceExecutionClientConfig, was {config:?}",
160                )
161            })?
162            .clone();
163
164        let product_type = binance_config.product_type;
165
166        binance_config.validate()?;
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                    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                let account_type = AccountType::Margin;
190                let oms_type = binance_config.oms_type.unwrap_or(OmsType::Netting);
191
192                let core = ExecutionClientCore::new(
193                    trader_id,
194                    ClientId::from(name),
195                    *BINANCE_VENUE,
196                    oms_type,
197                    binance_config.account_id,
198                    account_type,
199                    None, // base_currency
200                    cache,
201                );
202
203                let client = BinanceFuturesExecutionClient::new(core, binance_config)?;
204                Ok(Box::new(client))
205            }
206            _ => {
207                anyhow::bail!(
208                    "Unsupported product type for Binance execution client: {product_type:?}"
209                )
210            }
211        }
212    }
213
214    fn name(&self) -> &'static str {
215        BINANCE
216    }
217
218    fn config_type(&self) -> &'static str {
219        stringify!(BinanceExecutionClientConfig)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use std::{cell::RefCell, rc::Rc};
226
227    use nautilus_common::{
228        cache::Cache,
229        factories::{DataClientFactory, ExecutionClientFactory},
230    };
231    use rstest::rstest;
232
233    use super::*;
234
235    #[rstest]
236    fn test_binance_data_client_factory_creation() {
237        let factory = BinanceDataClientFactory::new();
238        assert_eq!(factory.name(), BINANCE);
239        assert_eq!(factory.config_type(), "BinanceDataClientConfig");
240    }
241
242    #[rstest]
243    fn test_binance_data_client_factory_default() {
244        let factory = BinanceDataClientFactory;
245        assert_eq!(factory.name(), BINANCE);
246    }
247
248    #[rstest]
249    #[case(BinanceProductType::Spot, Some(OmsType::Netting), OmsType::Hedging)]
250    #[case(BinanceProductType::UsdM, None, OmsType::Netting)]
251    #[case(BinanceProductType::UsdM, Some(OmsType::Hedging), OmsType::Hedging)]
252    fn test_binance_execution_client_factory_selects_oms_type(
253        #[case] product_type: BinanceProductType,
254        #[case] oms_type: Option<OmsType>,
255        #[case] expected: OmsType,
256    ) {
257        let factory = BinanceExecutionClientFactory::new();
258        let config = BinanceExecutionClientConfig {
259            product_type,
260            use_ws_trading: false,
261            oms_type,
262            api_key: Some("test_key".into()),
263            api_secret: Some("test_secret".into()),
264            ..Default::default()
265        };
266        let cache = Rc::new(RefCell::new(Cache::default()));
267
268        let client = factory
269            .create(
270                TraderId::from("TRADER-001"),
271                "BINANCE-TEST",
272                &config,
273                cache.into(),
274                Rc::new(RefCell::new(VirtualClock::new())),
275            )
276            .unwrap();
277
278        assert_eq!(client.oms_type(), expected);
279    }
280}