Skip to main content

nautilus_bybit/
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 Bybit clients and components.
17
18use std::{any::Any, 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::{AccountId, ClientId, TraderId},
32};
33
34use crate::{
35    common::{
36        consts::{BYBIT, BYBIT_VENUE},
37        enums::BybitProductType,
38    },
39    config::{BybitDataClientConfig, BybitExecutionClientConfig},
40    data::BybitDataClient,
41    execution::BybitExecutionClient,
42};
43
44impl ClientConfig for BybitDataClientConfig {
45    fn as_any(&self) -> &dyn Any {
46        self
47    }
48}
49
50impl ClientConfig for BybitExecutionClientConfig {
51    fn as_any(&self) -> &dyn Any {
52        self
53    }
54}
55
56/// Factory for creating Bybit data clients.
57#[derive(Debug, Clone)]
58#[cfg_attr(
59    feature = "python",
60    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
61)]
62#[cfg_attr(
63    feature = "python",
64    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
65)]
66pub struct BybitDataClientFactory;
67
68impl BybitDataClientFactory {
69    /// Creates a new [`BybitDataClientFactory`] instance.
70    #[must_use]
71    pub const fn new() -> Self {
72        Self
73    }
74}
75
76impl Default for BybitDataClientFactory {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl DataClientFactory for BybitDataClientFactory {
83    fn create(
84        &self,
85        name: &str,
86        config: &dyn ClientConfig,
87        _cache: CacheView,
88        _clock: Rc<RefCell<dyn Clock>>,
89    ) -> anyhow::Result<Box<dyn DataClient>> {
90        let bybit_config = config
91            .as_any()
92            .downcast_ref::<BybitDataClientConfig>()
93            .ok_or_else(|| {
94                anyhow::anyhow!(
95                    "Invalid config type for BybitDataClientFactory. Expected BybitDataClientConfig, was {config:?}",
96                )
97            })?
98            .clone();
99
100        let client_id = ClientId::from(name);
101        let client = BybitDataClient::new(client_id, bybit_config)?;
102        Ok(Box::new(client))
103    }
104
105    fn name(&self) -> &'static str {
106        BYBIT
107    }
108
109    fn config_type(&self) -> &'static str {
110        "BybitDataClientConfig"
111    }
112}
113
114/// Factory for creating Bybit execution clients.
115#[derive(Debug, Default, Clone)]
116#[cfg_attr(
117    feature = "python",
118    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
119)]
120#[cfg_attr(
121    feature = "python",
122    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
123)]
124pub struct BybitExecutionClientFactory;
125
126impl BybitExecutionClientFactory {
127    /// Creates a new [`BybitExecutionClientFactory`] instance.
128    #[must_use]
129    pub const fn new() -> Self {
130        Self
131    }
132}
133
134impl ExecutionClientFactory for BybitExecutionClientFactory {
135    fn create(
136        &self,
137        trader_id: TraderId,
138        name: &str,
139        config: &dyn ClientConfig,
140        cache: CacheView,
141        _clock: Rc<RefCell<dyn Clock>>,
142    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
143        let bybit_config = config
144            .as_any()
145            .downcast_ref::<BybitExecutionClientConfig>()
146            .ok_or_else(|| {
147                anyhow::anyhow!(
148                    "Invalid config type for BybitExecutionClientFactory. Expected BybitExecutionClientConfig, was {config:?}",
149                )
150            })?
151            .clone();
152
153        // Default to Linear if product_types is empty (matches execution client behavior)
154        let product_types = if bybit_config.product_types.is_empty() {
155            vec![BybitProductType::Linear]
156        } else {
157            bybit_config.product_types.clone()
158        };
159
160        let has_derivatives = product_types.iter().any(|t| {
161            matches!(
162                t,
163                BybitProductType::Linear | BybitProductType::Inverse | BybitProductType::Option
164            )
165        });
166
167        let account_type = if has_derivatives {
168            AccountType::Margin
169        } else {
170            AccountType::Cash
171        };
172
173        // Bybit uses netting for derivatives, hedging for spot
174        let oms_type = if has_derivatives {
175            OmsType::Netting
176        } else {
177            OmsType::Hedging
178        };
179
180        let account_id = bybit_config
181            .account_id
182            .unwrap_or_else(|| AccountId::from("BYBIT-001"));
183
184        let core = ExecutionClientCore::new(
185            trader_id,
186            ClientId::from(name),
187            *BYBIT_VENUE,
188            oms_type,
189            account_id,
190            account_type,
191            None, // base_currency
192            cache,
193        );
194
195        let client = BybitExecutionClient::new(core, bybit_config)?;
196
197        Ok(Box::new(client))
198    }
199
200    fn name(&self) -> &'static str {
201        BYBIT
202    }
203
204    fn config_type(&self) -> &'static str {
205        "BybitExecutionClientConfig"
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use std::{cell::RefCell, rc::Rc};
212
213    use nautilus_common::{
214        cache::Cache,
215        factories::{ClientConfig, ExecutionClientFactory},
216    };
217    use nautilus_model::identifiers::TraderId;
218    use rstest::rstest;
219
220    use super::*;
221    use crate::{common::enums::BybitProductType, config::BybitExecutionClientConfig};
222
223    #[rstest]
224    fn test_bybit_execution_client_factory_creation() {
225        let factory = BybitExecutionClientFactory::new();
226        assert_eq!(factory.name(), BYBIT);
227        assert_eq!(factory.config_type(), "BybitExecutionClientConfig");
228    }
229
230    #[rstest]
231    fn test_bybit_exec_client_config_implements_client_config() {
232        let config = BybitExecutionClientConfig::default();
233
234        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
235        let downcasted = boxed_config
236            .as_any()
237            .downcast_ref::<BybitExecutionClientConfig>();
238
239        assert!(downcasted.is_some());
240    }
241
242    #[rstest]
243    fn test_bybit_execution_client_factory_creates_client_for_spot() {
244        let factory = BybitExecutionClientFactory::new();
245        let config = BybitExecutionClientConfig {
246            product_types: vec![BybitProductType::Spot],
247            api_key: Some("test_key".into()),
248            api_secret: Some("test_secret".into()),
249            ..Default::default()
250        };
251
252        let cache = Rc::new(RefCell::new(Cache::default()));
253
254        let result = factory.create(
255            TraderId::from("TRADER-001"),
256            "BYBIT-TEST",
257            &config,
258            cache.into(),
259            Rc::new(RefCell::new(VirtualClock::new())),
260        );
261        assert!(result.is_ok());
262
263        let client = result.unwrap();
264        assert_eq!(client.client_id(), ClientId::from("BYBIT-TEST"));
265    }
266
267    #[rstest]
268    fn test_bybit_execution_client_factory_creates_client_for_derivatives() {
269        let factory = BybitExecutionClientFactory::new();
270        let config = BybitExecutionClientConfig {
271            product_types: vec![BybitProductType::Linear, BybitProductType::Inverse],
272            api_key: Some("test_key".into()),
273            api_secret: Some("test_secret".into()),
274            ..Default::default()
275        };
276
277        let cache = Rc::new(RefCell::new(Cache::default()));
278
279        let result = factory.create(
280            TraderId::from("TRADER-001"),
281            "BYBIT-DERIV",
282            &config,
283            cache.into(),
284            Rc::new(RefCell::new(VirtualClock::new())),
285        );
286        result.unwrap();
287    }
288
289    #[rstest]
290    fn test_bybit_execution_client_factory_rejects_wrong_config_type() {
291        let factory = BybitExecutionClientFactory::new();
292        let wrong_config = BybitDataClientConfig::default();
293
294        let cache = Rc::new(RefCell::new(Cache::default()));
295
296        let result = factory.create(
297            TraderId::from("TRADER-001"),
298            "BYBIT-TEST",
299            &wrong_config,
300            cache.into(),
301            Rc::new(RefCell::new(VirtualClock::new())),
302        );
303        assert!(result.is_err());
304        assert!(
305            result
306                .err()
307                .unwrap()
308                .to_string()
309                .contains("Invalid config type")
310        );
311    }
312}