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