nautilus_bybit/python/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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//! Bybit enumerations Python bindings.
17
18use std::str::FromStr;
19
20use nautilus_core::python::to_pyvalue_err;
21use pyo3::{PyTypeInfo, prelude::*, types::PyType};
22use strum::IntoEnumIterator;
23
24use crate::common::enums::{
25    BybitAccountType, BybitEnvironment, BybitMarginMode, BybitPositionMode, BybitProductType,
26};
27
28#[pymethods]
29impl BybitProductType {
30    #[new]
31    fn py_new(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<Self> {
32        let t = Self::type_object(py);
33        Self::py_from_str(&t, value)
34    }
35
36    fn __hash__(&self) -> isize {
37        *self as isize
38    }
39
40    fn __repr__(&self) -> String {
41        format!(
42            "<{}.{}: '{}'>",
43            stringify!(BybitProductType),
44            self.name(),
45            self.value(),
46        )
47    }
48
49    fn __str__(&self) -> String {
50        self.to_string()
51    }
52
53    #[getter]
54    #[must_use]
55    pub fn name(&self) -> &str {
56        self.as_ref()
57    }
58
59    #[getter]
60    #[must_use]
61    pub fn value(&self) -> String {
62        self.to_string().to_lowercase()
63    }
64
65    #[staticmethod]
66    #[must_use]
67    fn variants() -> Vec<String> {
68        Self::iter().map(|x| x.to_string()).collect()
69    }
70
71    #[classmethod]
72    #[pyo3(name = "from_str")]
73    fn py_from_str(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
74        let data_str: String = data.str()?.extract()?;
75        Self::from_str(&data_str).map_err(to_pyvalue_err)
76    }
77
78    #[classattr]
79    #[pyo3(name = "SPOT")]
80    fn py_spot() -> Self {
81        Self::Spot
82    }
83
84    #[classattr]
85    #[pyo3(name = "LINEAR")]
86    fn py_linear() -> Self {
87        Self::Linear
88    }
89
90    #[classattr]
91    #[pyo3(name = "INVERSE")]
92    fn py_inverse() -> Self {
93        Self::Inverse
94    }
95
96    #[classattr]
97    #[pyo3(name = "OPTION")]
98    fn py_option() -> Self {
99        Self::Option
100    }
101}
102
103#[pymethods]
104impl BybitEnvironment {
105    #[new]
106    fn py_new(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<Self> {
107        let t = Self::type_object(py);
108        Self::py_from_str(&t, value)
109    }
110
111    fn __hash__(&self) -> isize {
112        *self as isize
113    }
114
115    fn __repr__(&self) -> String {
116        format!(
117            "<{}.{}: {}>",
118            stringify!(BybitEnvironment),
119            self.name(),
120            *self as u8,
121        )
122    }
123
124    fn __str__(&self) -> String {
125        self.to_string()
126    }
127
128    #[getter]
129    #[must_use]
130    pub fn name(&self) -> &str {
131        self.as_ref()
132    }
133
134    #[getter]
135    #[must_use]
136    pub fn value(&self) -> String {
137        self.to_string().to_lowercase()
138    }
139
140    #[staticmethod]
141    #[must_use]
142    fn variants() -> Vec<String> {
143        Self::iter().map(|x| x.to_string()).collect()
144    }
145
146    #[classmethod]
147    #[pyo3(name = "from_str")]
148    fn py_from_str(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
149        let data_str: String = data.str()?.extract()?;
150        Self::from_str(&data_str).map_err(to_pyvalue_err)
151    }
152
153    #[classattr]
154    #[pyo3(name = "MAINNET")]
155    fn py_mainnet() -> Self {
156        Self::Mainnet
157    }
158
159    #[classattr]
160    #[pyo3(name = "DEMO")]
161    fn py_demo() -> Self {
162        Self::Demo
163    }
164
165    #[classattr]
166    #[pyo3(name = "TESTNET")]
167    fn py_testnet() -> Self {
168        Self::Testnet
169    }
170}
171
172#[pymethods]
173impl BybitAccountType {
174    #[new]
175    fn py_new(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<Self> {
176        let t = Self::type_object(py);
177        Self::py_from_str(&t, value)
178    }
179
180    fn __hash__(&self) -> isize {
181        *self as isize
182    }
183
184    fn __repr__(&self) -> String {
185        format!(
186            "<{}.{}: {}>",
187            stringify!(BybitAccountType),
188            self.name(),
189            *self as u8,
190        )
191    }
192
193    fn __str__(&self) -> String {
194        self.to_string()
195    }
196
197    #[getter]
198    #[must_use]
199    pub fn name(&self) -> &str {
200        self.as_ref()
201    }
202
203    #[getter]
204    #[must_use]
205    pub fn value(&self) -> String {
206        self.to_string().to_uppercase()
207    }
208
209    #[staticmethod]
210    #[must_use]
211    fn variants() -> Vec<String> {
212        Self::iter().map(|x| x.to_string()).collect()
213    }
214
215    #[classmethod]
216    #[pyo3(name = "from_str")]
217    fn py_from_str(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
218        let data_str: String = data.str()?.extract()?;
219        Self::from_str(&data_str).map_err(to_pyvalue_err)
220    }
221
222    #[classattr]
223    #[pyo3(name = "UNIFIED")]
224    fn py_unified() -> Self {
225        Self::Unified
226    }
227}
228
229#[pymethods]
230impl BybitMarginMode {
231    #[new]
232    fn py_new(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<Self> {
233        let t = Self::type_object(py);
234        Self::py_from_str(&t, value)
235    }
236
237    fn __hash__(&self) -> isize {
238        *self as isize
239    }
240
241    fn __repr__(&self) -> String {
242        format!(
243            "<{}.{}: '{}'>",
244            stringify!(BybitMarginMode),
245            self.name(),
246            self.value(),
247        )
248    }
249
250    fn __str__(&self) -> String {
251        self.to_string()
252    }
253
254    #[getter]
255    #[must_use]
256    pub fn name(&self) -> &str {
257        self.as_ref()
258    }
259
260    #[getter]
261    #[must_use]
262    pub fn value(&self) -> String {
263        match self {
264            Self::IsolatedMargin => "ISOLATED_MARGIN".to_string(),
265            Self::RegularMargin => "REGULAR_MARGIN".to_string(),
266            Self::PortfolioMargin => "PORTFOLIO_MARGIN".to_string(),
267        }
268    }
269
270    #[staticmethod]
271    #[must_use]
272    fn variants() -> Vec<String> {
273        Self::iter().map(|x| x.to_string()).collect()
274    }
275
276    #[classmethod]
277    #[pyo3(name = "from_str")]
278    fn py_from_str(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
279        let data_str: String = data.str()?.extract()?;
280        Self::from_str(&data_str).map_err(to_pyvalue_err)
281    }
282
283    #[classattr]
284    #[pyo3(name = "ISOLATED_MARGIN")]
285    fn py_isolated_margin() -> Self {
286        Self::IsolatedMargin
287    }
288
289    #[classattr]
290    #[pyo3(name = "REGULAR_MARGIN")]
291    fn py_regular_margin() -> Self {
292        Self::RegularMargin
293    }
294
295    #[classattr]
296    #[pyo3(name = "PORTFOLIO_MARGIN")]
297    fn py_portfolio_margin() -> Self {
298        Self::PortfolioMargin
299    }
300}
301
302#[pymethods]
303impl BybitPositionMode {
304    #[new]
305    fn py_new(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<Self> {
306        let t = Self::type_object(py);
307        Self::py_from_str(&t, value)
308    }
309
310    fn __hash__(&self) -> isize {
311        *self as isize
312    }
313
314    fn __repr__(&self) -> String {
315        format!(
316            "<{}.{}: {}>",
317            stringify!(BybitPositionMode),
318            self.name(),
319            self.value(),
320        )
321    }
322
323    fn __str__(&self) -> String {
324        self.to_string()
325    }
326
327    #[getter]
328    #[must_use]
329    pub fn name(&self) -> &str {
330        self.as_ref()
331    }
332
333    #[getter]
334    #[must_use]
335    pub fn value(&self) -> i32 {
336        *self as i32
337    }
338
339    #[staticmethod]
340    #[must_use]
341    fn variants() -> Vec<String> {
342        Self::iter().map(|x| x.to_string()).collect()
343    }
344
345    #[classmethod]
346    #[pyo3(name = "from_str")]
347    fn py_from_str(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
348        // Try to extract as integer first (for API payloads that send 0 or 3)
349        if let Ok(int_val) = data.extract::<i32>() {
350            return match int_val {
351                0 => Ok(Self::MergedSingle),
352                3 => Ok(Self::BothSides),
353                _ => Err(to_pyvalue_err(anyhow::anyhow!(
354                    "Invalid BybitPositionMode value: {int_val}"
355                ))),
356            };
357        }
358
359        // Fall back to string parsing for variant names
360        let data_str: String = data.str()?.extract()?;
361        Self::from_str(&data_str).map_err(to_pyvalue_err)
362    }
363
364    #[classattr]
365    #[pyo3(name = "MERGED_SINGLE")]
366    fn py_merged_single() -> Self {
367        Self::MergedSingle
368    }
369
370    #[classattr]
371    #[pyo3(name = "BOTH_SIDES")]
372    fn py_both_sides() -> Self {
373        Self::BothSides
374    }
375}