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")]
629 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
630 fn py_request_bars<'py>(
631 &self,
632 py: Python<'py>,
633 bar_type: BarType,
634 start: Option<Timestamp>,
635 end: Option<Timestamp>,
636 limit: Option<u32>,
637 ) -> PyResult<Bound<'py, PyAny>> {
638 let client = self.clone();
639
640 pyo3_async_runtimes::tokio::future_into_py(py, async move {
641 let bars = client
642 .request_bars(bar_type, start, end, limit)
643 .await
644 .map_err(to_pyvalue_err)?;
645
646 Python::attach(|py| {
647 let py_bars = bars
648 .into_iter()
649 .map(|bar| bar.into_py_any(py))
650 .collect::<PyResult<Vec<_>>>()?;
651 let pylist = PyList::new(py, py_bars)?;
652 Ok(pylist.into_py_any_unwrap(py))
653 })
654 })
655 }
656
657 #[pyo3(name = "request_orderbook_snapshot")]
663 #[pyo3(signature = (instrument_id, depth=None))]
664 fn py_request_orderbook_snapshot<'py>(
665 &self,
666 py: Python<'py>,
667 instrument_id: InstrumentId,
668 depth: Option<u32>,
669 ) -> PyResult<Bound<'py, PyAny>> {
670 let client = self.clone();
671
672 pyo3_async_runtimes::tokio::future_into_py(py, async move {
673 let deltas = client
674 .request_orderbook_snapshot(instrument_id, depth)
675 .await
676 .map_err(to_pyvalue_err)?;
677
678 Python::attach(|py| deltas.into_py_any(py))
679 })
680 }
681
682 #[pyo3(name = "request_funding_rates")]
688 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
689 fn py_request_funding_rates<'py>(
690 &self,
691 py: Python<'py>,
692 instrument_id: InstrumentId,
693 start: Option<Timestamp>,
694 end: Option<Timestamp>,
695 limit: Option<u32>,
696 ) -> PyResult<Bound<'py, PyAny>> {
697 let client = self.clone();
698
699 pyo3_async_runtimes::tokio::future_into_py(py, async move {
700 let rates = client
701 .request_funding_rates(instrument_id, start, end, limit)
702 .await
703 .map_err(to_pyvalue_err)?;
704
705 Python::attach(|py| {
706 let py_rates = rates
707 .into_iter()
708 .map(|rate| rate.into_py_any(py))
709 .collect::<PyResult<Vec<_>>>()?;
710 let pylist = PyList::new(py, py_rates)?;
711 Ok(pylist.into_py_any_unwrap(py))
712 })
713 })
714 }
715
716 #[pyo3(name = "request_mark_price")]
722 fn py_request_mark_price<'py>(
723 &self,
724 py: Python<'py>,
725 instrument_id: InstrumentId,
726 ) -> PyResult<Bound<'py, PyAny>> {
727 let client = self.clone();
728
729 pyo3_async_runtimes::tokio::future_into_py(py, async move {
730 let mark_price = client
731 .request_mark_price(instrument_id)
732 .await
733 .map_err(to_pyvalue_err)?;
734
735 Python::attach(|py| mark_price.into_py_any(py))
736 })
737 }
738
739 #[pyo3(name = "request_price_limit")]
745 fn py_request_price_limit<'py>(
746 &self,
747 py: Python<'py>,
748 instrument_id: InstrumentId,
749 ) -> PyResult<Bound<'py, PyAny>> {
750 let client = self.clone();
751
752 pyo3_async_runtimes::tokio::future_into_py(py, async move {
753 let price_limit = client
754 .request_price_limit(instrument_id)
755 .await
756 .map_err(to_pyvalue_err)?;
757
758 Python::attach(|py| {
759 let value = serde_json::to_value(price_limit).map_err(to_pyvalue_err)?;
760 value_to_pyobject(py, &value)
761 })
762 })
763 }
764
765 #[pyo3(name = "request_index_price")]
771 fn py_request_index_price<'py>(
772 &self,
773 py: Python<'py>,
774 instrument_id: InstrumentId,
775 ) -> PyResult<Bound<'py, PyAny>> {
776 let client = self.clone();
777
778 pyo3_async_runtimes::tokio::future_into_py(py, async move {
779 let index_price = client
780 .request_index_price(instrument_id)
781 .await
782 .map_err(to_pyvalue_err)?;
783
784 Python::attach(|py| index_price.into_py_any(py))
785 })
786 }
787
788 #[pyo3(name = "request_order_status_reports")]
799 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, open_only=false, limit=None))]
800 #[expect(clippy::too_many_arguments)]
801 fn py_request_order_status_reports<'py>(
802 &self,
803 py: Python<'py>,
804 account_id: AccountId,
805 instrument_type: Option<OKXInstrumentType>,
806 instrument_id: Option<InstrumentId>,
807 start: Option<Timestamp>,
808 end: Option<Timestamp>,
809 open_only: bool,
810 limit: Option<u32>,
811 ) -> PyResult<Bound<'py, PyAny>> {
812 let client = self.clone();
813
814 pyo3_async_runtimes::tokio::future_into_py(py, async move {
815 let reports = client
816 .request_order_status_reports(
817 account_id,
818 instrument_type,
819 instrument_id,
820 start,
821 end,
822 open_only,
823 limit,
824 )
825 .await
826 .map_err(to_pyvalue_err)?;
827
828 Python::attach(|py| {
829 let py_reports = reports
830 .into_iter()
831 .map(|report| report.into_py_any(py))
832 .collect::<PyResult<Vec<_>>>()?;
833 let pylist = PyList::new(py, py_reports)?;
834 Ok(pylist.into_py_any_unwrap(py))
835 })
836 })
837 }
838
839 #[pyo3(name = "request_algo_order_status_reports")]
845 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, algo_id=None, algo_client_order_id=None, state=None, limit=None))]
846 #[expect(clippy::too_many_arguments)]
847 fn py_request_algo_order_status_reports<'py>(
848 &self,
849 py: Python<'py>,
850 account_id: AccountId,
851 instrument_type: Option<OKXInstrumentType>,
852 instrument_id: Option<InstrumentId>,
853 algo_id: Option<String>,
854 algo_client_order_id: Option<ClientOrderId>,
855 state: Option<OKXAlgoOrderStatus>,
856 limit: Option<u32>,
857 ) -> PyResult<Bound<'py, PyAny>> {
858 let client = self.clone();
859
860 pyo3_async_runtimes::tokio::future_into_py(py, async move {
861 let reports = client
862 .request_algo_order_status_reports(
863 account_id,
864 instrument_type,
865 instrument_id,
866 algo_id,
867 algo_client_order_id,
868 state,
869 limit,
870 )
871 .await
872 .map_err(to_pyvalue_err)?;
873
874 Python::attach(|py| {
875 let py_reports = reports
876 .into_iter()
877 .map(|report| report.into_py_any(py))
878 .collect::<PyResult<Vec<_>>>()?;
879 let pylist = PyList::new(py, py_reports)?;
880 Ok(pylist.into_py_any_unwrap(py))
881 })
882 })
883 }
884
885 #[pyo3(name = "request_algo_order_status_report")]
891 fn py_request_algo_order_status_report<'py>(
892 &self,
893 py: Python<'py>,
894 account_id: AccountId,
895 instrument_id: InstrumentId,
896 client_order_id: ClientOrderId,
897 ) -> PyResult<Bound<'py, PyAny>> {
898 let client = self.clone();
899
900 pyo3_async_runtimes::tokio::future_into_py(py, async move {
901 let report = client
902 .request_algo_order_status_report(account_id, instrument_id, client_order_id)
903 .await
904 .map_err(to_pyvalue_err)?;
905
906 Python::attach(|py| match report {
907 Some(report) => report.into_py_any(py),
908 None => Ok(py.None()),
909 })
910 })
911 }
912
913 #[pyo3(name = "request_fill_reports")]
923 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, limit=None))]
924 #[expect(clippy::too_many_arguments)]
925 fn py_request_fill_reports<'py>(
926 &self,
927 py: Python<'py>,
928 account_id: AccountId,
929 instrument_type: Option<OKXInstrumentType>,
930 instrument_id: Option<InstrumentId>,
931 start: Option<Timestamp>,
932 end: Option<Timestamp>,
933 limit: Option<u32>,
934 ) -> PyResult<Bound<'py, PyAny>> {
935 let client = self.clone();
936
937 pyo3_async_runtimes::tokio::future_into_py(py, async move {
938 let trades = client
939 .request_fill_reports(
940 account_id,
941 instrument_type,
942 instrument_id,
943 start,
944 end,
945 limit,
946 )
947 .await
948 .map_err(to_pyvalue_err)?;
949
950 Python::attach(|py| {
951 let py_trades = trades
952 .into_iter()
953 .map(|trade| trade.into_py_any(py))
954 .collect::<PyResult<Vec<_>>>()?;
955 let pylist = PyList::new(py, py_trades)?;
956 Ok(pylist.into_py_any_unwrap(py))
957 })
958 })
959 }
960
961 #[pyo3(name = "request_position_status_reports")]
988 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None))]
989 fn py_request_position_status_reports<'py>(
990 &self,
991 py: Python<'py>,
992 account_id: AccountId,
993 instrument_type: Option<OKXInstrumentType>,
994 instrument_id: Option<InstrumentId>,
995 ) -> PyResult<Bound<'py, PyAny>> {
996 let client = self.clone();
997
998 pyo3_async_runtimes::tokio::future_into_py(py, async move {
999 let reports = client
1000 .request_position_status_reports(account_id, instrument_type, instrument_id)
1001 .await
1002 .map_err(to_pyvalue_err)?;
1003
1004 Python::attach(|py| {
1005 let py_reports = reports
1006 .into_iter()
1007 .map(|report| report.into_py_any(py))
1008 .collect::<PyResult<Vec<_>>>()?;
1009 let pylist = PyList::new(py, py_reports)?;
1010 Ok(pylist.into_py_any_unwrap(py))
1011 })
1012 })
1013 }
1014
1015 #[pyo3(name = "place_order")]
1025 #[pyo3(signature = (
1026 trader_id,
1027 strategy_id,
1028 instrument_id,
1029 td_mode,
1030 client_order_id,
1031 order_side,
1032 order_type,
1033 quantity,
1034 time_in_force=None,
1035 price=None,
1036 post_only=None,
1037 reduce_only=None,
1038 quote_quantity=None,
1039 position_side=None,
1040 attach_algo_ords=None,
1041 px_usd=None,
1042 px_vol=None,
1043 outcome=None,
1044 slippage_pct=None,
1045 ))]
1046 #[expect(clippy::too_many_arguments)]
1047 fn py_place_order<'py>(
1048 &self,
1049 py: Python<'py>,
1050 trader_id: TraderId,
1051 strategy_id: StrategyId,
1052 instrument_id: InstrumentId,
1053 td_mode: OKXTradeMode,
1054 client_order_id: ClientOrderId,
1055 order_side: OrderSide,
1056 order_type: OrderType,
1057 quantity: Quantity,
1058 time_in_force: Option<TimeInForce>,
1059 price: Option<Price>,
1060 post_only: Option<bool>,
1061 reduce_only: Option<bool>,
1062 quote_quantity: Option<bool>,
1063 position_side: Option<PositionSide>,
1064 attach_algo_ords: Option<Vec<Py<PyDict>>>,
1065 px_usd: Option<String>,
1066 px_vol: Option<String>,
1067 outcome: Option<String>,
1068 slippage_pct: Option<String>,
1069 ) -> PyResult<Bound<'py, PyAny>> {
1070 let attach_algo_ords = parse_attach_algo_ords(py, attach_algo_ords)?;
1071 let client = self.clone();
1072
1073 let _ = (trader_id, strategy_id);
1074
1075 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1076 let resp = client
1077 .place_order_with_domain_types(
1078 instrument_id,
1079 td_mode,
1080 client_order_id,
1081 order_side,
1082 order_type,
1083 quantity,
1084 time_in_force,
1085 price,
1086 post_only,
1087 reduce_only,
1088 quote_quantity,
1089 position_side,
1090 attach_algo_ords,
1091 px_usd,
1092 px_vol,
1093 outcome,
1094 slippage_pct,
1095 None,
1096 None,
1097 None,
1098 )
1099 .await
1100 .map_err(to_pyvalue_err)?;
1101
1102 Python::attach(|py| {
1103 let dict = PyDict::new(py);
1104
1105 if let Some(ord_id) = resp.ord_id {
1106 dict.set_item("ord_id", ord_id.as_str())?;
1107 }
1108
1109 if let Some(cl_ord_id) = resp.cl_ord_id {
1110 dict.set_item("cl_ord_id", cl_ord_id.as_str())?;
1111 }
1112
1113 if let Some(s_code) = resp.s_code {
1114 dict.set_item("s_code", s_code)?;
1115 }
1116
1117 if let Some(s_msg) = resp.s_msg {
1118 dict.set_item("s_msg", s_msg)?;
1119 }
1120
1121 if let Some(sub_code) = resp.sub_code {
1122 dict.set_item("sub_code", sub_code)?;
1123 }
1124
1125 Ok(dict.into_py_any_unwrap(py))
1126 })
1127 })
1128 }
1129
1130 #[pyo3(name = "place_algo_order")]
1140 #[pyo3(signature = (
1141 trader_id,
1142 strategy_id,
1143 instrument_id,
1144 td_mode,
1145 client_order_id,
1146 order_side,
1147 order_type,
1148 quantity,
1149 trigger_price=None,
1150 trigger_type=None,
1151 limit_price=None,
1152 reduce_only=None,
1153 close_fraction=None,
1154 callback_ratio=None,
1155 callback_spread=None,
1156 activation_price=None,
1157 ))]
1158 #[expect(clippy::too_many_arguments)]
1159 fn py_place_algo_order<'py>(
1160 &self,
1161 py: Python<'py>,
1162 trader_id: TraderId,
1163 strategy_id: StrategyId,
1164 instrument_id: InstrumentId,
1165 td_mode: OKXTradeMode,
1166 client_order_id: ClientOrderId,
1167 order_side: OrderSide,
1168 order_type: OrderType,
1169 quantity: Quantity,
1170 trigger_price: Option<Price>,
1171 trigger_type: Option<TriggerType>,
1172 limit_price: Option<Price>,
1173 reduce_only: Option<bool>,
1174 close_fraction: Option<String>,
1175 callback_ratio: Option<String>,
1176 callback_spread: Option<String>,
1177 activation_price: Option<Price>,
1178 ) -> PyResult<Bound<'py, PyAny>> {
1179 let client = self.clone();
1180
1181 let _ = (trader_id, strategy_id);
1183
1184 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1185 let resp = client
1186 .place_algo_order_with_domain_types(
1187 instrument_id,
1188 td_mode,
1189 client_order_id,
1190 order_side,
1191 order_type,
1192 quantity,
1193 trigger_price,
1194 trigger_type,
1195 limit_price,
1196 reduce_only,
1197 close_fraction,
1198 callback_ratio,
1199 callback_spread,
1200 activation_price,
1201 )
1202 .await
1203 .map_err(to_pyvalue_err)?;
1204
1205 Python::attach(|py| {
1206 let dict = PyDict::new(py);
1207 dict.set_item("algo_id", resp.algo_id)?;
1208 if let Some(algo_cl_ord_id) = resp.algo_cl_ord_id {
1209 dict.set_item("algo_cl_ord_id", algo_cl_ord_id)?;
1210 }
1211
1212 if let Some(s_code) = resp.s_code {
1213 dict.set_item("s_code", s_code)?;
1214 }
1215
1216 if let Some(s_msg) = resp.s_msg {
1217 dict.set_item("s_msg", s_msg)?;
1218 }
1219
1220 if let Some(req_id) = resp.req_id {
1221 dict.set_item("req_id", req_id)?;
1222 }
1223 Ok(dict.into_py_any_unwrap(py))
1224 })
1225 })
1226 }
1227
1228 #[pyo3(name = "cancel_algo_order")]
1238 fn py_cancel_algo_order<'py>(
1239 &self,
1240 py: Python<'py>,
1241 instrument_id: InstrumentId,
1242 algo_id: String,
1243 ) -> PyResult<Bound<'py, PyAny>> {
1244 let client = self.clone();
1245
1246 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1247 let resp = client
1248 .cancel_algo_order_with_domain_types(instrument_id, algo_id)
1249 .await
1250 .map_err(to_pyvalue_err)?;
1251
1252 Python::attach(|py| {
1253 let dict = PyDict::new(py);
1254 dict.set_item("algo_id", resp.algo_id)?;
1255 if let Some(s_code) = resp.s_code {
1256 dict.set_item("s_code", s_code)?;
1257 }
1258
1259 if let Some(s_msg) = resp.s_msg {
1260 dict.set_item("s_msg", s_msg)?;
1261 }
1262 Ok(dict.into_py_any_unwrap(py))
1263 })
1264 })
1265 }
1266
1267 #[pyo3(name = "cancel_order")]
1273 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
1274 fn py_cancel_order<'py>(
1275 &self,
1276 py: Python<'py>,
1277 instrument_id: InstrumentId,
1278 client_order_id: Option<ClientOrderId>,
1279 venue_order_id: Option<VenueOrderId>,
1280 ) -> PyResult<Bound<'py, PyAny>> {
1281 let client = self.clone();
1282
1283 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1284 let resp = client
1285 .cancel_order(instrument_id, client_order_id, venue_order_id)
1286 .await
1287 .map_err(to_pyvalue_err)?;
1288
1289 Python::attach(|py| {
1290 let dict = PyDict::new(py);
1291 dict.set_item("ord_id", resp.ord_id)?;
1292
1293 if let Some(cl_ord_id) = resp.cl_ord_id {
1294 dict.set_item("cl_ord_id", cl_ord_id)?;
1295 }
1296
1297 if let Some(s_code) = resp.s_code {
1298 dict.set_item("s_code", s_code)?;
1299 }
1300
1301 if let Some(s_msg) = resp.s_msg {
1302 dict.set_item("s_msg", s_msg)?;
1303 }
1304
1305 if let Some(ts) = resp.ts {
1306 dict.set_item("ts", ts)?;
1307 }
1308
1309 Ok(dict.into_py_any_unwrap(py))
1310 })
1311 })
1312 }
1313
1314 #[pyo3(name = "cancel_all_orders")]
1320 fn py_cancel_all_orders<'py>(
1321 &self,
1322 py: Python<'py>,
1323 instrument_id: InstrumentId,
1324 ) -> PyResult<Bound<'py, PyAny>> {
1325 let client = self.clone();
1326
1327 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1328 let responses = client
1329 .cancel_all_orders(instrument_id)
1330 .await
1331 .map_err(to_pyvalue_err)?;
1332
1333 Python::attach(|py| {
1334 let results: PyResult<Vec<_>> = responses
1335 .into_iter()
1336 .map(|resp| {
1337 let dict = PyDict::new(py);
1338 dict.set_item("ord_id", resp.ord_id)?;
1339
1340 if let Some(cl_ord_id) = resp.cl_ord_id {
1341 dict.set_item("cl_ord_id", cl_ord_id)?;
1342 }
1343
1344 if let Some(s_code) = resp.s_code {
1345 dict.set_item("s_code", s_code)?;
1346 }
1347
1348 if let Some(s_msg) = resp.s_msg {
1349 dict.set_item("s_msg", s_msg)?;
1350 }
1351
1352 if let Some(ts) = resp.ts {
1353 dict.set_item("ts", ts)?;
1354 }
1355
1356 Ok(dict)
1357 })
1358 .collect();
1359 Ok(PyList::new(py, results?)?.into_py_any_unwrap(py))
1360 })
1361 })
1362 }
1363
1364 #[expect(clippy::too_many_arguments)]
1374 #[pyo3(name = "amend_algo_order")]
1375 #[pyo3(signature = (
1376 instrument_id,
1377 algo_id,
1378 new_trigger_price=None,
1379 new_limit_price=None,
1380 new_quantity=None,
1381 new_callback_ratio=None,
1382 new_callback_spread=None,
1383 new_activation_price=None,
1384 new_sl_trigger_price=None,
1385 new_tp_trigger_price=None,
1386 new_tp_order_price=None,
1387 new_tp_trigger_px_type=None,
1388 new_sl_order_price=None,
1389 new_sl_trigger_px_type=None,
1390 ))]
1391 fn py_amend_algo_order<'py>(
1392 &self,
1393 py: Python<'py>,
1394 instrument_id: InstrumentId,
1395 algo_id: String,
1396 new_trigger_price: Option<Price>,
1397 new_limit_price: Option<Price>,
1398 new_quantity: Option<Quantity>,
1399 new_callback_ratio: Option<String>,
1400 new_callback_spread: Option<String>,
1401 new_activation_price: Option<Price>,
1402 new_sl_trigger_price: Option<Price>,
1403 new_tp_trigger_price: Option<Price>,
1404 new_tp_order_price: Option<String>,
1405 new_tp_trigger_px_type: Option<String>,
1406 new_sl_order_price: Option<String>,
1407 new_sl_trigger_px_type: Option<String>,
1408 ) -> PyResult<Bound<'py, PyAny>> {
1409 let client = self.clone();
1410
1411 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1412 let resp = client
1413 .amend_algo_order_with_domain_types(
1414 instrument_id,
1415 algo_id,
1416 new_trigger_price,
1417 new_sl_trigger_price,
1418 new_limit_price,
1419 new_quantity,
1420 new_callback_ratio,
1421 new_callback_spread,
1422 new_activation_price,
1423 new_tp_trigger_price,
1424 new_tp_order_price,
1425 new_tp_trigger_px_type,
1426 new_sl_order_price,
1427 new_sl_trigger_px_type,
1428 )
1429 .await
1430 .map_err(to_pyvalue_err)?;
1431
1432 Python::attach(|py| {
1433 let dict = PyDict::new(py);
1434 dict.set_item("algo_id", resp.algo_id)?;
1435 if let Some(s_code) = resp.s_code {
1436 dict.set_item("s_code", s_code)?;
1437 }
1438
1439 if let Some(s_msg) = resp.s_msg {
1440 dict.set_item("s_msg", s_msg)?;
1441 }
1442 Ok(dict.into_py_any_unwrap(py))
1443 })
1444 })
1445 }
1446
1447 #[pyo3(name = "cancel_algo_orders")]
1460 fn py_cancel_algo_orders<'py>(
1461 &self,
1462 py: Python<'py>,
1463 orders: Vec<(InstrumentId, String)>,
1464 ) -> PyResult<Bound<'py, PyAny>> {
1465 let client = self.clone();
1466
1467 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1468 let requests: Vec<_> = orders
1469 .into_iter()
1470 .map(|(instrument_id, algo_id)| OKXCancelAlgoOrderRequest {
1471 inst_id: instrument_id.symbol.to_string(),
1472 inst_id_code: None,
1473 algo_id: Some(algo_id),
1474 algo_cl_ord_id: None,
1475 })
1476 .collect();
1477
1478 let responses = client
1479 .cancel_algo_orders(requests)
1480 .await
1481 .map_err(to_pyvalue_err)?;
1482
1483 Python::attach(|py| {
1484 let results = responses
1485 .into_iter()
1486 .map(|resp| {
1487 let dict = PyDict::new(py);
1488 dict.set_item("algo_id", resp.algo_id)?;
1489 if let Some(s_code) = resp.s_code {
1490 dict.set_item("s_code", s_code)?;
1491 }
1492
1493 if let Some(s_msg) = resp.s_msg {
1494 dict.set_item("s_msg", s_msg)?;
1495 }
1496 Ok(dict)
1497 })
1498 .collect::<PyResult<Vec<_>>>()?;
1499 Ok(PyList::new(py, results)?.into_any().unbind())
1500 })
1501 })
1502 }
1503
1504 #[pyo3(name = "cancel_advance_algo_order")]
1505 fn py_cancel_advance_algo_order<'py>(
1506 &self,
1507 py: Python<'py>,
1508 instrument_id: InstrumentId,
1509 algo_id: String,
1510 ) -> PyResult<Bound<'py, PyAny>> {
1511 let client = self.clone();
1512
1513 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1514 let request = OKXCancelAlgoOrderRequest {
1515 inst_id: instrument_id.symbol.to_string(),
1516 inst_id_code: None,
1517 algo_id: Some(algo_id),
1518 algo_cl_ord_id: None,
1519 };
1520
1521 let mut responses = client
1522 .cancel_advance_algo_orders(vec![request])
1523 .await
1524 .map_err(to_pyvalue_err)?;
1525
1526 let resp = responses
1527 .pop()
1528 .ok_or_else(|| to_pyvalue_err("Empty response"))?;
1529
1530 Python::attach(|py| {
1531 let dict = PyDict::new(py);
1532 dict.set_item("algo_id", resp.algo_id)?;
1533
1534 if let Some(s_code) = resp.s_code {
1535 dict.set_item("s_code", s_code)?;
1536 }
1537
1538 if let Some(s_msg) = resp.s_msg {
1539 dict.set_item("s_msg", s_msg)?;
1540 }
1541 Ok(dict.into_py_any_unwrap(py))
1542 })
1543 })
1544 }
1545
1546 #[pyo3(name = "get_server_time")]
1554 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1555 let client = self.clone();
1556
1557 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1558 let timestamp = client.get_server_time().await.map_err(to_pyvalue_err)?;
1559
1560 Python::attach(|py| timestamp.into_py_any(py))
1561 })
1562 }
1563
1564 #[pyo3(name = "get_balance")]
1565 fn py_get_balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1566 let client = self.clone();
1567
1568 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1569 let accounts = client.inner.get_balance().await.map_err(to_pyvalue_err)?;
1570
1571 let details: Vec<_> = accounts
1572 .into_iter()
1573 .flat_map(|account| account.details)
1574 .collect();
1575
1576 Python::attach(|py| {
1577 let pylist = PyList::new(py, details)?;
1578 Ok(pylist.into_py_any_unwrap(py))
1579 })
1580 })
1581 }
1582}
1583
1584impl From<OKXHttpError> for PyErr {
1585 fn from(error: OKXHttpError) -> Self {
1586 match error {
1587 OKXHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
1589 OKXHttpError::HttpClientError(e) => to_pyruntime_err(format!("Network error: {e}")),
1590 OKXHttpError::RetryableStatus { status, body, .. } => {
1591 to_pyruntime_err(format!("Temporary HTTP status code {status}: {body}"))
1592 }
1593 OKXHttpError::UnexpectedStatus { status, body } => {
1594 to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
1595 }
1596 OKXHttpError::RetryableOkxError {
1597 error_code,
1598 message,
1599 ..
1600 } => to_pyruntime_err(format!("Temporary OKX error {error_code}: {message}")),
1601 OKXHttpError::MalformedResponse(msg) => {
1602 to_pyruntime_err(format!("Malformed response: {msg}"))
1603 }
1604 OKXHttpError::ResponseDecoding(msg) => {
1605 to_pyruntime_err(format!("Response decoding error: {msg}"))
1606 }
1607 OKXHttpError::OperationTimeout { timeout_ms } => {
1608 to_pyruntime_err(format!("Operation timed out after {timeout_ms}ms"))
1609 }
1610 OKXHttpError::RetryBudgetExceeded(msg) => {
1611 to_pyruntime_err(format!("Retry budget exceeded: {msg}"))
1612 }
1613 OKXHttpError::EmptyResponse => to_pyruntime_err("Empty response"),
1614 OKXHttpError::MissingCredentials => {
1616 to_pyvalue_err("Missing credentials for authenticated request")
1617 }
1618 OKXHttpError::ValidationError(msg) => {
1619 to_pyvalue_err(format!("Parameter validation error: {msg}"))
1620 }
1621 OKXHttpError::RequestSerialization(msg) => {
1622 to_pyvalue_err(format!("Request serialization error: {msg}"))
1623 }
1624 OKXHttpError::OkxError {
1625 error_code,
1626 message,
1627 } => to_pyvalue_err(format!("OKX error {error_code}: {message}")),
1628 }
1629 }
1630}