nautilus_deribit/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 Deribit HTTP client.
17
18use chrono::{DateTime, Utc};
19use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err};
20use nautilus_model::{
21    data::BarType,
22    identifiers::{AccountId, InstrumentId},
23    python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
24};
25use pyo3::{prelude::*, types::PyList};
26
27use crate::http::{
28    client::DeribitHttpClient,
29    error::DeribitHttpError,
30    models::{DeribitCurrency, DeribitInstrumentKind},
31};
32
33#[pymethods]
34impl DeribitHttpClient {
35    #[new]
36    #[pyo3(signature = (
37        api_key=None,
38        api_secret=None,
39        base_url=None,
40        is_testnet=false,
41        timeout_secs=None,
42        max_retries=None,
43        retry_delay_ms=None,
44        retry_delay_max_ms=None,
45        proxy_url=None,
46    ))]
47    #[allow(clippy::too_many_arguments)]
48    #[allow(unused_variables)]
49    fn py_new(
50        api_key: Option<String>,
51        api_secret: Option<String>,
52        base_url: Option<String>,
53        is_testnet: bool,
54        timeout_secs: Option<u64>,
55        max_retries: Option<u32>,
56        retry_delay_ms: Option<u64>,
57        retry_delay_max_ms: Option<u64>,
58        proxy_url: Option<String>,
59    ) -> PyResult<Self> {
60        Self::new_with_env(
61            api_key,
62            api_secret,
63            is_testnet,
64            timeout_secs,
65            max_retries,
66            retry_delay_ms,
67            retry_delay_max_ms,
68            proxy_url,
69        )
70        .map_err(to_pyvalue_err)
71    }
72
73    #[getter]
74    #[pyo3(name = "is_testnet")]
75    #[must_use]
76    pub fn py_is_testnet(&self) -> bool {
77        self.is_testnet()
78    }
79
80    #[pyo3(name = "is_initialized")]
81    #[must_use]
82    pub fn py_is_initialized(&self) -> bool {
83        self.is_cache_initialized()
84    }
85
86    /// # Errors
87    ///
88    /// Returns a Python exception if adding the instruments to the cache fails.
89    #[pyo3(name = "cache_instruments")]
90    pub fn py_cache_instruments(
91        &self,
92        py: Python<'_>,
93        instruments: Vec<Py<PyAny>>,
94    ) -> PyResult<()> {
95        let instruments: Result<Vec<_>, _> = instruments
96            .into_iter()
97            .map(|inst| pyobject_to_instrument_any(py, inst))
98            .collect();
99        self.cache_instruments(instruments?);
100        Ok(())
101    }
102
103    /// # Errors
104    ///
105    /// Returns a Python exception if adding the instrument to the cache fails.
106    #[pyo3(name = "cache_instrument")]
107    pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
108        let inst = pyobject_to_instrument_any(py, instrument)?;
109        self.cache_instruments(vec![inst]);
110        Ok(())
111    }
112
113    #[pyo3(name = "request_instruments")]
114    #[pyo3(signature = (currency, kind=None))]
115    fn py_request_instruments<'py>(
116        &self,
117        py: Python<'py>,
118        currency: DeribitCurrency,
119        kind: Option<DeribitInstrumentKind>,
120    ) -> PyResult<Bound<'py, PyAny>> {
121        let client = self.clone();
122
123        pyo3_async_runtimes::tokio::future_into_py(py, async move {
124            let instruments = client
125                .request_instruments(currency, kind)
126                .await
127                .map_err(to_pyvalue_err)?;
128
129            Python::attach(|py| {
130                let py_instruments: PyResult<Vec<_>> = instruments
131                    .into_iter()
132                    .map(|inst| instrument_any_to_pyobject(py, inst))
133                    .collect();
134                let pylist = PyList::new(py, py_instruments?)
135                    .unwrap()
136                    .into_any()
137                    .unbind();
138                Ok(pylist)
139            })
140        })
141    }
142
143    #[pyo3(name = "request_instrument")]
144    fn py_request_instrument<'py>(
145        &self,
146        py: Python<'py>,
147        instrument_id: InstrumentId,
148    ) -> PyResult<Bound<'py, PyAny>> {
149        let client = self.clone();
150
151        pyo3_async_runtimes::tokio::future_into_py(py, async move {
152            let instrument = client
153                .request_instrument(instrument_id)
154                .await
155                .map_err(to_pyvalue_err)?;
156
157            Python::attach(|py| instrument_any_to_pyobject(py, instrument))
158        })
159    }
160
161    #[pyo3(name = "request_account_state")]
162    fn py_request_account_state<'py>(
163        &self,
164        py: Python<'py>,
165        account_id: AccountId,
166    ) -> PyResult<Bound<'py, PyAny>> {
167        let client = self.clone();
168
169        pyo3_async_runtimes::tokio::future_into_py(py, async move {
170            let account_state = client
171                .request_account_state(account_id)
172                .await
173                .map_err(to_pyvalue_err)?;
174
175            Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
176        })
177    }
178
179    #[pyo3(name = "request_trades")]
180    #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
181    fn py_request_trades<'py>(
182        &self,
183        py: Python<'py>,
184        instrument_id: InstrumentId,
185        start: Option<DateTime<Utc>>,
186        end: Option<DateTime<Utc>>,
187        limit: Option<u32>,
188    ) -> PyResult<Bound<'py, PyAny>> {
189        let client = self.clone();
190
191        pyo3_async_runtimes::tokio::future_into_py(py, async move {
192            let trades = client
193                .request_trades(instrument_id, start, end, limit)
194                .await
195                .map_err(to_pyvalue_err)?;
196
197            Python::attach(|py| {
198                let pylist = PyList::new(
199                    py,
200                    trades.into_iter().map(|trade| trade.into_py_any_unwrap(py)),
201                )?;
202                Ok(pylist.into_py_any_unwrap(py))
203            })
204        })
205    }
206
207    #[pyo3(name = "request_bars")]
208    #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
209    fn py_request_bars<'py>(
210        &self,
211        py: Python<'py>,
212        bar_type: BarType,
213        start: Option<DateTime<Utc>>,
214        end: Option<DateTime<Utc>>,
215        limit: Option<u32>,
216    ) -> PyResult<Bound<'py, PyAny>> {
217        let client = self.clone();
218
219        pyo3_async_runtimes::tokio::future_into_py(py, async move {
220            let bars = client
221                .request_bars(bar_type, start, end, limit)
222                .await
223                .map_err(to_pyvalue_err)?;
224
225            Python::attach(|py| {
226                let pylist =
227                    PyList::new(py, bars.into_iter().map(|bar| bar.into_py_any_unwrap(py)))?;
228                Ok(pylist.into_py_any_unwrap(py))
229            })
230        })
231    }
232
233    #[pyo3(name = "request_book_snapshot")]
234    #[pyo3(signature = (instrument_id, depth=None))]
235    fn py_request_book_snapshot<'py>(
236        &self,
237        py: Python<'py>,
238        instrument_id: InstrumentId,
239        depth: Option<u32>,
240    ) -> PyResult<Bound<'py, PyAny>> {
241        let client = self.clone();
242
243        pyo3_async_runtimes::tokio::future_into_py(py, async move {
244            let book = client
245                .request_book_snapshot(instrument_id, depth)
246                .await
247                .map_err(to_pyvalue_err)?;
248
249            Python::attach(|py| Ok(book.into_py_any_unwrap(py)))
250        })
251    }
252}
253
254impl From<DeribitHttpError> for PyErr {
255    fn from(error: DeribitHttpError) -> Self {
256        match error {
257            // Runtime/operational errors
258            DeribitHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
259            DeribitHttpError::NetworkError(msg) => {
260                to_pyruntime_err(format!("Network error: {msg}"))
261            }
262            DeribitHttpError::UnexpectedStatus { status, body } => {
263                to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
264            }
265            DeribitHttpError::Timeout(msg) => to_pyruntime_err(format!("Request timeout: {msg}")),
266            // Validation/configuration errors
267            DeribitHttpError::MissingCredentials => {
268                to_pyvalue_err("Missing credentials for authenticated request")
269            }
270            DeribitHttpError::ValidationError(msg) => {
271                to_pyvalue_err(format!("Parameter validation error: {msg}"))
272            }
273            DeribitHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
274            DeribitHttpError::DeribitError {
275                error_code,
276                message,
277            } => to_pyvalue_err(format!("Deribit error {error_code}: {message}")),
278        }
279    }
280}