nautilus_bybit/python/
http.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//! Python bindings for the Bybit HTTP client.
17
18use nautilus_core::python::to_pyvalue_err;
19use nautilus_model::{
20    enums::{OrderSide, OrderType, TimeInForce},
21    identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
22    python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
23    types::{Price, Quantity},
24};
25use pyo3::{conversion::IntoPyObjectExt, prelude::*, types::PyList};
26
27use crate::{common::enums::BybitProductType, http::client::BybitHttpClient};
28
29#[pymethods]
30impl BybitHttpClient {
31    #[new]
32    #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, timeout_secs=None, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None))]
33    #[allow(clippy::too_many_arguments)]
34    fn py_new(
35        api_key: Option<String>,
36        api_secret: Option<String>,
37        base_url: Option<String>,
38        timeout_secs: Option<u64>,
39        max_retries: Option<u32>,
40        retry_delay_ms: Option<u64>,
41        retry_delay_max_ms: Option<u64>,
42    ) -> PyResult<Self> {
43        let timeout = timeout_secs.or(Some(60));
44
45        // Try to get credentials from parameters or environment variables
46        let key = api_key.or_else(|| std::env::var("BYBIT_API_KEY").ok());
47        let secret = api_secret.or_else(|| std::env::var("BYBIT_API_SECRET").ok());
48
49        if let (Some(k), Some(s)) = (key, secret) {
50            Self::with_credentials(
51                k,
52                s,
53                base_url,
54                timeout,
55                max_retries,
56                retry_delay_ms,
57                retry_delay_max_ms,
58            )
59            .map_err(to_pyvalue_err)
60        } else {
61            Self::new(
62                base_url,
63                timeout,
64                max_retries,
65                retry_delay_ms,
66                retry_delay_max_ms,
67            )
68            .map_err(to_pyvalue_err)
69        }
70    }
71
72    #[getter]
73    #[pyo3(name = "base_url")]
74    #[must_use]
75    pub fn py_base_url(&self) -> &str {
76        self.base_url()
77    }
78
79    #[getter]
80    #[pyo3(name = "api_key")]
81    #[must_use]
82    pub fn py_api_key(&self) -> Option<&str> {
83        self.credential().map(|c| c.api_key()).map(|u| u.as_str())
84    }
85
86    #[pyo3(name = "add_instrument")]
87    fn py_add_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
88        let inst_any = pyobject_to_instrument_any(py, instrument)?;
89        self.add_instrument(inst_any);
90        Ok(())
91    }
92
93    #[pyo3(name = "cancel_all_requests")]
94    fn py_cancel_all_requests(&self) {
95        self.cancel_all_requests();
96    }
97
98    #[pyo3(name = "request_instruments")]
99    #[pyo3(signature = (product_type, symbol=None))]
100    fn py_request_instruments<'py>(
101        &self,
102        py: Python<'py>,
103        product_type: BybitProductType,
104        symbol: Option<String>,
105    ) -> PyResult<Bound<'py, PyAny>> {
106        let client = self.clone();
107
108        pyo3_async_runtimes::tokio::future_into_py(py, async move {
109            let instruments = client
110                .request_instruments(product_type, symbol)
111                .await
112                .map_err(to_pyvalue_err)?;
113
114            Python::attach(|py| {
115                let py_instruments: PyResult<Vec<_>> = instruments
116                    .into_iter()
117                    .map(|inst| instrument_any_to_pyobject(py, inst))
118                    .collect();
119                let pylist = PyList::new(py, py_instruments?)
120                    .unwrap()
121                    .into_any()
122                    .unbind();
123                Ok(pylist)
124            })
125        })
126    }
127
128    #[pyo3(name = "submit_order")]
129    #[pyo3(signature = (
130        product_type,
131        instrument_id,
132        client_order_id,
133        order_side,
134        order_type,
135        quantity,
136        time_in_force,
137        price = None,
138        reduce_only = false
139    ))]
140    #[allow(clippy::too_many_arguments)]
141    fn py_submit_order<'py>(
142        &self,
143        py: Python<'py>,
144        product_type: BybitProductType,
145        instrument_id: InstrumentId,
146        client_order_id: ClientOrderId,
147        order_side: OrderSide,
148        order_type: OrderType,
149        quantity: Quantity,
150        time_in_force: TimeInForce,
151        price: Option<Price>,
152        reduce_only: bool,
153    ) -> PyResult<Bound<'py, PyAny>> {
154        let client = self.clone();
155
156        pyo3_async_runtimes::tokio::future_into_py(py, async move {
157            let report = client
158                .submit_order(
159                    product_type,
160                    instrument_id,
161                    client_order_id,
162                    order_side,
163                    order_type,
164                    quantity,
165                    time_in_force,
166                    price,
167                    reduce_only,
168                )
169                .await
170                .map_err(to_pyvalue_err)?;
171
172            Python::attach(|py| report.into_py_any(py))
173        })
174    }
175
176    #[pyo3(name = "modify_order")]
177    #[pyo3(signature = (
178        product_type,
179        instrument_id,
180        client_order_id=None,
181        venue_order_id=None,
182        quantity=None,
183        price=None
184    ))]
185    #[allow(clippy::too_many_arguments)]
186    fn py_modify_order<'py>(
187        &self,
188        py: Python<'py>,
189        product_type: BybitProductType,
190        instrument_id: InstrumentId,
191        client_order_id: Option<ClientOrderId>,
192        venue_order_id: Option<VenueOrderId>,
193        quantity: Option<Quantity>,
194        price: Option<Price>,
195    ) -> PyResult<Bound<'py, PyAny>> {
196        let client = self.clone();
197
198        pyo3_async_runtimes::tokio::future_into_py(py, async move {
199            let report = client
200                .modify_order(
201                    product_type,
202                    instrument_id,
203                    client_order_id,
204                    venue_order_id,
205                    quantity,
206                    price,
207                )
208                .await
209                .map_err(to_pyvalue_err)?;
210
211            Python::attach(|py| report.into_py_any(py))
212        })
213    }
214
215    #[pyo3(name = "cancel_order")]
216    #[pyo3(signature = (product_type, instrument_id, client_order_id=None, venue_order_id=None))]
217    fn py_cancel_order<'py>(
218        &self,
219        py: Python<'py>,
220        product_type: BybitProductType,
221        instrument_id: InstrumentId,
222        client_order_id: Option<ClientOrderId>,
223        venue_order_id: Option<VenueOrderId>,
224    ) -> PyResult<Bound<'py, PyAny>> {
225        let client = self.clone();
226
227        pyo3_async_runtimes::tokio::future_into_py(py, async move {
228            let report = client
229                .cancel_order(product_type, instrument_id, client_order_id, venue_order_id)
230                .await
231                .map_err(to_pyvalue_err)?;
232
233            Python::attach(|py| report.into_py_any(py))
234        })
235    }
236
237    #[pyo3(name = "cancel_all_orders")]
238    fn py_cancel_all_orders<'py>(
239        &self,
240        py: Python<'py>,
241        product_type: BybitProductType,
242        instrument_id: InstrumentId,
243    ) -> PyResult<Bound<'py, PyAny>> {
244        let client = self.clone();
245
246        pyo3_async_runtimes::tokio::future_into_py(py, async move {
247            let reports = client
248                .cancel_all_orders(product_type, instrument_id)
249                .await
250                .map_err(to_pyvalue_err)?;
251
252            Python::attach(|py| {
253                let py_reports: PyResult<Vec<_>> = reports
254                    .into_iter()
255                    .map(|report| report.into_py_any(py))
256                    .collect();
257                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
258                Ok(pylist)
259            })
260        })
261    }
262
263    #[pyo3(name = "query_order")]
264    #[pyo3(signature = (account_id, product_type, instrument_id, client_order_id=None, venue_order_id=None))]
265    fn py_query_order<'py>(
266        &self,
267        py: Python<'py>,
268        account_id: AccountId,
269        product_type: BybitProductType,
270        instrument_id: InstrumentId,
271        client_order_id: Option<ClientOrderId>,
272        venue_order_id: Option<VenueOrderId>,
273    ) -> PyResult<Bound<'py, PyAny>> {
274        let client = self.clone();
275
276        pyo3_async_runtimes::tokio::future_into_py(py, async move {
277            match client
278                .query_order(
279                    account_id,
280                    product_type,
281                    instrument_id,
282                    client_order_id,
283                    venue_order_id,
284                )
285                .await
286            {
287                Ok(Some(report)) => Python::attach(|py| report.into_py_any(py)),
288                Ok(None) => Ok(Python::attach(|py| py.None())),
289                Err(e) => Err(to_pyvalue_err(e)),
290            }
291        })
292    }
293
294    #[pyo3(name = "request_trades")]
295    #[pyo3(signature = (product_type, instrument_id, limit=None))]
296    fn py_request_trades<'py>(
297        &self,
298        py: Python<'py>,
299        product_type: BybitProductType,
300        instrument_id: InstrumentId,
301        limit: Option<u32>,
302    ) -> PyResult<Bound<'py, PyAny>> {
303        let client = self.clone();
304
305        pyo3_async_runtimes::tokio::future_into_py(py, async move {
306            let trades = client
307                .request_trades(product_type, instrument_id, limit)
308                .await
309                .map_err(to_pyvalue_err)?;
310
311            Python::attach(|py| {
312                let py_trades: PyResult<Vec<_>> = trades
313                    .into_iter()
314                    .map(|trade| trade.into_py_any(py))
315                    .collect();
316                let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
317                Ok(pylist)
318            })
319        })
320    }
321
322    #[pyo3(name = "request_bars")]
323    #[pyo3(signature = (product_type, bar_type, start=None, end=None, limit=None))]
324    fn py_request_bars<'py>(
325        &self,
326        py: Python<'py>,
327        product_type: BybitProductType,
328        bar_type: nautilus_model::data::BarType,
329        start: Option<i64>,
330        end: Option<i64>,
331        limit: Option<u32>,
332    ) -> PyResult<Bound<'py, PyAny>> {
333        let client = self.clone();
334
335        pyo3_async_runtimes::tokio::future_into_py(py, async move {
336            let bars = client
337                .request_bars(product_type, bar_type, start, end, limit)
338                .await
339                .map_err(to_pyvalue_err)?;
340
341            Python::attach(|py| {
342                let py_bars: PyResult<Vec<_>> =
343                    bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
344                let pylist = PyList::new(py, py_bars?).unwrap().into_any().unbind();
345                Ok(pylist)
346            })
347        })
348    }
349
350    #[pyo3(name = "request_fee_rates")]
351    #[pyo3(signature = (product_type, symbol=None, base_coin=None))]
352    fn py_request_fee_rates<'py>(
353        &self,
354        py: Python<'py>,
355        product_type: BybitProductType,
356        symbol: Option<String>,
357        base_coin: Option<String>,
358    ) -> PyResult<Bound<'py, PyAny>> {
359        let client = self.clone();
360
361        pyo3_async_runtimes::tokio::future_into_py(py, async move {
362            let fee_rates = client
363                .request_fee_rates(product_type, symbol, base_coin)
364                .await
365                .map_err(to_pyvalue_err)?;
366
367            Python::attach(|py| {
368                let py_fee_rates: PyResult<Vec<_>> = fee_rates
369                    .into_iter()
370                    .map(|rate| Py::new(py, rate))
371                    .collect();
372                let pylist = PyList::new(py, py_fee_rates?).unwrap().into_any().unbind();
373                Ok(pylist)
374            })
375        })
376    }
377
378    #[pyo3(name = "request_account_state")]
379    fn py_request_account_state<'py>(
380        &self,
381        py: Python<'py>,
382        account_type: crate::common::enums::BybitAccountType,
383        account_id: AccountId,
384    ) -> PyResult<Bound<'py, PyAny>> {
385        let client = self.clone();
386
387        pyo3_async_runtimes::tokio::future_into_py(py, async move {
388            let account_state = client
389                .request_account_state(account_type, account_id)
390                .await
391                .map_err(to_pyvalue_err)?;
392
393            Python::attach(|py| account_state.into_py_any(py))
394        })
395    }
396
397    #[pyo3(name = "request_order_status_reports")]
398    #[pyo3(signature = (account_id, product_type, instrument_id=None, open_only=false, limit=None))]
399    fn py_request_order_status_reports<'py>(
400        &self,
401        py: Python<'py>,
402        account_id: AccountId,
403        product_type: BybitProductType,
404        instrument_id: Option<InstrumentId>,
405        open_only: bool,
406        limit: Option<u32>,
407    ) -> PyResult<Bound<'py, PyAny>> {
408        let client = self.clone();
409
410        pyo3_async_runtimes::tokio::future_into_py(py, async move {
411            let reports = client
412                .request_order_status_reports(
413                    account_id,
414                    product_type,
415                    instrument_id,
416                    open_only,
417                    limit,
418                )
419                .await
420                .map_err(to_pyvalue_err)?;
421
422            Python::attach(|py| {
423                let py_reports: PyResult<Vec<_>> = reports
424                    .into_iter()
425                    .map(|report| report.into_py_any(py))
426                    .collect();
427                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
428                Ok(pylist)
429            })
430        })
431    }
432
433    #[pyo3(name = "request_fill_reports")]
434    #[pyo3(signature = (account_id, product_type, instrument_id=None, start=None, end=None, limit=None))]
435    #[allow(clippy::too_many_arguments)]
436    fn py_request_fill_reports<'py>(
437        &self,
438        py: Python<'py>,
439        account_id: AccountId,
440        product_type: BybitProductType,
441        instrument_id: Option<InstrumentId>,
442        start: Option<i64>,
443        end: Option<i64>,
444        limit: Option<u32>,
445    ) -> PyResult<Bound<'py, PyAny>> {
446        let client = self.clone();
447
448        pyo3_async_runtimes::tokio::future_into_py(py, async move {
449            let reports = client
450                .request_fill_reports(account_id, product_type, instrument_id, start, end, limit)
451                .await
452                .map_err(to_pyvalue_err)?;
453
454            Python::attach(|py| {
455                let py_reports: PyResult<Vec<_>> = reports
456                    .into_iter()
457                    .map(|report| report.into_py_any(py))
458                    .collect();
459                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
460                Ok(pylist)
461            })
462        })
463    }
464
465    #[pyo3(name = "request_position_status_reports")]
466    #[pyo3(signature = (account_id, product_type, instrument_id=None))]
467    fn py_request_position_status_reports<'py>(
468        &self,
469        py: Python<'py>,
470        account_id: AccountId,
471        product_type: BybitProductType,
472        instrument_id: Option<InstrumentId>,
473    ) -> PyResult<Bound<'py, PyAny>> {
474        let client = self.clone();
475
476        pyo3_async_runtimes::tokio::future_into_py(py, async move {
477            let reports = client
478                .request_position_status_reports(account_id, product_type, instrument_id)
479                .await
480                .map_err(to_pyvalue_err)?;
481
482            Python::attach(|py| {
483                let py_reports: PyResult<Vec<_>> = reports
484                    .into_iter()
485                    .map(|report| report.into_py_any(py))
486                    .collect();
487                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
488                Ok(pylist)
489            })
490        })
491    }
492}