1use futures_util::StreamExt;
39use nautilus_common::live::get_runtime;
40use nautilus_core::python::{call_python_threadsafe, to_pyruntime_err, to_pyvalue_err};
41use nautilus_model::{
42 data::{BarType, Data, OrderBookDeltas_API},
43 enums::{OrderSide, OrderType, TimeInForce, TriggerType},
44 identifiers::{AccountId, ClientOrderId, InstrumentId, StrategyId, TraderId},
45 python::{
46 data::data_to_pycapsule,
47 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
48 },
49 types::{Price, Quantity},
50};
51use nautilus_network::websocket::TransportBackend;
52use pyo3::{IntoPyObjectExt, prelude::*};
53
54use crate::{
55 common::{
56 enums::{DeribitEnvironment, DeribitTimeInForce, resolve_trigger_type},
57 parse::parse_instrument_kind_currency,
58 },
59 websocket::{
60 client::DeribitWebSocketClient,
61 enums::DeribitUpdateInterval,
62 messages::{DeribitOrderParams, NautilusWsMessage},
63 },
64};
65
66fn call_python_with_data<F>(call_soon: &Py<PyAny>, callback: &Py<PyAny>, data_converter: F)
67where
68 F: FnOnce(Python) -> PyResult<Py<PyAny>>,
69{
70 Python::attach(|py| match data_converter(py) {
71 Ok(py_obj) => call_python_threadsafe(py, call_soon, callback, py_obj),
72 Err(e) => log::error!("Failed to convert data to Python object: {e}"),
73 });
74}
75
76fn ws_data_to_pyobject(py: Python<'_>, data: Data) -> PyResult<Py<PyAny>> {
77 match data {
78 Data::Custom(custom) => Py::new(py, custom).map(|obj| obj.into_any()),
79 Data::OptionGreeks(greeks) => Py::new(py, greeks).map(|obj| obj.into_any()),
80 other => Ok(data_to_pycapsule(py, other)),
81 }
82}
83
84#[pymethods]
85#[pyo3_stub_gen::derive::gen_stub_pymethods]
86impl DeribitWebSocketClient {
87 #[new]
89 #[pyo3(signature = (
90 url=None,
91 api_key=None,
92 api_secret=None,
93 heartbeat_interval=30,
94 environment=DeribitEnvironment::Mainnet,
95 proxy_url=None,
96 ))]
97 fn py_new(
98 url: Option<String>,
99 api_key: Option<String>,
100 api_secret: Option<String>,
101 heartbeat_interval: u64,
102 environment: DeribitEnvironment,
103 proxy_url: Option<String>,
104 ) -> PyResult<Self> {
105 Self::new(
106 url,
107 api_key,
108 api_secret,
109 heartbeat_interval,
110 environment,
111 TransportBackend::default(),
112 proxy_url,
113 )
114 .map_err(to_pyvalue_err)
115 }
116
117 #[staticmethod]
125 #[pyo3(name = "new_public", signature = (environment, proxy_url = None))]
126 fn py_new_public(environment: DeribitEnvironment, proxy_url: Option<String>) -> PyResult<Self> {
127 Self::new_public(environment, proxy_url).map_err(to_pyvalue_err)
128 }
129
130 #[staticmethod]
137 #[pyo3(name = "with_credentials", signature = (environment, api_key = None, api_secret = None, account_id = None, proxy_url = None))]
138 fn py_with_credentials(
139 environment: DeribitEnvironment,
140 api_key: Option<String>,
141 api_secret: Option<String>,
142 account_id: Option<AccountId>,
143 proxy_url: Option<String>,
144 ) -> PyResult<Self> {
145 let mut client = Self::with_credentials(environment, api_key, api_secret, proxy_url)
146 .map_err(to_pyvalue_err)?;
147
148 if let Some(id) = account_id {
149 client.set_account_id(id);
150 }
151 Ok(client)
152 }
153
154 #[getter]
156 #[pyo3(name = "url")]
157 #[must_use]
158 pub fn py_url(&self) -> String {
159 self.url().to_string()
160 }
161
162 #[getter]
163 #[pyo3(name = "is_testnet")]
164 #[must_use]
165 pub fn py_is_testnet(&self) -> bool {
166 self.environment() == DeribitEnvironment::Testnet
167 }
168
169 #[pyo3(name = "is_active")]
171 #[must_use]
172 fn py_is_active(&self) -> bool {
173 self.is_active()
174 }
175
176 #[pyo3(name = "is_closed")]
178 #[must_use]
179 fn py_is_closed(&self) -> bool {
180 self.is_closed()
181 }
182
183 #[pyo3(name = "has_credentials")]
185 #[must_use]
186 fn py_has_credentials(&self) -> bool {
187 self.has_credentials()
188 }
189
190 #[pyo3(name = "is_authenticated")]
192 #[must_use]
193 fn py_is_authenticated(&self) -> bool {
194 self.is_authenticated()
195 }
196
197 #[pyo3(name = "cancel_all_requests")]
199 pub fn py_cancel_all_requests(&self) {
200 self.cancel_all_requests();
201 }
202
203 #[pyo3(name = "cache_instruments")]
205 pub fn py_cache_instruments(
206 &self,
207 py: Python<'_>,
208 instruments: Vec<Py<PyAny>>,
209 ) -> PyResult<()> {
210 let instruments: Result<Vec<_>, _> = instruments
211 .into_iter()
212 .map(|inst| pyobject_to_instrument_any(py, inst))
213 .collect();
214 self.cache_instruments(&instruments?);
215 Ok(())
216 }
217
218 #[pyo3(name = "cache_instrument")]
220 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
221 let inst = pyobject_to_instrument_any(py, instrument)?;
222 self.cache_instrument(inst);
223 Ok(())
224 }
225
226 #[pyo3(name = "set_account_id")]
228 pub fn py_set_account_id(&mut self, account_id: AccountId) {
229 self.set_account_id(account_id);
230 }
231
232 #[pyo3(name = "set_bars_timestamp_on_close")]
236 pub fn py_set_bars_timestamp_on_close(&mut self, value: bool) {
237 self.set_bars_timestamp_on_close(value);
238 }
239
240 #[pyo3(name = "connect")]
242 #[expect(clippy::needless_pass_by_value)]
243 fn py_connect<'py>(
244 &mut self,
245 py: Python<'py>,
246 loop_: Py<PyAny>,
247 instruments: Vec<Py<PyAny>>,
248 callback: Py<PyAny>,
249 ) -> PyResult<Bound<'py, PyAny>> {
250 let call_soon: Py<PyAny> = loop_.getattr(py, "call_soon_threadsafe")?;
251
252 let mut instruments_any = Vec::new();
253
254 for inst in instruments {
255 let inst_any = pyobject_to_instrument_any(py, inst)?;
256 instruments_any.push(inst_any);
257 }
258
259 self.cache_instruments(&instruments_any);
260
261 let mut client = self.clone();
262
263 pyo3_async_runtimes::tokio::future_into_py(py, async move {
264 client.connect().await.map_err(to_pyruntime_err)?;
265
266 let stream = client.stream().map_err(to_pyruntime_err)?;
267
268 get_runtime().spawn(async move {
270 let _client = client;
271 tokio::pin!(stream);
272
273 while let Some(msg) = stream.next().await {
274 match msg {
275 NautilusWsMessage::Instrument(msg) => {
276 call_python_with_data(&call_soon, &callback, |py| {
277 instrument_any_to_pyobject(py, *msg)
278 });
279 }
280 NautilusWsMessage::Data(msg) => Python::attach(|py| {
281 for data in msg {
282 match ws_data_to_pyobject(py, data) {
283 Ok(py_obj) => {
284 call_python_threadsafe(py, &call_soon, &callback, py_obj);
285 }
286 Err(e) => {
287 log::error!(
288 "Failed to convert WebSocket data payload: {e}"
289 );
290 }
291 }
292 }
293 }),
294 NautilusWsMessage::Deltas(msg) => Python::attach(|py| {
295 let py_obj =
296 data_to_pycapsule(py, Data::Deltas(OrderBookDeltas_API::new(msg)));
297 call_python_threadsafe(py, &call_soon, &callback, py_obj);
298 }),
299 NautilusWsMessage::Error(err) => {
300 log::warn!("WebSocket error: {err}");
301 }
302 NautilusWsMessage::Reconnected => {
303 log::info!("WebSocket reconnected");
304 }
305 NautilusWsMessage::Authenticated(auth_result) => {
306 log::debug!("WebSocket authenticated (scope: {})", auth_result.scope);
307 }
308 NautilusWsMessage::InstrumentStatus(status) => {
309 call_python_with_data(&call_soon, &callback, |py| {
310 status.into_py_any(py)
311 });
312 }
313 NautilusWsMessage::Raw(msg) => {
314 log::debug!("Received raw message, skipping: {msg}");
315 }
316 NautilusWsMessage::FundingRates(funding_rates) => Python::attach(|py| {
317 for funding_rate in funding_rates {
318 match Py::new(py, funding_rate) {
319 Ok(py_obj) => call_python_threadsafe(
320 py,
321 &call_soon,
322 &callback,
323 py_obj.into_any(),
324 ),
325 Err(e) => {
326 log::error!("Failed to create FundingRateUpdate: {e}");
327 }
328 }
329 }
330 }),
331 NautilusWsMessage::OptionGreeks(greeks) => {
332 call_python_with_data(&call_soon, &callback, |py| {
333 Py::new(py, greeks).map(|obj| obj.into_any())
334 });
335 }
336 NautilusWsMessage::OrderStatusReports(reports) => Python::attach(|py| {
338 for report in reports {
339 match Py::new(py, report) {
340 Ok(py_obj) => call_python_threadsafe(
341 py,
342 &call_soon,
343 &callback,
344 py_obj.into_any(),
345 ),
346 Err(e) => {
347 log::error!("Failed to create OrderStatusReport: {e}");
348 }
349 }
350 }
351 }),
352 NautilusWsMessage::FillReports(reports) => Python::attach(|py| {
353 for report in reports {
354 match Py::new(py, report) {
355 Ok(py_obj) => call_python_threadsafe(
356 py,
357 &call_soon,
358 &callback,
359 py_obj.into_any(),
360 ),
361 Err(e) => log::error!("Failed to create FillReport: {e}"),
362 }
363 }
364 }),
365 NautilusWsMessage::OrderRejected(msg) => {
366 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
367 }
368 NautilusWsMessage::OrderAccepted(msg) => {
369 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
370 }
371 NautilusWsMessage::OrderCanceled(msg) => {
372 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
373 }
374 NautilusWsMessage::OrderExpired(msg) => {
375 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
376 }
377 NautilusWsMessage::OrderUpdated(msg) => {
378 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
379 }
380 NautilusWsMessage::OrderCancelRejected(msg) => {
381 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
382 }
383 NautilusWsMessage::OrderModifyRejected(msg) => {
384 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
385 }
386 NautilusWsMessage::AccountState(msg) => {
387 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
388 }
389 NautilusWsMessage::AuthenticationFailed(reason) => {
390 log::error!("Authentication failed: {reason}");
391 }
392 }
393 }
394 });
395
396 Ok(())
397 })
398 }
399
400 #[pyo3(name = "wait_until_active")]
402 fn py_wait_until_active<'py>(
403 &self,
404 py: Python<'py>,
405 timeout_secs: f64,
406 ) -> PyResult<Bound<'py, PyAny>> {
407 let client = self.clone();
408
409 pyo3_async_runtimes::tokio::future_into_py(py, async move {
410 client
411 .wait_until_active(timeout_secs)
412 .await
413 .map_err(to_pyruntime_err)?;
414 Ok(())
415 })
416 }
417
418 #[pyo3(name = "close")]
424 fn py_close<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
425 let client = self.clone();
426
427 pyo3_async_runtimes::tokio::future_into_py(py, async move {
428 if let Err(e) = client.close().await {
429 log::warn!("Error on close: {e}");
430 }
431 Ok(())
432 })
433 }
434
435 #[pyo3(name = "authenticate")]
447 #[pyo3(signature = (session_name=None))]
448 fn py_authenticate<'py>(
449 &self,
450 py: Python<'py>,
451 session_name: Option<String>,
452 ) -> PyResult<Bound<'py, PyAny>> {
453 let client = self.clone();
454
455 pyo3_async_runtimes::tokio::future_into_py(py, async move {
456 client
457 .authenticate(session_name.as_deref())
458 .await
459 .map_err(to_pyruntime_err)?;
460 Ok(())
461 })
462 }
463
464 #[pyo3(name = "authenticate_session")]
469 fn py_authenticate_session<'py>(
470 &self,
471 py: Python<'py>,
472 session_name: String,
473 ) -> PyResult<Bound<'py, PyAny>> {
474 let client = self.clone();
475
476 pyo3_async_runtimes::tokio::future_into_py(py, async move {
477 client
478 .authenticate_session(&session_name)
479 .await
480 .map_err(|e| {
481 to_pyruntime_err(format!(
482 "Failed to authenticate Deribit websocket session '{session_name}': {e}"
483 ))
484 })?;
485 Ok(())
486 })
487 }
488
489 #[pyo3(name = "subscribe_trades")]
496 #[pyo3(signature = (instrument_id, interval=None))]
497 fn py_subscribe_trades<'py>(
498 &self,
499 py: Python<'py>,
500 instrument_id: InstrumentId,
501 interval: Option<DeribitUpdateInterval>,
502 ) -> PyResult<Bound<'py, PyAny>> {
503 let client = self.clone();
504
505 pyo3_async_runtimes::tokio::future_into_py(py, async move {
506 client
507 .subscribe_trades(instrument_id, interval)
508 .await
509 .map_err(to_pyvalue_err)
510 })
511 }
512
513 #[pyo3(name = "unsubscribe_trades")]
515 #[pyo3(signature = (instrument_id, interval=None))]
516 fn py_unsubscribe_trades<'py>(
517 &self,
518 py: Python<'py>,
519 instrument_id: InstrumentId,
520 interval: Option<DeribitUpdateInterval>,
521 ) -> PyResult<Bound<'py, PyAny>> {
522 let client = self.clone();
523
524 pyo3_async_runtimes::tokio::future_into_py(py, async move {
525 client
526 .unsubscribe_trades(instrument_id, interval)
527 .await
528 .map_err(to_pyvalue_err)
529 })
530 }
531
532 #[pyo3(name = "subscribe_book")]
539 #[pyo3(signature = (instrument_id, interval=None, depth=None))]
540 fn py_subscribe_book<'py>(
541 &self,
542 py: Python<'py>,
543 instrument_id: InstrumentId,
544 interval: Option<DeribitUpdateInterval>,
545 depth: Option<u32>,
546 ) -> PyResult<Bound<'py, PyAny>> {
547 let client = self.clone();
548
549 pyo3_async_runtimes::tokio::future_into_py(py, async move {
550 if let Some(d) = depth {
551 client
552 .subscribe_book_grouped(instrument_id, "none", d, interval)
553 .await
554 .map_err(to_pyvalue_err)
555 } else {
556 client
557 .subscribe_book(instrument_id, interval)
558 .await
559 .map_err(to_pyvalue_err)
560 }
561 })
562 }
563
564 #[pyo3(name = "unsubscribe_book")]
566 #[pyo3(signature = (instrument_id, interval=None, depth=None))]
567 fn py_unsubscribe_book<'py>(
568 &self,
569 py: Python<'py>,
570 instrument_id: InstrumentId,
571 interval: Option<DeribitUpdateInterval>,
572 depth: Option<u32>,
573 ) -> PyResult<Bound<'py, PyAny>> {
574 let client = self.clone();
575
576 pyo3_async_runtimes::tokio::future_into_py(py, async move {
577 if let Some(d) = depth {
578 client
579 .unsubscribe_book_grouped(instrument_id, "none", d, interval)
580 .await
581 .map_err(to_pyvalue_err)
582 } else {
583 client
584 .unsubscribe_book(instrument_id, interval)
585 .await
586 .map_err(to_pyvalue_err)
587 }
588 })
589 }
590
591 #[pyo3(name = "subscribe_book_grouped")]
597 #[pyo3(signature = (instrument_id, group, depth, interval=None))]
598 fn py_subscribe_book_grouped<'py>(
599 &self,
600 py: Python<'py>,
601 instrument_id: InstrumentId,
602 group: String,
603 depth: u32,
604 interval: Option<DeribitUpdateInterval>,
605 ) -> PyResult<Bound<'py, PyAny>> {
606 let client = self.clone();
607
608 pyo3_async_runtimes::tokio::future_into_py(py, async move {
609 client
610 .subscribe_book_grouped(instrument_id, &group, depth, interval)
611 .await
612 .map_err(to_pyvalue_err)
613 })
614 }
615
616 #[pyo3(name = "unsubscribe_book_grouped")]
620 #[pyo3(signature = (instrument_id, group, depth, interval=None))]
621 fn py_unsubscribe_book_grouped<'py>(
622 &self,
623 py: Python<'py>,
624 instrument_id: InstrumentId,
625 group: String,
626 depth: u32,
627 interval: Option<DeribitUpdateInterval>,
628 ) -> PyResult<Bound<'py, PyAny>> {
629 let client = self.clone();
630
631 pyo3_async_runtimes::tokio::future_into_py(py, async move {
632 client
633 .unsubscribe_book_grouped(instrument_id, &group, depth, interval)
634 .await
635 .map_err(to_pyvalue_err)
636 })
637 }
638
639 #[pyo3(name = "subscribe_ticker")]
646 #[pyo3(signature = (instrument_id, interval=None))]
647 fn py_subscribe_ticker<'py>(
648 &self,
649 py: Python<'py>,
650 instrument_id: InstrumentId,
651 interval: Option<DeribitUpdateInterval>,
652 ) -> PyResult<Bound<'py, PyAny>> {
653 let client = self.clone();
654
655 pyo3_async_runtimes::tokio::future_into_py(py, async move {
656 client
657 .subscribe_ticker(instrument_id, interval)
658 .await
659 .map_err(to_pyvalue_err)
660 })
661 }
662
663 #[pyo3(name = "unsubscribe_ticker")]
665 #[pyo3(signature = (instrument_id, interval=None))]
666 fn py_unsubscribe_ticker<'py>(
667 &self,
668 py: Python<'py>,
669 instrument_id: InstrumentId,
670 interval: Option<DeribitUpdateInterval>,
671 ) -> PyResult<Bound<'py, PyAny>> {
672 let client = self.clone();
673
674 pyo3_async_runtimes::tokio::future_into_py(py, async move {
675 client
676 .unsubscribe_ticker(instrument_id, interval)
677 .await
678 .map_err(to_pyvalue_err)
679 })
680 }
681
682 #[pyo3(name = "subscribe_mark_prices")]
687 #[pyo3(signature = (instrument_id, interval=None))]
688 fn py_subscribe_mark_prices<'py>(
689 &self,
690 py: Python<'py>,
691 instrument_id: InstrumentId,
692 interval: Option<DeribitUpdateInterval>,
693 ) -> PyResult<Bound<'py, PyAny>> {
694 self.add_mark_price_sub(instrument_id);
695 let client = self.clone();
696
697 pyo3_async_runtimes::tokio::future_into_py(py, async move {
698 client
699 .subscribe_ticker(instrument_id, interval)
700 .await
701 .map_err(to_pyvalue_err)
702 })
703 }
704
705 #[pyo3(name = "unsubscribe_mark_prices")]
710 #[pyo3(signature = (instrument_id, interval=None))]
711 fn py_unsubscribe_mark_prices<'py>(
712 &self,
713 py: Python<'py>,
714 instrument_id: InstrumentId,
715 interval: Option<DeribitUpdateInterval>,
716 ) -> PyResult<Bound<'py, PyAny>> {
717 self.remove_mark_price_sub(&instrument_id);
718 let client = self.clone();
719
720 pyo3_async_runtimes::tokio::future_into_py(py, async move {
721 client
722 .unsubscribe_ticker(instrument_id, interval)
723 .await
724 .map_err(to_pyvalue_err)
725 })
726 }
727
728 #[pyo3(name = "subscribe_index_prices")]
733 #[pyo3(signature = (instrument_id, interval=None))]
734 fn py_subscribe_index_prices<'py>(
735 &self,
736 py: Python<'py>,
737 instrument_id: InstrumentId,
738 interval: Option<DeribitUpdateInterval>,
739 ) -> PyResult<Bound<'py, PyAny>> {
740 self.add_index_price_sub(instrument_id);
741 let client = self.clone();
742
743 pyo3_async_runtimes::tokio::future_into_py(py, async move {
744 client
745 .subscribe_ticker(instrument_id, interval)
746 .await
747 .map_err(to_pyvalue_err)
748 })
749 }
750
751 #[pyo3(name = "unsubscribe_index_prices")]
756 #[pyo3(signature = (instrument_id, interval=None))]
757 fn py_unsubscribe_index_prices<'py>(
758 &self,
759 py: Python<'py>,
760 instrument_id: InstrumentId,
761 interval: Option<DeribitUpdateInterval>,
762 ) -> PyResult<Bound<'py, PyAny>> {
763 self.remove_index_price_sub(&instrument_id);
764 let client = self.clone();
765
766 pyo3_async_runtimes::tokio::future_into_py(py, async move {
767 client
768 .unsubscribe_ticker(instrument_id, interval)
769 .await
770 .map_err(to_pyvalue_err)
771 })
772 }
773
774 #[pyo3(name = "subscribe_option_greeks")]
779 #[pyo3(signature = (instrument_id, interval=None))]
780 fn py_subscribe_option_greeks<'py>(
781 &self,
782 py: Python<'py>,
783 instrument_id: InstrumentId,
784 interval: Option<DeribitUpdateInterval>,
785 ) -> PyResult<Bound<'py, PyAny>> {
786 self.add_option_greeks_sub(instrument_id);
787 let client = self.clone();
788
789 pyo3_async_runtimes::tokio::future_into_py(py, async move {
790 client
791 .subscribe_ticker(instrument_id, interval)
792 .await
793 .map_err(to_pyvalue_err)
794 })
795 }
796
797 #[pyo3(name = "unsubscribe_option_greeks")]
802 #[pyo3(signature = (instrument_id, interval=None))]
803 fn py_unsubscribe_option_greeks<'py>(
804 &self,
805 py: Python<'py>,
806 instrument_id: InstrumentId,
807 interval: Option<DeribitUpdateInterval>,
808 ) -> PyResult<Bound<'py, PyAny>> {
809 self.remove_option_greeks_sub(&instrument_id);
810 let client = self.clone();
811
812 pyo3_async_runtimes::tokio::future_into_py(py, async move {
813 client
814 .unsubscribe_ticker(instrument_id, interval)
815 .await
816 .map_err(to_pyvalue_err)
817 })
818 }
819
820 #[pyo3(name = "subscribe_quotes")]
824 fn py_subscribe_quotes<'py>(
825 &self,
826 py: Python<'py>,
827 instrument_id: InstrumentId,
828 ) -> PyResult<Bound<'py, PyAny>> {
829 let client = self.clone();
830
831 pyo3_async_runtimes::tokio::future_into_py(py, async move {
832 client
833 .subscribe_quotes(instrument_id)
834 .await
835 .map_err(to_pyvalue_err)
836 })
837 }
838
839 #[pyo3(name = "unsubscribe_quotes")]
841 fn py_unsubscribe_quotes<'py>(
842 &self,
843 py: Python<'py>,
844 instrument_id: InstrumentId,
845 ) -> PyResult<Bound<'py, PyAny>> {
846 let client = self.clone();
847
848 pyo3_async_runtimes::tokio::future_into_py(py, async move {
849 client
850 .unsubscribe_quotes(instrument_id)
851 .await
852 .map_err(to_pyvalue_err)
853 })
854 }
855
856 #[pyo3(name = "subscribe_user_orders")]
864 fn py_subscribe_user_orders<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
865 let client = self.clone();
866
867 pyo3_async_runtimes::tokio::future_into_py(py, async move {
868 client.subscribe_user_orders().await.map_err(to_pyvalue_err)
869 })
870 }
871
872 #[pyo3(name = "unsubscribe_user_orders")]
878 fn py_unsubscribe_user_orders<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
879 let client = self.clone();
880
881 pyo3_async_runtimes::tokio::future_into_py(py, async move {
882 client
883 .unsubscribe_user_orders()
884 .await
885 .map_err(to_pyvalue_err)
886 })
887 }
888
889 #[pyo3(name = "subscribe_user_trades")]
897 fn py_subscribe_user_trades<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
898 let client = self.clone();
899
900 pyo3_async_runtimes::tokio::future_into_py(py, async move {
901 client.subscribe_user_trades().await.map_err(to_pyvalue_err)
902 })
903 }
904
905 #[pyo3(name = "unsubscribe_user_trades")]
911 fn py_unsubscribe_user_trades<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
912 let client = self.clone();
913
914 pyo3_async_runtimes::tokio::future_into_py(py, async move {
915 client
916 .unsubscribe_user_trades()
917 .await
918 .map_err(to_pyvalue_err)
919 })
920 }
921
922 #[pyo3(name = "subscribe_user_portfolio")]
932 fn py_subscribe_user_portfolio<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
933 let client = self.clone();
934
935 pyo3_async_runtimes::tokio::future_into_py(py, async move {
936 client
937 .subscribe_user_portfolio()
938 .await
939 .map_err(to_pyvalue_err)
940 })
941 }
942
943 #[pyo3(name = "unsubscribe_user_portfolio")]
949 fn py_unsubscribe_user_portfolio<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
950 let client = self.clone();
951
952 pyo3_async_runtimes::tokio::future_into_py(py, async move {
953 client
954 .unsubscribe_user_portfolio()
955 .await
956 .map_err(to_pyvalue_err)
957 })
958 }
959
960 #[pyo3(name = "subscribe")]
962 fn py_subscribe<'py>(
963 &self,
964 py: Python<'py>,
965 channels: Vec<String>,
966 ) -> PyResult<Bound<'py, PyAny>> {
967 let client = self.clone();
968
969 pyo3_async_runtimes::tokio::future_into_py(py, async move {
970 client.subscribe(channels).await.map_err(to_pyvalue_err)
971 })
972 }
973
974 #[pyo3(name = "unsubscribe")]
976 fn py_unsubscribe<'py>(
977 &self,
978 py: Python<'py>,
979 channels: Vec<String>,
980 ) -> PyResult<Bound<'py, PyAny>> {
981 let client = self.clone();
982
983 pyo3_async_runtimes::tokio::future_into_py(py, async move {
984 client.unsubscribe(channels).await.map_err(to_pyvalue_err)
985 })
986 }
987
988 #[pyo3(name = "subscribe_perpetual_interest_rates")]
989 #[pyo3(signature = (instrument_id, interval=None))]
990 fn py_subscribe_perpetual_interest_rates<'py>(
991 &self,
992 py: Python<'py>,
993 instrument_id: InstrumentId,
994 interval: Option<DeribitUpdateInterval>,
995 ) -> PyResult<Bound<'py, PyAny>> {
996 let client = self.clone();
997
998 pyo3_async_runtimes::tokio::future_into_py(py, async move {
999 client
1000 .subscribe_perpetual_interests_rates_updates(instrument_id, interval)
1001 .await
1002 .map_err(to_pyvalue_err)
1003 })
1004 }
1005
1006 #[pyo3(name = "unsubscribe_perpetual_interest_rates")]
1007 #[pyo3(signature = (instrument_id, interval=None))]
1008 fn py_unsubscribe_perpetual_interest_rates<'py>(
1009 &self,
1010 py: Python<'py>,
1011 instrument_id: InstrumentId,
1012 interval: Option<DeribitUpdateInterval>,
1013 ) -> PyResult<Bound<'py, PyAny>> {
1014 let client = self.clone();
1015
1016 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1017 client
1018 .unsubscribe_perpetual_interest_rates_updates(instrument_id, interval)
1019 .await
1020 .map_err(to_pyvalue_err)
1021 })
1022 }
1023
1024 #[pyo3(name = "subscribe_instrument_status")]
1028 fn py_subscribe_instrument_status<'py>(
1029 &self,
1030 py: Python<'py>,
1031 instrument_id: InstrumentId,
1032 ) -> PyResult<Bound<'py, PyAny>> {
1033 let client = self.clone();
1034 let (kind, currency) = parse_instrument_kind_currency(&instrument_id);
1035
1036 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1037 client
1038 .subscribe_instrument_status(&kind, ¤cy)
1039 .await
1040 .map_err(to_pyvalue_err)
1041 })
1042 }
1043
1044 #[pyo3(name = "unsubscribe_instrument_status")]
1046 fn py_unsubscribe_instrument_status<'py>(
1047 &self,
1048 py: Python<'py>,
1049 instrument_id: InstrumentId,
1050 ) -> PyResult<Bound<'py, PyAny>> {
1051 let client = self.clone();
1052 let (kind, currency) = parse_instrument_kind_currency(&instrument_id);
1053
1054 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1055 client
1056 .unsubscribe_instrument_status(&kind, ¤cy)
1057 .await
1058 .map_err(to_pyvalue_err)
1059 })
1060 }
1061
1062 #[pyo3(name = "subscribe_volatility_index")]
1066 fn py_subscribe_volatility_index<'py>(
1067 &self,
1068 py: Python<'py>,
1069 index_name: String,
1070 ) -> PyResult<Bound<'py, PyAny>> {
1071 let client = self.clone();
1072
1073 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1074 client
1075 .subscribe_volatility_index(&index_name)
1076 .await
1077 .map_err(to_pyvalue_err)
1078 })
1079 }
1080
1081 #[pyo3(name = "unsubscribe_volatility_index")]
1083 fn py_unsubscribe_volatility_index<'py>(
1084 &self,
1085 py: Python<'py>,
1086 index_name: String,
1087 ) -> PyResult<Bound<'py, PyAny>> {
1088 let client = self.clone();
1089
1090 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1091 client
1092 .unsubscribe_volatility_index(&index_name)
1093 .await
1094 .map_err(to_pyvalue_err)
1095 })
1096 }
1097
1098 #[pyo3(name = "subscribe_chart")]
1106 fn py_subscribe_chart<'py>(
1107 &self,
1108 py: Python<'py>,
1109 instrument_id: InstrumentId,
1110 resolution: String,
1111 ) -> PyResult<Bound<'py, PyAny>> {
1112 let client = self.clone();
1113
1114 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1115 client
1116 .subscribe_chart(instrument_id, &resolution)
1117 .await
1118 .map_err(to_pyvalue_err)
1119 })
1120 }
1121
1122 #[pyo3(name = "unsubscribe_chart")]
1124 fn py_unsubscribe_chart<'py>(
1125 &self,
1126 py: Python<'py>,
1127 instrument_id: InstrumentId,
1128 resolution: String,
1129 ) -> PyResult<Bound<'py, PyAny>> {
1130 let client = self.clone();
1131
1132 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1133 client
1134 .unsubscribe_chart(instrument_id, &resolution)
1135 .await
1136 .map_err(to_pyvalue_err)
1137 })
1138 }
1139
1140 #[pyo3(name = "subscribe_bars")]
1145 fn py_subscribe_bars<'py>(
1146 &self,
1147 py: Python<'py>,
1148 bar_type: BarType,
1149 ) -> PyResult<Bound<'py, PyAny>> {
1150 let client = self.clone();
1151
1152 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1153 client
1154 .subscribe_bars(bar_type)
1155 .await
1156 .map_err(to_pyvalue_err)
1157 })
1158 }
1159
1160 #[pyo3(name = "unsubscribe_bars")]
1162 fn py_unsubscribe_bars<'py>(
1163 &self,
1164 py: Python<'py>,
1165 bar_type: BarType,
1166 ) -> PyResult<Bound<'py, PyAny>> {
1167 let client = self.clone();
1168
1169 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1170 client
1171 .unsubscribe_bars(bar_type)
1172 .await
1173 .map_err(to_pyvalue_err)
1174 })
1175 }
1176
1177 #[pyo3(name = "submit_order")]
1182 #[pyo3(signature = (
1183 order_side,
1184 quantity,
1185 order_type,
1186 client_order_id,
1187 trader_id,
1188 strategy_id,
1189 instrument_id,
1190 price=None,
1191 time_in_force=None,
1192 post_only=false,
1193 reduce_only=false,
1194 trigger_price=None,
1195 trigger_type=None,
1196 ))]
1197 #[expect(clippy::too_many_arguments)]
1198 fn py_submit_order<'py>(
1199 &self,
1200 py: Python<'py>,
1201 order_side: OrderSide,
1202 quantity: Quantity,
1203 order_type: OrderType,
1204 client_order_id: ClientOrderId,
1205 trader_id: TraderId,
1206 strategy_id: StrategyId,
1207 instrument_id: InstrumentId,
1208 price: Option<Price>,
1209 time_in_force: Option<TimeInForce>,
1210 post_only: bool,
1211 reduce_only: bool,
1212 trigger_price: Option<Price>,
1213 trigger_type: Option<TriggerType>,
1214 ) -> PyResult<Bound<'py, PyAny>> {
1215 let client = self.clone();
1216 let instrument_name = instrument_id.symbol.to_string();
1217
1218 let deribit_tif = time_in_force
1220 .map(|tif| {
1221 DeribitTimeInForce::try_from(tif)
1222 .map(|deribit_tif| deribit_tif.as_str().to_string())
1223 })
1224 .transpose()
1225 .map_err(to_pyvalue_err)?;
1226
1227 let params = DeribitOrderParams {
1228 instrument_name,
1229 amount: quantity.as_decimal(),
1230 order_type: order_type.to_string().to_lowercase(),
1231 label: Some(client_order_id.to_string()),
1232 price: price.map(|p| p.as_decimal()),
1233 time_in_force: deribit_tif,
1234 post_only: if post_only { Some(true) } else { None },
1235 reject_post_only: if post_only { Some(true) } else { None },
1236 reduce_only: if reduce_only { Some(true) } else { None },
1237 trigger_price: trigger_price.map(|p| p.as_decimal()),
1238 trigger: resolve_trigger_type(trigger_type),
1239 max_show: None,
1240 valid_until: None,
1241 };
1242
1243 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1244 client
1245 .submit_order(
1246 order_side,
1247 params,
1248 client_order_id,
1249 trader_id,
1250 strategy_id,
1251 instrument_id,
1252 )
1253 .await
1254 .map_err(to_pyruntime_err)?;
1255 Ok(())
1256 })
1257 }
1258
1259 #[pyo3(name = "modify_order")]
1264 #[expect(clippy::too_many_arguments)]
1265 fn py_modify_order<'py>(
1266 &self,
1267 py: Python<'py>,
1268 order_id: String,
1269 quantity: Quantity,
1270 price: Price,
1271 client_order_id: ClientOrderId,
1272 trader_id: TraderId,
1273 strategy_id: StrategyId,
1274 instrument_id: InstrumentId,
1275 ) -> PyResult<Bound<'py, PyAny>> {
1276 let client = self.clone();
1277
1278 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1279 client
1280 .modify_order(
1281 &order_id,
1282 quantity,
1283 price,
1284 client_order_id,
1285 trader_id,
1286 strategy_id,
1287 instrument_id,
1288 )
1289 .await
1290 .map_err(to_pyruntime_err)?;
1291 Ok(())
1292 })
1293 }
1294
1295 #[pyo3(name = "cancel_order")]
1300 fn py_cancel_order<'py>(
1301 &self,
1302 py: Python<'py>,
1303 order_id: String,
1304 client_order_id: ClientOrderId,
1305 trader_id: TraderId,
1306 strategy_id: StrategyId,
1307 instrument_id: InstrumentId,
1308 ) -> PyResult<Bound<'py, PyAny>> {
1309 let client = self.clone();
1310
1311 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1312 client
1313 .cancel_order(
1314 &order_id,
1315 client_order_id,
1316 trader_id,
1317 strategy_id,
1318 instrument_id,
1319 )
1320 .await
1321 .map_err(to_pyruntime_err)?;
1322 Ok(())
1323 })
1324 }
1325
1326 #[pyo3(name = "cancel_all_orders")]
1331 #[pyo3(signature = (instrument_id, order_type=None))]
1332 fn py_cancel_all_orders<'py>(
1333 &self,
1334 py: Python<'py>,
1335 instrument_id: InstrumentId,
1336 order_type: Option<String>,
1337 ) -> PyResult<Bound<'py, PyAny>> {
1338 let client = self.clone();
1339
1340 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1341 client
1342 .cancel_all_orders(instrument_id, order_type)
1343 .await
1344 .map_err(to_pyruntime_err)?;
1345 Ok(())
1346 })
1347 }
1348
1349 #[pyo3(name = "query_order")]
1354 fn py_query_order<'py>(
1355 &self,
1356 py: Python<'py>,
1357 order_id: String,
1358 client_order_id: ClientOrderId,
1359 trader_id: TraderId,
1360 strategy_id: StrategyId,
1361 instrument_id: InstrumentId,
1362 ) -> PyResult<Bound<'py, PyAny>> {
1363 let client = self.clone();
1364
1365 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1366 client
1367 .query_order(
1368 &order_id,
1369 client_order_id,
1370 trader_id,
1371 strategy_id,
1372 instrument_id,
1373 )
1374 .await
1375 .map_err(to_pyruntime_err)?;
1376 Ok(())
1377 })
1378 }
1379}