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")]
285 fn py_activate_feature<'py>(
286 &self,
287 py: Python<'py>,
288 feature: String,
289 ) -> PyResult<Bound<'py, PyAny>> {
290 let client = self.clone();
291
292 pyo3_async_runtimes::tokio::future_into_py(py, async move {
293 client
294 .activate_feature(&feature)
295 .await
296 .map_err(to_pyvalue_err)?;
297
298 Python::attach(|py| Ok(py.None()))
299 })
300 }
301
302 #[pyo3(name = "set_spot_trade_quote_ccy")]
304 fn py_set_spot_trade_quote_ccy(&self, ccy: Option<String>) {
305 self.set_spot_trade_quote_ccy(ccy);
306 }
307
308 #[pyo3(name = "request_instruments")]
323 #[pyo3(signature = (instrument_type, instrument_family=None))]
324 fn py_request_instruments<'py>(
325 &self,
326 py: Python<'py>,
327 instrument_type: OKXInstrumentType,
328 instrument_family: Option<String>,
329 ) -> PyResult<Bound<'py, PyAny>> {
330 let client = self.clone();
331
332 pyo3_async_runtimes::tokio::future_into_py(py, async move {
333 let (instruments, inst_id_codes) = client
334 .request_instruments(instrument_type, instrument_family)
335 .await
336 .map_err(to_pyvalue_err)?;
337
338 Python::attach(|py| {
339 let py_instruments: PyResult<Vec<_>> = instruments
340 .into_iter()
341 .map(|inst| instrument_any_to_pyobject(py, inst))
342 .collect();
343 let instruments_list = PyList::new(py, py_instruments?)?;
344
345 let py_codes: Vec<_> = inst_id_codes
347 .into_iter()
348 .map(|(inst_id, code)| (inst_id.to_string(), code))
349 .collect();
350 let codes_list = PyList::new(py, py_codes)?;
351
352 let result = PyTuple::new(py, [instruments_list.as_any(), codes_list.as_any()])?
353 .into_any()
354 .unbind();
355 Ok(result)
356 })
357 })
358 }
359
360 #[pyo3(name = "request_spread_instruments")]
366 #[pyo3(signature = (base_currency=None, instrument_id=None, spread_id=None, state=None))]
367 fn py_request_spread_instruments<'py>(
368 &self,
369 py: Python<'py>,
370 base_currency: Option<String>,
371 instrument_id: Option<InstrumentId>,
372 spread_id: Option<String>,
373 state: Option<String>,
374 ) -> PyResult<Bound<'py, PyAny>> {
375 let client = self.clone();
376
377 pyo3_async_runtimes::tokio::future_into_py(py, async move {
378 let instruments = client
379 .request_spread_instruments(GetSpreadsParams {
380 base_ccy: base_currency,
381 inst_id: instrument_id.map(|id| id.symbol.to_string()),
382 sprd_id: spread_id,
383 state,
384 })
385 .await
386 .map_err(to_pyvalue_err)?;
387
388 Python::attach(|py| {
389 let py_instruments: PyResult<Vec<_>> = instruments
390 .into_iter()
391 .map(|inst| instrument_any_to_pyobject(py, inst))
392 .collect();
393 Ok(PyList::new(py, py_instruments?)?.into_py_any_unwrap(py))
394 })
395 })
396 }
397
398 #[pyo3(name = "request_instrument")]
409 fn py_request_instrument<'py>(
410 &self,
411 py: Python<'py>,
412 instrument_id: InstrumentId,
413 ) -> PyResult<Bound<'py, PyAny>> {
414 let client = self.clone();
415
416 pyo3_async_runtimes::tokio::future_into_py(py, async move {
417 let instrument = client
418 .request_instrument(instrument_id)
419 .await
420 .map_err(to_pyvalue_err)?;
421
422 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
423 })
424 }
425
426 #[pyo3(name = "request_event_contract_series")]
432 #[pyo3(signature = (series_id=None))]
433 fn py_request_event_contract_series<'py>(
434 &self,
435 py: Python<'py>,
436 series_id: Option<String>,
437 ) -> PyResult<Bound<'py, PyAny>> {
438 let client = self.clone();
439
440 pyo3_async_runtimes::tokio::future_into_py(py, async move {
441 let series = client
442 .request_event_contract_series(GetEventContractSeriesParams { series_id })
443 .await
444 .map_err(to_pyvalue_err)?;
445
446 Python::attach(|py| serializable_items_to_pylist(py, series))
447 })
448 }
449
450 #[expect(clippy::too_many_arguments)]
456 #[pyo3(name = "request_event_contract_events")]
457 #[pyo3(signature = (series_id, event_id=None, state=None, limit=None, before=None, after=None))]
458 fn py_request_event_contract_events<'py>(
459 &self,
460 py: Python<'py>,
461 series_id: String,
462 event_id: Option<String>,
463 state: Option<String>,
464 limit: Option<String>,
465 before: Option<String>,
466 after: Option<String>,
467 ) -> PyResult<Bound<'py, PyAny>> {
468 let client = self.clone();
469
470 pyo3_async_runtimes::tokio::future_into_py(py, async move {
471 let events = client
472 .request_event_contract_events(GetEventContractEventsParams {
473 series_id,
474 event_id,
475 state,
476 limit,
477 before,
478 after,
479 })
480 .await
481 .map_err(to_pyvalue_err)?;
482
483 Python::attach(|py| serializable_items_to_pylist(py, events))
484 })
485 }
486
487 #[expect(clippy::too_many_arguments)]
493 #[pyo3(name = "request_event_contract_markets")]
494 #[pyo3(signature = (series_id, event_id=None, inst_id=None, state=None, limit=None, before=None, after=None))]
495 fn py_request_event_contract_markets<'py>(
496 &self,
497 py: Python<'py>,
498 series_id: String,
499 event_id: Option<String>,
500 inst_id: Option<String>,
501 state: Option<String>,
502 limit: Option<String>,
503 before: Option<String>,
504 after: Option<String>,
505 ) -> PyResult<Bound<'py, PyAny>> {
506 let client = self.clone();
507
508 pyo3_async_runtimes::tokio::future_into_py(py, async move {
509 let markets = client
510 .request_event_contract_markets(GetEventContractMarketsParams {
511 series_id,
512 event_id,
513 inst_id,
514 state,
515 limit,
516 before,
517 after,
518 })
519 .await
520 .map_err(to_pyvalue_err)?;
521
522 Python::attach(|py| serializable_items_to_pylist(py, markets))
523 })
524 }
525
526 #[pyo3(name = "request_account_state")]
535 #[pyo3(signature = (account_id, account_type=AccountType::Margin))]
536 fn py_request_account_state<'py>(
537 &self,
538 py: Python<'py>,
539 account_id: AccountId,
540 account_type: AccountType,
541 ) -> PyResult<Bound<'py, PyAny>> {
542 let client = self.clone();
543
544 pyo3_async_runtimes::tokio::future_into_py(py, async move {
545 let account_state = client
546 .request_account_state(account_id, account_type)
547 .await
548 .map_err(to_pyvalue_err)?;
549
550 Python::attach(|py| account_state.into_py_any(py))
551 })
552 }
553
554 #[pyo3(name = "request_trades")]
560 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
561 fn py_request_trades<'py>(
562 &self,
563 py: Python<'py>,
564 instrument_id: InstrumentId,
565 start: Option<Timestamp>,
566 end: Option<Timestamp>,
567 limit: Option<u32>,
568 ) -> PyResult<Bound<'py, PyAny>> {
569 let client = self.clone();
570
571 pyo3_async_runtimes::tokio::future_into_py(py, async move {
572 let trades = client
573 .request_trades(instrument_id, start, end, limit)
574 .await
575 .map_err(to_pyvalue_err)?;
576
577 Python::attach(|py| {
578 let py_trades = trades
579 .into_iter()
580 .map(|trade| trade.into_py_any(py))
581 .collect::<PyResult<Vec<_>>>()?;
582 let pylist = PyList::new(py, py_trades)?;
583 Ok(pylist.into_py_any_unwrap(py))
584 })
585 })
586 }
587
588 #[pyo3(name = "request_bars")]
634 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
635 fn py_request_bars<'py>(
636 &self,
637 py: Python<'py>,
638 bar_type: BarType,
639 start: Option<Timestamp>,
640 end: Option<Timestamp>,
641 limit: Option<u32>,
642 ) -> PyResult<Bound<'py, PyAny>> {
643 let client = self.clone();
644
645 pyo3_async_runtimes::tokio::future_into_py(py, async move {
646 let bars = client
647 .request_bars(bar_type, start, end, limit)
648 .await
649 .map_err(to_pyvalue_err)?;
650
651 Python::attach(|py| {
652 let py_bars = bars
653 .into_iter()
654 .map(|bar| bar.into_py_any(py))
655 .collect::<PyResult<Vec<_>>>()?;
656 let pylist = PyList::new(py, py_bars)?;
657 Ok(pylist.into_py_any_unwrap(py))
658 })
659 })
660 }
661
662 #[pyo3(name = "request_orderbook_snapshot")]
668 #[pyo3(signature = (instrument_id, depth=None))]
669 fn py_request_orderbook_snapshot<'py>(
670 &self,
671 py: Python<'py>,
672 instrument_id: InstrumentId,
673 depth: Option<u32>,
674 ) -> PyResult<Bound<'py, PyAny>> {
675 let client = self.clone();
676
677 pyo3_async_runtimes::tokio::future_into_py(py, async move {
678 let deltas = client
679 .request_orderbook_snapshot(instrument_id, depth)
680 .await
681 .map_err(to_pyvalue_err)?;
682
683 Python::attach(|py| deltas.into_py_any(py))
684 })
685 }
686
687 #[pyo3(name = "request_funding_rates")]
693 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
694 fn py_request_funding_rates<'py>(
695 &self,
696 py: Python<'py>,
697 instrument_id: InstrumentId,
698 start: Option<Timestamp>,
699 end: Option<Timestamp>,
700 limit: Option<u32>,
701 ) -> PyResult<Bound<'py, PyAny>> {
702 let client = self.clone();
703
704 pyo3_async_runtimes::tokio::future_into_py(py, async move {
705 let rates = client
706 .request_funding_rates(instrument_id, start, end, limit)
707 .await
708 .map_err(to_pyvalue_err)?;
709
710 Python::attach(|py| {
711 let py_rates = rates
712 .into_iter()
713 .map(|rate| rate.into_py_any(py))
714 .collect::<PyResult<Vec<_>>>()?;
715 let pylist = PyList::new(py, py_rates)?;
716 Ok(pylist.into_py_any_unwrap(py))
717 })
718 })
719 }
720
721 #[pyo3(name = "request_mark_price")]
727 fn py_request_mark_price<'py>(
728 &self,
729 py: Python<'py>,
730 instrument_id: InstrumentId,
731 ) -> PyResult<Bound<'py, PyAny>> {
732 let client = self.clone();
733
734 pyo3_async_runtimes::tokio::future_into_py(py, async move {
735 let mark_price = client
736 .request_mark_price(instrument_id)
737 .await
738 .map_err(to_pyvalue_err)?;
739
740 Python::attach(|py| mark_price.into_py_any(py))
741 })
742 }
743
744 #[pyo3(name = "request_price_limit")]
750 fn py_request_price_limit<'py>(
751 &self,
752 py: Python<'py>,
753 instrument_id: InstrumentId,
754 ) -> PyResult<Bound<'py, PyAny>> {
755 let client = self.clone();
756
757 pyo3_async_runtimes::tokio::future_into_py(py, async move {
758 let price_limit = client
759 .request_price_limit(instrument_id)
760 .await
761 .map_err(to_pyvalue_err)?;
762
763 Python::attach(|py| {
764 let value = serde_json::to_value(price_limit).map_err(to_pyvalue_err)?;
765 value_to_pyobject(py, &value)
766 })
767 })
768 }
769
770 #[pyo3(name = "request_index_price")]
776 fn py_request_index_price<'py>(
777 &self,
778 py: Python<'py>,
779 instrument_id: InstrumentId,
780 ) -> PyResult<Bound<'py, PyAny>> {
781 let client = self.clone();
782
783 pyo3_async_runtimes::tokio::future_into_py(py, async move {
784 let index_price = client
785 .request_index_price(instrument_id)
786 .await
787 .map_err(to_pyvalue_err)?;
788
789 Python::attach(|py| index_price.into_py_any(py))
790 })
791 }
792
793 #[pyo3(name = "request_order_status_reports")]
804 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, open_only=false, limit=None))]
805 #[expect(clippy::too_many_arguments)]
806 fn py_request_order_status_reports<'py>(
807 &self,
808 py: Python<'py>,
809 account_id: AccountId,
810 instrument_type: Option<OKXInstrumentType>,
811 instrument_id: Option<InstrumentId>,
812 start: Option<Timestamp>,
813 end: Option<Timestamp>,
814 open_only: bool,
815 limit: Option<u32>,
816 ) -> PyResult<Bound<'py, PyAny>> {
817 let client = self.clone();
818
819 pyo3_async_runtimes::tokio::future_into_py(py, async move {
820 let reports = client
821 .request_order_status_reports(
822 account_id,
823 instrument_type,
824 instrument_id,
825 start,
826 end,
827 open_only,
828 limit,
829 )
830 .await
831 .map_err(to_pyvalue_err)?;
832
833 Python::attach(|py| {
834 let py_reports = reports
835 .into_iter()
836 .map(|report| report.into_py_any(py))
837 .collect::<PyResult<Vec<_>>>()?;
838 let pylist = PyList::new(py, py_reports)?;
839 Ok(pylist.into_py_any_unwrap(py))
840 })
841 })
842 }
843
844 #[pyo3(name = "request_algo_order_status_reports")]
850 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, algo_id=None, algo_client_order_id=None, state=None, limit=None))]
851 #[expect(clippy::too_many_arguments)]
852 fn py_request_algo_order_status_reports<'py>(
853 &self,
854 py: Python<'py>,
855 account_id: AccountId,
856 instrument_type: Option<OKXInstrumentType>,
857 instrument_id: Option<InstrumentId>,
858 algo_id: Option<String>,
859 algo_client_order_id: Option<ClientOrderId>,
860 state: Option<OKXAlgoOrderStatus>,
861 limit: Option<u32>,
862 ) -> PyResult<Bound<'py, PyAny>> {
863 let client = self.clone();
864
865 pyo3_async_runtimes::tokio::future_into_py(py, async move {
866 let reports = client
867 .request_algo_order_status_reports(
868 account_id,
869 instrument_type,
870 instrument_id,
871 algo_id,
872 algo_client_order_id,
873 state,
874 limit,
875 )
876 .await
877 .map_err(to_pyvalue_err)?;
878
879 Python::attach(|py| {
880 let py_reports = reports
881 .into_iter()
882 .map(|report| report.into_py_any(py))
883 .collect::<PyResult<Vec<_>>>()?;
884 let pylist = PyList::new(py, py_reports)?;
885 Ok(pylist.into_py_any_unwrap(py))
886 })
887 })
888 }
889
890 #[pyo3(name = "request_algo_order_status_report")]
896 fn py_request_algo_order_status_report<'py>(
897 &self,
898 py: Python<'py>,
899 account_id: AccountId,
900 instrument_id: InstrumentId,
901 client_order_id: ClientOrderId,
902 ) -> PyResult<Bound<'py, PyAny>> {
903 let client = self.clone();
904
905 pyo3_async_runtimes::tokio::future_into_py(py, async move {
906 let report = client
907 .request_algo_order_status_report(account_id, instrument_id, client_order_id)
908 .await
909 .map_err(to_pyvalue_err)?;
910
911 Python::attach(|py| match report {
912 Some(report) => report.into_py_any(py),
913 None => Ok(py.None()),
914 })
915 })
916 }
917
918 #[pyo3(name = "request_fill_reports")]
928 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, limit=None))]
929 #[expect(clippy::too_many_arguments)]
930 fn py_request_fill_reports<'py>(
931 &self,
932 py: Python<'py>,
933 account_id: AccountId,
934 instrument_type: Option<OKXInstrumentType>,
935 instrument_id: Option<InstrumentId>,
936 start: Option<Timestamp>,
937 end: Option<Timestamp>,
938 limit: Option<u32>,
939 ) -> PyResult<Bound<'py, PyAny>> {
940 let client = self.clone();
941
942 pyo3_async_runtimes::tokio::future_into_py(py, async move {
943 let trades = client
944 .request_fill_reports(
945 account_id,
946 instrument_type,
947 instrument_id,
948 start,
949 end,
950 limit,
951 )
952 .await
953 .map_err(to_pyvalue_err)?;
954
955 Python::attach(|py| {
956 let py_trades = trades
957 .into_iter()
958 .map(|trade| trade.into_py_any(py))
959 .collect::<PyResult<Vec<_>>>()?;
960 let pylist = PyList::new(py, py_trades)?;
961 Ok(pylist.into_py_any_unwrap(py))
962 })
963 })
964 }
965
966 #[pyo3(name = "request_position_status_reports")]
994 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None))]
995 fn py_request_position_status_reports<'py>(
996 &self,
997 py: Python<'py>,
998 account_id: AccountId,
999 instrument_type: Option<OKXInstrumentType>,
1000 instrument_id: Option<InstrumentId>,
1001 ) -> PyResult<Bound<'py, PyAny>> {
1002 let client = self.clone();
1003
1004 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1005 let reports = client
1006 .request_position_status_reports(account_id, instrument_type, instrument_id)
1007 .await
1008 .map_err(to_pyvalue_err)?;
1009
1010 Python::attach(|py| {
1011 let py_reports = reports
1012 .into_iter()
1013 .map(|report| report.into_py_any(py))
1014 .collect::<PyResult<Vec<_>>>()?;
1015 let pylist = PyList::new(py, py_reports)?;
1016 Ok(pylist.into_py_any_unwrap(py))
1017 })
1018 })
1019 }
1020
1021 #[pyo3(name = "place_order")]
1031 #[pyo3(signature = (
1032 trader_id,
1033 strategy_id,
1034 instrument_id,
1035 td_mode,
1036 client_order_id,
1037 order_side,
1038 order_type,
1039 quantity,
1040 time_in_force=None,
1041 price=None,
1042 post_only=None,
1043 reduce_only=None,
1044 quote_quantity=None,
1045 position_side=None,
1046 attach_algo_ords=None,
1047 px_usd=None,
1048 px_vol=None,
1049 outcome=None,
1050 slippage_pct=None,
1051 ))]
1052 #[expect(clippy::too_many_arguments)]
1053 fn py_place_order<'py>(
1054 &self,
1055 py: Python<'py>,
1056 trader_id: TraderId,
1057 strategy_id: StrategyId,
1058 instrument_id: InstrumentId,
1059 td_mode: OKXTradeMode,
1060 client_order_id: ClientOrderId,
1061 order_side: OrderSide,
1062 order_type: OrderType,
1063 quantity: Quantity,
1064 time_in_force: Option<TimeInForce>,
1065 price: Option<Price>,
1066 post_only: Option<bool>,
1067 reduce_only: Option<bool>,
1068 quote_quantity: Option<bool>,
1069 position_side: Option<PositionSide>,
1070 attach_algo_ords: Option<Vec<Py<PyDict>>>,
1071 px_usd: Option<String>,
1072 px_vol: Option<String>,
1073 outcome: Option<String>,
1074 slippage_pct: Option<String>,
1075 ) -> PyResult<Bound<'py, PyAny>> {
1076 let attach_algo_ords = parse_attach_algo_ords(py, attach_algo_ords)?;
1077 let client = self.clone();
1078
1079 let _ = (trader_id, strategy_id);
1080
1081 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1082 let resp = client
1083 .place_order_with_domain_types(
1084 instrument_id,
1085 td_mode,
1086 client_order_id,
1087 order_side,
1088 order_type,
1089 quantity,
1090 time_in_force,
1091 price,
1092 post_only,
1093 reduce_only,
1094 quote_quantity,
1095 position_side,
1096 attach_algo_ords,
1097 px_usd,
1098 px_vol,
1099 outcome,
1100 slippage_pct,
1101 None,
1102 None,
1103 None,
1104 )
1105 .await
1106 .map_err(to_pyvalue_err)?;
1107
1108 Python::attach(|py| {
1109 let dict = PyDict::new(py);
1110
1111 if let Some(ord_id) = resp.ord_id {
1112 dict.set_item("ord_id", ord_id.as_str())?;
1113 }
1114
1115 if let Some(cl_ord_id) = resp.cl_ord_id {
1116 dict.set_item("cl_ord_id", cl_ord_id.as_str())?;
1117 }
1118
1119 if let Some(s_code) = resp.s_code {
1120 dict.set_item("s_code", s_code)?;
1121 }
1122
1123 if let Some(s_msg) = resp.s_msg {
1124 dict.set_item("s_msg", s_msg)?;
1125 }
1126
1127 if let Some(sub_code) = resp.sub_code {
1128 dict.set_item("sub_code", sub_code)?;
1129 }
1130
1131 Ok(dict.into_py_any_unwrap(py))
1132 })
1133 })
1134 }
1135
1136 #[pyo3(name = "place_algo_order")]
1146 #[pyo3(signature = (
1147 trader_id,
1148 strategy_id,
1149 instrument_id,
1150 td_mode,
1151 client_order_id,
1152 order_side,
1153 order_type,
1154 quantity,
1155 trigger_price=None,
1156 trigger_type=None,
1157 limit_price=None,
1158 reduce_only=None,
1159 close_fraction=None,
1160 callback_ratio=None,
1161 callback_spread=None,
1162 activation_price=None,
1163 quote_quantity=false,
1164 ))]
1165 #[expect(clippy::too_many_arguments)]
1166 fn py_place_algo_order<'py>(
1167 &self,
1168 py: Python<'py>,
1169 trader_id: TraderId,
1170 strategy_id: StrategyId,
1171 instrument_id: InstrumentId,
1172 td_mode: OKXTradeMode,
1173 client_order_id: ClientOrderId,
1174 order_side: OrderSide,
1175 order_type: OrderType,
1176 quantity: Quantity,
1177 trigger_price: Option<Price>,
1178 trigger_type: Option<TriggerType>,
1179 limit_price: Option<Price>,
1180 reduce_only: Option<bool>,
1181 close_fraction: Option<String>,
1182 callback_ratio: Option<String>,
1183 callback_spread: Option<String>,
1184 activation_price: Option<Price>,
1185 quote_quantity: bool,
1186 ) -> PyResult<Bound<'py, PyAny>> {
1187 let client = self.clone();
1188
1189 let _ = (trader_id, strategy_id);
1191
1192 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1193 let resp = client
1194 .place_algo_order_with_domain_types(
1195 instrument_id,
1196 td_mode,
1197 client_order_id,
1198 order_side,
1199 order_type,
1200 quantity,
1201 quote_quantity,
1202 trigger_price,
1203 trigger_type,
1204 limit_price,
1205 reduce_only,
1206 None,
1207 close_fraction,
1208 callback_ratio,
1209 callback_spread,
1210 activation_price,
1211 )
1212 .await
1213 .map_err(to_pyvalue_err)?;
1214
1215 Python::attach(|py| {
1216 let dict = PyDict::new(py);
1217 dict.set_item("algo_id", resp.algo_id)?;
1218 if let Some(algo_cl_ord_id) = resp.algo_cl_ord_id {
1219 dict.set_item("algo_cl_ord_id", algo_cl_ord_id)?;
1220 }
1221
1222 if let Some(s_code) = resp.s_code {
1223 dict.set_item("s_code", s_code)?;
1224 }
1225
1226 if let Some(s_msg) = resp.s_msg {
1227 dict.set_item("s_msg", s_msg)?;
1228 }
1229
1230 if let Some(req_id) = resp.req_id {
1231 dict.set_item("req_id", req_id)?;
1232 }
1233 Ok(dict.into_py_any_unwrap(py))
1234 })
1235 })
1236 }
1237
1238 #[pyo3(name = "cancel_algo_order")]
1248 fn py_cancel_algo_order<'py>(
1249 &self,
1250 py: Python<'py>,
1251 instrument_id: InstrumentId,
1252 algo_id: String,
1253 ) -> PyResult<Bound<'py, PyAny>> {
1254 let client = self.clone();
1255
1256 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1257 let resp = client
1258 .cancel_algo_order_with_domain_types(instrument_id, algo_id)
1259 .await
1260 .map_err(to_pyvalue_err)?;
1261
1262 Python::attach(|py| {
1263 let dict = PyDict::new(py);
1264 dict.set_item("algo_id", resp.algo_id)?;
1265 if let Some(s_code) = resp.s_code {
1266 dict.set_item("s_code", s_code)?;
1267 }
1268
1269 if let Some(s_msg) = resp.s_msg {
1270 dict.set_item("s_msg", s_msg)?;
1271 }
1272 Ok(dict.into_py_any_unwrap(py))
1273 })
1274 })
1275 }
1276
1277 #[pyo3(name = "cancel_order")]
1283 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
1284 fn py_cancel_order<'py>(
1285 &self,
1286 py: Python<'py>,
1287 instrument_id: InstrumentId,
1288 client_order_id: Option<ClientOrderId>,
1289 venue_order_id: Option<VenueOrderId>,
1290 ) -> PyResult<Bound<'py, PyAny>> {
1291 let client = self.clone();
1292
1293 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1294 let resp = client
1295 .cancel_order(instrument_id, client_order_id, venue_order_id)
1296 .await
1297 .map_err(to_pyvalue_err)?;
1298
1299 Python::attach(|py| {
1300 let dict = PyDict::new(py);
1301 dict.set_item("ord_id", resp.ord_id)?;
1302
1303 if let Some(cl_ord_id) = resp.cl_ord_id {
1304 dict.set_item("cl_ord_id", cl_ord_id)?;
1305 }
1306
1307 if let Some(s_code) = resp.s_code {
1308 dict.set_item("s_code", s_code)?;
1309 }
1310
1311 if let Some(s_msg) = resp.s_msg {
1312 dict.set_item("s_msg", s_msg)?;
1313 }
1314
1315 if let Some(ts) = resp.ts {
1316 dict.set_item("ts", ts)?;
1317 }
1318
1319 Ok(dict.into_py_any_unwrap(py))
1320 })
1321 })
1322 }
1323
1324 #[pyo3(name = "cancel_all_orders")]
1330 fn py_cancel_all_orders<'py>(
1331 &self,
1332 py: Python<'py>,
1333 instrument_id: InstrumentId,
1334 ) -> PyResult<Bound<'py, PyAny>> {
1335 let client = self.clone();
1336
1337 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1338 let responses = client
1339 .cancel_all_orders(instrument_id)
1340 .await
1341 .map_err(to_pyvalue_err)?;
1342
1343 Python::attach(|py| {
1344 let results: PyResult<Vec<_>> = responses
1345 .into_iter()
1346 .map(|resp| {
1347 let dict = PyDict::new(py);
1348 dict.set_item("ord_id", resp.ord_id)?;
1349
1350 if let Some(cl_ord_id) = resp.cl_ord_id {
1351 dict.set_item("cl_ord_id", cl_ord_id)?;
1352 }
1353
1354 if let Some(s_code) = resp.s_code {
1355 dict.set_item("s_code", s_code)?;
1356 }
1357
1358 if let Some(s_msg) = resp.s_msg {
1359 dict.set_item("s_msg", s_msg)?;
1360 }
1361
1362 if let Some(ts) = resp.ts {
1363 dict.set_item("ts", ts)?;
1364 }
1365
1366 Ok(dict)
1367 })
1368 .collect();
1369 Ok(PyList::new(py, results?)?.into_py_any_unwrap(py))
1370 })
1371 })
1372 }
1373
1374 #[expect(clippy::too_many_arguments)]
1384 #[pyo3(name = "amend_algo_order")]
1385 #[pyo3(signature = (
1386 instrument_id,
1387 algo_id,
1388 new_trigger_price=None,
1389 new_limit_price=None,
1390 new_quantity=None,
1391 new_callback_ratio=None,
1392 new_callback_spread=None,
1393 new_activation_price=None,
1394 new_sl_trigger_price=None,
1395 new_tp_trigger_price=None,
1396 new_tp_order_price=None,
1397 new_tp_trigger_px_type=None,
1398 new_sl_order_price=None,
1399 new_sl_trigger_px_type=None,
1400 ))]
1401 fn py_amend_algo_order<'py>(
1402 &self,
1403 py: Python<'py>,
1404 instrument_id: InstrumentId,
1405 algo_id: String,
1406 new_trigger_price: Option<Price>,
1407 new_limit_price: Option<Price>,
1408 new_quantity: Option<Quantity>,
1409 new_callback_ratio: Option<String>,
1410 new_callback_spread: Option<String>,
1411 new_activation_price: Option<Price>,
1412 new_sl_trigger_price: Option<Price>,
1413 new_tp_trigger_price: Option<Price>,
1414 new_tp_order_price: Option<String>,
1415 new_tp_trigger_px_type: Option<String>,
1416 new_sl_order_price: Option<String>,
1417 new_sl_trigger_px_type: Option<String>,
1418 ) -> PyResult<Bound<'py, PyAny>> {
1419 let client = self.clone();
1420
1421 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1422 let resp = client
1423 .amend_algo_order_with_domain_types(
1424 instrument_id,
1425 algo_id,
1426 new_trigger_price,
1427 new_sl_trigger_price,
1428 new_limit_price,
1429 new_quantity,
1430 new_callback_ratio,
1431 new_callback_spread,
1432 new_activation_price,
1433 new_tp_trigger_price,
1434 new_tp_order_price,
1435 new_tp_trigger_px_type,
1436 new_sl_order_price,
1437 new_sl_trigger_px_type,
1438 )
1439 .await
1440 .map_err(to_pyvalue_err)?;
1441
1442 Python::attach(|py| {
1443 let dict = PyDict::new(py);
1444 dict.set_item("algo_id", resp.algo_id)?;
1445 if let Some(s_code) = resp.s_code {
1446 dict.set_item("s_code", s_code)?;
1447 }
1448
1449 if let Some(s_msg) = resp.s_msg {
1450 dict.set_item("s_msg", s_msg)?;
1451 }
1452 Ok(dict.into_py_any_unwrap(py))
1453 })
1454 })
1455 }
1456
1457 #[pyo3(name = "cancel_algo_orders")]
1470 fn py_cancel_algo_orders<'py>(
1471 &self,
1472 py: Python<'py>,
1473 orders: Vec<(InstrumentId, String)>,
1474 ) -> PyResult<Bound<'py, PyAny>> {
1475 let client = self.clone();
1476
1477 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1478 let requests: Vec<_> = orders
1479 .into_iter()
1480 .map(|(instrument_id, algo_id)| OKXCancelAlgoOrderRequest {
1481 inst_id: instrument_id.symbol.to_string(),
1482 inst_id_code: None,
1483 algo_id: Some(algo_id),
1484 algo_cl_ord_id: None,
1485 })
1486 .collect();
1487
1488 let responses = client
1489 .cancel_algo_orders(requests)
1490 .await
1491 .map_err(to_pyvalue_err)?;
1492
1493 Python::attach(|py| {
1494 let results = responses
1495 .into_iter()
1496 .map(|resp| {
1497 let dict = PyDict::new(py);
1498 dict.set_item("algo_id", resp.algo_id)?;
1499 if let Some(s_code) = resp.s_code {
1500 dict.set_item("s_code", s_code)?;
1501 }
1502
1503 if let Some(s_msg) = resp.s_msg {
1504 dict.set_item("s_msg", s_msg)?;
1505 }
1506 Ok(dict)
1507 })
1508 .collect::<PyResult<Vec<_>>>()?;
1509 Ok(PyList::new(py, results)?.into_any().unbind())
1510 })
1511 })
1512 }
1513
1514 #[pyo3(name = "cancel_advance_algo_order")]
1515 fn py_cancel_advance_algo_order<'py>(
1516 &self,
1517 py: Python<'py>,
1518 instrument_id: InstrumentId,
1519 algo_id: String,
1520 ) -> PyResult<Bound<'py, PyAny>> {
1521 let client = self.clone();
1522
1523 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1524 let request = OKXCancelAlgoOrderRequest {
1525 inst_id: instrument_id.symbol.to_string(),
1526 inst_id_code: None,
1527 algo_id: Some(algo_id),
1528 algo_cl_ord_id: None,
1529 };
1530
1531 let mut responses = client
1532 .cancel_advance_algo_orders(vec![request])
1533 .await
1534 .map_err(to_pyvalue_err)?;
1535
1536 let resp = responses
1537 .pop()
1538 .ok_or_else(|| to_pyvalue_err("Empty response"))?;
1539
1540 Python::attach(|py| {
1541 let dict = PyDict::new(py);
1542 dict.set_item("algo_id", resp.algo_id)?;
1543
1544 if let Some(s_code) = resp.s_code {
1545 dict.set_item("s_code", s_code)?;
1546 }
1547
1548 if let Some(s_msg) = resp.s_msg {
1549 dict.set_item("s_msg", s_msg)?;
1550 }
1551 Ok(dict.into_py_any_unwrap(py))
1552 })
1553 })
1554 }
1555
1556 #[pyo3(name = "get_server_time")]
1564 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1565 let client = self.clone();
1566
1567 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1568 let timestamp = client.get_server_time().await.map_err(to_pyvalue_err)?;
1569
1570 Python::attach(|py| timestamp.into_py_any(py))
1571 })
1572 }
1573
1574 #[pyo3(name = "get_balance")]
1575 fn py_get_balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1576 let client = self.clone();
1577
1578 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1579 let accounts = client.inner.get_balance().await.map_err(to_pyvalue_err)?;
1580
1581 let details: Vec<_> = accounts
1582 .into_iter()
1583 .flat_map(|account| account.details)
1584 .collect();
1585
1586 Python::attach(|py| {
1587 let pylist = PyList::new(py, details)?;
1588 Ok(pylist.into_py_any_unwrap(py))
1589 })
1590 })
1591 }
1592}
1593
1594impl From<OKXHttpError> for PyErr {
1595 fn from(error: OKXHttpError) -> Self {
1596 match error {
1597 OKXHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
1599 OKXHttpError::HttpClientError(e) => to_pyruntime_err(format!("Network error: {e}")),
1600 OKXHttpError::RetryableStatus { status, body, .. } => {
1601 to_pyruntime_err(format!("Temporary HTTP status code {status}: {body}"))
1602 }
1603 OKXHttpError::UnexpectedStatus { status, body } => {
1604 to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
1605 }
1606 OKXHttpError::RetryableOkxError {
1607 error_code,
1608 message,
1609 ..
1610 } => to_pyruntime_err(format!("Temporary OKX error {error_code}: {message}")),
1611 OKXHttpError::MalformedResponse(msg) => {
1612 to_pyruntime_err(format!("Malformed response: {msg}"))
1613 }
1614 OKXHttpError::ResponseDecoding(msg) => {
1615 to_pyruntime_err(format!("Response decoding error: {msg}"))
1616 }
1617 OKXHttpError::OperationTimeout { timeout_ms } => {
1618 to_pyruntime_err(format!("Operation timed out after {timeout_ms}ms"))
1619 }
1620 OKXHttpError::RetryBudgetExceeded(msg) => {
1621 to_pyruntime_err(format!("Retry budget exceeded: {msg}"))
1622 }
1623 OKXHttpError::EmptyResponse => to_pyruntime_err("Empty response"),
1624 OKXHttpError::MissingCredentials => {
1626 to_pyvalue_err("Missing credentials for authenticated request")
1627 }
1628 OKXHttpError::ValidationError(msg) => {
1629 to_pyvalue_err(format!("Parameter validation error: {msg}"))
1630 }
1631 OKXHttpError::RequestSerialization(msg) => {
1632 to_pyvalue_err(format!("Request serialization error: {msg}"))
1633 }
1634 OKXHttpError::OkxError {
1635 error_code,
1636 message,
1637 } => to_pyvalue_err(format!("OKX error {error_code}: {message}")),
1638 }
1639 }
1640}