nautilus_dydx/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 dYdX HTTP client.
17
18use std::str::FromStr;
19
20use nautilus_core::python::to_pyvalue_err;
21use nautilus_model::python::instruments::instrument_any_to_pyobject;
22use pyo3::prelude::*;
23use rust_decimal::Decimal;
24use ustr::Ustr;
25
26use crate::http::client::DydxHttpClient;
27
28#[pymethods]
29impl DydxHttpClient {
30    /// Creates a new [`DydxHttpClient`] instance.
31    #[new]
32    #[pyo3(signature = (base_url=None, is_testnet=false))]
33    fn py_new(base_url: Option<String>, is_testnet: bool) -> PyResult<Self> {
34        // Mirror the Rust client's constructor signature with sensible defaults
35        Self::new(
36            base_url, None, // timeout_secs
37            None, // proxy_url
38            is_testnet, None, // retry_config
39        )
40        .map_err(to_pyvalue_err)
41    }
42
43    /// Returns `true` if the client is configured for testnet.
44    #[pyo3(name = "is_testnet")]
45    fn py_is_testnet(&self) -> bool {
46        self.is_testnet()
47    }
48
49    /// Returns the base URL for the HTTP client.
50    #[pyo3(name = "base_url")]
51    fn py_base_url(&self) -> String {
52        self.base_url().to_string()
53    }
54
55    /// Requests all available instruments from the dYdX Indexer API.
56    #[pyo3(name = "request_instruments")]
57    fn py_request_instruments<'py>(
58        &self,
59        py: Python<'py>,
60        maker_fee: Option<String>,
61        taker_fee: Option<String>,
62    ) -> PyResult<Bound<'py, PyAny>> {
63        let maker = maker_fee
64            .as_ref()
65            .map(|s| Decimal::from_str(s))
66            .transpose()
67            .map_err(to_pyvalue_err)?;
68
69        let taker = taker_fee
70            .as_ref()
71            .map(|s| Decimal::from_str(s))
72            .transpose()
73            .map_err(to_pyvalue_err)?;
74
75        let client = self.clone();
76
77        pyo3_async_runtimes::tokio::future_into_py(py, async move {
78            let instruments = client
79                .request_instruments(None, maker, taker)
80                .await
81                .map_err(to_pyvalue_err)?;
82
83            #[allow(deprecated)]
84            Python::with_gil(|py| {
85                let py_instruments: PyResult<Vec<Py<PyAny>>> = instruments
86                    .into_iter()
87                    .map(|inst| instrument_any_to_pyobject(py, inst))
88                    .collect();
89                py_instruments
90            })
91        })
92    }
93
94    /// Gets a cached instrument by symbol.
95    #[pyo3(name = "get_instrument")]
96    fn py_get_instrument(&self, py: Python<'_>, symbol: &str) -> PyResult<Option<Py<PyAny>>> {
97        let symbol_ustr = Ustr::from(symbol);
98        let instrument = self.get_instrument(&symbol_ustr);
99        match instrument {
100            Some(inst) => Ok(Some(instrument_any_to_pyobject(py, inst)?)),
101            None => Ok(None),
102        }
103    }
104
105    /// Returns the number of cached instruments.
106    #[pyo3(name = "instrument_count")]
107    fn py_instrument_count(&self) -> usize {
108        self.instruments().len()
109    }
110
111    /// Returns all cached instrument symbols.
112    #[pyo3(name = "instrument_symbols")]
113    fn py_instrument_symbols(&self) -> Vec<String> {
114        self.instruments()
115            .iter()
116            .map(|entry| entry.key().to_string())
117            .collect()
118    }
119
120    fn __repr__(&self) -> String {
121        format!(
122            "DydxHttpClient(base_url='{}', is_testnet={}, cached_instruments={})",
123            self.base_url(),
124            self.is_testnet(),
125            self.instruments().len()
126        )
127    }
128}