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, forward::ForwardPrice},
24 enums::{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 = "request_instruments")]
283 #[pyo3(signature = (instrument_type, instrument_family=None))]
284 fn py_request_instruments<'py>(
285 &self,
286 py: Python<'py>,
287 instrument_type: OKXInstrumentType,
288 instrument_family: Option<String>,
289 ) -> PyResult<Bound<'py, PyAny>> {
290 let client = self.clone();
291
292 pyo3_async_runtimes::tokio::future_into_py(py, async move {
293 let (instruments, inst_id_codes) = client
294 .request_instruments(instrument_type, instrument_family)
295 .await
296 .map_err(to_pyvalue_err)?;
297
298 Python::attach(|py| {
299 let py_instruments: PyResult<Vec<_>> = instruments
300 .into_iter()
301 .map(|inst| instrument_any_to_pyobject(py, inst))
302 .collect();
303 let instruments_list = PyList::new(py, py_instruments?)?;
304
305 let py_codes: Vec<_> = inst_id_codes
307 .into_iter()
308 .map(|(inst_id, code)| (inst_id.to_string(), code))
309 .collect();
310 let codes_list = PyList::new(py, py_codes)?;
311
312 let result = PyTuple::new(py, [instruments_list.as_any(), codes_list.as_any()])?
313 .into_any()
314 .unbind();
315 Ok(result)
316 })
317 })
318 }
319
320 #[pyo3(name = "request_spread_instruments")]
326 #[pyo3(signature = (base_currency=None, instrument_id=None, spread_id=None, state=None))]
327 fn py_request_spread_instruments<'py>(
328 &self,
329 py: Python<'py>,
330 base_currency: Option<String>,
331 instrument_id: Option<InstrumentId>,
332 spread_id: Option<String>,
333 state: Option<String>,
334 ) -> PyResult<Bound<'py, PyAny>> {
335 let client = self.clone();
336
337 pyo3_async_runtimes::tokio::future_into_py(py, async move {
338 let instruments = client
339 .request_spread_instruments(GetSpreadsParams {
340 base_ccy: base_currency,
341 inst_id: instrument_id.map(|id| id.symbol.to_string()),
342 sprd_id: spread_id,
343 state,
344 })
345 .await
346 .map_err(to_pyvalue_err)?;
347
348 Python::attach(|py| {
349 let py_instruments: PyResult<Vec<_>> = instruments
350 .into_iter()
351 .map(|inst| instrument_any_to_pyobject(py, inst))
352 .collect();
353 Ok(PyList::new(py, py_instruments?)?.into_py_any_unwrap(py))
354 })
355 })
356 }
357
358 #[pyo3(name = "request_instrument")]
369 fn py_request_instrument<'py>(
370 &self,
371 py: Python<'py>,
372 instrument_id: InstrumentId,
373 ) -> PyResult<Bound<'py, PyAny>> {
374 let client = self.clone();
375
376 pyo3_async_runtimes::tokio::future_into_py(py, async move {
377 let instrument = client
378 .request_instrument(instrument_id)
379 .await
380 .map_err(to_pyvalue_err)?;
381
382 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
383 })
384 }
385
386 #[pyo3(name = "request_event_contract_series")]
392 #[pyo3(signature = (series_id=None))]
393 fn py_request_event_contract_series<'py>(
394 &self,
395 py: Python<'py>,
396 series_id: Option<String>,
397 ) -> PyResult<Bound<'py, PyAny>> {
398 let client = self.clone();
399
400 pyo3_async_runtimes::tokio::future_into_py(py, async move {
401 let series = client
402 .request_event_contract_series(GetEventContractSeriesParams { series_id })
403 .await
404 .map_err(to_pyvalue_err)?;
405
406 Python::attach(|py| serializable_items_to_pylist(py, series))
407 })
408 }
409
410 #[expect(clippy::too_many_arguments)]
416 #[pyo3(name = "request_event_contract_events")]
417 #[pyo3(signature = (series_id, event_id=None, state=None, limit=None, before=None, after=None))]
418 fn py_request_event_contract_events<'py>(
419 &self,
420 py: Python<'py>,
421 series_id: String,
422 event_id: Option<String>,
423 state: Option<String>,
424 limit: Option<String>,
425 before: Option<String>,
426 after: Option<String>,
427 ) -> PyResult<Bound<'py, PyAny>> {
428 let client = self.clone();
429
430 pyo3_async_runtimes::tokio::future_into_py(py, async move {
431 let events = client
432 .request_event_contract_events(GetEventContractEventsParams {
433 series_id,
434 event_id,
435 state,
436 limit,
437 before,
438 after,
439 })
440 .await
441 .map_err(to_pyvalue_err)?;
442
443 Python::attach(|py| serializable_items_to_pylist(py, events))
444 })
445 }
446
447 #[expect(clippy::too_many_arguments)]
453 #[pyo3(name = "request_event_contract_markets")]
454 #[pyo3(signature = (series_id, event_id=None, inst_id=None, state=None, limit=None, before=None, after=None))]
455 fn py_request_event_contract_markets<'py>(
456 &self,
457 py: Python<'py>,
458 series_id: String,
459 event_id: Option<String>,
460 inst_id: Option<String>,
461 state: Option<String>,
462 limit: Option<String>,
463 before: Option<String>,
464 after: Option<String>,
465 ) -> PyResult<Bound<'py, PyAny>> {
466 let client = self.clone();
467
468 pyo3_async_runtimes::tokio::future_into_py(py, async move {
469 let markets = client
470 .request_event_contract_markets(GetEventContractMarketsParams {
471 series_id,
472 event_id,
473 inst_id,
474 state,
475 limit,
476 before,
477 after,
478 })
479 .await
480 .map_err(to_pyvalue_err)?;
481
482 Python::attach(|py| serializable_items_to_pylist(py, markets))
483 })
484 }
485
486 #[pyo3(name = "request_account_state")]
492 fn py_request_account_state<'py>(
493 &self,
494 py: Python<'py>,
495 account_id: AccountId,
496 ) -> PyResult<Bound<'py, PyAny>> {
497 let client = self.clone();
498
499 pyo3_async_runtimes::tokio::future_into_py(py, async move {
500 let account_state = client
501 .request_account_state(account_id)
502 .await
503 .map_err(to_pyvalue_err)?;
504
505 Python::attach(|py| account_state.into_py_any(py))
506 })
507 }
508
509 #[pyo3(name = "request_trades")]
515 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
516 fn py_request_trades<'py>(
517 &self,
518 py: Python<'py>,
519 instrument_id: InstrumentId,
520 start: Option<Timestamp>,
521 end: Option<Timestamp>,
522 limit: Option<u32>,
523 ) -> PyResult<Bound<'py, PyAny>> {
524 let client = self.clone();
525
526 pyo3_async_runtimes::tokio::future_into_py(py, async move {
527 let trades = client
528 .request_trades(instrument_id, start, end, limit)
529 .await
530 .map_err(to_pyvalue_err)?;
531
532 Python::attach(|py| {
533 let py_trades = trades
534 .into_iter()
535 .map(|trade| trade.into_py_any(py))
536 .collect::<PyResult<Vec<_>>>()?;
537 let pylist = PyList::new(py, py_trades)?;
538 Ok(pylist.into_py_any_unwrap(py))
539 })
540 })
541 }
542
543 #[pyo3(name = "request_bars")]
584 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
585 fn py_request_bars<'py>(
586 &self,
587 py: Python<'py>,
588 bar_type: BarType,
589 start: Option<Timestamp>,
590 end: Option<Timestamp>,
591 limit: Option<u32>,
592 ) -> PyResult<Bound<'py, PyAny>> {
593 let client = self.clone();
594
595 pyo3_async_runtimes::tokio::future_into_py(py, async move {
596 let bars = client
597 .request_bars(bar_type, start, end, limit)
598 .await
599 .map_err(to_pyvalue_err)?;
600
601 Python::attach(|py| {
602 let py_bars = bars
603 .into_iter()
604 .map(|bar| bar.into_py_any(py))
605 .collect::<PyResult<Vec<_>>>()?;
606 let pylist = PyList::new(py, py_bars)?;
607 Ok(pylist.into_py_any_unwrap(py))
608 })
609 })
610 }
611
612 #[pyo3(name = "request_orderbook_snapshot")]
618 #[pyo3(signature = (instrument_id, depth=None))]
619 fn py_request_orderbook_snapshot<'py>(
620 &self,
621 py: Python<'py>,
622 instrument_id: InstrumentId,
623 depth: Option<u32>,
624 ) -> PyResult<Bound<'py, PyAny>> {
625 let client = self.clone();
626
627 pyo3_async_runtimes::tokio::future_into_py(py, async move {
628 let deltas = client
629 .request_orderbook_snapshot(instrument_id, depth)
630 .await
631 .map_err(to_pyvalue_err)?;
632
633 Python::attach(|py| deltas.into_py_any(py))
634 })
635 }
636
637 #[pyo3(name = "request_funding_rates")]
643 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
644 fn py_request_funding_rates<'py>(
645 &self,
646 py: Python<'py>,
647 instrument_id: InstrumentId,
648 start: Option<Timestamp>,
649 end: Option<Timestamp>,
650 limit: Option<u32>,
651 ) -> PyResult<Bound<'py, PyAny>> {
652 let client = self.clone();
653
654 pyo3_async_runtimes::tokio::future_into_py(py, async move {
655 let rates = client
656 .request_funding_rates(instrument_id, start, end, limit)
657 .await
658 .map_err(to_pyvalue_err)?;
659
660 Python::attach(|py| {
661 let py_rates = rates
662 .into_iter()
663 .map(|rate| rate.into_py_any(py))
664 .collect::<PyResult<Vec<_>>>()?;
665 let pylist = PyList::new(py, py_rates)?;
666 Ok(pylist.into_py_any_unwrap(py))
667 })
668 })
669 }
670
671 #[pyo3(name = "request_forward_prices")]
677 #[pyo3(signature = (underlying, instrument_id=None))]
678 fn py_request_forward_prices<'py>(
679 &self,
680 py: Python<'py>,
681 underlying: String,
682 instrument_id: Option<InstrumentId>,
683 ) -> PyResult<Bound<'py, PyAny>> {
684 let client = self.clone();
685
686 pyo3_async_runtimes::tokio::future_into_py(py, async move {
687 let forward_prices: Vec<ForwardPrice> = client
688 .request_forward_prices(&underlying, instrument_id)
689 .await
690 .map_err(to_pyvalue_err)?;
691
692 Python::attach(|py| {
693 let py_prices = forward_prices
694 .into_iter()
695 .map(|price| price.into_py_any(py))
696 .collect::<PyResult<Vec<_>>>()?;
697 let pylist = PyList::new(py, py_prices)?;
698 Ok(pylist.into_py_any_unwrap(py))
699 })
700 })
701 }
702
703 #[pyo3(name = "request_mark_price")]
709 fn py_request_mark_price<'py>(
710 &self,
711 py: Python<'py>,
712 instrument_id: InstrumentId,
713 ) -> PyResult<Bound<'py, PyAny>> {
714 let client = self.clone();
715
716 pyo3_async_runtimes::tokio::future_into_py(py, async move {
717 let mark_price = client
718 .request_mark_price(instrument_id)
719 .await
720 .map_err(to_pyvalue_err)?;
721
722 Python::attach(|py| mark_price.into_py_any(py))
723 })
724 }
725
726 #[pyo3(name = "request_price_limit")]
732 fn py_request_price_limit<'py>(
733 &self,
734 py: Python<'py>,
735 instrument_id: InstrumentId,
736 ) -> PyResult<Bound<'py, PyAny>> {
737 let client = self.clone();
738
739 pyo3_async_runtimes::tokio::future_into_py(py, async move {
740 let price_limit = client
741 .request_price_limit(instrument_id)
742 .await
743 .map_err(to_pyvalue_err)?;
744
745 Python::attach(|py| {
746 let value = serde_json::to_value(price_limit).map_err(to_pyvalue_err)?;
747 value_to_pyobject(py, &value)
748 })
749 })
750 }
751
752 #[pyo3(name = "request_index_price")]
758 fn py_request_index_price<'py>(
759 &self,
760 py: Python<'py>,
761 instrument_id: InstrumentId,
762 ) -> PyResult<Bound<'py, PyAny>> {
763 let client = self.clone();
764
765 pyo3_async_runtimes::tokio::future_into_py(py, async move {
766 let index_price = client
767 .request_index_price(instrument_id)
768 .await
769 .map_err(to_pyvalue_err)?;
770
771 Python::attach(|py| index_price.into_py_any(py))
772 })
773 }
774
775 #[pyo3(name = "request_order_status_reports")]
786 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, open_only=false, limit=None))]
787 #[expect(clippy::too_many_arguments)]
788 fn py_request_order_status_reports<'py>(
789 &self,
790 py: Python<'py>,
791 account_id: AccountId,
792 instrument_type: Option<OKXInstrumentType>,
793 instrument_id: Option<InstrumentId>,
794 start: Option<Timestamp>,
795 end: Option<Timestamp>,
796 open_only: bool,
797 limit: Option<u32>,
798 ) -> PyResult<Bound<'py, PyAny>> {
799 let client = self.clone();
800
801 pyo3_async_runtimes::tokio::future_into_py(py, async move {
802 let reports = client
803 .request_order_status_reports(
804 account_id,
805 instrument_type,
806 instrument_id,
807 start,
808 end,
809 open_only,
810 limit,
811 )
812 .await
813 .map_err(to_pyvalue_err)?;
814
815 Python::attach(|py| {
816 let py_reports = reports
817 .into_iter()
818 .map(|report| report.into_py_any(py))
819 .collect::<PyResult<Vec<_>>>()?;
820 let pylist = PyList::new(py, py_reports)?;
821 Ok(pylist.into_py_any_unwrap(py))
822 })
823 })
824 }
825
826 #[pyo3(name = "request_algo_order_status_reports")]
832 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, algo_id=None, algo_client_order_id=None, state=None, limit=None))]
833 #[expect(clippy::too_many_arguments)]
834 fn py_request_algo_order_status_reports<'py>(
835 &self,
836 py: Python<'py>,
837 account_id: AccountId,
838 instrument_type: Option<OKXInstrumentType>,
839 instrument_id: Option<InstrumentId>,
840 algo_id: Option<String>,
841 algo_client_order_id: Option<ClientOrderId>,
842 state: Option<OKXAlgoOrderStatus>,
843 limit: Option<u32>,
844 ) -> PyResult<Bound<'py, PyAny>> {
845 let client = self.clone();
846
847 pyo3_async_runtimes::tokio::future_into_py(py, async move {
848 let reports = client
849 .request_algo_order_status_reports(
850 account_id,
851 instrument_type,
852 instrument_id,
853 algo_id,
854 algo_client_order_id,
855 state,
856 limit,
857 )
858 .await
859 .map_err(to_pyvalue_err)?;
860
861 Python::attach(|py| {
862 let py_reports = reports
863 .into_iter()
864 .map(|report| report.into_py_any(py))
865 .collect::<PyResult<Vec<_>>>()?;
866 let pylist = PyList::new(py, py_reports)?;
867 Ok(pylist.into_py_any_unwrap(py))
868 })
869 })
870 }
871
872 #[pyo3(name = "request_algo_order_status_report")]
878 fn py_request_algo_order_status_report<'py>(
879 &self,
880 py: Python<'py>,
881 account_id: AccountId,
882 instrument_id: InstrumentId,
883 client_order_id: ClientOrderId,
884 ) -> PyResult<Bound<'py, PyAny>> {
885 let client = self.clone();
886
887 pyo3_async_runtimes::tokio::future_into_py(py, async move {
888 let report = client
889 .request_algo_order_status_report(account_id, instrument_id, client_order_id)
890 .await
891 .map_err(to_pyvalue_err)?;
892
893 Python::attach(|py| match report {
894 Some(report) => report.into_py_any(py),
895 None => Ok(py.None()),
896 })
897 })
898 }
899
900 #[pyo3(name = "request_fill_reports")]
910 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, limit=None))]
911 #[expect(clippy::too_many_arguments)]
912 fn py_request_fill_reports<'py>(
913 &self,
914 py: Python<'py>,
915 account_id: AccountId,
916 instrument_type: Option<OKXInstrumentType>,
917 instrument_id: Option<InstrumentId>,
918 start: Option<Timestamp>,
919 end: Option<Timestamp>,
920 limit: Option<u32>,
921 ) -> PyResult<Bound<'py, PyAny>> {
922 let client = self.clone();
923
924 pyo3_async_runtimes::tokio::future_into_py(py, async move {
925 let trades = client
926 .request_fill_reports(
927 account_id,
928 instrument_type,
929 instrument_id,
930 start,
931 end,
932 limit,
933 )
934 .await
935 .map_err(to_pyvalue_err)?;
936
937 Python::attach(|py| {
938 let py_trades = trades
939 .into_iter()
940 .map(|trade| trade.into_py_any(py))
941 .collect::<PyResult<Vec<_>>>()?;
942 let pylist = PyList::new(py, py_trades)?;
943 Ok(pylist.into_py_any_unwrap(py))
944 })
945 })
946 }
947
948 #[pyo3(name = "request_position_status_reports")]
975 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None))]
976 fn py_request_position_status_reports<'py>(
977 &self,
978 py: Python<'py>,
979 account_id: AccountId,
980 instrument_type: Option<OKXInstrumentType>,
981 instrument_id: Option<InstrumentId>,
982 ) -> PyResult<Bound<'py, PyAny>> {
983 let client = self.clone();
984
985 pyo3_async_runtimes::tokio::future_into_py(py, async move {
986 let reports = client
987 .request_position_status_reports(account_id, instrument_type, instrument_id)
988 .await
989 .map_err(to_pyvalue_err)?;
990
991 Python::attach(|py| {
992 let py_reports = reports
993 .into_iter()
994 .map(|report| report.into_py_any(py))
995 .collect::<PyResult<Vec<_>>>()?;
996 let pylist = PyList::new(py, py_reports)?;
997 Ok(pylist.into_py_any_unwrap(py))
998 })
999 })
1000 }
1001
1002 #[pyo3(name = "place_order")]
1012 #[pyo3(signature = (
1013 trader_id,
1014 strategy_id,
1015 instrument_id,
1016 td_mode,
1017 client_order_id,
1018 order_side,
1019 order_type,
1020 quantity,
1021 time_in_force=None,
1022 price=None,
1023 post_only=None,
1024 reduce_only=None,
1025 quote_quantity=None,
1026 position_side=None,
1027 attach_algo_ords=None,
1028 px_usd=None,
1029 px_vol=None,
1030 speed_bump=None,
1031 outcome=None,
1032 slippage_pct=None,
1033 ))]
1034 #[expect(clippy::too_many_arguments)]
1035 fn py_place_order<'py>(
1036 &self,
1037 py: Python<'py>,
1038 trader_id: TraderId,
1039 strategy_id: StrategyId,
1040 instrument_id: InstrumentId,
1041 td_mode: OKXTradeMode,
1042 client_order_id: ClientOrderId,
1043 order_side: OrderSide,
1044 order_type: OrderType,
1045 quantity: Quantity,
1046 time_in_force: Option<TimeInForce>,
1047 price: Option<Price>,
1048 post_only: Option<bool>,
1049 reduce_only: Option<bool>,
1050 quote_quantity: Option<bool>,
1051 position_side: Option<PositionSide>,
1052 attach_algo_ords: Option<Vec<Py<PyDict>>>,
1053 px_usd: Option<String>,
1054 px_vol: Option<String>,
1055 speed_bump: Option<String>,
1056 outcome: Option<String>,
1057 slippage_pct: Option<String>,
1058 ) -> PyResult<Bound<'py, PyAny>> {
1059 let attach_algo_ords = parse_attach_algo_ords(py, attach_algo_ords)?;
1060 let client = self.clone();
1061
1062 let _ = (trader_id, strategy_id);
1063
1064 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1065 let resp = client
1066 .place_order_with_domain_types(
1067 instrument_id,
1068 td_mode,
1069 client_order_id,
1070 order_side,
1071 order_type,
1072 quantity,
1073 time_in_force,
1074 price,
1075 post_only,
1076 reduce_only,
1077 quote_quantity,
1078 position_side,
1079 attach_algo_ords,
1080 px_usd,
1081 px_vol,
1082 speed_bump,
1083 outcome,
1084 slippage_pct,
1085 None,
1086 None,
1087 None,
1088 )
1089 .await
1090 .map_err(to_pyvalue_err)?;
1091
1092 Python::attach(|py| {
1093 let dict = PyDict::new(py);
1094
1095 if let Some(ord_id) = resp.ord_id {
1096 dict.set_item("ord_id", ord_id.as_str())?;
1097 }
1098
1099 if let Some(cl_ord_id) = resp.cl_ord_id {
1100 dict.set_item("cl_ord_id", cl_ord_id.as_str())?;
1101 }
1102
1103 if let Some(s_code) = resp.s_code {
1104 dict.set_item("s_code", s_code)?;
1105 }
1106
1107 if let Some(s_msg) = resp.s_msg {
1108 dict.set_item("s_msg", s_msg)?;
1109 }
1110
1111 if let Some(sub_code) = resp.sub_code {
1112 dict.set_item("sub_code", sub_code)?;
1113 }
1114
1115 Ok(dict.into_py_any_unwrap(py))
1116 })
1117 })
1118 }
1119
1120 #[pyo3(name = "place_algo_order")]
1130 #[pyo3(signature = (
1131 trader_id,
1132 strategy_id,
1133 instrument_id,
1134 td_mode,
1135 client_order_id,
1136 order_side,
1137 order_type,
1138 quantity,
1139 trigger_price=None,
1140 trigger_type=None,
1141 limit_price=None,
1142 reduce_only=None,
1143 close_fraction=None,
1144 callback_ratio=None,
1145 callback_spread=None,
1146 activation_price=None,
1147 ))]
1148 #[expect(clippy::too_many_arguments)]
1149 fn py_place_algo_order<'py>(
1150 &self,
1151 py: Python<'py>,
1152 trader_id: TraderId,
1153 strategy_id: StrategyId,
1154 instrument_id: InstrumentId,
1155 td_mode: OKXTradeMode,
1156 client_order_id: ClientOrderId,
1157 order_side: OrderSide,
1158 order_type: OrderType,
1159 quantity: Quantity,
1160 trigger_price: Option<Price>,
1161 trigger_type: Option<TriggerType>,
1162 limit_price: Option<Price>,
1163 reduce_only: Option<bool>,
1164 close_fraction: Option<String>,
1165 callback_ratio: Option<String>,
1166 callback_spread: Option<String>,
1167 activation_price: Option<Price>,
1168 ) -> PyResult<Bound<'py, PyAny>> {
1169 let client = self.clone();
1170
1171 let _ = (trader_id, strategy_id);
1173
1174 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1175 let resp = client
1176 .place_algo_order_with_domain_types(
1177 instrument_id,
1178 td_mode,
1179 client_order_id,
1180 order_side,
1181 order_type,
1182 quantity,
1183 trigger_price,
1184 trigger_type,
1185 limit_price,
1186 reduce_only,
1187 close_fraction,
1188 callback_ratio,
1189 callback_spread,
1190 activation_price,
1191 )
1192 .await
1193 .map_err(to_pyvalue_err)?;
1194
1195 Python::attach(|py| {
1196 let dict = PyDict::new(py);
1197 dict.set_item("algo_id", resp.algo_id)?;
1198 if let Some(algo_cl_ord_id) = resp.algo_cl_ord_id {
1199 dict.set_item("algo_cl_ord_id", algo_cl_ord_id)?;
1200 }
1201
1202 if let Some(s_code) = resp.s_code {
1203 dict.set_item("s_code", s_code)?;
1204 }
1205
1206 if let Some(s_msg) = resp.s_msg {
1207 dict.set_item("s_msg", s_msg)?;
1208 }
1209
1210 if let Some(req_id) = resp.req_id {
1211 dict.set_item("req_id", req_id)?;
1212 }
1213 Ok(dict.into_py_any_unwrap(py))
1214 })
1215 })
1216 }
1217
1218 #[pyo3(name = "cancel_algo_order")]
1228 fn py_cancel_algo_order<'py>(
1229 &self,
1230 py: Python<'py>,
1231 instrument_id: InstrumentId,
1232 algo_id: String,
1233 ) -> PyResult<Bound<'py, PyAny>> {
1234 let client = self.clone();
1235
1236 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1237 let resp = client
1238 .cancel_algo_order_with_domain_types(instrument_id, algo_id)
1239 .await
1240 .map_err(to_pyvalue_err)?;
1241
1242 Python::attach(|py| {
1243 let dict = PyDict::new(py);
1244 dict.set_item("algo_id", resp.algo_id)?;
1245 if let Some(s_code) = resp.s_code {
1246 dict.set_item("s_code", s_code)?;
1247 }
1248
1249 if let Some(s_msg) = resp.s_msg {
1250 dict.set_item("s_msg", s_msg)?;
1251 }
1252 Ok(dict.into_py_any_unwrap(py))
1253 })
1254 })
1255 }
1256
1257 #[pyo3(name = "cancel_order")]
1263 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
1264 fn py_cancel_order<'py>(
1265 &self,
1266 py: Python<'py>,
1267 instrument_id: InstrumentId,
1268 client_order_id: Option<ClientOrderId>,
1269 venue_order_id: Option<VenueOrderId>,
1270 ) -> PyResult<Bound<'py, PyAny>> {
1271 let client = self.clone();
1272
1273 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1274 let resp = client
1275 .cancel_order(instrument_id, client_order_id, venue_order_id)
1276 .await
1277 .map_err(to_pyvalue_err)?;
1278
1279 Python::attach(|py| {
1280 let dict = PyDict::new(py);
1281 dict.set_item("ord_id", resp.ord_id)?;
1282
1283 if let Some(cl_ord_id) = resp.cl_ord_id {
1284 dict.set_item("cl_ord_id", cl_ord_id)?;
1285 }
1286
1287 if let Some(s_code) = resp.s_code {
1288 dict.set_item("s_code", s_code)?;
1289 }
1290
1291 if let Some(s_msg) = resp.s_msg {
1292 dict.set_item("s_msg", s_msg)?;
1293 }
1294
1295 if let Some(ts) = resp.ts {
1296 dict.set_item("ts", ts)?;
1297 }
1298
1299 Ok(dict.into_py_any_unwrap(py))
1300 })
1301 })
1302 }
1303
1304 #[pyo3(name = "cancel_all_orders")]
1310 fn py_cancel_all_orders<'py>(
1311 &self,
1312 py: Python<'py>,
1313 instrument_id: InstrumentId,
1314 ) -> PyResult<Bound<'py, PyAny>> {
1315 let client = self.clone();
1316
1317 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1318 let responses = client
1319 .cancel_all_orders(instrument_id)
1320 .await
1321 .map_err(to_pyvalue_err)?;
1322
1323 Python::attach(|py| {
1324 let results: PyResult<Vec<_>> = responses
1325 .into_iter()
1326 .map(|resp| {
1327 let dict = PyDict::new(py);
1328 dict.set_item("ord_id", resp.ord_id)?;
1329
1330 if let Some(cl_ord_id) = resp.cl_ord_id {
1331 dict.set_item("cl_ord_id", cl_ord_id)?;
1332 }
1333
1334 if let Some(s_code) = resp.s_code {
1335 dict.set_item("s_code", s_code)?;
1336 }
1337
1338 if let Some(s_msg) = resp.s_msg {
1339 dict.set_item("s_msg", s_msg)?;
1340 }
1341
1342 if let Some(ts) = resp.ts {
1343 dict.set_item("ts", ts)?;
1344 }
1345
1346 Ok(dict)
1347 })
1348 .collect();
1349 Ok(PyList::new(py, results?)?.into_py_any_unwrap(py))
1350 })
1351 })
1352 }
1353
1354 #[expect(clippy::too_many_arguments)]
1364 #[pyo3(name = "amend_algo_order")]
1365 #[pyo3(signature = (
1366 instrument_id,
1367 algo_id,
1368 new_trigger_price=None,
1369 new_limit_price=None,
1370 new_quantity=None,
1371 new_callback_ratio=None,
1372 new_callback_spread=None,
1373 new_activation_price=None,
1374 new_sl_trigger_price=None,
1375 new_tp_trigger_price=None,
1376 new_tp_order_price=None,
1377 new_tp_trigger_px_type=None,
1378 new_sl_order_price=None,
1379 new_sl_trigger_px_type=None,
1380 ))]
1381 fn py_amend_algo_order<'py>(
1382 &self,
1383 py: Python<'py>,
1384 instrument_id: InstrumentId,
1385 algo_id: String,
1386 new_trigger_price: Option<Price>,
1387 new_limit_price: Option<Price>,
1388 new_quantity: Option<Quantity>,
1389 new_callback_ratio: Option<String>,
1390 new_callback_spread: Option<String>,
1391 new_activation_price: Option<Price>,
1392 new_sl_trigger_price: Option<Price>,
1393 new_tp_trigger_price: Option<Price>,
1394 new_tp_order_price: Option<String>,
1395 new_tp_trigger_px_type: Option<String>,
1396 new_sl_order_price: Option<String>,
1397 new_sl_trigger_px_type: Option<String>,
1398 ) -> PyResult<Bound<'py, PyAny>> {
1399 let client = self.clone();
1400
1401 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1402 let resp = client
1403 .amend_algo_order_with_domain_types(
1404 instrument_id,
1405 algo_id,
1406 new_trigger_price,
1407 new_sl_trigger_price,
1408 new_limit_price,
1409 new_quantity,
1410 new_callback_ratio,
1411 new_callback_spread,
1412 new_activation_price,
1413 new_tp_trigger_price,
1414 new_tp_order_price,
1415 new_tp_trigger_px_type,
1416 new_sl_order_price,
1417 new_sl_trigger_px_type,
1418 )
1419 .await
1420 .map_err(to_pyvalue_err)?;
1421
1422 Python::attach(|py| {
1423 let dict = PyDict::new(py);
1424 dict.set_item("algo_id", resp.algo_id)?;
1425 if let Some(s_code) = resp.s_code {
1426 dict.set_item("s_code", s_code)?;
1427 }
1428
1429 if let Some(s_msg) = resp.s_msg {
1430 dict.set_item("s_msg", s_msg)?;
1431 }
1432 Ok(dict.into_py_any_unwrap(py))
1433 })
1434 })
1435 }
1436
1437 #[pyo3(name = "cancel_algo_orders")]
1450 fn py_cancel_algo_orders<'py>(
1451 &self,
1452 py: Python<'py>,
1453 orders: Vec<(InstrumentId, String)>,
1454 ) -> PyResult<Bound<'py, PyAny>> {
1455 let client = self.clone();
1456
1457 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1458 let requests: Vec<_> = orders
1459 .into_iter()
1460 .map(|(instrument_id, algo_id)| OKXCancelAlgoOrderRequest {
1461 inst_id: instrument_id.symbol.to_string(),
1462 inst_id_code: None,
1463 algo_id: Some(algo_id),
1464 algo_cl_ord_id: None,
1465 })
1466 .collect();
1467
1468 let responses = client
1469 .cancel_algo_orders(requests)
1470 .await
1471 .map_err(to_pyvalue_err)?;
1472
1473 Python::attach(|py| {
1474 let results = responses
1475 .into_iter()
1476 .map(|resp| {
1477 let dict = PyDict::new(py);
1478 dict.set_item("algo_id", resp.algo_id)?;
1479 if let Some(s_code) = resp.s_code {
1480 dict.set_item("s_code", s_code)?;
1481 }
1482
1483 if let Some(s_msg) = resp.s_msg {
1484 dict.set_item("s_msg", s_msg)?;
1485 }
1486 Ok(dict)
1487 })
1488 .collect::<PyResult<Vec<_>>>()?;
1489 Ok(PyList::new(py, results)?.into_any().unbind())
1490 })
1491 })
1492 }
1493
1494 #[pyo3(name = "cancel_advance_algo_order")]
1495 fn py_cancel_advance_algo_order<'py>(
1496 &self,
1497 py: Python<'py>,
1498 instrument_id: InstrumentId,
1499 algo_id: String,
1500 ) -> PyResult<Bound<'py, PyAny>> {
1501 let client = self.clone();
1502
1503 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1504 let request = OKXCancelAlgoOrderRequest {
1505 inst_id: instrument_id.symbol.to_string(),
1506 inst_id_code: None,
1507 algo_id: Some(algo_id),
1508 algo_cl_ord_id: None,
1509 };
1510
1511 let mut responses = client
1512 .cancel_advance_algo_orders(vec![request])
1513 .await
1514 .map_err(to_pyvalue_err)?;
1515
1516 let resp = responses
1517 .pop()
1518 .ok_or_else(|| to_pyvalue_err("Empty response"))?;
1519
1520 Python::attach(|py| {
1521 let dict = PyDict::new(py);
1522 dict.set_item("algo_id", resp.algo_id)?;
1523
1524 if let Some(s_code) = resp.s_code {
1525 dict.set_item("s_code", s_code)?;
1526 }
1527
1528 if let Some(s_msg) = resp.s_msg {
1529 dict.set_item("s_msg", s_msg)?;
1530 }
1531 Ok(dict.into_py_any_unwrap(py))
1532 })
1533 })
1534 }
1535
1536 #[pyo3(name = "get_server_time")]
1544 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1545 let client = self.clone();
1546
1547 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1548 let timestamp = client.get_server_time().await.map_err(to_pyvalue_err)?;
1549
1550 Python::attach(|py| timestamp.into_py_any(py))
1551 })
1552 }
1553
1554 #[pyo3(name = "get_balance")]
1555 fn py_get_balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1556 let client = self.clone();
1557
1558 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1559 let accounts = client.inner.get_balance().await.map_err(to_pyvalue_err)?;
1560
1561 let details: Vec<_> = accounts
1562 .into_iter()
1563 .flat_map(|account| account.details)
1564 .collect();
1565
1566 Python::attach(|py| {
1567 let pylist = PyList::new(py, details)?;
1568 Ok(pylist.into_py_any_unwrap(py))
1569 })
1570 })
1571 }
1572}
1573
1574impl From<OKXHttpError> for PyErr {
1575 fn from(error: OKXHttpError) -> Self {
1576 match error {
1577 OKXHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
1579 OKXHttpError::HttpClientError(e) => to_pyruntime_err(format!("Network error: {e}")),
1580 OKXHttpError::UnexpectedStatus { status, body } => {
1581 to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
1582 }
1583 OKXHttpError::OperationTimeout { timeout_ms } => {
1584 to_pyruntime_err(format!("Operation timed out after {timeout_ms}ms"))
1585 }
1586 OKXHttpError::RetryBudgetExceeded(msg) => {
1587 to_pyruntime_err(format!("Retry budget exceeded: {msg}"))
1588 }
1589 OKXHttpError::EmptyResponse => to_pyruntime_err("Empty response"),
1590 OKXHttpError::MissingCredentials => {
1592 to_pyvalue_err("Missing credentials for authenticated request")
1593 }
1594 OKXHttpError::ValidationError(msg) => {
1595 to_pyvalue_err(format!("Parameter validation error: {msg}"))
1596 }
1597 OKXHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
1598 OKXHttpError::OkxError {
1599 error_code,
1600 message,
1601 } => to_pyvalue_err(format!("OKX error {error_code}: {message}")),
1602 }
1603 }
1604}