1use chrono::{DateTime, Utc};
19use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err};
20use nautilus_model::{
21 data::{BarType, forward::ForwardPrice},
22 enums::{OrderSide, OrderType, PositionSide, TimeInForce, TriggerType},
23 identifiers::{AccountId, ClientOrderId, InstrumentId, StrategyId, TraderId},
24 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
25 types::{Price, Quantity},
26};
27use pyo3::{
28 conversion::IntoPyObjectExt,
29 prelude::*,
30 types::{PyDict, PyList, PyTuple},
31};
32
33use super::{extract_optional_string, extract_optional_trigger_type};
34use crate::{
35 common::enums::{
36 OKXEnvironment, OKXInstrumentType, OKXOrderStatus, OKXPositionMode, OKXTradeMode,
37 },
38 http::{
39 client::OKXHttpClient,
40 error::OKXHttpError,
41 models::{OKXAttachAlgoOrdRequest, OKXCancelAlgoOrderRequest},
42 },
43};
44
45fn parse_attach_algo_ords(
46 py: Python<'_>,
47 attach_algo_ords: Option<Vec<Py<PyDict>>>,
48) -> PyResult<Option<Vec<OKXAttachAlgoOrdRequest>>> {
49 attach_algo_ords
50 .map(|items| {
51 items
52 .into_iter()
53 .map(|item| {
54 let dict = item.bind(py);
55 Ok(OKXAttachAlgoOrdRequest {
56 attach_algo_cl_ord_id: extract_optional_string(
57 dict,
58 "attach_algo_cl_ord_id",
59 )?,
60 sl_trigger_px: extract_optional_string(dict, "sl_trigger_px")?,
61 sl_ord_px: extract_optional_string(dict, "sl_ord_px")?,
62 sl_trigger_px_type: extract_optional_trigger_type(
63 dict,
64 "sl_trigger_px_type",
65 )?,
66 tp_trigger_px: extract_optional_string(dict, "tp_trigger_px")?,
67 tp_ord_px: extract_optional_string(dict, "tp_ord_px")?,
68 tp_trigger_px_type: extract_optional_trigger_type(
69 dict,
70 "tp_trigger_px_type",
71 )?,
72 })
73 })
74 .collect::<PyResult<Vec<_>>>()
75 })
76 .transpose()
77}
78
79#[pymethods]
80#[pyo3_stub_gen::derive::gen_stub_pymethods]
81impl OKXHttpClient {
82 #[new]
87 #[pyo3(signature = (
88 api_key=None,
89 api_secret=None,
90 api_passphrase=None,
91 base_url=None,
92 timeout_secs=60,
93 max_retries=3,
94 retry_delay_ms=1_000,
95 retry_delay_max_ms=10_000,
96 environment=OKXEnvironment::Live,
97 proxy_url=None,
98 ))]
99 #[expect(clippy::too_many_arguments)]
100 fn py_new(
101 api_key: Option<String>,
102 api_secret: Option<String>,
103 api_passphrase: Option<String>,
104 base_url: Option<String>,
105 timeout_secs: u64,
106 max_retries: u32,
107 retry_delay_ms: u64,
108 retry_delay_max_ms: u64,
109 environment: OKXEnvironment,
110 proxy_url: Option<String>,
111 ) -> PyResult<Self> {
112 Self::with_credentials(
113 api_key,
114 api_secret,
115 api_passphrase,
116 base_url,
117 timeout_secs,
118 max_retries,
119 retry_delay_ms,
120 retry_delay_max_ms,
121 environment,
122 proxy_url,
123 )
124 .map_err(to_pyvalue_err)
125 }
126
127 #[staticmethod]
134 #[pyo3(name = "from_env")]
135 fn py_from_env() -> PyResult<Self> {
136 Self::from_env().map_err(to_pyvalue_err)
137 }
138
139 #[getter]
141 #[pyo3(name = "base_url")]
142 #[must_use]
143 pub fn py_base_url(&self) -> &str {
144 self.base_url()
145 }
146
147 #[getter]
149 #[pyo3(name = "api_key")]
150 #[must_use]
151 pub fn py_api_key(&self) -> Option<&str> {
152 self.api_key()
153 }
154
155 #[getter]
157 #[pyo3(name = "api_key_masked")]
158 #[must_use]
159 pub fn py_api_key_masked(&self) -> Option<String> {
160 self.api_key_masked()
161 }
162
163 #[pyo3(name = "is_initialized")]
167 #[must_use]
168 pub fn py_is_initialized(&self) -> bool {
169 self.is_initialized()
170 }
171
172 #[pyo3(name = "get_cached_symbols")]
175 #[must_use]
176 pub fn py_get_cached_symbols(&self) -> Vec<String> {
177 self.get_cached_symbols()
178 }
179
180 #[pyo3(name = "cancel_all_requests")]
182 pub fn py_cancel_all_requests(&self) {
183 self.cancel_all_requests();
184 }
185
186 #[pyo3(name = "cache_instruments")]
190 pub fn py_cache_instruments(
191 &self,
192 py: Python<'_>,
193 instruments: Vec<Py<PyAny>>,
194 ) -> PyResult<()> {
195 let instruments: Result<Vec<_>, _> = instruments
196 .into_iter()
197 .map(|inst| pyobject_to_instrument_any(py, inst))
198 .collect();
199 self.cache_instruments(&instruments?);
200 Ok(())
201 }
202
203 #[pyo3(name = "cache_instrument")]
207 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
208 self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
209 Ok(())
210 }
211
212 #[pyo3(name = "set_position_mode")]
221 fn py_set_position_mode<'py>(
222 &self,
223 py: Python<'py>,
224 position_mode: OKXPositionMode,
225 ) -> PyResult<Bound<'py, PyAny>> {
226 let client = self.clone();
227
228 pyo3_async_runtimes::tokio::future_into_py(py, async move {
229 client
230 .set_position_mode(position_mode)
231 .await
232 .map_err(to_pyvalue_err)?;
233
234 Python::attach(|py| Ok(py.None()))
235 })
236 }
237
238 #[pyo3(name = "request_instruments")]
246 #[pyo3(signature = (instrument_type, instrument_family=None))]
247 fn py_request_instruments<'py>(
248 &self,
249 py: Python<'py>,
250 instrument_type: OKXInstrumentType,
251 instrument_family: Option<String>,
252 ) -> PyResult<Bound<'py, PyAny>> {
253 let client = self.clone();
254
255 pyo3_async_runtimes::tokio::future_into_py(py, async move {
256 let (instruments, inst_id_codes) = client
257 .request_instruments(instrument_type, instrument_family)
258 .await
259 .map_err(to_pyvalue_err)?;
260
261 Python::attach(|py| {
262 let py_instruments: PyResult<Vec<_>> = instruments
263 .into_iter()
264 .map(|inst| instrument_any_to_pyobject(py, inst))
265 .collect();
266 let instruments_list = PyList::new(py, py_instruments?).unwrap();
267
268 let py_codes: Vec<_> = inst_id_codes
270 .into_iter()
271 .map(|(inst_id, code)| (inst_id.to_string(), code))
272 .collect();
273 let codes_list = PyList::new(py, py_codes).unwrap();
274
275 let result = PyTuple::new(py, [instruments_list.as_any(), codes_list.as_any()])
276 .unwrap()
277 .into_any()
278 .unbind();
279 Ok(result)
280 })
281 })
282 }
283
284 #[pyo3(name = "request_instrument")]
288 fn py_request_instrument<'py>(
289 &self,
290 py: Python<'py>,
291 instrument_id: InstrumentId,
292 ) -> PyResult<Bound<'py, PyAny>> {
293 let client = self.clone();
294
295 pyo3_async_runtimes::tokio::future_into_py(py, async move {
296 let instrument = client
297 .request_instrument(instrument_id)
298 .await
299 .map_err(to_pyvalue_err)?;
300
301 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
302 })
303 }
304
305 #[pyo3(name = "request_account_state")]
307 fn py_request_account_state<'py>(
308 &self,
309 py: Python<'py>,
310 account_id: AccountId,
311 ) -> PyResult<Bound<'py, PyAny>> {
312 let client = self.clone();
313
314 pyo3_async_runtimes::tokio::future_into_py(py, async move {
315 let account_state = client
316 .request_account_state(account_id)
317 .await
318 .map_err(to_pyvalue_err)?;
319
320 Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
321 })
322 }
323
324 #[pyo3(name = "request_trades")]
326 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
327 fn py_request_trades<'py>(
328 &self,
329 py: Python<'py>,
330 instrument_id: InstrumentId,
331 start: Option<DateTime<Utc>>,
332 end: Option<DateTime<Utc>>,
333 limit: Option<u32>,
334 ) -> PyResult<Bound<'py, PyAny>> {
335 let client = self.clone();
336
337 pyo3_async_runtimes::tokio::future_into_py(py, async move {
338 let trades = client
339 .request_trades(instrument_id, start, end, limit)
340 .await
341 .map_err(to_pyvalue_err)?;
342
343 Python::attach(|py| {
344 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
345 Ok(pylist.into_py_any_unwrap(py))
346 })
347 })
348 }
349
350 #[pyo3(name = "request_bars")]
387 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
388 fn py_request_bars<'py>(
389 &self,
390 py: Python<'py>,
391 bar_type: BarType,
392 start: Option<DateTime<Utc>>,
393 end: Option<DateTime<Utc>>,
394 limit: Option<u32>,
395 ) -> PyResult<Bound<'py, PyAny>> {
396 let client = self.clone();
397
398 pyo3_async_runtimes::tokio::future_into_py(py, async move {
399 let bars = client
400 .request_bars(bar_type, start, end, limit)
401 .await
402 .map_err(to_pyvalue_err)?;
403
404 Python::attach(|py| {
405 let pylist =
406 PyList::new(py, bars.into_iter().map(|bar| bar.into_py_any_unwrap(py)))?;
407 Ok(pylist.into_py_any_unwrap(py))
408 })
409 })
410 }
411
412 #[pyo3(name = "request_orderbook_snapshot")]
414 #[pyo3(signature = (instrument_id, depth=None))]
415 fn py_request_orderbook_snapshot<'py>(
416 &self,
417 py: Python<'py>,
418 instrument_id: InstrumentId,
419 depth: Option<u32>,
420 ) -> PyResult<Bound<'py, PyAny>> {
421 let client = self.clone();
422
423 pyo3_async_runtimes::tokio::future_into_py(py, async move {
424 let deltas = client
425 .request_orderbook_snapshot(instrument_id, depth)
426 .await
427 .map_err(to_pyvalue_err)?;
428
429 Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
430 })
431 }
432
433 #[pyo3(name = "request_funding_rates")]
435 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
436 fn py_request_funding_rates<'py>(
437 &self,
438 py: Python<'py>,
439 instrument_id: InstrumentId,
440 start: Option<DateTime<Utc>>,
441 end: Option<DateTime<Utc>>,
442 limit: Option<u32>,
443 ) -> PyResult<Bound<'py, PyAny>> {
444 let client = self.clone();
445
446 pyo3_async_runtimes::tokio::future_into_py(py, async move {
447 let rates = client
448 .request_funding_rates(instrument_id, start, end, limit)
449 .await
450 .map_err(to_pyvalue_err)?;
451
452 Python::attach(|py| {
453 let pylist = PyList::new(py, rates.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
454 Ok(pylist.into_py_any_unwrap(py))
455 })
456 })
457 }
458
459 #[pyo3(name = "request_forward_prices")]
461 #[pyo3(signature = (underlying, instrument_id=None))]
462 fn py_request_forward_prices<'py>(
463 &self,
464 py: Python<'py>,
465 underlying: String,
466 instrument_id: Option<InstrumentId>,
467 ) -> PyResult<Bound<'py, PyAny>> {
468 let client = self.clone();
469
470 pyo3_async_runtimes::tokio::future_into_py(py, async move {
471 let forward_prices: Vec<ForwardPrice> = client
472 .request_forward_prices(&underlying, instrument_id)
473 .await
474 .map_err(to_pyvalue_err)?;
475
476 Python::attach(|py| {
477 let pylist = PyList::new(
478 py,
479 forward_prices
480 .into_iter()
481 .map(|price| price.into_py_any_unwrap(py)),
482 )?;
483 Ok(pylist.into_py_any_unwrap(py))
484 })
485 })
486 }
487
488 #[pyo3(name = "request_mark_price")]
490 fn py_request_mark_price<'py>(
491 &self,
492 py: Python<'py>,
493 instrument_id: InstrumentId,
494 ) -> PyResult<Bound<'py, PyAny>> {
495 let client = self.clone();
496
497 pyo3_async_runtimes::tokio::future_into_py(py, async move {
498 let mark_price = client
499 .request_mark_price(instrument_id)
500 .await
501 .map_err(to_pyvalue_err)?;
502
503 Python::attach(|py| Ok(mark_price.into_py_any_unwrap(py)))
504 })
505 }
506
507 #[pyo3(name = "request_index_price")]
509 fn py_request_index_price<'py>(
510 &self,
511 py: Python<'py>,
512 instrument_id: InstrumentId,
513 ) -> PyResult<Bound<'py, PyAny>> {
514 let client = self.clone();
515
516 pyo3_async_runtimes::tokio::future_into_py(py, async move {
517 let index_price = client
518 .request_index_price(instrument_id)
519 .await
520 .map_err(to_pyvalue_err)?;
521
522 Python::attach(|py| Ok(index_price.into_py_any_unwrap(py)))
523 })
524 }
525
526 #[pyo3(name = "request_order_status_reports")]
533 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, open_only=false, limit=None))]
534 #[expect(clippy::too_many_arguments)]
535 fn py_request_order_status_reports<'py>(
536 &self,
537 py: Python<'py>,
538 account_id: AccountId,
539 instrument_type: Option<OKXInstrumentType>,
540 instrument_id: Option<InstrumentId>,
541 start: Option<DateTime<Utc>>,
542 end: Option<DateTime<Utc>>,
543 open_only: bool,
544 limit: Option<u32>,
545 ) -> PyResult<Bound<'py, PyAny>> {
546 let client = self.clone();
547
548 pyo3_async_runtimes::tokio::future_into_py(py, async move {
549 let reports = client
550 .request_order_status_reports(
551 account_id,
552 instrument_type,
553 instrument_id,
554 start,
555 end,
556 open_only,
557 limit,
558 )
559 .await
560 .map_err(to_pyvalue_err)?;
561
562 Python::attach(|py| {
563 let pylist =
564 PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
565 Ok(pylist.into_py_any_unwrap(py))
566 })
567 })
568 }
569
570 #[pyo3(name = "request_algo_order_status_reports")]
572 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, algo_id=None, algo_client_order_id=None, state=None, limit=None))]
573 #[expect(clippy::too_many_arguments)]
574 fn py_request_algo_order_status_reports<'py>(
575 &self,
576 py: Python<'py>,
577 account_id: AccountId,
578 instrument_type: Option<OKXInstrumentType>,
579 instrument_id: Option<InstrumentId>,
580 algo_id: Option<String>,
581 algo_client_order_id: Option<ClientOrderId>,
582 state: Option<OKXOrderStatus>,
583 limit: Option<u32>,
584 ) -> PyResult<Bound<'py, PyAny>> {
585 let client = self.clone();
586
587 pyo3_async_runtimes::tokio::future_into_py(py, async move {
588 let reports = client
589 .request_algo_order_status_reports(
590 account_id,
591 instrument_type,
592 instrument_id,
593 algo_id,
594 algo_client_order_id,
595 state,
596 limit,
597 )
598 .await
599 .map_err(to_pyvalue_err)?;
600
601 Python::attach(|py| {
602 let pylist =
603 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
604 Ok(pylist.into_py_any_unwrap(py))
605 })
606 })
607 }
608
609 #[pyo3(name = "request_algo_order_status_report")]
611 fn py_request_algo_order_status_report<'py>(
612 &self,
613 py: Python<'py>,
614 account_id: AccountId,
615 instrument_id: InstrumentId,
616 client_order_id: ClientOrderId,
617 ) -> PyResult<Bound<'py, PyAny>> {
618 let client = self.clone();
619
620 pyo3_async_runtimes::tokio::future_into_py(py, async move {
621 let report = client
622 .request_algo_order_status_report(account_id, instrument_id, client_order_id)
623 .await
624 .map_err(to_pyvalue_err)?;
625
626 Python::attach(|py| match report {
627 Some(report) => Ok(report.into_py_any_unwrap(py)),
628 None => Ok(py.None()),
629 })
630 })
631 }
632
633 #[pyo3(name = "request_fill_reports")]
639 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, limit=None))]
640 #[expect(clippy::too_many_arguments)]
641 fn py_request_fill_reports<'py>(
642 &self,
643 py: Python<'py>,
644 account_id: AccountId,
645 instrument_type: Option<OKXInstrumentType>,
646 instrument_id: Option<InstrumentId>,
647 start: Option<DateTime<Utc>>,
648 end: Option<DateTime<Utc>>,
649 limit: Option<u32>,
650 ) -> PyResult<Bound<'py, PyAny>> {
651 let client = self.clone();
652
653 pyo3_async_runtimes::tokio::future_into_py(py, async move {
654 let trades = client
655 .request_fill_reports(
656 account_id,
657 instrument_type,
658 instrument_id,
659 start,
660 end,
661 limit,
662 )
663 .await
664 .map_err(to_pyvalue_err)?;
665
666 Python::attach(|py| {
667 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
668 Ok(pylist.into_py_any_unwrap(py))
669 })
670 })
671 }
672
673 #[pyo3(name = "request_position_status_reports")]
696 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None))]
697 fn py_request_position_status_reports<'py>(
698 &self,
699 py: Python<'py>,
700 account_id: AccountId,
701 instrument_type: Option<OKXInstrumentType>,
702 instrument_id: Option<InstrumentId>,
703 ) -> PyResult<Bound<'py, PyAny>> {
704 let client = self.clone();
705
706 pyo3_async_runtimes::tokio::future_into_py(py, async move {
707 let reports = client
708 .request_position_status_reports(account_id, instrument_type, instrument_id)
709 .await
710 .map_err(to_pyvalue_err)?;
711
712 Python::attach(|py| {
713 let pylist =
714 PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
715 Ok(pylist.into_py_any_unwrap(py))
716 })
717 })
718 }
719
720 #[pyo3(name = "place_order")]
726 #[pyo3(signature = (
727 trader_id,
728 strategy_id,
729 instrument_id,
730 td_mode,
731 client_order_id,
732 order_side,
733 order_type,
734 quantity,
735 time_in_force=None,
736 price=None,
737 post_only=None,
738 reduce_only=None,
739 quote_quantity=None,
740 position_side=None,
741 attach_algo_ords=None,
742 px_usd=None,
743 px_vol=None,
744 ))]
745 #[expect(clippy::too_many_arguments)]
746 fn py_place_order<'py>(
747 &self,
748 py: Python<'py>,
749 trader_id: TraderId,
750 strategy_id: StrategyId,
751 instrument_id: InstrumentId,
752 td_mode: OKXTradeMode,
753 client_order_id: ClientOrderId,
754 order_side: OrderSide,
755 order_type: OrderType,
756 quantity: Quantity,
757 time_in_force: Option<TimeInForce>,
758 price: Option<Price>,
759 post_only: Option<bool>,
760 reduce_only: Option<bool>,
761 quote_quantity: Option<bool>,
762 position_side: Option<PositionSide>,
763 attach_algo_ords: Option<Vec<Py<PyDict>>>,
764 px_usd: Option<String>,
765 px_vol: Option<String>,
766 ) -> PyResult<Bound<'py, PyAny>> {
767 let attach_algo_ords = parse_attach_algo_ords(py, attach_algo_ords)?;
768 let client = self.clone();
769
770 let _ = (trader_id, strategy_id);
771
772 pyo3_async_runtimes::tokio::future_into_py(py, async move {
773 let resp = client
774 .place_order_with_domain_types(
775 instrument_id,
776 td_mode,
777 client_order_id,
778 order_side,
779 order_type,
780 quantity,
781 time_in_force,
782 price,
783 post_only,
784 reduce_only,
785 quote_quantity,
786 position_side,
787 attach_algo_ords,
788 px_usd,
789 px_vol,
790 )
791 .await
792 .map_err(to_pyvalue_err)?;
793
794 Python::attach(|py| {
795 let dict = PyDict::new(py);
796
797 if let Some(ord_id) = resp.ord_id {
798 dict.set_item("ord_id", ord_id.as_str())?;
799 }
800
801 if let Some(cl_ord_id) = resp.cl_ord_id {
802 dict.set_item("cl_ord_id", cl_ord_id.as_str())?;
803 }
804
805 if let Some(s_code) = resp.s_code {
806 dict.set_item("s_code", s_code)?;
807 }
808
809 if let Some(s_msg) = resp.s_msg {
810 dict.set_item("s_msg", s_msg)?;
811 }
812
813 Ok(dict.into_py_any_unwrap(py))
814 })
815 })
816 }
817
818 #[pyo3(name = "place_algo_order")]
824 #[pyo3(signature = (
825 trader_id,
826 strategy_id,
827 instrument_id,
828 td_mode,
829 client_order_id,
830 order_side,
831 order_type,
832 quantity,
833 trigger_price=None,
834 trigger_type=None,
835 limit_price=None,
836 reduce_only=None,
837 close_fraction=None,
838 callback_ratio=None,
839 callback_spread=None,
840 activation_price=None,
841 ))]
842 #[expect(clippy::too_many_arguments)]
843 fn py_place_algo_order<'py>(
844 &self,
845 py: Python<'py>,
846 trader_id: TraderId,
847 strategy_id: StrategyId,
848 instrument_id: InstrumentId,
849 td_mode: OKXTradeMode,
850 client_order_id: ClientOrderId,
851 order_side: OrderSide,
852 order_type: OrderType,
853 quantity: Quantity,
854 trigger_price: Option<Price>,
855 trigger_type: Option<TriggerType>,
856 limit_price: Option<Price>,
857 reduce_only: Option<bool>,
858 close_fraction: Option<String>,
859 callback_ratio: Option<String>,
860 callback_spread: Option<String>,
861 activation_price: Option<Price>,
862 ) -> PyResult<Bound<'py, PyAny>> {
863 let client = self.clone();
864
865 let _ = (trader_id, strategy_id);
867
868 pyo3_async_runtimes::tokio::future_into_py(py, async move {
869 let resp = client
870 .place_algo_order_with_domain_types(
871 instrument_id,
872 td_mode,
873 client_order_id,
874 order_side,
875 order_type,
876 quantity,
877 trigger_price,
878 trigger_type,
879 limit_price,
880 reduce_only,
881 close_fraction,
882 callback_ratio,
883 callback_spread,
884 activation_price,
885 )
886 .await
887 .map_err(to_pyvalue_err)?;
888
889 Python::attach(|py| {
890 let dict = PyDict::new(py);
891 dict.set_item("algo_id", resp.algo_id)?;
892 if let Some(algo_cl_ord_id) = resp.algo_cl_ord_id {
893 dict.set_item("algo_cl_ord_id", algo_cl_ord_id)?;
894 }
895
896 if let Some(s_code) = resp.s_code {
897 dict.set_item("s_code", s_code)?;
898 }
899
900 if let Some(s_msg) = resp.s_msg {
901 dict.set_item("s_msg", s_msg)?;
902 }
903
904 if let Some(req_id) = resp.req_id {
905 dict.set_item("req_id", req_id)?;
906 }
907 Ok(dict.into_py_any_unwrap(py))
908 })
909 })
910 }
911
912 #[pyo3(name = "cancel_algo_order")]
918 fn py_cancel_algo_order<'py>(
919 &self,
920 py: Python<'py>,
921 instrument_id: InstrumentId,
922 algo_id: String,
923 ) -> PyResult<Bound<'py, PyAny>> {
924 let client = self.clone();
925
926 pyo3_async_runtimes::tokio::future_into_py(py, async move {
927 let resp = client
928 .cancel_algo_order_with_domain_types(instrument_id, algo_id)
929 .await
930 .map_err(to_pyvalue_err)?;
931
932 Python::attach(|py| {
933 let dict = PyDict::new(py);
934 dict.set_item("algo_id", resp.algo_id)?;
935 if let Some(s_code) = resp.s_code {
936 dict.set_item("s_code", s_code)?;
937 }
938
939 if let Some(s_msg) = resp.s_msg {
940 dict.set_item("s_msg", s_msg)?;
941 }
942 Ok(dict.into_py_any_unwrap(py))
943 })
944 })
945 }
946
947 #[expect(clippy::too_many_arguments)]
953 #[pyo3(name = "amend_algo_order")]
954 #[pyo3(signature = (
955 instrument_id,
956 algo_id,
957 new_trigger_price=None,
958 new_limit_price=None,
959 new_quantity=None,
960 new_callback_ratio=None,
961 new_callback_spread=None,
962 new_activation_price=None,
963 ))]
964 fn py_amend_algo_order<'py>(
965 &self,
966 py: Python<'py>,
967 instrument_id: InstrumentId,
968 algo_id: String,
969 new_trigger_price: Option<Price>,
970 new_limit_price: Option<Price>,
971 new_quantity: Option<Quantity>,
972 new_callback_ratio: Option<String>,
973 new_callback_spread: Option<String>,
974 new_activation_price: Option<Price>,
975 ) -> PyResult<Bound<'py, PyAny>> {
976 let client = self.clone();
977
978 pyo3_async_runtimes::tokio::future_into_py(py, async move {
979 let resp = client
980 .amend_algo_order_with_domain_types(
981 instrument_id,
982 algo_id,
983 new_trigger_price,
984 new_limit_price,
985 new_quantity,
986 new_callback_ratio,
987 new_callback_spread,
988 new_activation_price,
989 )
990 .await
991 .map_err(to_pyvalue_err)?;
992
993 Python::attach(|py| {
994 let dict = PyDict::new(py);
995 dict.set_item("algo_id", resp.algo_id)?;
996 if let Some(s_code) = resp.s_code {
997 dict.set_item("s_code", s_code)?;
998 }
999
1000 if let Some(s_msg) = resp.s_msg {
1001 dict.set_item("s_msg", s_msg)?;
1002 }
1003 Ok(dict.into_py_any_unwrap(py))
1004 })
1005 })
1006 }
1007
1008 #[pyo3(name = "cancel_algo_orders")]
1017 fn py_cancel_algo_orders<'py>(
1018 &self,
1019 py: Python<'py>,
1020 orders: Vec<(InstrumentId, String)>,
1021 ) -> PyResult<Bound<'py, PyAny>> {
1022 let client = self.clone();
1023
1024 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1025 let requests: Vec<_> = orders
1026 .into_iter()
1027 .map(|(instrument_id, algo_id)| OKXCancelAlgoOrderRequest {
1028 inst_id: instrument_id.symbol.to_string(),
1029 inst_id_code: None,
1030 algo_id: Some(algo_id),
1031 algo_cl_ord_id: None,
1032 })
1033 .collect();
1034
1035 let responses = client
1036 .cancel_algo_orders(requests)
1037 .await
1038 .map_err(to_pyvalue_err)?;
1039
1040 Python::attach(|py| {
1041 let results: Vec<_> = responses
1042 .into_iter()
1043 .map(|resp| {
1044 let dict = PyDict::new(py);
1045 dict.set_item("algo_id", resp.algo_id).expect("set algo_id");
1046 if let Some(s_code) = resp.s_code {
1047 dict.set_item("s_code", s_code).expect("set s_code");
1048 }
1049
1050 if let Some(s_msg) = resp.s_msg {
1051 dict.set_item("s_msg", s_msg).expect("set s_msg");
1052 }
1053 dict
1054 })
1055 .collect();
1056 let pylist = PyList::new(py, results)?;
1057 Ok(pylist.into_py_any_unwrap(py))
1058 })
1059 })
1060 }
1061
1062 #[pyo3(name = "cancel_advance_algo_order")]
1063 fn py_cancel_advance_algo_order<'py>(
1064 &self,
1065 py: Python<'py>,
1066 instrument_id: InstrumentId,
1067 algo_id: String,
1068 ) -> PyResult<Bound<'py, PyAny>> {
1069 let client = self.clone();
1070
1071 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1072 let request = OKXCancelAlgoOrderRequest {
1073 inst_id: instrument_id.symbol.to_string(),
1074 inst_id_code: None,
1075 algo_id: Some(algo_id),
1076 algo_cl_ord_id: None,
1077 };
1078
1079 let mut responses = client
1080 .cancel_advance_algo_orders(vec![request])
1081 .await
1082 .map_err(to_pyvalue_err)?;
1083
1084 let resp = responses
1085 .pop()
1086 .ok_or_else(|| to_pyvalue_err("Empty response"))?;
1087
1088 Python::attach(|py| {
1089 let dict = PyDict::new(py);
1090 dict.set_item("algo_id", resp.algo_id)?;
1091
1092 if let Some(s_code) = resp.s_code {
1093 dict.set_item("s_code", s_code)?;
1094 }
1095
1096 if let Some(s_msg) = resp.s_msg {
1097 dict.set_item("s_msg", s_msg)?;
1098 }
1099 Ok(dict.into_py_any_unwrap(py))
1100 })
1101 })
1102 }
1103
1104 #[pyo3(name = "get_server_time")]
1112 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1113 let client = self.clone();
1114
1115 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1116 let timestamp = client.get_server_time().await.map_err(to_pyvalue_err)?;
1117
1118 Python::attach(|py| timestamp.into_py_any(py))
1119 })
1120 }
1121
1122 #[pyo3(name = "get_balance")]
1123 fn py_get_balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1124 let client = self.clone();
1125
1126 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1127 let accounts = client.inner.get_balance().await.map_err(to_pyvalue_err)?;
1128
1129 let details: Vec<_> = accounts
1130 .into_iter()
1131 .flat_map(|account| account.details)
1132 .collect();
1133
1134 Python::attach(|py| {
1135 let pylist = PyList::new(py, details)?;
1136 Ok(pylist.into_py_any_unwrap(py))
1137 })
1138 })
1139 }
1140}
1141
1142impl From<OKXHttpError> for PyErr {
1143 fn from(error: OKXHttpError) -> Self {
1144 match error {
1145 OKXHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
1147 OKXHttpError::HttpClientError(e) => to_pyruntime_err(format!("Network error: {e}")),
1148 OKXHttpError::UnexpectedStatus { status, body } => {
1149 to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
1150 }
1151 OKXHttpError::MissingCredentials => {
1153 to_pyvalue_err("Missing credentials for authenticated request")
1154 }
1155 OKXHttpError::ValidationError(msg) => {
1156 to_pyvalue_err(format!("Parameter validation error: {msg}"))
1157 }
1158 OKXHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
1159 OKXHttpError::OkxError {
1160 error_code,
1161 message,
1162 } => to_pyvalue_err(format!("OKX error {error_code}: {message}")),
1163 }
1164 }
1165}