1use jiff::Timestamp;
19use nautilus_core::python::{
20 IntoPyObjectNautilusExt, params::value_to_pyobject, to_pyruntime_err, to_pyvalue_err,
21};
22use nautilus_model::{
23 data::BarType,
24 enums::{AccountType, OrderSide, OrderType, PositionSide, TimeInForce, TriggerType},
25 identifiers::{AccountId, ClientOrderId, InstrumentId, StrategyId, TraderId, VenueOrderId},
26 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
27 types::{Price, Quantity},
28};
29use pyo3::{
30 conversion::IntoPyObjectExt,
31 prelude::*,
32 types::{PyDict, PyList, PyTuple},
33};
34
35use super::{extract_optional_string, extract_optional_trigger_type};
36use crate::{
37 common::enums::{
38 OKXAlgoOrderStatus, OKXEnvironment, OKXInstrumentType, OKXPositionMode, OKXTradeMode,
39 },
40 http::{
41 client::OKXHttpClient,
42 error::OKXHttpError,
43 models::{OKXAttachAlgoOrdRequest, OKXCancelAlgoOrderRequest},
44 query::{
45 GetEventContractEventsParams, GetEventContractMarketsParams,
46 GetEventContractSeriesParams, GetSpreadsParams,
47 },
48 },
49};
50
51fn serializable_items_to_pylist<T>(py: Python<'_>, items: Vec<T>) -> PyResult<Py<PyAny>>
52where
53 T: serde::Serialize,
54{
55 let py_items: PyResult<Vec<_>> = items
56 .into_iter()
57 .map(|item| {
58 let value = serde_json::to_value(item).map_err(to_pyvalue_err)?;
59 value_to_pyobject(py, &value)
60 })
61 .collect();
62 Ok(PyList::new(py, py_items?)?.into_py_any_unwrap(py))
63}
64
65fn parse_attach_algo_ords(
66 py: Python<'_>,
67 attach_algo_ords: Option<Vec<Py<PyDict>>>,
68) -> PyResult<Option<Vec<OKXAttachAlgoOrdRequest>>> {
69 attach_algo_ords
70 .map(|items| {
71 items
72 .into_iter()
73 .map(|item| {
74 let dict = item.bind(py);
75 Ok(OKXAttachAlgoOrdRequest {
76 attach_algo_cl_ord_id: extract_optional_string(
77 dict,
78 "attach_algo_cl_ord_id",
79 )?,
80 sl_trigger_px: extract_optional_string(dict, "sl_trigger_px")?,
81 sl_ord_px: extract_optional_string(dict, "sl_ord_px")?,
82 sl_trigger_px_type: extract_optional_trigger_type(
83 dict,
84 "sl_trigger_px_type",
85 )?,
86 tp_trigger_px: extract_optional_string(dict, "tp_trigger_px")?,
87 tp_ord_px: extract_optional_string(dict, "tp_ord_px")?,
88 tp_trigger_px_type: extract_optional_trigger_type(
89 dict,
90 "tp_trigger_px_type",
91 )?,
92 callback_ratio: extract_optional_string(dict, "callback_ratio")?,
93 callback_spread: extract_optional_string(dict, "callback_spread")?,
94 active_px: extract_optional_string(dict, "active_px")?,
95 new_callback_ratio: extract_optional_string(dict, "new_callback_ratio")?,
96 new_callback_spread: extract_optional_string(dict, "new_callback_spread")?,
97 new_active_px: extract_optional_string(dict, "new_active_px")?,
98 })
99 })
100 .collect::<PyResult<Vec<_>>>()
101 })
102 .transpose()
103}
104
105#[pymethods]
106#[pyo3_stub_gen::derive::gen_stub_pymethods]
107impl OKXHttpClient {
108 #[new]
113 #[pyo3(signature = (
114 api_key=None,
115 api_secret=None,
116 api_passphrase=None,
117 base_url=None,
118 timeout_secs=60,
119 max_retries=3,
120 retry_delay_ms=1_000,
121 retry_delay_max_ms=10_000,
122 environment=OKXEnvironment::Live,
123 proxy_url=None,
124 ))]
125 #[expect(clippy::too_many_arguments)]
126 fn py_new(
127 api_key: Option<String>,
128 api_secret: Option<String>,
129 api_passphrase: Option<String>,
130 base_url: Option<String>,
131 timeout_secs: u64,
132 max_retries: u32,
133 retry_delay_ms: u64,
134 retry_delay_max_ms: u64,
135 environment: OKXEnvironment,
136 proxy_url: Option<String>,
137 ) -> PyResult<Self> {
138 Self::with_credentials(
139 api_key,
140 api_secret,
141 api_passphrase,
142 base_url,
143 timeout_secs,
144 max_retries,
145 retry_delay_ms,
146 retry_delay_max_ms,
147 environment,
148 proxy_url,
149 )
150 .map_err(to_pyvalue_err)
151 }
152
153 #[staticmethod]
160 #[pyo3(name = "from_env")]
161 fn py_from_env() -> PyResult<Self> {
162 Self::from_env().map_err(to_pyvalue_err)
163 }
164
165 #[getter]
167 #[pyo3(name = "base_url")]
168 #[must_use]
169 pub fn py_base_url(&self) -> &str {
170 self.base_url()
171 }
172
173 #[getter]
175 #[pyo3(name = "api_key")]
176 #[must_use]
177 pub fn py_api_key(&self) -> Option<&str> {
178 self.api_key()
179 }
180
181 #[getter]
183 #[pyo3(name = "api_key_masked")]
184 #[must_use]
185 pub fn py_api_key_masked(&self) -> Option<String> {
186 self.api_key_masked()
187 }
188
189 #[pyo3(name = "is_initialized")]
193 #[must_use]
194 pub fn py_is_initialized(&self) -> bool {
195 self.is_initialized()
196 }
197
198 #[pyo3(name = "get_cached_symbols")]
201 #[must_use]
202 pub fn py_get_cached_symbols(&self) -> Vec<String> {
203 self.get_cached_symbols()
204 }
205
206 #[pyo3(name = "cancel_all_requests")]
208 pub fn py_cancel_all_requests(&self) {
209 self.cancel_all_requests();
210 }
211
212 #[pyo3(name = "cache_instruments")]
216 pub fn py_cache_instruments(
217 &self,
218 py: Python<'_>,
219 instruments: Vec<Py<PyAny>>,
220 ) -> PyResult<()> {
221 let instruments: Result<Vec<_>, _> = instruments
222 .into_iter()
223 .map(|inst| pyobject_to_instrument_any(py, inst))
224 .collect();
225 self.cache_instruments(&instruments?);
226 Ok(())
227 }
228
229 #[pyo3(name = "cache_instrument")]
233 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
234 self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
235 Ok(())
236 }
237
238 #[pyo3(name = "set_position_mode")]
251 fn py_set_position_mode<'py>(
252 &self,
253 py: Python<'py>,
254 position_mode: OKXPositionMode,
255 ) -> PyResult<Bound<'py, PyAny>> {
256 let client = self.clone();
257
258 pyo3_async_runtimes::tokio::future_into_py(py, async move {
259 client
260 .set_position_mode(position_mode)
261 .await
262 .map_err(to_pyvalue_err)?;
263
264 Python::attach(|py| Ok(py.None()))
265 })
266 }
267
268 #[pyo3(name = "activate_feature")]
282 fn py_activate_feature<'py>(
283 &self,
284 py: Python<'py>,
285 feature: String,
286 ) -> PyResult<Bound<'py, PyAny>> {
287 let client = self.clone();
288
289 pyo3_async_runtimes::tokio::future_into_py(py, async move {
290 client
291 .activate_feature(&feature)
292 .await
293 .map_err(to_pyvalue_err)?;
294
295 Python::attach(|py| Ok(py.None()))
296 })
297 }
298
299 #[pyo3(name = "set_spot_trade_quote_ccy")]
301 fn py_set_spot_trade_quote_ccy(&self, ccy: Option<String>) {
302 self.set_spot_trade_quote_ccy(ccy);
303 }
304
305 #[pyo3(name = "request_instruments")]
320 #[pyo3(signature = (instrument_type, instrument_family=None))]
321 fn py_request_instruments<'py>(
322 &self,
323 py: Python<'py>,
324 instrument_type: OKXInstrumentType,
325 instrument_family: Option<String>,
326 ) -> PyResult<Bound<'py, PyAny>> {
327 let client = self.clone();
328
329 pyo3_async_runtimes::tokio::future_into_py(py, async move {
330 let (instruments, inst_id_codes) = client
331 .request_instruments(instrument_type, instrument_family)
332 .await
333 .map_err(to_pyvalue_err)?;
334
335 Python::attach(|py| {
336 let py_instruments: PyResult<Vec<_>> = instruments
337 .into_iter()
338 .map(|inst| instrument_any_to_pyobject(py, inst))
339 .collect();
340 let instruments_list = PyList::new(py, py_instruments?)?;
341
342 let py_codes: Vec<_> = inst_id_codes
344 .into_iter()
345 .map(|(inst_id, code)| (inst_id.to_string(), code))
346 .collect();
347 let codes_list = PyList::new(py, py_codes)?;
348
349 let result = PyTuple::new(py, [instruments_list.as_any(), codes_list.as_any()])?
350 .into_any()
351 .unbind();
352 Ok(result)
353 })
354 })
355 }
356
357 #[pyo3(name = "request_spread_instruments")]
363 #[pyo3(signature = (base_currency=None, instrument_id=None, spread_id=None, state=None))]
364 fn py_request_spread_instruments<'py>(
365 &self,
366 py: Python<'py>,
367 base_currency: Option<String>,
368 instrument_id: Option<InstrumentId>,
369 spread_id: Option<String>,
370 state: Option<String>,
371 ) -> PyResult<Bound<'py, PyAny>> {
372 let client = self.clone();
373
374 pyo3_async_runtimes::tokio::future_into_py(py, async move {
375 let instruments = client
376 .request_spread_instruments(GetSpreadsParams {
377 base_ccy: base_currency,
378 inst_id: instrument_id.map(|id| id.symbol.to_string()),
379 sprd_id: spread_id,
380 state,
381 })
382 .await
383 .map_err(to_pyvalue_err)?;
384
385 Python::attach(|py| {
386 let py_instruments: PyResult<Vec<_>> = instruments
387 .into_iter()
388 .map(|inst| instrument_any_to_pyobject(py, inst))
389 .collect();
390 Ok(PyList::new(py, py_instruments?)?.into_py_any_unwrap(py))
391 })
392 })
393 }
394
395 #[pyo3(name = "request_instrument")]
406 fn py_request_instrument<'py>(
407 &self,
408 py: Python<'py>,
409 instrument_id: InstrumentId,
410 ) -> PyResult<Bound<'py, PyAny>> {
411 let client = self.clone();
412
413 pyo3_async_runtimes::tokio::future_into_py(py, async move {
414 let instrument = client
415 .request_instrument(instrument_id)
416 .await
417 .map_err(to_pyvalue_err)?;
418
419 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
420 })
421 }
422
423 #[pyo3(name = "request_event_contract_series")]
429 #[pyo3(signature = (series_id=None))]
430 fn py_request_event_contract_series<'py>(
431 &self,
432 py: Python<'py>,
433 series_id: Option<String>,
434 ) -> PyResult<Bound<'py, PyAny>> {
435 let client = self.clone();
436
437 pyo3_async_runtimes::tokio::future_into_py(py, async move {
438 let series = client
439 .request_event_contract_series(GetEventContractSeriesParams { series_id })
440 .await
441 .map_err(to_pyvalue_err)?;
442
443 Python::attach(|py| serializable_items_to_pylist(py, series))
444 })
445 }
446
447 #[expect(clippy::too_many_arguments)]
453 #[pyo3(name = "request_event_contract_events")]
454 #[pyo3(signature = (series_id, event_id=None, state=None, limit=None, before=None, after=None))]
455 fn py_request_event_contract_events<'py>(
456 &self,
457 py: Python<'py>,
458 series_id: String,
459 event_id: Option<String>,
460 state: Option<String>,
461 limit: Option<String>,
462 before: Option<String>,
463 after: Option<String>,
464 ) -> PyResult<Bound<'py, PyAny>> {
465 let client = self.clone();
466
467 pyo3_async_runtimes::tokio::future_into_py(py, async move {
468 let events = client
469 .request_event_contract_events(GetEventContractEventsParams {
470 series_id,
471 event_id,
472 state,
473 limit,
474 before,
475 after,
476 })
477 .await
478 .map_err(to_pyvalue_err)?;
479
480 Python::attach(|py| serializable_items_to_pylist(py, events))
481 })
482 }
483
484 #[expect(clippy::too_many_arguments)]
490 #[pyo3(name = "request_event_contract_markets")]
491 #[pyo3(signature = (series_id, event_id=None, inst_id=None, state=None, limit=None, before=None, after=None))]
492 fn py_request_event_contract_markets<'py>(
493 &self,
494 py: Python<'py>,
495 series_id: String,
496 event_id: Option<String>,
497 inst_id: Option<String>,
498 state: Option<String>,
499 limit: Option<String>,
500 before: Option<String>,
501 after: Option<String>,
502 ) -> PyResult<Bound<'py, PyAny>> {
503 let client = self.clone();
504
505 pyo3_async_runtimes::tokio::future_into_py(py, async move {
506 let markets = client
507 .request_event_contract_markets(GetEventContractMarketsParams {
508 series_id,
509 event_id,
510 inst_id,
511 state,
512 limit,
513 before,
514 after,
515 })
516 .await
517 .map_err(to_pyvalue_err)?;
518
519 Python::attach(|py| serializable_items_to_pylist(py, markets))
520 })
521 }
522
523 #[pyo3(name = "request_account_state")]
532 #[pyo3(signature = (account_id, account_type=AccountType::Margin))]
533 fn py_request_account_state<'py>(
534 &self,
535 py: Python<'py>,
536 account_id: AccountId,
537 account_type: AccountType,
538 ) -> PyResult<Bound<'py, PyAny>> {
539 let client = self.clone();
540
541 pyo3_async_runtimes::tokio::future_into_py(py, async move {
542 let account_state = client
543 .request_account_state(account_id, account_type)
544 .await
545 .map_err(to_pyvalue_err)?;
546
547 Python::attach(|py| account_state.into_py_any(py))
548 })
549 }
550
551 #[pyo3(name = "request_trades")]
557 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
558 fn py_request_trades<'py>(
559 &self,
560 py: Python<'py>,
561 instrument_id: InstrumentId,
562 start: Option<Timestamp>,
563 end: Option<Timestamp>,
564 limit: Option<u32>,
565 ) -> PyResult<Bound<'py, PyAny>> {
566 let client = self.clone();
567
568 pyo3_async_runtimes::tokio::future_into_py(py, async move {
569 let trades = client
570 .request_trades(instrument_id, start, end, limit)
571 .await
572 .map_err(to_pyvalue_err)?;
573
574 Python::attach(|py| {
575 let py_trades = trades
576 .into_iter()
577 .map(|trade| trade.into_py_any(py))
578 .collect::<PyResult<Vec<_>>>()?;
579 let pylist = PyList::new(py, py_trades)?;
580 Ok(pylist.into_py_any_unwrap(py))
581 })
582 })
583 }
584
585 #[pyo3(name = "request_bars")]
626 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
627 fn py_request_bars<'py>(
628 &self,
629 py: Python<'py>,
630 bar_type: BarType,
631 start: Option<Timestamp>,
632 end: Option<Timestamp>,
633 limit: Option<u32>,
634 ) -> PyResult<Bound<'py, PyAny>> {
635 let client = self.clone();
636
637 pyo3_async_runtimes::tokio::future_into_py(py, async move {
638 let bars = client
639 .request_bars(bar_type, start, end, limit)
640 .await
641 .map_err(to_pyvalue_err)?;
642
643 Python::attach(|py| {
644 let py_bars = bars
645 .into_iter()
646 .map(|bar| bar.into_py_any(py))
647 .collect::<PyResult<Vec<_>>>()?;
648 let pylist = PyList::new(py, py_bars)?;
649 Ok(pylist.into_py_any_unwrap(py))
650 })
651 })
652 }
653
654 #[pyo3(name = "request_orderbook_snapshot")]
660 #[pyo3(signature = (instrument_id, depth=None))]
661 fn py_request_orderbook_snapshot<'py>(
662 &self,
663 py: Python<'py>,
664 instrument_id: InstrumentId,
665 depth: Option<u32>,
666 ) -> PyResult<Bound<'py, PyAny>> {
667 let client = self.clone();
668
669 pyo3_async_runtimes::tokio::future_into_py(py, async move {
670 let deltas = client
671 .request_orderbook_snapshot(instrument_id, depth)
672 .await
673 .map_err(to_pyvalue_err)?;
674
675 Python::attach(|py| deltas.into_py_any(py))
676 })
677 }
678
679 #[pyo3(name = "request_funding_rates")]
685 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
686 fn py_request_funding_rates<'py>(
687 &self,
688 py: Python<'py>,
689 instrument_id: InstrumentId,
690 start: Option<Timestamp>,
691 end: Option<Timestamp>,
692 limit: Option<u32>,
693 ) -> PyResult<Bound<'py, PyAny>> {
694 let client = self.clone();
695
696 pyo3_async_runtimes::tokio::future_into_py(py, async move {
697 let rates = client
698 .request_funding_rates(instrument_id, start, end, limit)
699 .await
700 .map_err(to_pyvalue_err)?;
701
702 Python::attach(|py| {
703 let py_rates = rates
704 .into_iter()
705 .map(|rate| rate.into_py_any(py))
706 .collect::<PyResult<Vec<_>>>()?;
707 let pylist = PyList::new(py, py_rates)?;
708 Ok(pylist.into_py_any_unwrap(py))
709 })
710 })
711 }
712
713 #[pyo3(name = "request_mark_price")]
719 fn py_request_mark_price<'py>(
720 &self,
721 py: Python<'py>,
722 instrument_id: InstrumentId,
723 ) -> PyResult<Bound<'py, PyAny>> {
724 let client = self.clone();
725
726 pyo3_async_runtimes::tokio::future_into_py(py, async move {
727 let mark_price = client
728 .request_mark_price(instrument_id)
729 .await
730 .map_err(to_pyvalue_err)?;
731
732 Python::attach(|py| mark_price.into_py_any(py))
733 })
734 }
735
736 #[pyo3(name = "request_price_limit")]
742 fn py_request_price_limit<'py>(
743 &self,
744 py: Python<'py>,
745 instrument_id: InstrumentId,
746 ) -> PyResult<Bound<'py, PyAny>> {
747 let client = self.clone();
748
749 pyo3_async_runtimes::tokio::future_into_py(py, async move {
750 let price_limit = client
751 .request_price_limit(instrument_id)
752 .await
753 .map_err(to_pyvalue_err)?;
754
755 Python::attach(|py| {
756 let value = serde_json::to_value(price_limit).map_err(to_pyvalue_err)?;
757 value_to_pyobject(py, &value)
758 })
759 })
760 }
761
762 #[pyo3(name = "request_index_price")]
768 fn py_request_index_price<'py>(
769 &self,
770 py: Python<'py>,
771 instrument_id: InstrumentId,
772 ) -> PyResult<Bound<'py, PyAny>> {
773 let client = self.clone();
774
775 pyo3_async_runtimes::tokio::future_into_py(py, async move {
776 let index_price = client
777 .request_index_price(instrument_id)
778 .await
779 .map_err(to_pyvalue_err)?;
780
781 Python::attach(|py| index_price.into_py_any(py))
782 })
783 }
784
785 #[pyo3(name = "request_order_status_reports")]
796 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, open_only=false, limit=None))]
797 #[expect(clippy::too_many_arguments)]
798 fn py_request_order_status_reports<'py>(
799 &self,
800 py: Python<'py>,
801 account_id: AccountId,
802 instrument_type: Option<OKXInstrumentType>,
803 instrument_id: Option<InstrumentId>,
804 start: Option<Timestamp>,
805 end: Option<Timestamp>,
806 open_only: bool,
807 limit: Option<u32>,
808 ) -> PyResult<Bound<'py, PyAny>> {
809 let client = self.clone();
810
811 pyo3_async_runtimes::tokio::future_into_py(py, async move {
812 let reports = client
813 .request_order_status_reports(
814 account_id,
815 instrument_type,
816 instrument_id,
817 start,
818 end,
819 open_only,
820 limit,
821 )
822 .await
823 .map_err(to_pyvalue_err)?;
824
825 Python::attach(|py| {
826 let py_reports = reports
827 .into_iter()
828 .map(|report| report.into_py_any(py))
829 .collect::<PyResult<Vec<_>>>()?;
830 let pylist = PyList::new(py, py_reports)?;
831 Ok(pylist.into_py_any_unwrap(py))
832 })
833 })
834 }
835
836 #[pyo3(name = "request_algo_order_status_reports")]
842 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, algo_id=None, algo_client_order_id=None, state=None, limit=None))]
843 #[expect(clippy::too_many_arguments)]
844 fn py_request_algo_order_status_reports<'py>(
845 &self,
846 py: Python<'py>,
847 account_id: AccountId,
848 instrument_type: Option<OKXInstrumentType>,
849 instrument_id: Option<InstrumentId>,
850 algo_id: Option<String>,
851 algo_client_order_id: Option<ClientOrderId>,
852 state: Option<OKXAlgoOrderStatus>,
853 limit: Option<u32>,
854 ) -> PyResult<Bound<'py, PyAny>> {
855 let client = self.clone();
856
857 pyo3_async_runtimes::tokio::future_into_py(py, async move {
858 let reports = client
859 .request_algo_order_status_reports(
860 account_id,
861 instrument_type,
862 instrument_id,
863 algo_id,
864 algo_client_order_id,
865 state,
866 limit,
867 )
868 .await
869 .map_err(to_pyvalue_err)?;
870
871 Python::attach(|py| {
872 let py_reports = reports
873 .into_iter()
874 .map(|report| report.into_py_any(py))
875 .collect::<PyResult<Vec<_>>>()?;
876 let pylist = PyList::new(py, py_reports)?;
877 Ok(pylist.into_py_any_unwrap(py))
878 })
879 })
880 }
881
882 #[pyo3(name = "request_algo_order_status_report")]
888 fn py_request_algo_order_status_report<'py>(
889 &self,
890 py: Python<'py>,
891 account_id: AccountId,
892 instrument_id: InstrumentId,
893 client_order_id: ClientOrderId,
894 ) -> PyResult<Bound<'py, PyAny>> {
895 let client = self.clone();
896
897 pyo3_async_runtimes::tokio::future_into_py(py, async move {
898 let report = client
899 .request_algo_order_status_report(account_id, instrument_id, client_order_id)
900 .await
901 .map_err(to_pyvalue_err)?;
902
903 Python::attach(|py| match report {
904 Some(report) => report.into_py_any(py),
905 None => Ok(py.None()),
906 })
907 })
908 }
909
910 #[pyo3(name = "request_fill_reports")]
920 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, limit=None))]
921 #[expect(clippy::too_many_arguments)]
922 fn py_request_fill_reports<'py>(
923 &self,
924 py: Python<'py>,
925 account_id: AccountId,
926 instrument_type: Option<OKXInstrumentType>,
927 instrument_id: Option<InstrumentId>,
928 start: Option<Timestamp>,
929 end: Option<Timestamp>,
930 limit: Option<u32>,
931 ) -> PyResult<Bound<'py, PyAny>> {
932 let client = self.clone();
933
934 pyo3_async_runtimes::tokio::future_into_py(py, async move {
935 let trades = client
936 .request_fill_reports(
937 account_id,
938 instrument_type,
939 instrument_id,
940 start,
941 end,
942 limit,
943 )
944 .await
945 .map_err(to_pyvalue_err)?;
946
947 Python::attach(|py| {
948 let py_trades = trades
949 .into_iter()
950 .map(|trade| trade.into_py_any(py))
951 .collect::<PyResult<Vec<_>>>()?;
952 let pylist = PyList::new(py, py_trades)?;
953 Ok(pylist.into_py_any_unwrap(py))
954 })
955 })
956 }
957
958 #[pyo3(name = "request_position_status_reports")]
985 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None))]
986 fn py_request_position_status_reports<'py>(
987 &self,
988 py: Python<'py>,
989 account_id: AccountId,
990 instrument_type: Option<OKXInstrumentType>,
991 instrument_id: Option<InstrumentId>,
992 ) -> PyResult<Bound<'py, PyAny>> {
993 let client = self.clone();
994
995 pyo3_async_runtimes::tokio::future_into_py(py, async move {
996 let reports = client
997 .request_position_status_reports(account_id, instrument_type, instrument_id)
998 .await
999 .map_err(to_pyvalue_err)?;
1000
1001 Python::attach(|py| {
1002 let py_reports = reports
1003 .into_iter()
1004 .map(|report| report.into_py_any(py))
1005 .collect::<PyResult<Vec<_>>>()?;
1006 let pylist = PyList::new(py, py_reports)?;
1007 Ok(pylist.into_py_any_unwrap(py))
1008 })
1009 })
1010 }
1011
1012 #[pyo3(name = "place_order")]
1022 #[pyo3(signature = (
1023 trader_id,
1024 strategy_id,
1025 instrument_id,
1026 td_mode,
1027 client_order_id,
1028 order_side,
1029 order_type,
1030 quantity,
1031 time_in_force=None,
1032 price=None,
1033 post_only=None,
1034 reduce_only=None,
1035 quote_quantity=None,
1036 position_side=None,
1037 attach_algo_ords=None,
1038 px_usd=None,
1039 px_vol=None,
1040 outcome=None,
1041 slippage_pct=None,
1042 ))]
1043 #[expect(clippy::too_many_arguments)]
1044 fn py_place_order<'py>(
1045 &self,
1046 py: Python<'py>,
1047 trader_id: TraderId,
1048 strategy_id: StrategyId,
1049 instrument_id: InstrumentId,
1050 td_mode: OKXTradeMode,
1051 client_order_id: ClientOrderId,
1052 order_side: OrderSide,
1053 order_type: OrderType,
1054 quantity: Quantity,
1055 time_in_force: Option<TimeInForce>,
1056 price: Option<Price>,
1057 post_only: Option<bool>,
1058 reduce_only: Option<bool>,
1059 quote_quantity: Option<bool>,
1060 position_side: Option<PositionSide>,
1061 attach_algo_ords: Option<Vec<Py<PyDict>>>,
1062 px_usd: Option<String>,
1063 px_vol: Option<String>,
1064 outcome: Option<String>,
1065 slippage_pct: Option<String>,
1066 ) -> PyResult<Bound<'py, PyAny>> {
1067 let attach_algo_ords = parse_attach_algo_ords(py, attach_algo_ords)?;
1068 let client = self.clone();
1069
1070 let _ = (trader_id, strategy_id);
1071
1072 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1073 let resp = client
1074 .place_order_with_domain_types(
1075 instrument_id,
1076 td_mode,
1077 client_order_id,
1078 order_side,
1079 order_type,
1080 quantity,
1081 time_in_force,
1082 price,
1083 post_only,
1084 reduce_only,
1085 quote_quantity,
1086 position_side,
1087 attach_algo_ords,
1088 px_usd,
1089 px_vol,
1090 outcome,
1091 slippage_pct,
1092 None,
1093 None,
1094 None,
1095 )
1096 .await
1097 .map_err(to_pyvalue_err)?;
1098
1099 Python::attach(|py| {
1100 let dict = PyDict::new(py);
1101
1102 if let Some(ord_id) = resp.ord_id {
1103 dict.set_item("ord_id", ord_id.as_str())?;
1104 }
1105
1106 if let Some(cl_ord_id) = resp.cl_ord_id {
1107 dict.set_item("cl_ord_id", cl_ord_id.as_str())?;
1108 }
1109
1110 if let Some(s_code) = resp.s_code {
1111 dict.set_item("s_code", s_code)?;
1112 }
1113
1114 if let Some(s_msg) = resp.s_msg {
1115 dict.set_item("s_msg", s_msg)?;
1116 }
1117
1118 if let Some(sub_code) = resp.sub_code {
1119 dict.set_item("sub_code", sub_code)?;
1120 }
1121
1122 Ok(dict.into_py_any_unwrap(py))
1123 })
1124 })
1125 }
1126
1127 #[pyo3(name = "place_algo_order")]
1137 #[pyo3(signature = (
1138 trader_id,
1139 strategy_id,
1140 instrument_id,
1141 td_mode,
1142 client_order_id,
1143 order_side,
1144 order_type,
1145 quantity,
1146 trigger_price=None,
1147 trigger_type=None,
1148 limit_price=None,
1149 reduce_only=None,
1150 close_fraction=None,
1151 callback_ratio=None,
1152 callback_spread=None,
1153 activation_price=None,
1154 ))]
1155 #[expect(clippy::too_many_arguments)]
1156 fn py_place_algo_order<'py>(
1157 &self,
1158 py: Python<'py>,
1159 trader_id: TraderId,
1160 strategy_id: StrategyId,
1161 instrument_id: InstrumentId,
1162 td_mode: OKXTradeMode,
1163 client_order_id: ClientOrderId,
1164 order_side: OrderSide,
1165 order_type: OrderType,
1166 quantity: Quantity,
1167 trigger_price: Option<Price>,
1168 trigger_type: Option<TriggerType>,
1169 limit_price: Option<Price>,
1170 reduce_only: Option<bool>,
1171 close_fraction: Option<String>,
1172 callback_ratio: Option<String>,
1173 callback_spread: Option<String>,
1174 activation_price: Option<Price>,
1175 ) -> PyResult<Bound<'py, PyAny>> {
1176 let client = self.clone();
1177
1178 let _ = (trader_id, strategy_id);
1180
1181 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1182 let resp = client
1183 .place_algo_order_with_domain_types(
1184 instrument_id,
1185 td_mode,
1186 client_order_id,
1187 order_side,
1188 order_type,
1189 quantity,
1190 trigger_price,
1191 trigger_type,
1192 limit_price,
1193 reduce_only,
1194 close_fraction,
1195 callback_ratio,
1196 callback_spread,
1197 activation_price,
1198 )
1199 .await
1200 .map_err(to_pyvalue_err)?;
1201
1202 Python::attach(|py| {
1203 let dict = PyDict::new(py);
1204 dict.set_item("algo_id", resp.algo_id)?;
1205 if let Some(algo_cl_ord_id) = resp.algo_cl_ord_id {
1206 dict.set_item("algo_cl_ord_id", algo_cl_ord_id)?;
1207 }
1208
1209 if let Some(s_code) = resp.s_code {
1210 dict.set_item("s_code", s_code)?;
1211 }
1212
1213 if let Some(s_msg) = resp.s_msg {
1214 dict.set_item("s_msg", s_msg)?;
1215 }
1216
1217 if let Some(req_id) = resp.req_id {
1218 dict.set_item("req_id", req_id)?;
1219 }
1220 Ok(dict.into_py_any_unwrap(py))
1221 })
1222 })
1223 }
1224
1225 #[pyo3(name = "cancel_algo_order")]
1235 fn py_cancel_algo_order<'py>(
1236 &self,
1237 py: Python<'py>,
1238 instrument_id: InstrumentId,
1239 algo_id: String,
1240 ) -> PyResult<Bound<'py, PyAny>> {
1241 let client = self.clone();
1242
1243 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1244 let resp = client
1245 .cancel_algo_order_with_domain_types(instrument_id, algo_id)
1246 .await
1247 .map_err(to_pyvalue_err)?;
1248
1249 Python::attach(|py| {
1250 let dict = PyDict::new(py);
1251 dict.set_item("algo_id", resp.algo_id)?;
1252 if let Some(s_code) = resp.s_code {
1253 dict.set_item("s_code", s_code)?;
1254 }
1255
1256 if let Some(s_msg) = resp.s_msg {
1257 dict.set_item("s_msg", s_msg)?;
1258 }
1259 Ok(dict.into_py_any_unwrap(py))
1260 })
1261 })
1262 }
1263
1264 #[pyo3(name = "cancel_order")]
1270 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
1271 fn py_cancel_order<'py>(
1272 &self,
1273 py: Python<'py>,
1274 instrument_id: InstrumentId,
1275 client_order_id: Option<ClientOrderId>,
1276 venue_order_id: Option<VenueOrderId>,
1277 ) -> PyResult<Bound<'py, PyAny>> {
1278 let client = self.clone();
1279
1280 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1281 let resp = client
1282 .cancel_order(instrument_id, client_order_id, venue_order_id)
1283 .await
1284 .map_err(to_pyvalue_err)?;
1285
1286 Python::attach(|py| {
1287 let dict = PyDict::new(py);
1288 dict.set_item("ord_id", resp.ord_id)?;
1289
1290 if let Some(cl_ord_id) = resp.cl_ord_id {
1291 dict.set_item("cl_ord_id", cl_ord_id)?;
1292 }
1293
1294 if let Some(s_code) = resp.s_code {
1295 dict.set_item("s_code", s_code)?;
1296 }
1297
1298 if let Some(s_msg) = resp.s_msg {
1299 dict.set_item("s_msg", s_msg)?;
1300 }
1301
1302 if let Some(ts) = resp.ts {
1303 dict.set_item("ts", ts)?;
1304 }
1305
1306 Ok(dict.into_py_any_unwrap(py))
1307 })
1308 })
1309 }
1310
1311 #[pyo3(name = "cancel_all_orders")]
1317 fn py_cancel_all_orders<'py>(
1318 &self,
1319 py: Python<'py>,
1320 instrument_id: InstrumentId,
1321 ) -> PyResult<Bound<'py, PyAny>> {
1322 let client = self.clone();
1323
1324 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1325 let responses = client
1326 .cancel_all_orders(instrument_id)
1327 .await
1328 .map_err(to_pyvalue_err)?;
1329
1330 Python::attach(|py| {
1331 let results: PyResult<Vec<_>> = responses
1332 .into_iter()
1333 .map(|resp| {
1334 let dict = PyDict::new(py);
1335 dict.set_item("ord_id", resp.ord_id)?;
1336
1337 if let Some(cl_ord_id) = resp.cl_ord_id {
1338 dict.set_item("cl_ord_id", cl_ord_id)?;
1339 }
1340
1341 if let Some(s_code) = resp.s_code {
1342 dict.set_item("s_code", s_code)?;
1343 }
1344
1345 if let Some(s_msg) = resp.s_msg {
1346 dict.set_item("s_msg", s_msg)?;
1347 }
1348
1349 if let Some(ts) = resp.ts {
1350 dict.set_item("ts", ts)?;
1351 }
1352
1353 Ok(dict)
1354 })
1355 .collect();
1356 Ok(PyList::new(py, results?)?.into_py_any_unwrap(py))
1357 })
1358 })
1359 }
1360
1361 #[expect(clippy::too_many_arguments)]
1371 #[pyo3(name = "amend_algo_order")]
1372 #[pyo3(signature = (
1373 instrument_id,
1374 algo_id,
1375 new_trigger_price=None,
1376 new_limit_price=None,
1377 new_quantity=None,
1378 new_callback_ratio=None,
1379 new_callback_spread=None,
1380 new_activation_price=None,
1381 new_sl_trigger_price=None,
1382 new_tp_trigger_price=None,
1383 new_tp_order_price=None,
1384 new_tp_trigger_px_type=None,
1385 new_sl_order_price=None,
1386 new_sl_trigger_px_type=None,
1387 ))]
1388 fn py_amend_algo_order<'py>(
1389 &self,
1390 py: Python<'py>,
1391 instrument_id: InstrumentId,
1392 algo_id: String,
1393 new_trigger_price: Option<Price>,
1394 new_limit_price: Option<Price>,
1395 new_quantity: Option<Quantity>,
1396 new_callback_ratio: Option<String>,
1397 new_callback_spread: Option<String>,
1398 new_activation_price: Option<Price>,
1399 new_sl_trigger_price: Option<Price>,
1400 new_tp_trigger_price: Option<Price>,
1401 new_tp_order_price: Option<String>,
1402 new_tp_trigger_px_type: Option<String>,
1403 new_sl_order_price: Option<String>,
1404 new_sl_trigger_px_type: Option<String>,
1405 ) -> PyResult<Bound<'py, PyAny>> {
1406 let client = self.clone();
1407
1408 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1409 let resp = client
1410 .amend_algo_order_with_domain_types(
1411 instrument_id,
1412 algo_id,
1413 new_trigger_price,
1414 new_sl_trigger_price,
1415 new_limit_price,
1416 new_quantity,
1417 new_callback_ratio,
1418 new_callback_spread,
1419 new_activation_price,
1420 new_tp_trigger_price,
1421 new_tp_order_price,
1422 new_tp_trigger_px_type,
1423 new_sl_order_price,
1424 new_sl_trigger_px_type,
1425 )
1426 .await
1427 .map_err(to_pyvalue_err)?;
1428
1429 Python::attach(|py| {
1430 let dict = PyDict::new(py);
1431 dict.set_item("algo_id", resp.algo_id)?;
1432 if let Some(s_code) = resp.s_code {
1433 dict.set_item("s_code", s_code)?;
1434 }
1435
1436 if let Some(s_msg) = resp.s_msg {
1437 dict.set_item("s_msg", s_msg)?;
1438 }
1439 Ok(dict.into_py_any_unwrap(py))
1440 })
1441 })
1442 }
1443
1444 #[pyo3(name = "cancel_algo_orders")]
1457 fn py_cancel_algo_orders<'py>(
1458 &self,
1459 py: Python<'py>,
1460 orders: Vec<(InstrumentId, String)>,
1461 ) -> PyResult<Bound<'py, PyAny>> {
1462 let client = self.clone();
1463
1464 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1465 let requests: Vec<_> = orders
1466 .into_iter()
1467 .map(|(instrument_id, algo_id)| OKXCancelAlgoOrderRequest {
1468 inst_id: instrument_id.symbol.to_string(),
1469 inst_id_code: None,
1470 algo_id: Some(algo_id),
1471 algo_cl_ord_id: None,
1472 })
1473 .collect();
1474
1475 let responses = client
1476 .cancel_algo_orders(requests)
1477 .await
1478 .map_err(to_pyvalue_err)?;
1479
1480 Python::attach(|py| {
1481 let results = responses
1482 .into_iter()
1483 .map(|resp| {
1484 let dict = PyDict::new(py);
1485 dict.set_item("algo_id", resp.algo_id)?;
1486 if let Some(s_code) = resp.s_code {
1487 dict.set_item("s_code", s_code)?;
1488 }
1489
1490 if let Some(s_msg) = resp.s_msg {
1491 dict.set_item("s_msg", s_msg)?;
1492 }
1493 Ok(dict)
1494 })
1495 .collect::<PyResult<Vec<_>>>()?;
1496 Ok(PyList::new(py, results)?.into_any().unbind())
1497 })
1498 })
1499 }
1500
1501 #[pyo3(name = "cancel_advance_algo_order")]
1502 fn py_cancel_advance_algo_order<'py>(
1503 &self,
1504 py: Python<'py>,
1505 instrument_id: InstrumentId,
1506 algo_id: String,
1507 ) -> PyResult<Bound<'py, PyAny>> {
1508 let client = self.clone();
1509
1510 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1511 let request = OKXCancelAlgoOrderRequest {
1512 inst_id: instrument_id.symbol.to_string(),
1513 inst_id_code: None,
1514 algo_id: Some(algo_id),
1515 algo_cl_ord_id: None,
1516 };
1517
1518 let mut responses = client
1519 .cancel_advance_algo_orders(vec![request])
1520 .await
1521 .map_err(to_pyvalue_err)?;
1522
1523 let resp = responses
1524 .pop()
1525 .ok_or_else(|| to_pyvalue_err("Empty response"))?;
1526
1527 Python::attach(|py| {
1528 let dict = PyDict::new(py);
1529 dict.set_item("algo_id", resp.algo_id)?;
1530
1531 if let Some(s_code) = resp.s_code {
1532 dict.set_item("s_code", s_code)?;
1533 }
1534
1535 if let Some(s_msg) = resp.s_msg {
1536 dict.set_item("s_msg", s_msg)?;
1537 }
1538 Ok(dict.into_py_any_unwrap(py))
1539 })
1540 })
1541 }
1542
1543 #[pyo3(name = "get_server_time")]
1551 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1552 let client = self.clone();
1553
1554 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1555 let timestamp = client.get_server_time().await.map_err(to_pyvalue_err)?;
1556
1557 Python::attach(|py| timestamp.into_py_any(py))
1558 })
1559 }
1560
1561 #[pyo3(name = "get_balance")]
1562 fn py_get_balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1563 let client = self.clone();
1564
1565 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1566 let accounts = client.inner.get_balance().await.map_err(to_pyvalue_err)?;
1567
1568 let details: Vec<_> = accounts
1569 .into_iter()
1570 .flat_map(|account| account.details)
1571 .collect();
1572
1573 Python::attach(|py| {
1574 let pylist = PyList::new(py, details)?;
1575 Ok(pylist.into_py_any_unwrap(py))
1576 })
1577 })
1578 }
1579}
1580
1581impl From<OKXHttpError> for PyErr {
1582 fn from(error: OKXHttpError) -> Self {
1583 match error {
1584 OKXHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
1586 OKXHttpError::HttpClientError(e) => to_pyruntime_err(format!("Network error: {e}")),
1587 OKXHttpError::RetryableStatus { status, body, .. } => {
1588 to_pyruntime_err(format!("Temporary HTTP status code {status}: {body}"))
1589 }
1590 OKXHttpError::UnexpectedStatus { status, body } => {
1591 to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
1592 }
1593 OKXHttpError::RetryableOkxError {
1594 error_code,
1595 message,
1596 ..
1597 } => to_pyruntime_err(format!("Temporary OKX error {error_code}: {message}")),
1598 OKXHttpError::MalformedResponse(msg) => {
1599 to_pyruntime_err(format!("Malformed response: {msg}"))
1600 }
1601 OKXHttpError::ResponseDecoding(msg) => {
1602 to_pyruntime_err(format!("Response decoding error: {msg}"))
1603 }
1604 OKXHttpError::OperationTimeout { timeout_ms } => {
1605 to_pyruntime_err(format!("Operation timed out after {timeout_ms}ms"))
1606 }
1607 OKXHttpError::RetryBudgetExceeded(msg) => {
1608 to_pyruntime_err(format!("Retry budget exceeded: {msg}"))
1609 }
1610 OKXHttpError::EmptyResponse => to_pyruntime_err("Empty response"),
1611 OKXHttpError::MissingCredentials => {
1613 to_pyvalue_err("Missing credentials for authenticated request")
1614 }
1615 OKXHttpError::ValidationError(msg) => {
1616 to_pyvalue_err(format!("Parameter validation error: {msg}"))
1617 }
1618 OKXHttpError::RequestSerialization(msg) => {
1619 to_pyvalue_err(format!("Request serialization error: {msg}"))
1620 }
1621 OKXHttpError::OkxError {
1622 error_code,
1623 message,
1624 } => to_pyvalue_err(format!("OKX error {error_code}: {message}")),
1625 }
1626 }
1627}