Skip to main content

nautilus_bybit/python/
http.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 for the Bybit HTTP client.
17
18use chrono::{DateTime, Utc};
19use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
20use nautilus_model::{
21    data::BarType,
22    enums::{OrderSide, OrderType, TimeInForce},
23    identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
24    python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
25    types::{Price, Quantity},
26};
27use pyo3::{conversion::IntoPyObjectExt, prelude::*, types::PyList};
28
29use crate::{
30    common::enums::{
31        BybitMarginMode, BybitOpenOnly, BybitOrderFilter, BybitPositionMode, BybitProductType,
32    },
33    http::{
34        client::{BybitHttpClient, BybitRawHttpClient},
35        error::BybitHttpError,
36        models::BybitOrderCursorList,
37    },
38};
39
40#[pymethods]
41impl BybitRawHttpClient {
42    #[new]
43    #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, testnet=false, timeout_secs=None, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None, recv_window_ms=None, proxy_url=None))]
44    #[allow(clippy::too_many_arguments)]
45    fn py_new(
46        api_key: Option<String>,
47        api_secret: Option<String>,
48        base_url: Option<String>,
49        demo: bool,
50        testnet: bool,
51        timeout_secs: Option<u64>,
52        max_retries: Option<u32>,
53        retry_delay_ms: Option<u64>,
54        retry_delay_max_ms: Option<u64>,
55        recv_window_ms: Option<u64>,
56        proxy_url: Option<String>,
57    ) -> PyResult<Self> {
58        Self::new_with_env(
59            api_key,
60            api_secret,
61            base_url,
62            demo,
63            testnet,
64            timeout_secs.or(Some(60)),
65            max_retries,
66            retry_delay_ms,
67            retry_delay_max_ms,
68            recv_window_ms,
69            proxy_url,
70        )
71        .map_err(to_pyvalue_err)
72    }
73
74    #[getter]
75    #[pyo3(name = "base_url")]
76    #[must_use]
77    pub fn py_base_url(&self) -> &str {
78        self.base_url()
79    }
80
81    #[getter]
82    #[pyo3(name = "api_key")]
83    #[must_use]
84    pub fn py_api_key(&self) -> Option<String> {
85        self.credential().map(|c| c.api_key().to_string())
86    }
87
88    #[getter]
89    #[pyo3(name = "recv_window_ms")]
90    #[must_use]
91    pub fn py_recv_window_ms(&self) -> u64 {
92        self.recv_window_ms()
93    }
94
95    #[pyo3(name = "cancel_all_requests")]
96    fn py_cancel_all_requests(&self) {
97        self.cancel_all_requests();
98    }
99
100    #[pyo3(name = "get_server_time")]
101    fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
102        let client = self.clone();
103
104        pyo3_async_runtimes::tokio::future_into_py(py, async move {
105            let response = client.get_server_time().await.map_err(to_pyvalue_err)?;
106
107            Python::attach(|py| {
108                let server_time = Py::new(py, response.result)?;
109                Ok(server_time.into_any())
110            })
111        })
112    }
113
114    #[pyo3(name = "get_open_orders")]
115    #[pyo3(signature = (category, symbol=None, base_coin=None, settle_coin=None, order_id=None, order_link_id=None, open_only=None, order_filter=None, limit=None, cursor=None))]
116    #[allow(clippy::too_many_arguments)]
117    fn py_get_open_orders<'py>(
118        &self,
119        py: Python<'py>,
120        category: BybitProductType,
121        symbol: Option<String>,
122        base_coin: Option<String>,
123        settle_coin: Option<String>,
124        order_id: Option<String>,
125        order_link_id: Option<String>,
126        open_only: Option<BybitOpenOnly>,
127        order_filter: Option<BybitOrderFilter>,
128        limit: Option<u32>,
129        cursor: Option<String>,
130    ) -> PyResult<Bound<'py, PyAny>> {
131        let client = self.clone();
132
133        pyo3_async_runtimes::tokio::future_into_py(py, async move {
134            let response = client
135                .get_open_orders(
136                    category,
137                    symbol,
138                    base_coin,
139                    settle_coin,
140                    order_id,
141                    order_link_id,
142                    open_only,
143                    order_filter,
144                    limit,
145                    cursor,
146                )
147                .await
148                .map_err(to_pyvalue_err)?;
149
150            Python::attach(|py| {
151                let open_orders = BybitOrderCursorList::from(response.result);
152                let py_open_orders = Py::new(py, open_orders)?;
153                Ok(py_open_orders.into_any())
154            })
155        })
156    }
157}
158
159#[pymethods]
160impl BybitHttpClient {
161    #[new]
162    #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, testnet=false, timeout_secs=None, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None, recv_window_ms=None, proxy_url=None))]
163    #[allow(clippy::too_many_arguments)]
164    fn py_new(
165        api_key: Option<String>,
166        api_secret: Option<String>,
167        base_url: Option<String>,
168        demo: bool,
169        testnet: bool,
170        timeout_secs: Option<u64>,
171        max_retries: Option<u32>,
172        retry_delay_ms: Option<u64>,
173        retry_delay_max_ms: Option<u64>,
174        recv_window_ms: Option<u64>,
175        proxy_url: Option<String>,
176    ) -> PyResult<Self> {
177        Self::new_with_env(
178            api_key,
179            api_secret,
180            base_url,
181            demo,
182            testnet,
183            timeout_secs.or(Some(60)),
184            max_retries,
185            retry_delay_ms,
186            retry_delay_max_ms,
187            recv_window_ms,
188            proxy_url,
189        )
190        .map_err(to_pyvalue_err)
191    }
192
193    #[getter]
194    #[pyo3(name = "base_url")]
195    #[must_use]
196    pub fn py_base_url(&self) -> &str {
197        self.base_url()
198    }
199
200    #[getter]
201    #[pyo3(name = "api_key")]
202    #[must_use]
203    pub fn py_api_key(&self) -> Option<&str> {
204        self.credential().map(|c| c.api_key()).map(|u| u.as_str())
205    }
206
207    #[getter]
208    #[pyo3(name = "api_key_masked")]
209    #[must_use]
210    pub fn py_api_key_masked(&self) -> Option<String> {
211        self.credential().map(|c| c.api_key_masked())
212    }
213
214    #[pyo3(name = "cache_instrument")]
215    fn py_cache_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
216        let inst_any = pyobject_to_instrument_any(py, instrument)?;
217        self.cache_instrument(inst_any);
218        Ok(())
219    }
220
221    #[pyo3(name = "cancel_all_requests")]
222    fn py_cancel_all_requests(&self) {
223        self.cancel_all_requests();
224    }
225
226    #[pyo3(name = "set_use_spot_position_reports")]
227    fn py_set_use_spot_position_reports(&self, value: bool) {
228        self.set_use_spot_position_reports(value);
229    }
230
231    #[pyo3(name = "set_margin_mode")]
232    fn py_set_margin_mode<'py>(
233        &self,
234        py: Python<'py>,
235        margin_mode: BybitMarginMode,
236    ) -> PyResult<Bound<'py, PyAny>> {
237        let client = self.clone();
238
239        pyo3_async_runtimes::tokio::future_into_py(py, async move {
240            client
241                .set_margin_mode(margin_mode)
242                .await
243                .map_err(to_pyvalue_err)?;
244
245            Python::attach(|py| Ok(py.None()))
246        })
247    }
248
249    #[pyo3(name = "get_account_details")]
250    fn py_get_account_details<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
251        let client = self.clone();
252
253        pyo3_async_runtimes::tokio::future_into_py(py, async move {
254            let response = client.get_account_details().await.map_err(to_pyvalue_err)?;
255
256            Python::attach(|py| {
257                let account_details = Py::new(py, response.result)?;
258                Ok(account_details.into_any())
259            })
260        })
261    }
262
263    #[pyo3(name = "set_leverage")]
264    #[pyo3(signature = (product_type, symbol, buy_leverage, sell_leverage))]
265    fn py_set_leverage<'py>(
266        &self,
267        py: Python<'py>,
268        product_type: BybitProductType,
269        symbol: String,
270        buy_leverage: String,
271        sell_leverage: String,
272    ) -> PyResult<Bound<'py, PyAny>> {
273        let client = self.clone();
274
275        pyo3_async_runtimes::tokio::future_into_py(py, async move {
276            client
277                .set_leverage(product_type, &symbol, &buy_leverage, &sell_leverage)
278                .await
279                .map_err(to_pyvalue_err)?;
280
281            Python::attach(|py| Ok(py.None()))
282        })
283    }
284
285    #[pyo3(name = "switch_mode")]
286    #[pyo3(signature = (product_type, mode, symbol=None, coin=None))]
287    fn py_switch_mode<'py>(
288        &self,
289        py: Python<'py>,
290        product_type: BybitProductType,
291        mode: BybitPositionMode,
292        symbol: Option<String>,
293        coin: Option<String>,
294    ) -> PyResult<Bound<'py, PyAny>> {
295        let client = self.clone();
296
297        pyo3_async_runtimes::tokio::future_into_py(py, async move {
298            client
299                .switch_mode(product_type, mode, symbol, coin)
300                .await
301                .map_err(to_pyvalue_err)?;
302
303            Python::attach(|py| Ok(py.None()))
304        })
305    }
306
307    #[pyo3(name = "get_spot_borrow_amount")]
308    fn py_get_spot_borrow_amount<'py>(
309        &self,
310        py: Python<'py>,
311        coin: String,
312    ) -> PyResult<Bound<'py, PyAny>> {
313        let client = self.clone();
314
315        pyo3_async_runtimes::tokio::future_into_py(py, async move {
316            let borrow_amount = client
317                .get_spot_borrow_amount(&coin)
318                .await
319                .map_err(to_pyvalue_err)?;
320
321            Ok(borrow_amount)
322        })
323    }
324
325    #[pyo3(name = "borrow_spot")]
326    #[pyo3(signature = (coin, amount))]
327    fn py_borrow_spot<'py>(
328        &self,
329        py: Python<'py>,
330        coin: String,
331        amount: Quantity,
332    ) -> PyResult<Bound<'py, PyAny>> {
333        let client = self.clone();
334
335        pyo3_async_runtimes::tokio::future_into_py(py, async move {
336            client
337                .borrow_spot(&coin, amount)
338                .await
339                .map_err(to_pyvalue_err)?;
340
341            Python::attach(|py| Ok(py.None()))
342        })
343    }
344
345    #[pyo3(name = "repay_spot_borrow")]
346    #[pyo3(signature = (coin, amount=None))]
347    fn py_repay_spot_borrow<'py>(
348        &self,
349        py: Python<'py>,
350        coin: String,
351        amount: Option<Quantity>,
352    ) -> PyResult<Bound<'py, PyAny>> {
353        let client = self.clone();
354
355        pyo3_async_runtimes::tokio::future_into_py(py, async move {
356            client
357                .repay_spot_borrow(&coin, amount)
358                .await
359                .map_err(to_pyvalue_err)?;
360
361            Python::attach(|py| Ok(py.None()))
362        })
363    }
364
365    #[pyo3(name = "request_instruments")]
366    #[pyo3(signature = (product_type, symbol=None))]
367    fn py_request_instruments<'py>(
368        &self,
369        py: Python<'py>,
370        product_type: BybitProductType,
371        symbol: Option<String>,
372    ) -> PyResult<Bound<'py, PyAny>> {
373        let client = self.clone();
374
375        pyo3_async_runtimes::tokio::future_into_py(py, async move {
376            let instruments = client
377                .request_instruments(product_type, symbol)
378                .await
379                .map_err(to_pyvalue_err)?;
380
381            Python::attach(|py| {
382                let py_instruments: PyResult<Vec<_>> = instruments
383                    .into_iter()
384                    .map(|inst| instrument_any_to_pyobject(py, inst))
385                    .collect();
386                let pylist = PyList::new(py, py_instruments?)
387                    .unwrap()
388                    .into_any()
389                    .unbind();
390                Ok(pylist)
391            })
392        })
393    }
394
395    #[pyo3(name = "request_tickers")]
396    fn py_request_tickers<'py>(
397        &self,
398        py: Python<'py>,
399        params: crate::python::params::BybitTickersParams,
400    ) -> PyResult<Bound<'py, PyAny>> {
401        let client = self.clone();
402
403        pyo3_async_runtimes::tokio::future_into_py(py, async move {
404            let tickers = client
405                .request_tickers(&params.into())
406                .await
407                .map_err(to_pyvalue_err)?;
408
409            Python::attach(|py| {
410                let py_tickers: PyResult<Vec<_>> = tickers
411                    .into_iter()
412                    .map(|ticker| Py::new(py, ticker))
413                    .collect();
414                let pylist = PyList::new(py, py_tickers?).unwrap().into_any().unbind();
415                Ok(pylist)
416            })
417        })
418    }
419
420    #[pyo3(name = "submit_order")]
421    #[pyo3(signature = (
422        account_id,
423        product_type,
424        instrument_id,
425        client_order_id,
426        order_side,
427        order_type,
428        quantity,
429        time_in_force = None,
430        price = None,
431        trigger_price = None,
432        post_only = None,
433        reduce_only = false,
434        is_quote_quantity = false,
435        is_leverage = false
436    ))]
437    #[allow(clippy::too_many_arguments)]
438    fn py_submit_order<'py>(
439        &self,
440        py: Python<'py>,
441        account_id: AccountId,
442        product_type: BybitProductType,
443        instrument_id: InstrumentId,
444        client_order_id: ClientOrderId,
445        order_side: OrderSide,
446        order_type: OrderType,
447        quantity: Quantity,
448        time_in_force: Option<TimeInForce>,
449        price: Option<Price>,
450        trigger_price: Option<Price>,
451        post_only: Option<bool>,
452        reduce_only: bool,
453        is_quote_quantity: bool,
454        is_leverage: bool,
455    ) -> PyResult<Bound<'py, PyAny>> {
456        let client = self.clone();
457
458        pyo3_async_runtimes::tokio::future_into_py(py, async move {
459            let report = client
460                .submit_order(
461                    account_id,
462                    product_type,
463                    instrument_id,
464                    client_order_id,
465                    order_side,
466                    order_type,
467                    quantity,
468                    time_in_force,
469                    price,
470                    trigger_price,
471                    post_only,
472                    reduce_only,
473                    is_quote_quantity,
474                    is_leverage,
475                )
476                .await
477                .map_err(to_pyvalue_err)?;
478
479            Python::attach(|py| report.into_py_any(py))
480        })
481    }
482
483    #[pyo3(name = "modify_order")]
484    #[pyo3(signature = (
485        account_id,
486        product_type,
487        instrument_id,
488        client_order_id=None,
489        venue_order_id=None,
490        quantity=None,
491        price=None
492    ))]
493    #[allow(clippy::too_many_arguments)]
494    fn py_modify_order<'py>(
495        &self,
496        py: Python<'py>,
497        account_id: AccountId,
498        product_type: BybitProductType,
499        instrument_id: InstrumentId,
500        client_order_id: Option<ClientOrderId>,
501        venue_order_id: Option<VenueOrderId>,
502        quantity: Option<Quantity>,
503        price: Option<Price>,
504    ) -> PyResult<Bound<'py, PyAny>> {
505        let client = self.clone();
506
507        pyo3_async_runtimes::tokio::future_into_py(py, async move {
508            let report = client
509                .modify_order(
510                    account_id,
511                    product_type,
512                    instrument_id,
513                    client_order_id,
514                    venue_order_id,
515                    quantity,
516                    price,
517                )
518                .await
519                .map_err(to_pyvalue_err)?;
520
521            Python::attach(|py| report.into_py_any(py))
522        })
523    }
524
525    #[pyo3(name = "cancel_order")]
526    #[pyo3(signature = (account_id, product_type, instrument_id, client_order_id=None, venue_order_id=None))]
527    fn py_cancel_order<'py>(
528        &self,
529        py: Python<'py>,
530        account_id: AccountId,
531        product_type: BybitProductType,
532        instrument_id: InstrumentId,
533        client_order_id: Option<ClientOrderId>,
534        venue_order_id: Option<VenueOrderId>,
535    ) -> PyResult<Bound<'py, PyAny>> {
536        let client = self.clone();
537
538        pyo3_async_runtimes::tokio::future_into_py(py, async move {
539            let report = client
540                .cancel_order(
541                    account_id,
542                    product_type,
543                    instrument_id,
544                    client_order_id,
545                    venue_order_id,
546                )
547                .await
548                .map_err(to_pyvalue_err)?;
549
550            Python::attach(|py| report.into_py_any(py))
551        })
552    }
553
554    #[pyo3(name = "cancel_all_orders")]
555    fn py_cancel_all_orders<'py>(
556        &self,
557        py: Python<'py>,
558        account_id: AccountId,
559        product_type: BybitProductType,
560        instrument_id: InstrumentId,
561    ) -> PyResult<Bound<'py, PyAny>> {
562        let client = self.clone();
563
564        pyo3_async_runtimes::tokio::future_into_py(py, async move {
565            let reports = client
566                .cancel_all_orders(account_id, product_type, instrument_id)
567                .await
568                .map_err(to_pyvalue_err)?;
569
570            Python::attach(|py| {
571                let py_reports: PyResult<Vec<_>> = reports
572                    .into_iter()
573                    .map(|report| report.into_py_any(py))
574                    .collect();
575                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
576                Ok(pylist)
577            })
578        })
579    }
580
581    #[pyo3(name = "query_order")]
582    #[pyo3(signature = (account_id, product_type, instrument_id, client_order_id=None, venue_order_id=None))]
583    fn py_query_order<'py>(
584        &self,
585        py: Python<'py>,
586        account_id: AccountId,
587        product_type: BybitProductType,
588        instrument_id: InstrumentId,
589        client_order_id: Option<ClientOrderId>,
590        venue_order_id: Option<VenueOrderId>,
591    ) -> PyResult<Bound<'py, PyAny>> {
592        let client = self.clone();
593
594        pyo3_async_runtimes::tokio::future_into_py(py, async move {
595            match client
596                .query_order(
597                    account_id,
598                    product_type,
599                    instrument_id,
600                    client_order_id,
601                    venue_order_id,
602                )
603                .await
604            {
605                Ok(Some(report)) => Python::attach(|py| report.into_py_any(py)),
606                Ok(None) => Ok(Python::attach(|py| py.None())),
607                Err(e) => Err(to_pyvalue_err(e)),
608            }
609        })
610    }
611
612    #[pyo3(name = "request_trades")]
613    #[pyo3(signature = (product_type, instrument_id, limit=None))]
614    fn py_request_trades<'py>(
615        &self,
616        py: Python<'py>,
617        product_type: BybitProductType,
618        instrument_id: InstrumentId,
619        limit: Option<u32>,
620    ) -> PyResult<Bound<'py, PyAny>> {
621        let client = self.clone();
622
623        pyo3_async_runtimes::tokio::future_into_py(py, async move {
624            let trades = client
625                .request_trades(product_type, instrument_id, limit)
626                .await
627                .map_err(to_pyvalue_err)?;
628
629            Python::attach(|py| {
630                let py_trades: PyResult<Vec<_>> = trades
631                    .into_iter()
632                    .map(|trade| trade.into_py_any(py))
633                    .collect();
634                let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
635                Ok(pylist)
636            })
637        })
638    }
639
640    #[pyo3(name = "request_funding_rates")]
641    #[pyo3(signature = (product_type, instrument_id, start=None, end=None, limit=None))]
642    fn py_request_funding_rates<'py>(
643        &self,
644        py: Python<'py>,
645        product_type: BybitProductType,
646        instrument_id: InstrumentId,
647        start: Option<DateTime<Utc>>,
648        end: Option<DateTime<Utc>>,
649        limit: Option<u32>,
650    ) -> PyResult<Bound<'py, PyAny>> {
651        let client = self.clone();
652
653        pyo3_async_runtimes::tokio::future_into_py(py, async move {
654            let funding_rates = client
655                .request_funding_rates(product_type, instrument_id, start, end, limit)
656                .await
657                .map_err(to_pyvalue_err)?;
658
659            Python::attach(|py| {
660                let py_funding_rates: PyResult<Vec<_>> = funding_rates
661                    .into_iter()
662                    .map(|funding_rate| funding_rate.into_py_any(py))
663                    .collect();
664                let pylist = PyList::new(py, py_funding_rates?)
665                    .unwrap()
666                    .into_any()
667                    .unbind();
668                Ok(pylist)
669            })
670        })
671    }
672
673    #[pyo3(name = "request_orderbook_snapshot")]
674    #[pyo3(signature = (product_type, instrument_id, limit=None))]
675    fn py_request_orderbook_snapshot<'py>(
676        &self,
677        py: Python<'py>,
678        product_type: BybitProductType,
679        instrument_id: InstrumentId,
680        limit: Option<u32>,
681    ) -> PyResult<Bound<'py, PyAny>> {
682        let client = self.clone();
683
684        pyo3_async_runtimes::tokio::future_into_py(py, async move {
685            let deltas = client
686                .request_orderbook_snapshot(product_type, instrument_id, limit)
687                .await
688                .map_err(to_pyvalue_err)?;
689
690            Python::attach(|py| Ok(deltas.into_py_any(py).unwrap()))
691        })
692    }
693
694    #[pyo3(name = "request_bars")]
695    #[pyo3(signature = (product_type, bar_type, start=None, end=None, limit=None, timestamp_on_close=true))]
696    #[allow(clippy::too_many_arguments)]
697    fn py_request_bars<'py>(
698        &self,
699        py: Python<'py>,
700        product_type: BybitProductType,
701        bar_type: BarType,
702        start: Option<DateTime<Utc>>,
703        end: Option<DateTime<Utc>>,
704        limit: Option<u32>,
705        timestamp_on_close: bool,
706    ) -> PyResult<Bound<'py, PyAny>> {
707        let client = self.clone();
708
709        pyo3_async_runtimes::tokio::future_into_py(py, async move {
710            let bars = client
711                .request_bars(
712                    product_type,
713                    bar_type,
714                    start,
715                    end,
716                    limit,
717                    timestamp_on_close,
718                )
719                .await
720                .map_err(to_pyvalue_err)?;
721
722            Python::attach(|py| {
723                let py_bars: PyResult<Vec<_>> =
724                    bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
725                let pylist = PyList::new(py, py_bars?).unwrap().into_any().unbind();
726                Ok(pylist)
727            })
728        })
729    }
730
731    #[pyo3(name = "request_fee_rates")]
732    #[pyo3(signature = (product_type, symbol=None, base_coin=None))]
733    fn py_request_fee_rates<'py>(
734        &self,
735        py: Python<'py>,
736        product_type: BybitProductType,
737        symbol: Option<String>,
738        base_coin: Option<String>,
739    ) -> PyResult<Bound<'py, PyAny>> {
740        let client = self.clone();
741
742        pyo3_async_runtimes::tokio::future_into_py(py, async move {
743            let fee_rates = client
744                .request_fee_rates(product_type, symbol, base_coin)
745                .await
746                .map_err(to_pyvalue_err)?;
747
748            Python::attach(|py| {
749                let py_fee_rates: PyResult<Vec<_>> = fee_rates
750                    .into_iter()
751                    .map(|rate| Py::new(py, rate))
752                    .collect();
753                let pylist = PyList::new(py, py_fee_rates?).unwrap().into_any().unbind();
754                Ok(pylist)
755            })
756        })
757    }
758
759    #[pyo3(name = "request_account_state")]
760    fn py_request_account_state<'py>(
761        &self,
762        py: Python<'py>,
763        account_type: crate::common::enums::BybitAccountType,
764        account_id: AccountId,
765    ) -> PyResult<Bound<'py, PyAny>> {
766        let client = self.clone();
767
768        pyo3_async_runtimes::tokio::future_into_py(py, async move {
769            let account_state = client
770                .request_account_state(account_type, account_id)
771                .await
772                .map_err(to_pyvalue_err)?;
773
774            Python::attach(|py| account_state.into_py_any(py))
775        })
776    }
777
778    #[pyo3(name = "request_order_status_reports")]
779    #[pyo3(signature = (account_id, product_type, instrument_id=None, open_only=false, start=None, end=None, limit=None))]
780    #[allow(clippy::too_many_arguments)]
781    fn py_request_order_status_reports<'py>(
782        &self,
783        py: Python<'py>,
784        account_id: AccountId,
785        product_type: BybitProductType,
786        instrument_id: Option<InstrumentId>,
787        open_only: bool,
788        start: Option<DateTime<Utc>>,
789        end: Option<DateTime<Utc>>,
790        limit: Option<u32>,
791    ) -> PyResult<Bound<'py, PyAny>> {
792        let client = self.clone();
793
794        pyo3_async_runtimes::tokio::future_into_py(py, async move {
795            let reports = client
796                .request_order_status_reports(
797                    account_id,
798                    product_type,
799                    instrument_id,
800                    open_only,
801                    start,
802                    end,
803                    limit,
804                )
805                .await
806                .map_err(to_pyvalue_err)?;
807
808            Python::attach(|py| {
809                let py_reports: PyResult<Vec<_>> = reports
810                    .into_iter()
811                    .map(|report| report.into_py_any(py))
812                    .collect();
813                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
814                Ok(pylist)
815            })
816        })
817    }
818
819    #[pyo3(name = "request_fill_reports")]
820    #[pyo3(signature = (account_id, product_type, instrument_id=None, start=None, end=None, limit=None))]
821    #[allow(clippy::too_many_arguments)]
822    fn py_request_fill_reports<'py>(
823        &self,
824        py: Python<'py>,
825        account_id: AccountId,
826        product_type: BybitProductType,
827        instrument_id: Option<InstrumentId>,
828        start: Option<i64>,
829        end: Option<i64>,
830        limit: Option<u32>,
831    ) -> PyResult<Bound<'py, PyAny>> {
832        let client = self.clone();
833
834        pyo3_async_runtimes::tokio::future_into_py(py, async move {
835            let reports = client
836                .request_fill_reports(account_id, product_type, instrument_id, start, end, limit)
837                .await
838                .map_err(to_pyvalue_err)?;
839
840            Python::attach(|py| {
841                let py_reports: PyResult<Vec<_>> = reports
842                    .into_iter()
843                    .map(|report| report.into_py_any(py))
844                    .collect();
845                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
846                Ok(pylist)
847            })
848        })
849    }
850
851    #[pyo3(name = "request_position_status_reports")]
852    #[pyo3(signature = (account_id, product_type, instrument_id=None))]
853    fn py_request_position_status_reports<'py>(
854        &self,
855        py: Python<'py>,
856        account_id: AccountId,
857        product_type: BybitProductType,
858        instrument_id: Option<InstrumentId>,
859    ) -> PyResult<Bound<'py, PyAny>> {
860        let client = self.clone();
861
862        pyo3_async_runtimes::tokio::future_into_py(py, async move {
863            let reports = client
864                .request_position_status_reports(account_id, product_type, instrument_id)
865                .await
866                .map_err(to_pyvalue_err)?;
867
868            Python::attach(|py| {
869                let py_reports: PyResult<Vec<_>> = reports
870                    .into_iter()
871                    .map(|report| report.into_py_any(py))
872                    .collect();
873                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
874                Ok(pylist)
875            })
876        })
877    }
878}
879
880impl From<BybitHttpError> for PyErr {
881    fn from(error: BybitHttpError) -> Self {
882        match error {
883            // Runtime/operational errors
884            BybitHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
885            BybitHttpError::NetworkError(msg) => to_pyruntime_err(format!("Network error: {msg}")),
886            BybitHttpError::UnexpectedStatus { status, body } => {
887                to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
888            }
889            // Validation/configuration errors
890            BybitHttpError::MissingCredentials => {
891                to_pyvalue_err("Missing credentials for authenticated request")
892            }
893            BybitHttpError::ValidationError(msg) => {
894                to_pyvalue_err(format!("Parameter validation error: {msg}"))
895            }
896            BybitHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
897            BybitHttpError::BuildError(e) => to_pyvalue_err(format!("Build error: {e}")),
898            BybitHttpError::BybitError {
899                error_code,
900                message,
901            } => to_pyvalue_err(format!("Bybit error {error_code}: {message}")),
902        }
903    }
904}