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},
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 pyo3::{IntoPyObjectExt, prelude::*};
52
53use crate::{
54 common::{enums::DeribitTimeInForce, parse::parse_instrument_kind_currency},
55 websocket::{
56 client::DeribitWebSocketClient,
57 enums::DeribitUpdateInterval,
58 messages::{DeribitOrderParams, NautilusWsMessage},
59 },
60};
61
62fn call_python_with_data<F>(call_soon: &Py<PyAny>, callback: &Py<PyAny>, data_converter: F)
63where
64 F: FnOnce(Python) -> PyResult<Py<PyAny>>,
65{
66 Python::attach(|py| match data_converter(py) {
67 Ok(py_obj) => call_python_threadsafe(py, call_soon, callback, py_obj),
68 Err(e) => log::error!("Failed to convert data to Python object: {e}"),
69 });
70}
71
72#[pymethods]
73impl DeribitWebSocketClient {
74 #[new]
75 #[pyo3(signature = (
76 url=None,
77 api_key=None,
78 api_secret=None,
79 heartbeat_interval=None,
80 is_testnet=false,
81 ))]
82 fn py_new(
83 url: Option<String>,
84 api_key: Option<String>,
85 api_secret: Option<String>,
86 heartbeat_interval: Option<u64>,
87 is_testnet: bool,
88 ) -> PyResult<Self> {
89 Self::new(url, api_key, api_secret, heartbeat_interval, is_testnet).map_err(to_pyvalue_err)
90 }
91
92 #[staticmethod]
93 #[pyo3(name = "new_public")]
94 fn py_new_public(is_testnet: bool) -> PyResult<Self> {
95 Self::new_public(is_testnet).map_err(to_pyvalue_err)
96 }
97
98 #[staticmethod]
99 #[pyo3(name = "with_credentials", signature = (is_testnet, account_id = None))]
100 fn py_with_credentials(is_testnet: bool, account_id: Option<AccountId>) -> PyResult<Self> {
101 let mut client = Self::with_credentials(is_testnet).map_err(to_pyvalue_err)?;
102
103 if let Some(id) = account_id {
104 client.set_account_id(id);
105 }
106 Ok(client)
107 }
108
109 #[getter]
110 #[pyo3(name = "url")]
111 #[must_use]
112 pub fn py_url(&self) -> String {
113 self.url().to_string()
114 }
115
116 #[getter]
117 #[pyo3(name = "is_testnet")]
118 #[must_use]
119 pub fn py_is_testnet(&self) -> bool {
120 self.url().contains("test")
122 }
123
124 #[pyo3(name = "is_active")]
125 #[must_use]
126 fn py_is_active(&self) -> bool {
127 self.is_active()
128 }
129
130 #[pyo3(name = "is_closed")]
131 #[must_use]
132 fn py_is_closed(&self) -> bool {
133 self.is_closed()
134 }
135
136 #[pyo3(name = "has_credentials")]
137 #[must_use]
138 fn py_has_credentials(&self) -> bool {
139 self.has_credentials()
140 }
141
142 #[pyo3(name = "is_authenticated")]
143 #[must_use]
144 fn py_is_authenticated(&self) -> bool {
145 self.is_authenticated()
146 }
147
148 #[pyo3(name = "cancel_all_requests")]
149 pub fn py_cancel_all_requests(&self) {
150 self.cancel_all_requests();
151 }
152
153 #[pyo3(name = "cache_instruments")]
157 pub fn py_cache_instruments(
158 &self,
159 py: Python<'_>,
160 instruments: Vec<Py<PyAny>>,
161 ) -> PyResult<()> {
162 let instruments: Result<Vec<_>, _> = instruments
163 .into_iter()
164 .map(|inst| pyobject_to_instrument_any(py, inst))
165 .collect();
166 self.cache_instruments(instruments?);
167 Ok(())
168 }
169
170 #[pyo3(name = "cache_instrument")]
174 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
175 let inst = pyobject_to_instrument_any(py, instrument)?;
176 self.cache_instrument(inst);
177 Ok(())
178 }
179
180 #[pyo3(name = "set_account_id")]
181 pub fn py_set_account_id(&mut self, account_id: AccountId) {
182 self.set_account_id(account_id);
183 }
184
185 #[pyo3(name = "set_bars_timestamp_on_close")]
186 pub fn py_set_bars_timestamp_on_close(&mut self, value: bool) {
187 self.set_bars_timestamp_on_close(value);
188 }
189
190 #[pyo3(name = "connect")]
191 fn py_connect<'py>(
192 &mut self,
193 py: Python<'py>,
194 loop_: Py<PyAny>,
195 instruments: Vec<Py<PyAny>>,
196 callback: Py<PyAny>,
197 ) -> PyResult<Bound<'py, PyAny>> {
198 let call_soon: Py<PyAny> = loop_.getattr(py, "call_soon_threadsafe")?;
199
200 let mut instruments_any = Vec::new();
201 for inst in instruments {
202 let inst_any = pyobject_to_instrument_any(py, inst)?;
203 instruments_any.push(inst_any);
204 }
205
206 self.cache_instruments(instruments_any);
207
208 let mut client = self.clone();
209
210 pyo3_async_runtimes::tokio::future_into_py(py, async move {
211 client.connect().await.map_err(to_pyruntime_err)?;
212
213 let stream = client.stream().map_err(to_pyruntime_err)?;
214
215 get_runtime().spawn(async move {
217 let _client = client;
218 tokio::pin!(stream);
219
220 while let Some(msg) = stream.next().await {
221 match msg {
222 NautilusWsMessage::Instrument(msg) => {
223 call_python_with_data(&call_soon, &callback, |py| {
224 instrument_any_to_pyobject(py, *msg)
225 });
226 }
227 NautilusWsMessage::Data(msg) => Python::attach(|py| {
228 for data in msg {
229 let py_obj = data_to_pycapsule(py, data);
230 call_python_threadsafe(py, &call_soon, &callback, py_obj);
231 }
232 }),
233 NautilusWsMessage::Deltas(msg) => Python::attach(|py| {
234 let py_obj =
235 data_to_pycapsule(py, Data::Deltas(OrderBookDeltas_API::new(msg)));
236 call_python_threadsafe(py, &call_soon, &callback, py_obj);
237 }),
238 NautilusWsMessage::Error(err) => {
239 log::error!("WebSocket error: {err}");
240 }
241 NautilusWsMessage::Reconnected => {
242 log::info!("WebSocket reconnected");
243 }
244 NautilusWsMessage::Authenticated(auth_result) => {
245 log::info!("WebSocket authenticated (scope: {})", auth_result.scope);
246 }
247 NautilusWsMessage::InstrumentStatus(status) => {
248 call_python_with_data(&call_soon, &callback, |py| {
249 status.into_py_any(py)
250 });
251 }
252 NautilusWsMessage::Raw(msg) => {
253 log::debug!("Received raw message, skipping: {msg}");
254 }
255 NautilusWsMessage::FundingRates(funding_rates) => Python::attach(|py| {
256 for funding_rate in funding_rates {
257 match Py::new(py, funding_rate) {
258 Ok(py_obj) => call_python_threadsafe(
259 py,
260 &call_soon,
261 &callback,
262 py_obj.into_any(),
263 ),
264 Err(e) => {
265 log::error!("Failed to create FundingRateUpdate: {e}");
266 }
267 }
268 }
269 }),
270 NautilusWsMessage::OrderStatusReports(reports) => Python::attach(|py| {
272 for report in reports {
273 match Py::new(py, report) {
274 Ok(py_obj) => call_python_threadsafe(
275 py,
276 &call_soon,
277 &callback,
278 py_obj.into_any(),
279 ),
280 Err(e) => {
281 log::error!("Failed to create OrderStatusReport: {e}");
282 }
283 }
284 }
285 }),
286 NautilusWsMessage::FillReports(reports) => Python::attach(|py| {
287 for report in reports {
288 match Py::new(py, report) {
289 Ok(py_obj) => call_python_threadsafe(
290 py,
291 &call_soon,
292 &callback,
293 py_obj.into_any(),
294 ),
295 Err(e) => log::error!("Failed to create FillReport: {e}"),
296 }
297 }
298 }),
299 NautilusWsMessage::OrderRejected(msg) => {
300 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
301 }
302 NautilusWsMessage::OrderAccepted(msg) => {
303 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
304 }
305 NautilusWsMessage::OrderCanceled(msg) => {
306 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
307 }
308 NautilusWsMessage::OrderExpired(msg) => {
309 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
310 }
311 NautilusWsMessage::OrderUpdated(msg) => {
312 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
313 }
314 NautilusWsMessage::OrderCancelRejected(msg) => {
315 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
316 }
317 NautilusWsMessage::OrderModifyRejected(msg) => {
318 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
319 }
320 NautilusWsMessage::AccountState(msg) => {
321 call_python_with_data(&call_soon, &callback, |py| msg.into_py_any(py));
322 }
323 NautilusWsMessage::AuthenticationFailed(reason) => {
324 log::error!("Authentication failed: {reason}");
325 }
326 }
327 }
328 });
329
330 Ok(())
331 })
332 }
333
334 #[pyo3(name = "wait_until_active")]
335 fn py_wait_until_active<'py>(
336 &self,
337 py: Python<'py>,
338 timeout_secs: f64,
339 ) -> PyResult<Bound<'py, PyAny>> {
340 let client = self.clone();
341
342 pyo3_async_runtimes::tokio::future_into_py(py, async move {
343 client
344 .wait_until_active(timeout_secs)
345 .await
346 .map_err(to_pyruntime_err)?;
347 Ok(())
348 })
349 }
350
351 #[pyo3(name = "close")]
352 fn py_close<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
353 let client = self.clone();
354
355 pyo3_async_runtimes::tokio::future_into_py(py, async move {
356 if let Err(e) = client.close().await {
357 log::error!("Error on close: {e}");
358 }
359 Ok(())
360 })
361 }
362
363 #[pyo3(name = "authenticate")]
364 #[pyo3(signature = (session_name=None))]
365 fn py_authenticate<'py>(
366 &self,
367 py: Python<'py>,
368 session_name: Option<String>,
369 ) -> PyResult<Bound<'py, PyAny>> {
370 let client = self.clone();
371
372 pyo3_async_runtimes::tokio::future_into_py(py, async move {
373 client
374 .authenticate(session_name.as_deref())
375 .await
376 .map_err(to_pyruntime_err)?;
377 Ok(())
378 })
379 }
380
381 #[pyo3(name = "authenticate_session")]
382 fn py_authenticate_session<'py>(
383 &self,
384 py: Python<'py>,
385 session_name: String,
386 ) -> PyResult<Bound<'py, PyAny>> {
387 let client = self.clone();
388
389 pyo3_async_runtimes::tokio::future_into_py(py, async move {
390 client
391 .authenticate_session(&session_name)
392 .await
393 .map_err(|e| {
394 to_pyruntime_err(format!(
395 "Failed to authenticate Deribit websocket session '{session_name}': {e}"
396 ))
397 })?;
398 Ok(())
399 })
400 }
401
402 #[pyo3(name = "subscribe_trades")]
403 #[pyo3(signature = (instrument_id, interval=None))]
404 fn py_subscribe_trades<'py>(
405 &self,
406 py: Python<'py>,
407 instrument_id: InstrumentId,
408 interval: Option<DeribitUpdateInterval>,
409 ) -> PyResult<Bound<'py, PyAny>> {
410 let client = self.clone();
411
412 pyo3_async_runtimes::tokio::future_into_py(py, async move {
413 client
414 .subscribe_trades(instrument_id, interval)
415 .await
416 .map_err(to_pyvalue_err)
417 })
418 }
419
420 #[pyo3(name = "unsubscribe_trades")]
421 #[pyo3(signature = (instrument_id, interval=None))]
422 fn py_unsubscribe_trades<'py>(
423 &self,
424 py: Python<'py>,
425 instrument_id: InstrumentId,
426 interval: Option<DeribitUpdateInterval>,
427 ) -> PyResult<Bound<'py, PyAny>> {
428 let client = self.clone();
429
430 pyo3_async_runtimes::tokio::future_into_py(py, async move {
431 client
432 .unsubscribe_trades(instrument_id, interval)
433 .await
434 .map_err(to_pyvalue_err)
435 })
436 }
437
438 #[pyo3(name = "subscribe_book")]
439 #[pyo3(signature = (instrument_id, interval=None, depth=None))]
440 fn py_subscribe_book<'py>(
441 &self,
442 py: Python<'py>,
443 instrument_id: InstrumentId,
444 interval: Option<DeribitUpdateInterval>,
445 depth: Option<u32>,
446 ) -> PyResult<Bound<'py, PyAny>> {
447 let client = self.clone();
448
449 pyo3_async_runtimes::tokio::future_into_py(py, async move {
450 if let Some(d) = depth {
451 client
452 .subscribe_book_grouped(instrument_id, "none", d, interval)
453 .await
454 .map_err(to_pyvalue_err)
455 } else {
456 client
457 .subscribe_book(instrument_id, interval)
458 .await
459 .map_err(to_pyvalue_err)
460 }
461 })
462 }
463
464 #[pyo3(name = "unsubscribe_book")]
465 #[pyo3(signature = (instrument_id, interval=None, depth=None))]
466 fn py_unsubscribe_book<'py>(
467 &self,
468 py: Python<'py>,
469 instrument_id: InstrumentId,
470 interval: Option<DeribitUpdateInterval>,
471 depth: Option<u32>,
472 ) -> PyResult<Bound<'py, PyAny>> {
473 let client = self.clone();
474
475 pyo3_async_runtimes::tokio::future_into_py(py, async move {
476 if let Some(d) = depth {
477 client
478 .unsubscribe_book_grouped(instrument_id, "none", d, interval)
479 .await
480 .map_err(to_pyvalue_err)
481 } else {
482 client
483 .unsubscribe_book(instrument_id, interval)
484 .await
485 .map_err(to_pyvalue_err)
486 }
487 })
488 }
489
490 #[pyo3(name = "subscribe_book_grouped")]
491 #[pyo3(signature = (instrument_id, group, depth, interval=None))]
492 fn py_subscribe_book_grouped<'py>(
493 &self,
494 py: Python<'py>,
495 instrument_id: InstrumentId,
496 group: String,
497 depth: u32,
498 interval: Option<DeribitUpdateInterval>,
499 ) -> PyResult<Bound<'py, PyAny>> {
500 let client = self.clone();
501
502 pyo3_async_runtimes::tokio::future_into_py(py, async move {
503 client
504 .subscribe_book_grouped(instrument_id, &group, depth, interval)
505 .await
506 .map_err(to_pyvalue_err)
507 })
508 }
509
510 #[pyo3(name = "unsubscribe_book_grouped")]
511 #[pyo3(signature = (instrument_id, group, depth, interval=None))]
512 fn py_unsubscribe_book_grouped<'py>(
513 &self,
514 py: Python<'py>,
515 instrument_id: InstrumentId,
516 group: String,
517 depth: u32,
518 interval: Option<DeribitUpdateInterval>,
519 ) -> PyResult<Bound<'py, PyAny>> {
520 let client = self.clone();
521
522 pyo3_async_runtimes::tokio::future_into_py(py, async move {
523 client
524 .unsubscribe_book_grouped(instrument_id, &group, depth, interval)
525 .await
526 .map_err(to_pyvalue_err)
527 })
528 }
529
530 #[pyo3(name = "subscribe_ticker")]
531 #[pyo3(signature = (instrument_id, interval=None))]
532 fn py_subscribe_ticker<'py>(
533 &self,
534 py: Python<'py>,
535 instrument_id: InstrumentId,
536 interval: Option<DeribitUpdateInterval>,
537 ) -> PyResult<Bound<'py, PyAny>> {
538 let client = self.clone();
539
540 pyo3_async_runtimes::tokio::future_into_py(py, async move {
541 client
542 .subscribe_ticker(instrument_id, interval)
543 .await
544 .map_err(to_pyvalue_err)
545 })
546 }
547
548 #[pyo3(name = "unsubscribe_ticker")]
549 #[pyo3(signature = (instrument_id, interval=None))]
550 fn py_unsubscribe_ticker<'py>(
551 &self,
552 py: Python<'py>,
553 instrument_id: InstrumentId,
554 interval: Option<DeribitUpdateInterval>,
555 ) -> PyResult<Bound<'py, PyAny>> {
556 let client = self.clone();
557
558 pyo3_async_runtimes::tokio::future_into_py(py, async move {
559 client
560 .unsubscribe_ticker(instrument_id, interval)
561 .await
562 .map_err(to_pyvalue_err)
563 })
564 }
565
566 #[pyo3(name = "subscribe_quotes")]
567 fn py_subscribe_quotes<'py>(
568 &self,
569 py: Python<'py>,
570 instrument_id: InstrumentId,
571 ) -> PyResult<Bound<'py, PyAny>> {
572 let client = self.clone();
573
574 pyo3_async_runtimes::tokio::future_into_py(py, async move {
575 client
576 .subscribe_quotes(instrument_id)
577 .await
578 .map_err(to_pyvalue_err)
579 })
580 }
581
582 #[pyo3(name = "unsubscribe_quotes")]
583 fn py_unsubscribe_quotes<'py>(
584 &self,
585 py: Python<'py>,
586 instrument_id: InstrumentId,
587 ) -> PyResult<Bound<'py, PyAny>> {
588 let client = self.clone();
589
590 pyo3_async_runtimes::tokio::future_into_py(py, async move {
591 client
592 .unsubscribe_quotes(instrument_id)
593 .await
594 .map_err(to_pyvalue_err)
595 })
596 }
597
598 #[pyo3(name = "subscribe_user_orders")]
599 fn py_subscribe_user_orders<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
600 let client = self.clone();
601
602 pyo3_async_runtimes::tokio::future_into_py(py, async move {
603 client.subscribe_user_orders().await.map_err(to_pyvalue_err)
604 })
605 }
606
607 #[pyo3(name = "unsubscribe_user_orders")]
608 fn py_unsubscribe_user_orders<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
609 let client = self.clone();
610
611 pyo3_async_runtimes::tokio::future_into_py(py, async move {
612 client
613 .unsubscribe_user_orders()
614 .await
615 .map_err(to_pyvalue_err)
616 })
617 }
618
619 #[pyo3(name = "subscribe_user_trades")]
620 fn py_subscribe_user_trades<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
621 let client = self.clone();
622
623 pyo3_async_runtimes::tokio::future_into_py(py, async move {
624 client.subscribe_user_trades().await.map_err(to_pyvalue_err)
625 })
626 }
627
628 #[pyo3(name = "unsubscribe_user_trades")]
629 fn py_unsubscribe_user_trades<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
630 let client = self.clone();
631
632 pyo3_async_runtimes::tokio::future_into_py(py, async move {
633 client
634 .unsubscribe_user_trades()
635 .await
636 .map_err(to_pyvalue_err)
637 })
638 }
639
640 #[pyo3(name = "subscribe_user_portfolio")]
641 fn py_subscribe_user_portfolio<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
642 let client = self.clone();
643
644 pyo3_async_runtimes::tokio::future_into_py(py, async move {
645 client
646 .subscribe_user_portfolio()
647 .await
648 .map_err(to_pyvalue_err)
649 })
650 }
651
652 #[pyo3(name = "unsubscribe_user_portfolio")]
653 fn py_unsubscribe_user_portfolio<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
654 let client = self.clone();
655
656 pyo3_async_runtimes::tokio::future_into_py(py, async move {
657 client
658 .unsubscribe_user_portfolio()
659 .await
660 .map_err(to_pyvalue_err)
661 })
662 }
663
664 #[pyo3(name = "subscribe")]
665 fn py_subscribe<'py>(
666 &self,
667 py: Python<'py>,
668 channels: Vec<String>,
669 ) -> PyResult<Bound<'py, PyAny>> {
670 let client = self.clone();
671
672 pyo3_async_runtimes::tokio::future_into_py(py, async move {
673 client.subscribe(channels).await.map_err(to_pyvalue_err)
674 })
675 }
676
677 #[pyo3(name = "unsubscribe")]
678 fn py_unsubscribe<'py>(
679 &self,
680 py: Python<'py>,
681 channels: Vec<String>,
682 ) -> PyResult<Bound<'py, PyAny>> {
683 let client = self.clone();
684
685 pyo3_async_runtimes::tokio::future_into_py(py, async move {
686 client.unsubscribe(channels).await.map_err(to_pyvalue_err)
687 })
688 }
689
690 #[pyo3(name = "subscribe_perpetual_interest_rates")]
691 #[pyo3(signature = (instrument_id, interval=None))]
692 fn py_subscribe_perpetual_interest_rates<'py>(
693 &self,
694 py: Python<'py>,
695 instrument_id: InstrumentId,
696 interval: Option<DeribitUpdateInterval>,
697 ) -> PyResult<Bound<'py, PyAny>> {
698 let client = self.clone();
699
700 pyo3_async_runtimes::tokio::future_into_py(py, async move {
701 client
702 .subscribe_perpetual_interests_rates_updates(instrument_id, interval)
703 .await
704 .map_err(to_pyvalue_err)
705 })
706 }
707
708 #[pyo3(name = "unsubscribe_perpetual_interest_rates")]
709 #[pyo3(signature = (instrument_id, interval=None))]
710 fn py_unsubscribe_perpetual_interest_rates<'py>(
711 &self,
712 py: Python<'py>,
713 instrument_id: InstrumentId,
714 interval: Option<DeribitUpdateInterval>,
715 ) -> PyResult<Bound<'py, PyAny>> {
716 let client = self.clone();
717
718 pyo3_async_runtimes::tokio::future_into_py(py, async move {
719 client
720 .unsubscribe_perpetual_interest_rates_updates(instrument_id, interval)
721 .await
722 .map_err(to_pyvalue_err)
723 })
724 }
725
726 #[pyo3(name = "subscribe_instrument_status")]
727 fn py_subscribe_instrument_status<'py>(
728 &self,
729 py: Python<'py>,
730 instrument_id: InstrumentId,
731 ) -> PyResult<Bound<'py, PyAny>> {
732 let client = self.clone();
733 let (kind, currency) = parse_instrument_kind_currency(&instrument_id);
734
735 pyo3_async_runtimes::tokio::future_into_py(py, async move {
736 client
737 .subscribe_instrument_status(&kind, ¤cy)
738 .await
739 .map_err(to_pyvalue_err)
740 })
741 }
742
743 #[pyo3(name = "unsubscribe_instrument_status")]
744 fn py_unsubscribe_instrument_status<'py>(
745 &self,
746 py: Python<'py>,
747 instrument_id: InstrumentId,
748 ) -> PyResult<Bound<'py, PyAny>> {
749 let client = self.clone();
750 let (kind, currency) = parse_instrument_kind_currency(&instrument_id);
751
752 pyo3_async_runtimes::tokio::future_into_py(py, async move {
753 client
754 .unsubscribe_instrument_status(&kind, ¤cy)
755 .await
756 .map_err(to_pyvalue_err)
757 })
758 }
759
760 #[pyo3(name = "subscribe_chart")]
761 fn py_subscribe_chart<'py>(
762 &self,
763 py: Python<'py>,
764 instrument_id: InstrumentId,
765 resolution: String,
766 ) -> PyResult<Bound<'py, PyAny>> {
767 let client = self.clone();
768
769 pyo3_async_runtimes::tokio::future_into_py(py, async move {
770 client
771 .subscribe_chart(instrument_id, &resolution)
772 .await
773 .map_err(to_pyvalue_err)
774 })
775 }
776
777 #[pyo3(name = "unsubscribe_chart")]
778 fn py_unsubscribe_chart<'py>(
779 &self,
780 py: Python<'py>,
781 instrument_id: InstrumentId,
782 resolution: String,
783 ) -> PyResult<Bound<'py, PyAny>> {
784 let client = self.clone();
785
786 pyo3_async_runtimes::tokio::future_into_py(py, async move {
787 client
788 .unsubscribe_chart(instrument_id, &resolution)
789 .await
790 .map_err(to_pyvalue_err)
791 })
792 }
793
794 #[pyo3(name = "subscribe_bars")]
795 fn py_subscribe_bars<'py>(
796 &self,
797 py: Python<'py>,
798 bar_type: BarType,
799 ) -> PyResult<Bound<'py, PyAny>> {
800 let client = self.clone();
801
802 pyo3_async_runtimes::tokio::future_into_py(py, async move {
803 client
804 .subscribe_bars(bar_type)
805 .await
806 .map_err(to_pyvalue_err)
807 })
808 }
809
810 #[pyo3(name = "unsubscribe_bars")]
811 fn py_unsubscribe_bars<'py>(
812 &self,
813 py: Python<'py>,
814 bar_type: BarType,
815 ) -> PyResult<Bound<'py, PyAny>> {
816 let client = self.clone();
817
818 pyo3_async_runtimes::tokio::future_into_py(py, async move {
819 client
820 .unsubscribe_bars(bar_type)
821 .await
822 .map_err(to_pyvalue_err)
823 })
824 }
825
826 #[pyo3(name = "submit_order")]
827 #[pyo3(signature = (
828 order_side,
829 quantity,
830 order_type,
831 client_order_id,
832 trader_id,
833 strategy_id,
834 instrument_id,
835 price=None,
836 time_in_force=None,
837 post_only=false,
838 reduce_only=false,
839 trigger_price=None,
840 trigger=None,
841 ))]
842 #[allow(clippy::too_many_arguments)]
843 fn py_submit_order<'py>(
844 &self,
845 py: Python<'py>,
846 order_side: OrderSide,
847 quantity: Quantity,
848 order_type: OrderType,
849 client_order_id: ClientOrderId,
850 trader_id: TraderId,
851 strategy_id: StrategyId,
852 instrument_id: InstrumentId,
853 price: Option<Price>,
854 time_in_force: Option<TimeInForce>,
855 post_only: bool,
856 reduce_only: bool,
857 trigger_price: Option<Price>,
858 trigger: Option<String>,
859 ) -> PyResult<Bound<'py, PyAny>> {
860 let client = self.clone();
861 let instrument_name = instrument_id.symbol.to_string();
862
863 let deribit_tif = time_in_force
865 .map(|tif| {
866 DeribitTimeInForce::try_from(tif)
867 .map(|deribit_tif| deribit_tif.as_str().to_string())
868 })
869 .transpose()
870 .map_err(to_pyvalue_err)?;
871
872 let params = DeribitOrderParams {
873 instrument_name,
874 amount: quantity.as_decimal(),
875 order_type: order_type.to_string().to_lowercase(),
876 label: Some(client_order_id.to_string()),
877 price: price.map(|p| p.as_decimal()),
878 time_in_force: deribit_tif,
879 post_only: if post_only { Some(true) } else { None },
880 reject_post_only: if post_only { Some(true) } else { None },
881 reduce_only: if reduce_only { Some(true) } else { None },
882 trigger_price: trigger_price.map(|p| p.as_decimal()),
883 trigger,
884 max_show: None,
885 valid_until: None,
886 };
887
888 pyo3_async_runtimes::tokio::future_into_py(py, async move {
889 client
890 .submit_order(
891 order_side,
892 params,
893 client_order_id,
894 trader_id,
895 strategy_id,
896 instrument_id,
897 )
898 .await
899 .map_err(to_pyruntime_err)?;
900 Ok(())
901 })
902 }
903
904 #[pyo3(name = "modify_order")]
905 #[allow(clippy::too_many_arguments)]
906 fn py_modify_order<'py>(
907 &self,
908 py: Python<'py>,
909 order_id: String,
910 quantity: Quantity,
911 price: Price,
912 client_order_id: ClientOrderId,
913 trader_id: TraderId,
914 strategy_id: StrategyId,
915 instrument_id: InstrumentId,
916 ) -> PyResult<Bound<'py, PyAny>> {
917 let client = self.clone();
918
919 pyo3_async_runtimes::tokio::future_into_py(py, async move {
920 client
921 .modify_order(
922 &order_id,
923 quantity,
924 price,
925 client_order_id,
926 trader_id,
927 strategy_id,
928 instrument_id,
929 )
930 .await
931 .map_err(to_pyruntime_err)?;
932 Ok(())
933 })
934 }
935
936 #[pyo3(name = "cancel_order")]
937 fn py_cancel_order<'py>(
938 &self,
939 py: Python<'py>,
940 order_id: String,
941 client_order_id: ClientOrderId,
942 trader_id: TraderId,
943 strategy_id: StrategyId,
944 instrument_id: InstrumentId,
945 ) -> PyResult<Bound<'py, PyAny>> {
946 let client = self.clone();
947
948 pyo3_async_runtimes::tokio::future_into_py(py, async move {
949 client
950 .cancel_order(
951 &order_id,
952 client_order_id,
953 trader_id,
954 strategy_id,
955 instrument_id,
956 )
957 .await
958 .map_err(to_pyruntime_err)?;
959 Ok(())
960 })
961 }
962
963 #[pyo3(name = "cancel_all_orders")]
964 #[pyo3(signature = (instrument_id, order_type=None))]
965 fn py_cancel_all_orders<'py>(
966 &self,
967 py: Python<'py>,
968 instrument_id: InstrumentId,
969 order_type: Option<String>,
970 ) -> PyResult<Bound<'py, PyAny>> {
971 let client = self.clone();
972
973 pyo3_async_runtimes::tokio::future_into_py(py, async move {
974 client
975 .cancel_all_orders(instrument_id, order_type)
976 .await
977 .map_err(to_pyruntime_err)?;
978 Ok(())
979 })
980 }
981
982 #[pyo3(name = "query_order")]
983 fn py_query_order<'py>(
984 &self,
985 py: Python<'py>,
986 order_id: String,
987 client_order_id: ClientOrderId,
988 trader_id: TraderId,
989 strategy_id: StrategyId,
990 instrument_id: InstrumentId,
991 ) -> PyResult<Bound<'py, PyAny>> {
992 let client = self.clone();
993
994 pyo3_async_runtimes::tokio::future_into_py(py, async move {
995 client
996 .query_order(
997 &order_id,
998 client_order_id,
999 trader_id,
1000 strategy_id,
1001 instrument_id,
1002 )
1003 .await
1004 .map_err(to_pyruntime_err)?;
1005 Ok(())
1006 })
1007 }
1008}