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