Skip to main content

nautilus_bybit/python/
mod.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//! Python bindings from `pyo3`.
17
18pub mod config;
19pub mod enums;
20pub mod factories;
21pub mod http;
22pub mod params;
23pub mod types;
24
25use nautilus_common::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
26use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
27use nautilus_model::{
28    data::ensure_rust_extractor_registered,
29    enums::{BarAggregation, OrderSide},
30};
31use nautilus_system::get_global_pyo3_registry;
32use pyo3::prelude::*;
33
34use crate::{
35    common::{
36        consts::{BYBIT, BYBIT_CLIENT_ID, BYBIT_VENUE},
37        enums::{BybitOrderSide, BybitPositionIdx, BybitPositionMode},
38        parse::{bar_spec_to_bybit_interval, extract_raw_symbol, resolve_position_idx},
39        symbol::BybitSymbol,
40    },
41    config::{BybitDataClientConfig, BybitExecutionClientConfig},
42    data_types::{BybitLiquidation, register_bybit_custom_data},
43    factories::{BybitDataClientFactory, BybitExecutionClientFactory},
44};
45
46/// Extracts the raw symbol from a Bybit symbol by removing the product type suffix.
47///
48/// # Examples
49/// - `"ETHUSDT-LINEAR"` → `"ETHUSDT"`
50/// - `"BTCUSDT-SPOT"` → `"BTCUSDT"`
51/// - `"ETHUSDT"` → `"ETHUSDT"` (no suffix)
52#[pyfunction]
53#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.bybit")]
54#[pyo3(name = "bybit_extract_raw_symbol")]
55fn py_bybit_extract_raw_symbol(symbol: &str) -> &str {
56    extract_raw_symbol(symbol)
57}
58
59/// Converts a Nautilus bar aggregation and step to a Bybit kline interval string.
60///
61/// # Errors
62///
63/// Returns an error if the aggregation type or step is not supported by Bybit.
64#[pyfunction]
65#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.bybit")]
66#[pyo3(name = "bybit_bar_spec_to_interval")]
67fn py_bybit_bar_spec_to_interval(aggregation: BarAggregation, step: u64) -> PyResult<String> {
68    let interval = bar_spec_to_bybit_interval(aggregation, step).map_err(to_pyvalue_err)?;
69    Ok(interval.to_string())
70}
71
72/// Extracts the product type from a Bybit symbol.
73///
74/// # Examples
75/// - `"ETHUSDT-LINEAR"` → `BybitProductType.LINEAR`
76/// - `"BTCUSDT-SPOT"` → `BybitProductType.SPOT`
77/// - `"BTCUSD-INVERSE"` → `BybitProductType.INVERSE`
78/// - `"ETH-26JUN26-16000-P-OPTION"` → `BybitProductType.OPTION`
79///
80/// # Errors
81///
82/// Returns an error if the symbol does not contain a valid Bybit product type suffix.
83#[pyfunction]
84#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.bybit")]
85#[pyo3(name = "bybit_product_type_from_symbol")]
86fn py_bybit_product_type_from_symbol(
87    symbol: &str,
88) -> PyResult<crate::common::enums::BybitProductType> {
89    let bybit_symbol = BybitSymbol::new(symbol).map_err(to_pyvalue_err)?;
90    Ok(bybit_symbol.product_type())
91}
92
93/// Resolves the Bybit `positionIdx` for an outgoing order.
94///
95/// Returns `None` when no position mode is configured. Otherwise returns the
96/// hedge-mode index for the position being affected (long or short), accounting
97/// for `is_reduce_only`. A `manual_override` always wins.
98#[pyfunction]
99#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.bybit")]
100#[pyo3(name = "bybit_resolve_position_idx")]
101#[pyo3(signature = (position_mode, order_side, is_reduce_only, manual_override=None))]
102fn py_bybit_resolve_position_idx(
103    position_mode: Option<BybitPositionMode>,
104    order_side: OrderSide,
105    is_reduce_only: bool,
106    manual_override: Option<BybitPositionIdx>,
107) -> Option<BybitPositionIdx> {
108    let bybit_side = BybitOrderSide::from(order_side);
109    resolve_position_idx(position_mode, bybit_side, is_reduce_only, manual_override)
110}
111
112#[expect(clippy::needless_pass_by_value)]
113fn extract_bybit_data_factory(
114    py: Python<'_>,
115    factory: Py<PyAny>,
116) -> PyResult<Box<dyn DataClientFactory>> {
117    match factory.extract::<BybitDataClientFactory>(py) {
118        Ok(f) => Ok(Box::new(f)),
119        Err(e) => Err(to_pyvalue_err(format!(
120            "Failed to extract BybitDataClientFactory: {e}"
121        ))),
122    }
123}
124
125#[expect(clippy::needless_pass_by_value)]
126fn extract_bybit_exec_factory(
127    py: Python<'_>,
128    factory: Py<PyAny>,
129) -> PyResult<Box<dyn ExecutionClientFactory>> {
130    match factory.extract::<BybitExecutionClientFactory>(py) {
131        Ok(f) => Ok(Box::new(f)),
132        Err(e) => Err(to_pyvalue_err(format!(
133            "Failed to extract BybitExecutionClientFactory: {e}"
134        ))),
135    }
136}
137
138#[expect(clippy::needless_pass_by_value)]
139fn extract_bybit_data_config(py: Python<'_>, config: Py<PyAny>) -> PyResult<Box<dyn ClientConfig>> {
140    match config.extract::<BybitDataClientConfig>(py) {
141        Ok(c) => Ok(Box::new(c)),
142        Err(e) => Err(to_pyvalue_err(format!(
143            "Failed to extract BybitDataClientConfig: {e}"
144        ))),
145    }
146}
147
148#[expect(clippy::needless_pass_by_value)]
149fn extract_bybit_exec_config(py: Python<'_>, config: Py<PyAny>) -> PyResult<Box<dyn ClientConfig>> {
150    match config.extract::<BybitExecutionClientConfig>(py) {
151        Ok(c) => Ok(Box::new(c)),
152        Err(e) => Err(to_pyvalue_err(format!(
153            "Failed to extract BybitExecutionClientConfig: {e}"
154        ))),
155    }
156}
157
158/// Exposed through `nautilus_trader.adapters.bybit`.
159///
160/// # Errors
161///
162/// Returns an error if any bindings fail to register with the Python module.
163#[pymodule]
164#[rustfmt::skip]
165pub fn bybit(_: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
166    m.add(stringify!(BYBIT), BYBIT)?;
167    m.add(stringify!(BYBIT_CLIENT_ID), *BYBIT_CLIENT_ID)?;
168    m.add(stringify!(BYBIT_VENUE), *BYBIT_VENUE)?;
169    m.add_class::<crate::common::enums::BybitAccountType>()?;
170    m.add_class::<crate::common::enums::BybitCancelType>()?;
171    m.add_class::<crate::common::enums::BybitEnvironment>()?;
172    m.add_class::<crate::common::enums::BybitMarginAction>()?;
173    m.add_class::<crate::common::enums::BybitMarginMode>()?;
174    m.add_class::<crate::common::enums::BybitOpenOnly>()?;
175    m.add_class::<crate::common::enums::BybitOrderFilter>()?;
176    m.add_class::<crate::common::enums::BybitOrderSide>()?;
177    m.add_class::<crate::common::enums::BybitOrderStatus>()?;
178    m.add_class::<crate::common::enums::BybitOrderType>()?;
179    m.add_class::<crate::common::enums::BybitPositionIdx>()?;
180    m.add_class::<crate::common::enums::BybitPositionMode>()?;
181    m.add_class::<crate::common::enums::BybitProductType>()?;
182    m.add_class::<crate::common::enums::BybitStopOrderType>()?;
183    m.add_class::<crate::common::enums::BybitTimeInForce>()?;
184    m.add_class::<crate::common::enums::BybitTpSlMode>()?;
185    m.add_class::<crate::common::enums::BybitTriggerDirection>()?;
186    m.add_class::<crate::common::enums::BybitTriggerType>()?;
187    m.add_class::<crate::http::client::BybitHttpClient>()?;
188    m.add_class::<crate::http::client::BybitRawHttpClient>()?;
189    m.add_class::<crate::http::models::BybitServerTime>()?;
190    m.add_class::<crate::http::models::BybitAccountDetails>()?;
191    m.add_class::<crate::http::models::BybitApiKeyPermissions>()?;
192    m.add_class::<crate::http::models::BybitFeeRate>()?;
193    m.add_class::<crate::http::models::BybitOrder>()?;
194    m.add_class::<crate::http::models::BybitOrderCursorList>()?;
195    m.add_class::<crate::http::models::BybitTickerData>()?;
196    m.add_class::<crate::common::types::BybitMarginBorrowResult>()?;
197    m.add_class::<crate::common::types::BybitMarginRepayResult>()?;
198    m.add_class::<crate::common::types::BybitMarginStatusResult>()?;
199    m.add_class::<params::BybitTickersParams>()?;
200    m.add_class::<params::BybitNativeTpSlParams>()?;
201    m.add_class::<BybitLiquidation>()?;
202    m.add_class::<BybitDataClientConfig>()?;
203    m.add_class::<BybitDataClientFactory>()?;
204    m.add_class::<BybitExecutionClientConfig>()?;
205    m.add_class::<BybitExecutionClientFactory>()?;
206    m.add_function(wrap_pyfunction!(py_bybit_extract_raw_symbol, m)?)?;
207    m.add_function(wrap_pyfunction!(py_bybit_bar_spec_to_interval, m)?)?;
208    m.add_function(wrap_pyfunction!(py_bybit_product_type_from_symbol, m)?)?;
209    m.add_function(wrap_pyfunction!(py_bybit_resolve_position_idx, m)?)?;
210
211    let registry = get_global_pyo3_registry();
212
213    if let Err(e) =
214        registry.register_factory_extractor(BYBIT.to_string(), extract_bybit_data_factory)
215    {
216        return Err(to_pyruntime_err(format!(
217            "Failed to register Bybit data factory extractor: {e}"
218        )));
219    }
220
221    if let Err(e) = registry
222        .register_exec_factory_extractor(BYBIT.to_string(), extract_bybit_exec_factory)
223    {
224        return Err(to_pyruntime_err(format!(
225            "Failed to register Bybit exec factory extractor: {e}"
226        )));
227    }
228
229    if let Err(e) = registry.register_config_extractor(
230        "BybitDataClientConfig".to_string(),
231        extract_bybit_data_config,
232    ) {
233        return Err(to_pyruntime_err(format!(
234            "Failed to register Bybit data config extractor: {e}"
235        )));
236    }
237
238    if let Err(e) = registry.register_config_extractor(
239        "BybitExecutionClientConfig".to_string(),
240        extract_bybit_exec_config,
241    ) {
242        return Err(to_pyruntime_err(format!(
243            "Failed to register Bybit exec config extractor: {e}"
244        )));
245    }
246
247    register_bybit_custom_data();
248    let _result = ensure_rust_extractor_registered::<BybitLiquidation>();
249
250    Ok(())
251}