Skip to main content

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::{conversion::IntoPyObjectExt, prelude::*, types::PyList};
26
27use crate::http::{
28    client::DeribitHttpClient,
29    error::DeribitHttpError,
30    models::{DeribitCurrency, DeribitProductType},
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            base_url,
64            is_testnet,
65            timeout_secs,
66            max_retries,
67            retry_delay_ms,
68            retry_delay_max_ms,
69            proxy_url,
70        )
71        .map_err(to_pyvalue_err)
72    }
73
74    #[getter]
75    #[pyo3(name = "is_testnet")]
76    #[must_use]
77    pub fn py_is_testnet(&self) -> bool {
78        self.is_testnet()
79    }
80
81    #[pyo3(name = "is_initialized")]
82    #[must_use]
83    pub fn py_is_initialized(&self) -> bool {
84        self.is_cache_initialized()
85    }
86
87    /// # Errors
88    ///
89    /// Returns a Python exception if adding the instruments to the cache fails.
90    #[pyo3(name = "cache_instruments")]
91    pub fn py_cache_instruments(
92        &self,
93        py: Python<'_>,
94        instruments: Vec<Py<PyAny>>,
95    ) -> PyResult<()> {
96        let instruments: Result<Vec<_>, _> = instruments
97            .into_iter()
98            .map(|inst| pyobject_to_instrument_any(py, inst))
99            .collect();
100        self.cache_instruments(instruments?);
101        Ok(())
102    }
103
104    /// # Errors
105    ///
106    /// Returns a Python exception if adding the instrument to the cache fails.
107    #[pyo3(name = "cache_instrument")]
108    pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
109        let inst = pyobject_to_instrument_any(py, instrument)?;
110        self.cache_instruments(vec![inst]);
111        Ok(())
112    }
113
114    #[pyo3(name = "request_instruments")]
115    #[pyo3(signature = (currency, product_type=None))]
116    fn py_request_instruments<'py>(
117        &self,
118        py: Python<'py>,
119        currency: DeribitCurrency,
120        product_type: Option<DeribitProductType>,
121    ) -> PyResult<Bound<'py, PyAny>> {
122        let client = self.clone();
123
124        pyo3_async_runtimes::tokio::future_into_py(py, async move {
125            let instruments = client
126                .request_instruments(currency, product_type)
127                .await
128                .map_err(to_pyvalue_err)?;
129
130            Python::attach(|py| {
131                let py_instruments: PyResult<Vec<_>> = instruments
132                    .into_iter()
133                    .map(|inst| instrument_any_to_pyobject(py, inst))
134                    .collect();
135                let pylist = PyList::new(py, py_instruments?)
136                    .unwrap()
137                    .into_any()
138                    .unbind();
139                Ok(pylist)
140            })
141        })
142    }
143
144    #[pyo3(name = "request_instrument")]
145    fn py_request_instrument<'py>(
146        &self,
147        py: Python<'py>,
148        instrument_id: InstrumentId,
149    ) -> PyResult<Bound<'py, PyAny>> {
150        let client = self.clone();
151
152        pyo3_async_runtimes::tokio::future_into_py(py, async move {
153            let instrument = client
154                .request_instrument(instrument_id)
155                .await
156                .map_err(to_pyvalue_err)?;
157
158            Python::attach(|py| instrument_any_to_pyobject(py, instrument))
159        })
160    }
161
162    #[pyo3(name = "request_account_state")]
163    fn py_request_account_state<'py>(
164        &self,
165        py: Python<'py>,
166        account_id: AccountId,
167    ) -> PyResult<Bound<'py, PyAny>> {
168        let client = self.clone();
169
170        pyo3_async_runtimes::tokio::future_into_py(py, async move {
171            let account_state = client
172                .request_account_state(account_id)
173                .await
174                .map_err(to_pyvalue_err)?;
175
176            Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
177        })
178    }
179
180    #[pyo3(name = "request_trades")]
181    #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
182    fn py_request_trades<'py>(
183        &self,
184        py: Python<'py>,
185        instrument_id: InstrumentId,
186        start: Option<DateTime<Utc>>,
187        end: Option<DateTime<Utc>>,
188        limit: Option<u32>,
189    ) -> PyResult<Bound<'py, PyAny>> {
190        let client = self.clone();
191
192        pyo3_async_runtimes::tokio::future_into_py(py, async move {
193            let trades = client
194                .request_trades(instrument_id, start, end, limit)
195                .await
196                .map_err(to_pyvalue_err)?;
197
198            Python::attach(|py| {
199                let pylist = PyList::new(
200                    py,
201                    trades.into_iter().map(|trade| trade.into_py_any_unwrap(py)),
202                )?;
203                Ok(pylist.into_py_any_unwrap(py))
204            })
205        })
206    }
207
208    #[pyo3(name = "request_bars")]
209    #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
210    fn py_request_bars<'py>(
211        &self,
212        py: Python<'py>,
213        bar_type: BarType,
214        start: Option<DateTime<Utc>>,
215        end: Option<DateTime<Utc>>,
216        limit: Option<u32>,
217    ) -> PyResult<Bound<'py, PyAny>> {
218        let client = self.clone();
219
220        pyo3_async_runtimes::tokio::future_into_py(py, async move {
221            let bars = client
222                .request_bars(bar_type, start, end, limit)
223                .await
224                .map_err(to_pyvalue_err)?;
225
226            Python::attach(|py| {
227                let pylist =
228                    PyList::new(py, bars.into_iter().map(|bar| bar.into_py_any_unwrap(py)))?;
229                Ok(pylist.into_py_any_unwrap(py))
230            })
231        })
232    }
233
234    #[pyo3(name = "request_book_snapshot")]
235    #[pyo3(signature = (instrument_id, depth=None))]
236    fn py_request_book_snapshot<'py>(
237        &self,
238        py: Python<'py>,
239        instrument_id: InstrumentId,
240        depth: Option<u32>,
241    ) -> PyResult<Bound<'py, PyAny>> {
242        let client = self.clone();
243
244        pyo3_async_runtimes::tokio::future_into_py(py, async move {
245            let book = client
246                .request_book_snapshot(instrument_id, depth)
247                .await
248                .map_err(to_pyvalue_err)?;
249
250            Python::attach(|py| Ok(book.into_py_any_unwrap(py)))
251        })
252    }
253
254    #[pyo3(name = "request_order_status_reports")]
255    #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None, open_only=true))]
256    fn py_request_order_status_reports<'py>(
257        &self,
258        py: Python<'py>,
259        account_id: AccountId,
260        instrument_id: Option<InstrumentId>,
261        start: Option<u64>,
262        end: Option<u64>,
263        open_only: bool,
264    ) -> PyResult<Bound<'py, PyAny>> {
265        let client = self.clone();
266
267        pyo3_async_runtimes::tokio::future_into_py(py, async move {
268            let reports = client
269                .request_order_status_reports(
270                    account_id,
271                    instrument_id,
272                    start.map(nautilus_core::UnixNanos::from),
273                    end.map(nautilus_core::UnixNanos::from),
274                    open_only,
275                )
276                .await
277                .map_err(to_pyvalue_err)?;
278
279            Python::attach(|py| {
280                let py_reports: PyResult<Vec<_>> = reports
281                    .into_iter()
282                    .map(|report| report.into_py_any(py))
283                    .collect();
284                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
285                Ok(pylist)
286            })
287        })
288    }
289
290    #[pyo3(name = "request_fill_reports")]
291    #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None))]
292    fn py_request_fill_reports<'py>(
293        &self,
294        py: Python<'py>,
295        account_id: AccountId,
296        instrument_id: Option<InstrumentId>,
297        start: Option<u64>,
298        end: Option<u64>,
299    ) -> PyResult<Bound<'py, PyAny>> {
300        let client = self.clone();
301
302        pyo3_async_runtimes::tokio::future_into_py(py, async move {
303            let reports = client
304                .request_fill_reports(
305                    account_id,
306                    instrument_id,
307                    start.map(nautilus_core::UnixNanos::from),
308                    end.map(nautilus_core::UnixNanos::from),
309                )
310                .await
311                .map_err(to_pyvalue_err)?;
312
313            Python::attach(|py| {
314                let py_reports: PyResult<Vec<_>> = reports
315                    .into_iter()
316                    .map(|report| report.into_py_any(py))
317                    .collect();
318                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
319                Ok(pylist)
320            })
321        })
322    }
323
324    #[pyo3(name = "request_position_status_reports")]
325    #[pyo3(signature = (account_id, instrument_id=None))]
326    fn py_request_position_status_reports<'py>(
327        &self,
328        py: Python<'py>,
329        account_id: AccountId,
330        instrument_id: Option<InstrumentId>,
331    ) -> PyResult<Bound<'py, PyAny>> {
332        let client = self.clone();
333
334        pyo3_async_runtimes::tokio::future_into_py(py, async move {
335            let reports = client
336                .request_position_status_reports(account_id, instrument_id)
337                .await
338                .map_err(to_pyvalue_err)?;
339
340            Python::attach(|py| {
341                let py_reports: PyResult<Vec<_>> = reports
342                    .into_iter()
343                    .map(|report| report.into_py_any(py))
344                    .collect();
345                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
346                Ok(pylist)
347            })
348        })
349    }
350}
351
352impl From<DeribitHttpError> for PyErr {
353    fn from(error: DeribitHttpError) -> Self {
354        match error {
355            // Runtime/operational errors
356            DeribitHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
357            DeribitHttpError::NetworkError(msg) => {
358                to_pyruntime_err(format!("Network error: {msg}"))
359            }
360            DeribitHttpError::UnexpectedStatus { status, body } => {
361                to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
362            }
363            DeribitHttpError::Timeout(msg) => to_pyruntime_err(format!("Request timeout: {msg}")),
364            // Validation/configuration errors
365            DeribitHttpError::MissingCredentials => {
366                to_pyvalue_err("Missing credentials for authenticated request")
367            }
368            DeribitHttpError::ValidationError(msg) => {
369                to_pyvalue_err(format!("Parameter validation error: {msg}"))
370            }
371            DeribitHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
372            DeribitHttpError::DeribitError {
373                error_code,
374                message,
375            } => to_pyvalue_err(format!("Deribit error {error_code}: {message}")),
376        }
377    }
378}