nautilus_bitmex/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
16use nautilus_core::python::to_pyvalue_err;
17use nautilus_model::{
18    enums::{OrderSide, OrderType, TimeInForce},
19    identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
20    python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
21    types::{Price, Quantity},
22};
23use pyo3::{conversion::IntoPyObjectExt, prelude::*, types::PyList};
24
25use crate::http::client::BitmexHttpClient;
26
27#[pymethods]
28impl BitmexHttpClient {
29    #[new]
30    #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, testnet=false, timeout_secs=None, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None))]
31    #[allow(clippy::too_many_arguments)]
32    fn py_new(
33        api_key: Option<&str>,
34        api_secret: Option<&str>,
35        base_url: Option<&str>,
36        testnet: bool,
37        timeout_secs: Option<u64>,
38        max_retries: Option<u32>,
39        retry_delay_ms: Option<u64>,
40        retry_delay_max_ms: Option<u64>,
41    ) -> PyResult<Self> {
42        let timeout = timeout_secs.or(Some(60));
43
44        // Try to use with_credentials if we have any credentials or need env vars
45        if api_key.is_none() && api_secret.is_none() && !testnet && base_url.is_none() {
46            // Try to load from environment
47            match Self::with_credentials(
48                None,
49                None,
50                base_url.map(String::from),
51                timeout,
52                max_retries,
53                retry_delay_ms,
54                retry_delay_max_ms,
55            ) {
56                Ok(client) => Ok(client),
57                Err(_) => {
58                    // Fall back to unauthenticated client
59                    Self::new(
60                        base_url.map(String::from),
61                        None,
62                        None,
63                        testnet,
64                        timeout,
65                        max_retries,
66                        retry_delay_ms,
67                        retry_delay_max_ms,
68                    )
69                    .map_err(to_pyvalue_err)
70                }
71            }
72        } else {
73            Self::new(
74                base_url.map(String::from),
75                api_key.map(String::from),
76                api_secret.map(String::from),
77                testnet,
78                timeout,
79                max_retries,
80                retry_delay_ms,
81                retry_delay_max_ms,
82            )
83            .map_err(to_pyvalue_err)
84        }
85    }
86
87    #[staticmethod]
88    #[pyo3(name = "from_env")]
89    fn py_from_env() -> PyResult<Self> {
90        Self::from_env().map_err(to_pyvalue_err)
91    }
92
93    #[getter]
94    #[pyo3(name = "base_url")]
95    #[must_use]
96    pub fn py_base_url(&self) -> &str {
97        self.base_url()
98    }
99
100    #[getter]
101    #[pyo3(name = "api_key")]
102    #[must_use]
103    pub fn py_api_key(&self) -> Option<&str> {
104        self.api_key()
105    }
106
107    #[pyo3(name = "update_position_leverage")]
108    fn py_update_position_leverage<'py>(
109        &self,
110        py: Python<'py>,
111        _symbol: String,
112        _leverage: f64,
113    ) -> PyResult<Bound<'py, PyAny>> {
114        let _client = self.clone();
115
116        pyo3_async_runtimes::tokio::future_into_py(py, async move {
117            // Call the leverage update method once it's implemented
118            // let report = client.update_position_leverage(&symbol, leverage)
119            //     .await
120            //     .map_err(to_pyvalue_err)?;
121
122            Python::with_gil(|py| -> PyResult<PyObject> {
123                // report.into_py_any(py).map_err(to_pyvalue_err)
124                Ok(py.None())
125            })
126        })
127    }
128
129    #[pyo3(name = "request_instrument")]
130    fn py_request_instrument<'py>(
131        &self,
132        py: Python<'py>,
133        instrument_id: InstrumentId,
134    ) -> PyResult<Bound<'py, PyAny>> {
135        let client = self.clone();
136
137        pyo3_async_runtimes::tokio::future_into_py(py, async move {
138            let instrument = client
139                .request_instrument(instrument_id)
140                .await
141                .map_err(to_pyvalue_err)?;
142
143            Python::with_gil(|py| match instrument {
144                Some(inst) => instrument_any_to_pyobject(py, inst),
145                None => Ok(py.None()),
146            })
147        })
148    }
149
150    #[pyo3(name = "request_instruments")]
151    fn py_request_instruments<'py>(
152        &self,
153        py: Python<'py>,
154        active_only: bool,
155    ) -> PyResult<Bound<'py, PyAny>> {
156        let client = self.clone();
157
158        pyo3_async_runtimes::tokio::future_into_py(py, async move {
159            let instruments = client
160                .request_instruments(active_only)
161                .await
162                .map_err(to_pyvalue_err)?;
163
164            Python::with_gil(|py| {
165                let py_instruments: PyResult<Vec<_>> = instruments
166                    .into_iter()
167                    .map(|inst| instrument_any_to_pyobject(py, inst))
168                    .collect();
169                let pylist = PyList::new(py, py_instruments?)
170                    .unwrap()
171                    .into_any()
172                    .unbind();
173                Ok(pylist)
174            })
175        })
176    }
177
178    #[pyo3(name = "request_trades")]
179    #[pyo3(signature = (instrument_id, limit=None))]
180    fn py_request_trades<'py>(
181        &self,
182        py: Python<'py>,
183        instrument_id: InstrumentId,
184        limit: Option<u32>,
185    ) -> PyResult<Bound<'py, PyAny>> {
186        let client = self.clone();
187
188        pyo3_async_runtimes::tokio::future_into_py(py, async move {
189            let trades = client
190                .request_trades(instrument_id, limit)
191                .await
192                .map_err(to_pyvalue_err)?;
193
194            Python::with_gil(|py| {
195                let py_trades: PyResult<Vec<_>> = trades
196                    .into_iter()
197                    .map(|trade| trade.into_py_any(py))
198                    .collect();
199                let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
200                Ok(pylist)
201            })
202        })
203    }
204
205    #[pyo3(name = "query_order")]
206    #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
207    fn py_query_order<'py>(
208        &self,
209        py: Python<'py>,
210        instrument_id: InstrumentId,
211        client_order_id: Option<ClientOrderId>,
212        venue_order_id: Option<VenueOrderId>,
213    ) -> PyResult<Bound<'py, PyAny>> {
214        let client = self.clone();
215
216        pyo3_async_runtimes::tokio::future_into_py(py, async move {
217            match client
218                .query_order(instrument_id, client_order_id, venue_order_id)
219                .await
220            {
221                Ok(Some(report)) => Python::with_gil(|py| report.into_py_any(py)),
222                Ok(None) => Ok(Python::with_gil(|py| py.None())),
223                Err(e) => Err(to_pyvalue_err(e)),
224            }
225        })
226    }
227
228    #[pyo3(name = "request_order_status_reports")]
229    #[pyo3(signature = (instrument_id=None, open_only=false, limit=None))]
230    fn py_request_order_status_reports<'py>(
231        &self,
232        py: Python<'py>,
233        instrument_id: Option<InstrumentId>,
234        open_only: bool,
235        limit: Option<u32>,
236    ) -> PyResult<Bound<'py, PyAny>> {
237        let client = self.clone();
238
239        pyo3_async_runtimes::tokio::future_into_py(py, async move {
240            let reports = client
241                .request_order_status_reports(instrument_id, open_only, limit)
242                .await
243                .map_err(to_pyvalue_err)?;
244
245            Python::with_gil(|py| {
246                let py_reports: PyResult<Vec<_>> = reports
247                    .into_iter()
248                    .map(|report| report.into_py_any(py))
249                    .collect();
250                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
251                Ok(pylist)
252            })
253        })
254    }
255
256    #[pyo3(name = "request_fill_reports")]
257    #[pyo3(signature = (instrument_id=None, limit=None))]
258    fn py_request_fill_reports<'py>(
259        &self,
260        py: Python<'py>,
261        instrument_id: Option<InstrumentId>,
262        limit: Option<u32>,
263    ) -> PyResult<Bound<'py, PyAny>> {
264        let client = self.clone();
265
266        pyo3_async_runtimes::tokio::future_into_py(py, async move {
267            let reports = client
268                .request_fill_reports(instrument_id, limit)
269                .await
270                .map_err(to_pyvalue_err)?;
271
272            Python::with_gil(|py| {
273                let py_reports: PyResult<Vec<_>> = reports
274                    .into_iter()
275                    .map(|report| report.into_py_any(py))
276                    .collect();
277                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
278                Ok(pylist)
279            })
280        })
281    }
282
283    #[pyo3(name = "request_position_status_reports")]
284    fn py_request_position_status_reports<'py>(
285        &self,
286        py: Python<'py>,
287    ) -> PyResult<Bound<'py, PyAny>> {
288        let client = self.clone();
289
290        pyo3_async_runtimes::tokio::future_into_py(py, async move {
291            let reports = client
292                .request_position_status_reports()
293                .await
294                .map_err(to_pyvalue_err)?;
295
296            Python::with_gil(|py| {
297                let py_reports: PyResult<Vec<_>> = reports
298                    .into_iter()
299                    .map(|report| report.into_py_any(py))
300                    .collect();
301                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
302                Ok(pylist)
303            })
304        })
305    }
306
307    #[pyo3(name = "submit_order")]
308    #[pyo3(signature = (
309        instrument_id,
310        client_order_id,
311        order_side,
312        order_type,
313        quantity,
314        time_in_force,
315        price = None,
316        trigger_price = None,
317        display_qty = None,
318        post_only = false,
319        reduce_only = false
320    ))]
321    #[allow(clippy::too_many_arguments)]
322    fn py_submit_order<'py>(
323        &self,
324        py: Python<'py>,
325        instrument_id: InstrumentId,
326        client_order_id: ClientOrderId,
327        order_side: OrderSide,
328        order_type: OrderType,
329        quantity: Quantity,
330        time_in_force: TimeInForce,
331        price: Option<Price>,
332        trigger_price: Option<Price>,
333        display_qty: Option<Quantity>,
334        post_only: bool,
335        reduce_only: bool,
336    ) -> PyResult<Bound<'py, PyAny>> {
337        let client = self.clone();
338
339        pyo3_async_runtimes::tokio::future_into_py(py, async move {
340            let report = client
341                .submit_order(
342                    instrument_id,
343                    client_order_id,
344                    order_side,
345                    order_type,
346                    quantity,
347                    time_in_force,
348                    price,
349                    trigger_price,
350                    display_qty,
351                    post_only,
352                    reduce_only,
353                )
354                .await
355                .map_err(to_pyvalue_err)?;
356
357            Python::with_gil(|py| report.into_py_any(py))
358        })
359    }
360
361    #[pyo3(name = "cancel_order")]
362    #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
363    fn py_cancel_order<'py>(
364        &self,
365        py: Python<'py>,
366        instrument_id: InstrumentId,
367        client_order_id: Option<ClientOrderId>,
368        venue_order_id: Option<VenueOrderId>,
369    ) -> PyResult<Bound<'py, PyAny>> {
370        let client = self.clone();
371
372        pyo3_async_runtimes::tokio::future_into_py(py, async move {
373            let report = client
374                .cancel_order(instrument_id, client_order_id, venue_order_id)
375                .await
376                .map_err(to_pyvalue_err)?;
377
378            Python::with_gil(|py| report.into_py_any(py))
379        })
380    }
381
382    #[pyo3(name = "cancel_orders")]
383    #[pyo3(signature = (instrument_id, client_order_ids=None, venue_order_ids=None))]
384    fn py_cancel_orders<'py>(
385        &self,
386        py: Python<'py>,
387        instrument_id: InstrumentId,
388        client_order_ids: Option<Vec<ClientOrderId>>,
389        venue_order_ids: Option<Vec<VenueOrderId>>,
390    ) -> PyResult<Bound<'py, PyAny>> {
391        let client = self.clone();
392
393        pyo3_async_runtimes::tokio::future_into_py(py, async move {
394            let reports = client
395                .cancel_orders(instrument_id, client_order_ids, venue_order_ids)
396                .await
397                .map_err(to_pyvalue_err)?;
398
399            Python::with_gil(|py| {
400                let py_reports: PyResult<Vec<_>> = reports
401                    .into_iter()
402                    .map(|report| report.into_py_any(py))
403                    .collect();
404                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
405                Ok(pylist)
406            })
407        })
408    }
409
410    #[pyo3(name = "cancel_all_orders")]
411    #[pyo3(signature = (instrument_id, order_side))]
412    fn py_cancel_all_orders<'py>(
413        &self,
414        py: Python<'py>,
415        instrument_id: InstrumentId,
416        order_side: Option<OrderSide>,
417    ) -> PyResult<Bound<'py, PyAny>> {
418        let client = self.clone();
419
420        pyo3_async_runtimes::tokio::future_into_py(py, async move {
421            let reports = client
422                .cancel_all_orders(instrument_id, order_side)
423                .await
424                .map_err(to_pyvalue_err)?;
425
426            Python::with_gil(|py| {
427                let py_reports: PyResult<Vec<_>> = reports
428                    .into_iter()
429                    .map(|report| report.into_py_any(py))
430                    .collect();
431                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
432                Ok(pylist)
433            })
434        })
435    }
436
437    #[pyo3(name = "modify_order")]
438    #[pyo3(signature = (
439        instrument_id,
440        client_order_id=None,
441        venue_order_id=None,
442        quantity=None,
443        price=None,
444        trigger_price=None
445    ))]
446    #[allow(clippy::too_many_arguments)]
447    fn py_modify_order<'py>(
448        &self,
449        py: Python<'py>,
450        instrument_id: InstrumentId,
451        client_order_id: Option<ClientOrderId>,
452        venue_order_id: Option<VenueOrderId>,
453        quantity: Option<Quantity>,
454        price: Option<Price>,
455        trigger_price: Option<Price>,
456    ) -> PyResult<Bound<'py, PyAny>> {
457        let client = self.clone();
458
459        pyo3_async_runtimes::tokio::future_into_py(py, async move {
460            let report = client
461                .modify_order(
462                    instrument_id,
463                    client_order_id,
464                    venue_order_id,
465                    quantity,
466                    price,
467                    trigger_price,
468                )
469                .await
470                .map_err(to_pyvalue_err)?;
471
472            Python::with_gil(|py| report.into_py_any(py))
473        })
474    }
475
476    #[pyo3(name = "add_instrument")]
477    fn py_add_instrument(&mut self, py: Python, instrument: PyObject) -> PyResult<()> {
478        let inst_any = pyobject_to_instrument_any(py, instrument)?;
479        self.add_instrument(inst_any);
480        Ok(())
481    }
482
483    #[pyo3(name = "http_get_margin")]
484    fn py_http_get_margin<'py>(
485        &self,
486        py: Python<'py>,
487        currency: String,
488    ) -> PyResult<Bound<'py, PyAny>> {
489        let client = self.clone();
490
491        pyo3_async_runtimes::tokio::future_into_py(py, async move {
492            let margin = client
493                .http_get_margin(&currency)
494                .await
495                .map_err(to_pyvalue_err)?;
496
497            Python::with_gil(|py| {
498                // Create a simple Python object with just the account field we need
499                // We can expand this if more fields are needed
500                let account = margin.account;
501                account.into_py_any(py)
502            })
503        })
504    }
505
506    #[pyo3(name = "request_account_state")]
507    fn py_request_account_state<'py>(
508        &self,
509        py: Python<'py>,
510        account_id: AccountId,
511    ) -> PyResult<Bound<'py, PyAny>> {
512        let client = self.clone();
513
514        pyo3_async_runtimes::tokio::future_into_py(py, async move {
515            let account_state = client
516                .request_account_state(account_id)
517                .await
518                .map_err(to_pyvalue_err)?;
519
520            Python::with_gil(|py| account_state.into_py_any(py).map_err(to_pyvalue_err))
521        })
522    }
523
524    #[pyo3(name = "submit_orders_bulk")]
525    fn py_submit_orders_bulk<'py>(
526        &self,
527        py: Python<'py>,
528        orders: Vec<PyObject>,
529    ) -> PyResult<Bound<'py, PyAny>> {
530        let _client = self.clone();
531
532        // Convert Python objects to PostOrderParams
533        let _params = Python::with_gil(|_py| {
534            orders
535                .into_iter()
536                .map(|obj| {
537                    // Extract order parameters from Python dict
538                    // This is a placeholder - actual implementation would need proper conversion
539                    Ok(obj)
540                })
541                .collect::<PyResult<Vec<_>>>()
542        })?;
543
544        pyo3_async_runtimes::tokio::future_into_py(py, async move {
545            // Call the bulk order method once it's implemented
546            // let reports = client.submit_orders_bulk(params).await.map_err(to_pyvalue_err)?;
547
548            Python::with_gil(|py| -> PyResult<PyObject> {
549                let py_list = PyList::new(py, Vec::<PyObject>::new())?;
550                // for report in reports {
551                //     py_list.append(report.into_py_any(py)?)?;
552                // }
553                Ok(py_list.into())
554            })
555        })
556    }
557
558    #[pyo3(name = "modify_orders_bulk")]
559    fn py_modify_orders_bulk<'py>(
560        &self,
561        py: Python<'py>,
562        orders: Vec<PyObject>,
563    ) -> PyResult<Bound<'py, PyAny>> {
564        let _client = self.clone();
565
566        // Convert Python objects to PutOrderParams
567        let _params = Python::with_gil(|_py| {
568            orders
569                .into_iter()
570                .map(|obj| {
571                    // Extract order parameters from Python dict
572                    // This is a placeholder - actual implementation would need proper conversion
573                    Ok(obj)
574                })
575                .collect::<PyResult<Vec<_>>>()
576        })?;
577
578        pyo3_async_runtimes::tokio::future_into_py(py, async move {
579            // Call the bulk amend method once it's implemented
580            // let reports = client.modify_orders_bulk(params).await.map_err(to_pyvalue_err)?;
581
582            Python::with_gil(|py| -> PyResult<PyObject> {
583                let py_list = PyList::new(py, Vec::<PyObject>::new())?;
584                // for report in reports {
585                //     py_list.append(report.into_py_any(py)?)?;
586                // }
587                Ok(py_list.into())
588            })
589        })
590    }
591}