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