1use jiff::Timestamp;
19use nautilus_core::{
20 python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err},
21 time::get_atomic_clock_realtime,
22};
23use nautilus_model::{
24 data::BarType,
25 identifiers::{AccountId, InstrumentId},
26 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
27};
28use pyo3::{conversion::IntoPyObjectExt, prelude::*, types::PyList};
29
30use crate::{
31 common::enums::DeribitEnvironment,
32 data_types::DeribitBookSummary,
33 http::{
34 client::DeribitHttpClient,
35 error::DeribitHttpError,
36 models::{DeribitCurrency, DeribitProductType},
37 },
38};
39
40#[pymethods]
41#[pyo3_stub_gen::derive::gen_stub_pymethods]
42impl DeribitHttpClient {
43 #[new]
48 #[pyo3(signature = (
49 api_key=None,
50 api_secret=None,
51 base_url=None,
52 environment=DeribitEnvironment::Mainnet,
53 timeout_secs=10,
54 max_retries=3,
55 retry_delay_ms=1000,
56 retry_delay_max_ms=10_000,
57 proxy_url=None,
58 ))]
59 #[expect(clippy::too_many_arguments)]
60 #[allow(unused_variables)]
61 fn py_new(
62 api_key: Option<String>,
63 api_secret: Option<String>,
64 base_url: Option<String>,
65 environment: DeribitEnvironment,
66 timeout_secs: u64,
67 max_retries: u32,
68 retry_delay_ms: u64,
69 retry_delay_max_ms: u64,
70 proxy_url: Option<String>,
71 ) -> PyResult<Self> {
72 Self::new_with_env(
73 api_key,
74 api_secret,
75 base_url,
76 environment,
77 timeout_secs,
78 max_retries,
79 retry_delay_ms,
80 retry_delay_max_ms,
81 proxy_url,
82 )
83 .map_err(to_pyvalue_err)
84 }
85
86 #[getter]
88 #[pyo3(name = "is_testnet")]
89 #[must_use]
90 pub fn py_is_testnet(&self) -> bool {
91 self.is_testnet()
92 }
93
94 #[pyo3(name = "is_initialized")]
95 #[must_use]
96 pub fn py_is_initialized(&self) -> bool {
97 self.is_cache_initialized()
98 }
99
100 #[pyo3(name = "cache_instruments")]
102 pub fn py_cache_instruments(
103 &self,
104 py: Python<'_>,
105 instruments: Vec<Py<PyAny>>,
106 ) -> PyResult<()> {
107 let instruments: Result<Vec<_>, _> = instruments
108 .into_iter()
109 .map(|inst| pyobject_to_instrument_any(py, inst))
110 .collect();
111 self.cache_instruments(&instruments?);
112 Ok(())
113 }
114
115 #[pyo3(name = "cache_instrument")]
119 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
120 let inst = pyobject_to_instrument_any(py, instrument)?;
121 self.cache_instruments(std::slice::from_ref(&inst));
122 Ok(())
123 }
124
125 #[pyo3(name = "request_instruments")]
131 #[pyo3(signature = (currency, product_type=None))]
132 fn py_request_instruments<'py>(
133 &self,
134 py: Python<'py>,
135 currency: DeribitCurrency,
136 product_type: Option<DeribitProductType>,
137 ) -> PyResult<Bound<'py, PyAny>> {
138 let client = self.clone();
139
140 pyo3_async_runtimes::tokio::future_into_py(py, async move {
141 let instruments = client
142 .request_instruments(currency, product_type)
143 .await
144 .map_err(to_pyvalue_err)?;
145
146 Python::attach(|py| {
147 let py_instruments: PyResult<Vec<_>> = instruments
148 .into_iter()
149 .map(|inst| instrument_any_to_pyobject(py, inst))
150 .collect();
151 let pylist = PyList::new(py, py_instruments?)?.into_any().unbind();
152 Ok(pylist)
153 })
154 })
155 }
156
157 #[pyo3(name = "request_option_expirations")]
163 fn py_request_option_expirations<'py>(
164 &self,
165 py: Python<'py>,
166 currency: DeribitCurrency,
167 ) -> PyResult<Bound<'py, PyAny>> {
168 let client = self.clone();
169
170 pyo3_async_runtimes::tokio::future_into_py(py, async move {
171 let expirations = client
172 .request_option_expirations(currency)
173 .await
174 .map_err(to_pyvalue_err)?;
175
176 Python::attach(|py| {
177 let pylist = PyList::new(py, expirations)?.into_any().unbind();
178 Ok(pylist)
179 })
180 })
181 }
182
183 #[pyo3(name = "request_instrument")]
195 fn py_request_instrument<'py>(
196 &self,
197 py: Python<'py>,
198 instrument_id: InstrumentId,
199 ) -> PyResult<Bound<'py, PyAny>> {
200 let client = self.clone();
201
202 pyo3_async_runtimes::tokio::future_into_py(py, async move {
203 let instrument = client
204 .request_instrument(instrument_id)
205 .await
206 .map_err(to_pyvalue_err)?;
207
208 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
209 })
210 }
211
212 #[pyo3(name = "request_account_state")]
223 fn py_request_account_state<'py>(
224 &self,
225 py: Python<'py>,
226 account_id: AccountId,
227 ) -> PyResult<Bound<'py, PyAny>> {
228 let client = self.clone();
229
230 pyo3_async_runtimes::tokio::future_into_py(py, async move {
231 let account_state = client
232 .request_account_state(account_id)
233 .await
234 .map_err(to_pyvalue_err)?;
235
236 Python::attach(|py| account_state.into_py_any(py))
237 })
238 }
239
240 #[pyo3(name = "request_trades")]
264 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
265 fn py_request_trades<'py>(
266 &self,
267 py: Python<'py>,
268 instrument_id: InstrumentId,
269 start: Option<Timestamp>,
270 end: Option<Timestamp>,
271 limit: Option<u32>,
272 ) -> PyResult<Bound<'py, PyAny>> {
273 let client = self.clone();
274
275 pyo3_async_runtimes::tokio::future_into_py(py, async move {
276 let trades = client
277 .request_trades(instrument_id, start, end, limit)
278 .await
279 .map_err(to_pyvalue_err)?;
280
281 Python::attach(|py| {
282 let py_trades = trades
283 .into_iter()
284 .map(|trade| trade.into_py_any(py))
285 .collect::<PyResult<Vec<_>>>()?;
286 let pylist = PyList::new(py, py_trades)?;
287 Ok(pylist.into_py_any_unwrap(py))
288 })
289 })
290 }
291
292 #[pyo3(name = "request_bars")]
308 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
309 fn py_request_bars<'py>(
310 &self,
311 py: Python<'py>,
312 bar_type: BarType,
313 start: Option<Timestamp>,
314 end: Option<Timestamp>,
315 limit: Option<u32>,
316 ) -> PyResult<Bound<'py, PyAny>> {
317 let client = self.clone();
318
319 pyo3_async_runtimes::tokio::future_into_py(py, async move {
320 let bars = client
321 .request_bars(bar_type, start, end, limit)
322 .await
323 .map_err(to_pyvalue_err)?;
324
325 Python::attach(|py| {
326 let py_bars = bars
327 .into_iter()
328 .map(|bar| bar.into_py_any(py))
329 .collect::<PyResult<Vec<_>>>()?;
330 let pylist = PyList::new(py, py_bars)?;
331 Ok(pylist.into_py_any_unwrap(py))
332 })
333 })
334 }
335
336 #[pyo3(name = "request_book_snapshot")]
352 #[pyo3(signature = (instrument_id, depth=None))]
353 fn py_request_book_snapshot<'py>(
354 &self,
355 py: Python<'py>,
356 instrument_id: InstrumentId,
357 depth: Option<u32>,
358 ) -> PyResult<Bound<'py, PyAny>> {
359 let client = self.clone();
360
361 pyo3_async_runtimes::tokio::future_into_py(py, async move {
362 let book = client
363 .request_book_snapshot(instrument_id, depth)
364 .await
365 .map_err(to_pyvalue_err)?;
366
367 Python::attach(|py| book.into_py_any(py))
368 })
369 }
370
371 #[pyo3(name = "request_order_status_reports")]
384 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None, open_only=true))]
385 fn py_request_order_status_reports<'py>(
386 &self,
387 py: Python<'py>,
388 account_id: AccountId,
389 instrument_id: Option<InstrumentId>,
390 start: Option<u64>,
391 end: Option<u64>,
392 open_only: bool,
393 ) -> PyResult<Bound<'py, PyAny>> {
394 let client = self.clone();
395
396 pyo3_async_runtimes::tokio::future_into_py(py, async move {
397 let reports = client
398 .request_order_status_reports(
399 account_id,
400 instrument_id,
401 start.map(nautilus_core::UnixNanos::from),
402 end.map(nautilus_core::UnixNanos::from),
403 open_only,
404 )
405 .await
406 .map_err(to_pyvalue_err)?;
407
408 Python::attach(|py| {
409 let py_reports: PyResult<Vec<_>> = reports
410 .into_iter()
411 .map(|report| report.into_py_any(py))
412 .collect();
413 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
414 Ok(pylist)
415 })
416 })
417 }
418
419 #[pyo3(name = "request_fill_reports")]
432 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None))]
433 fn py_request_fill_reports<'py>(
434 &self,
435 py: Python<'py>,
436 account_id: AccountId,
437 instrument_id: Option<InstrumentId>,
438 start: Option<u64>,
439 end: Option<u64>,
440 ) -> PyResult<Bound<'py, PyAny>> {
441 let client = self.clone();
442
443 pyo3_async_runtimes::tokio::future_into_py(py, async move {
444 let reports = client
445 .request_fill_reports(
446 account_id,
447 instrument_id,
448 start.map(nautilus_core::UnixNanos::from),
449 end.map(nautilus_core::UnixNanos::from),
450 )
451 .await
452 .map_err(to_pyvalue_err)?;
453
454 Python::attach(|py| {
455 let py_reports: PyResult<Vec<_>> = reports
456 .into_iter()
457 .map(|report| report.into_py_any(py))
458 .collect();
459 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
460 Ok(pylist)
461 })
462 })
463 }
464
465 #[pyo3(name = "request_position_status_reports")]
477 #[pyo3(signature = (account_id, instrument_id=None))]
478 fn py_request_position_status_reports<'py>(
479 &self,
480 py: Python<'py>,
481 account_id: AccountId,
482 instrument_id: Option<InstrumentId>,
483 ) -> PyResult<Bound<'py, PyAny>> {
484 let client = self.clone();
485
486 pyo3_async_runtimes::tokio::future_into_py(py, async move {
487 let reports = client
488 .request_position_status_reports(account_id, instrument_id)
489 .await
490 .map_err(to_pyvalue_err)?;
491
492 Python::attach(|py| {
493 let py_reports: PyResult<Vec<_>> = reports
494 .into_iter()
495 .map(|report| report.into_py_any(py))
496 .collect();
497 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
498 Ok(pylist)
499 })
500 })
501 }
502
503 #[pyo3(name = "request_book_summaries")]
512 #[pyo3(signature = (currency, kind=None))]
513 fn py_request_book_summaries<'py>(
514 &self,
515 py: Python<'py>,
516 currency: String,
517 kind: Option<String>,
518 ) -> PyResult<Bound<'py, PyAny>> {
519 let client = self.clone();
520
521 pyo3_async_runtimes::tokio::future_into_py(py, async move {
522 let currency = currency.trim().to_ascii_uppercase();
523 if currency.is_empty() {
524 return Err(to_pyvalue_err(
525 "request_book_summaries requires a non-empty currency",
526 ));
527 }
528 let kind = kind
529 .as_deref()
530 .map(str::trim)
531 .filter(|value| !value.is_empty())
532 .map_or_else(|| "option".to_string(), str::to_ascii_lowercase);
533 let summaries = client
534 .request_book_summaries_kind(¤cy, Some(kind.as_str()))
535 .await
536 .map_err(to_pyvalue_err)?;
537 let ts = get_atomic_clock_realtime().get_time_ns();
539
540 Python::attach(|py| {
541 let py_items: PyResult<Vec<_>> = summaries
542 .into_iter()
543 .map(|raw| Py::new(py, DeribitBookSummary::from_raw(raw, ts)))
544 .collect();
545 let pylist = PyList::new(py, py_items?)?.into_any().unbind();
546 Ok(pylist)
547 })
548 })
549 }
550}
551
552impl From<DeribitHttpError> for PyErr {
553 fn from(error: DeribitHttpError) -> Self {
554 match error {
555 DeribitHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
557 DeribitHttpError::NetworkError(msg) => {
558 to_pyruntime_err(format!("Network error: {msg}"))
559 }
560 DeribitHttpError::UnexpectedStatus { status, body } => {
561 to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
562 }
563 DeribitHttpError::Timeout(msg) => to_pyruntime_err(format!("Request timeout: {msg}")),
564 DeribitHttpError::MissingCredentials => {
566 to_pyvalue_err("Missing credentials for authenticated request")
567 }
568 DeribitHttpError::ValidationError(msg) => {
569 to_pyvalue_err(format!("Parameter validation error: {msg}"))
570 }
571 DeribitHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
572 DeribitHttpError::DeribitError {
573 error_code,
574 message,
575 } => to_pyvalue_err(format!("Deribit error {error_code}: {message}")),
576 }
577 }
578}