1use std::collections::HashMap;
17
18use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
19use nautilus_model::{
20 data::BarType,
21 enums::{OrderSide, OrderType, TimeInForce},
22 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
23 instruments::Instrument,
24 orders::OrderAny,
25 python::{
26 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
27 orders::pyobject_to_order_any,
28 },
29 types::{Price, Quantity},
30};
31use pyo3::{IntoPyObjectExt, prelude::*, types::PyList};
32use rust_decimal::Decimal;
33use serde_json::to_string;
34
35use crate::{
36 common::enums::HyperliquidEnvironment,
37 http::{client::HyperliquidHttpClient, parse::HyperliquidMarketType},
38};
39
40#[pymethods]
41#[pyo3_stub_gen::derive::gen_stub_pymethods]
42impl HyperliquidHttpClient {
43 #[new]
49 #[pyo3(signature = (private_key=None, vault_address=None, account_address=None, environment=HyperliquidEnvironment::Mainnet, timeout_secs=60, proxy_url=None, normalize_prices=true, include_builder_attribution=true))]
50 #[expect(clippy::too_many_arguments)]
51 fn py_new(
52 private_key: Option<String>,
53 vault_address: Option<String>,
54 account_address: Option<&str>,
55 environment: HyperliquidEnvironment,
56 timeout_secs: u64,
57 proxy_url: Option<String>,
58 normalize_prices: bool,
59 include_builder_attribution: bool,
60 ) -> PyResult<Self> {
61 let mut client = Self::with_credentials(
62 private_key,
63 vault_address,
64 account_address,
65 environment,
66 timeout_secs,
67 proxy_url,
68 )
69 .map_err(to_pyvalue_err)?;
70 client.set_normalize_prices(normalize_prices);
71 client.set_include_builder_attribution(include_builder_attribution);
72 Ok(client)
73 }
74
75 #[staticmethod]
81 #[pyo3(name = "from_env", signature = (environment=HyperliquidEnvironment::Mainnet, include_builder_attribution=true))]
82 fn py_from_env(
83 environment: HyperliquidEnvironment,
84 include_builder_attribution: bool,
85 ) -> PyResult<Self> {
86 let mut client = Self::from_env(environment).map_err(to_pyvalue_err)?;
87 client.set_include_builder_attribution(include_builder_attribution);
88 Ok(client)
89 }
90
91 #[staticmethod]
97 #[pyo3(name = "from_credentials", signature = (private_key, vault_address=None, environment=HyperliquidEnvironment::Mainnet, timeout_secs=60, proxy_url=None, include_builder_attribution=true))]
98 fn py_from_credentials(
99 private_key: &str,
100 vault_address: Option<&str>,
101 environment: HyperliquidEnvironment,
102 timeout_secs: u64,
103 proxy_url: Option<String>,
104 include_builder_attribution: bool,
105 ) -> PyResult<Self> {
106 let mut client = Self::from_credentials(
107 private_key,
108 vault_address,
109 environment,
110 timeout_secs,
111 proxy_url,
112 )
113 .map_err(to_pyvalue_err)?;
114 client.set_include_builder_attribution(include_builder_attribution);
115 Ok(client)
116 }
117
118 #[pyo3(name = "cache_instrument")]
123 fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
124 self.cache_instrument(&pyobject_to_instrument_any(py, instrument)?);
125 Ok(())
126 }
127
128 #[pyo3(name = "set_account_id")]
132 fn py_set_account_id(&mut self, account_id: &str) {
133 let account_id = AccountId::from(account_id);
134 self.set_account_id(account_id);
135 }
136
137 #[pyo3(name = "get_user_address")]
143 fn py_get_user_address(&self) -> PyResult<String> {
144 self.get_user_address().map_err(to_pyvalue_err)
145 }
146
147 #[pyo3(name = "get_spot_fill_coin_mapping")]
155 fn py_get_spot_fill_coin_mapping(&self) -> HashMap<String, String> {
156 self.get_spot_fill_coin_mapping()
157 .into_iter()
158 .map(|(k, v)| (k.to_string(), v.to_string()))
159 .collect()
160 }
161
162 #[pyo3(name = "get_spot_meta")]
164 fn py_get_spot_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
165 let client = self.clone();
166 pyo3_async_runtimes::tokio::future_into_py(py, async move {
167 let meta = client.get_spot_meta().await.map_err(to_pyvalue_err)?;
168 to_string(&meta).map_err(to_pyvalue_err)
169 })
170 }
171
172 #[pyo3(name = "get_perp_meta")]
173 fn py_get_perp_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
174 let client = self.clone();
175 pyo3_async_runtimes::tokio::future_into_py(py, async move {
176 let meta = client.load_perp_meta().await.map_err(to_pyvalue_err)?;
177 to_string(&meta).map_err(to_pyvalue_err)
178 })
179 }
180
181 #[pyo3(name = "build_all_dex_asset_ctxs_instrument_ids")]
186 fn py_build_all_dex_asset_ctxs_instrument_ids<'py>(
187 &self,
188 py: Python<'py>,
189 ) -> PyResult<Bound<'py, PyAny>> {
190 let client = self.clone();
191 pyo3_async_runtimes::tokio::future_into_py(py, async move {
192 let mapping = client
193 .build_all_dex_asset_ctxs_instrument_ids()
194 .await
195 .map_err(to_pyvalue_err)?;
196 Ok(mapping.into_iter().collect::<HashMap<_, _>>())
197 })
198 }
199
200 #[pyo3(name = "load_instrument_definitions", signature = (include_spot=true, include_perps=true, include_perps_hip3=false, include_outcomes=false))]
201 fn py_load_instrument_definitions<'py>(
202 &self,
203 py: Python<'py>,
204 include_spot: bool,
205 include_perps: bool,
206 include_perps_hip3: bool,
207 include_outcomes: bool,
208 ) -> PyResult<Bound<'py, PyAny>> {
209 let client = self.clone();
210
211 pyo3_async_runtimes::tokio::future_into_py(py, async move {
212 let mut defs = client
213 .request_instrument_defs()
214 .await
215 .map_err(to_pyvalue_err)?;
216
217 defs.retain(|def| match def.market_type {
218 HyperliquidMarketType::Perp => {
219 if def.is_hip3 {
220 include_perps_hip3
221 } else {
222 include_perps
223 }
224 }
225 HyperliquidMarketType::Spot => include_spot,
226 HyperliquidMarketType::Outcome => include_outcomes,
227 });
228
229 let mut instruments = client.convert_defs(defs);
230 instruments.sort_by_key(|instrument| instrument.id());
231
232 Python::attach(|py| {
233 let mut py_instruments = Vec::with_capacity(instruments.len());
234 for instrument in instruments {
235 py_instruments.push(instrument_any_to_pyobject(py, instrument)?);
236 }
237
238 let py_list = PyList::new(py, &py_instruments)?;
239 Ok(py_list.into_any().unbind())
240 })
241 })
242 }
243
244 #[pyo3(name = "request_quote_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
245 fn py_request_quote_ticks<'py>(
246 &self,
247 py: Python<'py>,
248 instrument_id: InstrumentId,
249 start: Option<jiff::Timestamp>,
250 end: Option<jiff::Timestamp>,
251 limit: Option<u32>,
252 ) -> PyResult<Bound<'py, PyAny>> {
253 let _ = (instrument_id, start, end, limit);
254 pyo3_async_runtimes::tokio::future_into_py(py, async move {
255 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
256 "Hyperliquid does not provide historical quotes via HTTP API"
257 )))
258 })
259 }
260
261 #[pyo3(name = "request_trade_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
262 fn py_request_trade_ticks<'py>(
263 &self,
264 py: Python<'py>,
265 instrument_id: InstrumentId,
266 start: Option<jiff::Timestamp>,
267 end: Option<jiff::Timestamp>,
268 limit: Option<u32>,
269 ) -> PyResult<Bound<'py, PyAny>> {
270 let _ = (instrument_id, start, end, limit);
271 pyo3_async_runtimes::tokio::future_into_py(py, async move {
272 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
273 "Hyperliquid does not provide historical market trades via HTTP API"
274 )))
275 })
276 }
277
278 #[pyo3(name = "request_public_trades", signature = (instrument_id, start=None, end=None, limit=None))]
287 #[gen_stub(override_return_type(type_repr = "typing.Any", imports = ("typing",)))]
288 fn py_request_public_trades<'py>(
289 &self,
290 py: Python<'py>,
291 instrument_id: InstrumentId,
292 start: Option<jiff::Timestamp>,
293 end: Option<jiff::Timestamp>,
294 limit: Option<u32>,
295 ) -> PyResult<Bound<'py, PyAny>> {
296 let client = self.clone();
297
298 pyo3_async_runtimes::tokio::future_into_py(py, async move {
299 let trades = client
300 .request_public_trades(instrument_id, start, end, limit.map(|limit| limit as usize))
301 .await
302 .map_err(to_pyvalue_err)?;
303
304 Python::attach(|py| {
305 let py_trades = trades
306 .into_iter()
307 .map(|trade| trade.into_py_any(py))
308 .collect::<PyResult<Vec<_>>>()?;
309 let pylist = PyList::new(py, py_trades)?;
310 Ok(pylist.into_py_any_unwrap(py))
311 })
312 })
313 }
314
315 #[pyo3(name = "request_bars", signature = (bar_type, start=None, end=None, limit=None))]
332 fn py_request_bars<'py>(
333 &self,
334 py: Python<'py>,
335 bar_type: BarType,
336 start: Option<jiff::Timestamp>,
337 end: Option<jiff::Timestamp>,
338 limit: Option<u32>,
339 ) -> PyResult<Bound<'py, PyAny>> {
340 let client = self.clone();
341
342 pyo3_async_runtimes::tokio::future_into_py(py, async move {
343 let bars = client
344 .request_bars(bar_type, start, end, limit)
345 .await
346 .map_err(to_pyvalue_err)?;
347
348 Python::attach(|py| {
349 let py_bars = bars
350 .into_iter()
351 .map(|bar| bar.into_py_any(py))
352 .collect::<PyResult<Vec<_>>>()?;
353 let pylist = PyList::new(py, py_bars)?;
354 Ok(pylist.into_py_any_unwrap(py))
355 })
356 })
357 }
358
359 #[pyo3(name = "submit_order", signature = (
366 instrument_id,
367 client_order_id,
368 order_side,
369 order_type,
370 quantity,
371 time_in_force,
372 price=None,
373 trigger_price=None,
374 post_only=false,
375 reduce_only=false,
376 ))]
377 #[expect(clippy::too_many_arguments)]
378 fn py_submit_order<'py>(
379 &self,
380 py: Python<'py>,
381 instrument_id: InstrumentId,
382 client_order_id: ClientOrderId,
383 order_side: OrderSide,
384 order_type: OrderType,
385 quantity: Quantity,
386 time_in_force: TimeInForce,
387 price: Option<Price>,
388 trigger_price: Option<Price>,
389 post_only: bool,
390 reduce_only: bool,
391 ) -> PyResult<Bound<'py, PyAny>> {
392 let client = self.clone();
393
394 pyo3_async_runtimes::tokio::future_into_py(py, async move {
395 let report = client
396 .submit_order(
397 instrument_id,
398 client_order_id,
399 order_side,
400 order_type,
401 quantity,
402 time_in_force,
403 price,
404 trigger_price,
405 post_only,
406 reduce_only,
407 )
408 .await
409 .map_err(to_pyvalue_err)?;
410
411 Python::attach(|py| report.into_py_any(py))
412 })
413 }
414
415 #[pyo3(name = "cancel_order", signature = (
425 instrument_id,
426 client_order_id=None,
427 venue_order_id=None,
428 ))]
429 fn py_cancel_order<'py>(
430 &self,
431 py: Python<'py>,
432 instrument_id: InstrumentId,
433 client_order_id: Option<ClientOrderId>,
434 venue_order_id: Option<VenueOrderId>,
435 ) -> PyResult<Bound<'py, PyAny>> {
436 let client = self.clone();
437
438 pyo3_async_runtimes::tokio::future_into_py(py, async move {
439 client
440 .cancel_order(instrument_id, client_order_id, venue_order_id)
441 .await
442 .map_err(to_pyvalue_err)?;
443 Ok(())
444 })
445 }
446
447 #[pyo3(name = "modify_order")]
457 #[expect(clippy::too_many_arguments)]
458 fn py_modify_order<'py>(
459 &self,
460 py: Python<'py>,
461 instrument_id: InstrumentId,
462 venue_order_id: Option<VenueOrderId>,
463 order_side: OrderSide,
464 order_type: OrderType,
465 price: Price,
466 quantity: Quantity,
467 trigger_price: Option<Price>,
468 reduce_only: bool,
469 post_only: bool,
470 time_in_force: TimeInForce,
471 client_order_id: Option<ClientOrderId>,
472 ) -> PyResult<Bound<'py, PyAny>> {
473 let client = self.clone();
474
475 pyo3_async_runtimes::tokio::future_into_py(py, async move {
476 client
477 .modify_order(
478 instrument_id,
479 venue_order_id,
480 order_side,
481 order_type,
482 price,
483 quantity,
484 trigger_price,
485 reduce_only,
486 post_only,
487 time_in_force,
488 client_order_id,
489 )
490 .await
491 .map_err(to_pyvalue_err)?;
492 Ok(())
493 })
494 }
495
496 #[pyo3(name = "submit_orders")]
503 fn py_submit_orders<'py>(
504 &self,
505 py: Python<'py>,
506 orders: Vec<Py<PyAny>>,
507 ) -> PyResult<Bound<'py, PyAny>> {
508 let client = self.clone();
509
510 pyo3_async_runtimes::tokio::future_into_py(py, async move {
511 let order_anys: Vec<OrderAny> = Python::attach(|py| {
512 orders
513 .into_iter()
514 .map(|order| pyobject_to_order_any(py, order))
515 .collect::<PyResult<Vec<_>>>()
516 .map_err(to_pyvalue_err)
517 })?;
518
519 let order_refs: Vec<&OrderAny> = order_anys.iter().collect();
520
521 let reports = client
522 .submit_orders(&order_refs)
523 .await
524 .map_err(to_pyvalue_err)?;
525
526 Python::attach(|py| {
527 let py_reports = reports
528 .into_iter()
529 .map(|report| report.into_py_any(py))
530 .collect::<PyResult<Vec<_>>>()?;
531 let pylist = PyList::new(py, py_reports)?;
532 Ok(pylist.into_py_any_unwrap(py))
533 })
534 })
535 }
536
537 #[pyo3(name = "request_order_status_reports")]
550 fn py_request_order_status_reports<'py>(
551 &self,
552 py: Python<'py>,
553 instrument_id: Option<&str>,
554 ) -> PyResult<Bound<'py, PyAny>> {
555 let client = self.clone();
556 let instrument_id = instrument_id.map(InstrumentId::from);
557
558 pyo3_async_runtimes::tokio::future_into_py(py, async move {
559 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
560 let reports = client
561 .request_order_status_reports(&account_address, instrument_id)
562 .await
563 .map_err(to_pyvalue_err)?;
564
565 Python::attach(|py| {
566 let py_reports = reports
567 .into_iter()
568 .map(|report| report.into_py_any(py))
569 .collect::<PyResult<Vec<_>>>()?;
570 let pylist = PyList::new(py, py_reports)?;
571 Ok(pylist.into_py_any_unwrap(py))
572 })
573 })
574 }
575
576 #[pyo3(name = "request_order_status_report")]
586 #[pyo3(signature = (venue_order_id=None, client_order_id=None))]
587 fn py_request_order_status_report<'py>(
588 &self,
589 py: Python<'py>,
590 venue_order_id: Option<&str>,
591 client_order_id: Option<&str>,
592 ) -> PyResult<Bound<'py, PyAny>> {
593 let client = self.clone();
594 let venue_order_id = venue_order_id.map(VenueOrderId::from);
595 let client_order_id = client_order_id.map(ClientOrderId::from);
596
597 pyo3_async_runtimes::tokio::future_into_py(py, async move {
598 if venue_order_id.is_none() && client_order_id.is_none() {
599 return Err(to_pyvalue_err(
600 "at least one of venue_order_id or client_order_id is required",
601 ));
602 }
603
604 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
605
606 if let Some(coid) = client_order_id.as_ref()
607 && let Some(report) = client
608 .request_order_status_report_by_client_order_id(&account_address, coid)
609 .await
610 .map_err(to_pyvalue_err)?
611 {
612 return Python::attach(|py| report.into_py_any(py));
613 }
614
615 let report = if let Some(vid) = venue_order_id.as_ref() {
616 let oid: u64 = vid
617 .as_str()
618 .parse()
619 .map_err(|e| to_pyvalue_err(format!("invalid venue_order_id: {e}")))?;
620
621 client
622 .request_order_status_report(&account_address, oid)
623 .await
624 .map_err(to_pyvalue_err)?
625 } else {
626 None
627 };
628
629 Python::attach(|py| match report {
630 Some(report) => report.into_py_any(py),
631 None => Ok(py.None()),
632 })
633 })
634 }
635
636 #[pyo3(name = "request_fill_reports")]
650 fn py_request_fill_reports<'py>(
651 &self,
652 py: Python<'py>,
653 instrument_id: Option<&str>,
654 ) -> PyResult<Bound<'py, PyAny>> {
655 let client = self.clone();
656 let instrument_id = instrument_id.map(InstrumentId::from);
657
658 pyo3_async_runtimes::tokio::future_into_py(py, async move {
659 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
660 let reports = client
661 .request_fill_reports(&account_address, instrument_id)
662 .await
663 .map_err(to_pyvalue_err)?;
664
665 Python::attach(|py| {
666 let py_reports = reports
667 .into_iter()
668 .map(|report| report.into_py_any(py))
669 .collect::<PyResult<Vec<_>>>()?;
670 let pylist = PyList::new(py, py_reports)?;
671 Ok(pylist.into_py_any_unwrap(py))
672 })
673 })
674 }
675
676 #[pyo3(name = "request_position_status_reports")]
700 fn py_request_position_status_reports<'py>(
701 &self,
702 py: Python<'py>,
703 instrument_id: Option<&str>,
704 ) -> PyResult<Bound<'py, PyAny>> {
705 let client = self.clone();
706 let instrument_id = instrument_id.map(InstrumentId::from);
707
708 pyo3_async_runtimes::tokio::future_into_py(py, async move {
709 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
710 let reports = client
711 .request_position_status_reports(&account_address, instrument_id)
712 .await
713 .map_err(to_pyvalue_err)?;
714
715 Python::attach(|py| {
716 let py_reports = reports
717 .into_iter()
718 .map(|report| report.into_py_any(py))
719 .collect::<PyResult<Vec<_>>>()?;
720 let pylist = PyList::new(py, py_reports)?;
721 Ok(pylist.into_py_any_unwrap(py))
722 })
723 })
724 }
725
726 #[pyo3(name = "request_account_state")]
740 fn py_request_account_state<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
741 let client = self.clone();
742
743 pyo3_async_runtimes::tokio::future_into_py(py, async move {
744 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
745 let account_state = client
746 .request_account_state(&account_address)
747 .await
748 .map_err(to_pyvalue_err)?;
749
750 Python::attach(|py| account_state.into_py_any(py))
751 })
752 }
753
754 #[pyo3(name = "request_spot_balances")]
765 fn py_request_spot_balances<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
766 let client = self.clone();
767
768 pyo3_async_runtimes::tokio::future_into_py(py, async move {
769 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
770 let balances = client
771 .request_spot_balances(&account_address)
772 .await
773 .map_err(to_pyvalue_err)?;
774
775 Python::attach(|py| {
776 let py_balances = balances
777 .into_iter()
778 .map(|balance| balance.into_py_any(py))
779 .collect::<PyResult<Vec<_>>>()?;
780 let pylist = PyList::new(py, py_balances)?;
781 Ok(pylist.into_py_any_unwrap(py))
782 })
783 })
784 }
785
786 #[pyo3(name = "request_spot_position_status_reports")]
801 fn py_request_spot_position_status_reports<'py>(
802 &self,
803 py: Python<'py>,
804 instrument_id: Option<&str>,
805 ) -> PyResult<Bound<'py, PyAny>> {
806 let client = self.clone();
807 let instrument_id = instrument_id.map(InstrumentId::from);
808
809 pyo3_async_runtimes::tokio::future_into_py(py, async move {
810 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
811 let reports = client
812 .request_spot_position_status_reports(&account_address, instrument_id)
813 .await
814 .map_err(to_pyvalue_err)?;
815
816 Python::attach(|py| {
817 let py_reports = reports
818 .into_iter()
819 .map(|report| report.into_py_any(py))
820 .collect::<PyResult<Vec<_>>>()?;
821 let pylist = PyList::new(py, py_reports)?;
822 Ok(pylist.into_py_any_unwrap(py))
823 })
824 })
825 }
826
827 #[pyo3(name = "info_spot_clearinghouse_state")]
829 fn py_info_spot_clearinghouse_state<'py>(
830 &self,
831 py: Python<'py>,
832 ) -> PyResult<Bound<'py, PyAny>> {
833 let client = self.clone();
834
835 pyo3_async_runtimes::tokio::future_into_py(py, async move {
836 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
837 let json = client
838 .info_spot_clearinghouse_state(&account_address)
839 .await
840 .map_err(to_pyvalue_err)?;
841 to_string(&json).map_err(to_pyvalue_err)
842 })
843 }
844
845 #[pyo3(name = "info_user_fees")]
847 fn py_info_user_fees<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
848 let client = self.clone();
849
850 pyo3_async_runtimes::tokio::future_into_py(py, async move {
851 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
852 let json = client
853 .info_user_fees(&account_address)
854 .await
855 .map_err(to_pyvalue_err)?;
856 to_string(&json).map_err(to_pyvalue_err)
857 })
858 }
859
860 #[pyo3(name = "submit_split_outcome")]
874 fn py_submit_split_outcome<'py>(
875 &self,
876 py: Python<'py>,
877 outcome: u32,
878 amount: Decimal,
879 ) -> PyResult<Bound<'py, PyAny>> {
880 let client = self.clone();
881
882 pyo3_async_runtimes::tokio::future_into_py(py, async move {
883 let response = client
884 .submit_split_outcome(outcome, amount)
885 .await
886 .map_err(to_pyvalue_err)?;
887 to_string(&response).map_err(to_pyvalue_err)
888 })
889 }
890
891 #[pyo3(name = "submit_merge_outcome", signature = (outcome, amount=None))]
902 fn py_submit_merge_outcome<'py>(
903 &self,
904 py: Python<'py>,
905 outcome: u32,
906 amount: Option<Decimal>,
907 ) -> PyResult<Bound<'py, PyAny>> {
908 let client = self.clone();
909
910 pyo3_async_runtimes::tokio::future_into_py(py, async move {
911 let response = client
912 .submit_merge_outcome(outcome, amount)
913 .await
914 .map_err(to_pyvalue_err)?;
915 to_string(&response).map_err(to_pyvalue_err)
916 })
917 }
918
919 #[pyo3(name = "submit_merge_question", signature = (question, amount=None))]
929 fn py_submit_merge_question<'py>(
930 &self,
931 py: Python<'py>,
932 question: u32,
933 amount: Option<Decimal>,
934 ) -> PyResult<Bound<'py, PyAny>> {
935 let client = self.clone();
936
937 pyo3_async_runtimes::tokio::future_into_py(py, async move {
938 let response = client
939 .submit_merge_question(question, amount)
940 .await
941 .map_err(to_pyvalue_err)?;
942 to_string(&response).map_err(to_pyvalue_err)
943 })
944 }
945
946 #[pyo3(name = "submit_negate_outcome")]
956 fn py_submit_negate_outcome<'py>(
957 &self,
958 py: Python<'py>,
959 question: u32,
960 outcome: u32,
961 amount: Decimal,
962 ) -> PyResult<Bound<'py, PyAny>> {
963 let client = self.clone();
964
965 pyo3_async_runtimes::tokio::future_into_py(py, async move {
966 let response = client
967 .submit_negate_outcome(question, outcome, amount)
968 .await
969 .map_err(to_pyvalue_err)?;
970 to_string(&response).map_err(to_pyvalue_err)
971 })
972 }
973}