1use chrono::{DateTime, Utc};
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 OKXEnvironment, OKXInstrumentType, OKXOrderStatus, 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")]
247 fn py_set_position_mode<'py>(
248 &self,
249 py: Python<'py>,
250 position_mode: OKXPositionMode,
251 ) -> PyResult<Bound<'py, PyAny>> {
252 let client = self.clone();
253
254 pyo3_async_runtimes::tokio::future_into_py(py, async move {
255 client
256 .set_position_mode(position_mode)
257 .await
258 .map_err(to_pyvalue_err)?;
259
260 Python::attach(|py| Ok(py.None()))
261 })
262 }
263
264 #[pyo3(name = "request_instruments")]
272 #[pyo3(signature = (instrument_type, instrument_family=None))]
273 fn py_request_instruments<'py>(
274 &self,
275 py: Python<'py>,
276 instrument_type: OKXInstrumentType,
277 instrument_family: Option<String>,
278 ) -> PyResult<Bound<'py, PyAny>> {
279 let client = self.clone();
280
281 pyo3_async_runtimes::tokio::future_into_py(py, async move {
282 let (instruments, inst_id_codes) = client
283 .request_instruments(instrument_type, instrument_family)
284 .await
285 .map_err(to_pyvalue_err)?;
286
287 Python::attach(|py| {
288 let py_instruments: PyResult<Vec<_>> = instruments
289 .into_iter()
290 .map(|inst| instrument_any_to_pyobject(py, inst))
291 .collect();
292 let instruments_list = PyList::new(py, py_instruments?).unwrap();
293
294 let py_codes: Vec<_> = inst_id_codes
296 .into_iter()
297 .map(|(inst_id, code)| (inst_id.to_string(), code))
298 .collect();
299 let codes_list = PyList::new(py, py_codes).unwrap();
300
301 let result = PyTuple::new(py, [instruments_list.as_any(), codes_list.as_any()])
302 .unwrap()
303 .into_any()
304 .unbind();
305 Ok(result)
306 })
307 })
308 }
309
310 #[pyo3(name = "request_spread_instruments")]
312 #[pyo3(signature = (base_currency=None, instrument_id=None, spread_id=None, state=None))]
313 fn py_request_spread_instruments<'py>(
314 &self,
315 py: Python<'py>,
316 base_currency: Option<String>,
317 instrument_id: Option<InstrumentId>,
318 spread_id: Option<String>,
319 state: Option<String>,
320 ) -> PyResult<Bound<'py, PyAny>> {
321 let client = self.clone();
322
323 pyo3_async_runtimes::tokio::future_into_py(py, async move {
324 let instruments = client
325 .request_spread_instruments(GetSpreadsParams {
326 base_ccy: base_currency,
327 inst_id: instrument_id.map(|id| id.symbol.to_string()),
328 sprd_id: spread_id,
329 state,
330 })
331 .await
332 .map_err(to_pyvalue_err)?;
333
334 Python::attach(|py| {
335 let py_instruments: PyResult<Vec<_>> = instruments
336 .into_iter()
337 .map(|inst| instrument_any_to_pyobject(py, inst))
338 .collect();
339 Ok(PyList::new(py, py_instruments?)?.into_py_any_unwrap(py))
340 })
341 })
342 }
343
344 #[pyo3(name = "request_instrument")]
348 fn py_request_instrument<'py>(
349 &self,
350 py: Python<'py>,
351 instrument_id: InstrumentId,
352 ) -> PyResult<Bound<'py, PyAny>> {
353 let client = self.clone();
354
355 pyo3_async_runtimes::tokio::future_into_py(py, async move {
356 let instrument = client
357 .request_instrument(instrument_id)
358 .await
359 .map_err(to_pyvalue_err)?;
360
361 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
362 })
363 }
364
365 #[pyo3(name = "request_event_contract_series")]
367 #[pyo3(signature = (series_id=None))]
368 fn py_request_event_contract_series<'py>(
369 &self,
370 py: Python<'py>,
371 series_id: Option<String>,
372 ) -> PyResult<Bound<'py, PyAny>> {
373 let client = self.clone();
374
375 pyo3_async_runtimes::tokio::future_into_py(py, async move {
376 let series = client
377 .request_event_contract_series(GetEventContractSeriesParams { series_id })
378 .await
379 .map_err(to_pyvalue_err)?;
380
381 Python::attach(|py| serializable_items_to_pylist(py, series))
382 })
383 }
384
385 #[expect(clippy::too_many_arguments)]
387 #[pyo3(name = "request_event_contract_events")]
388 #[pyo3(signature = (series_id, event_id=None, state=None, limit=None, before=None, after=None))]
389 fn py_request_event_contract_events<'py>(
390 &self,
391 py: Python<'py>,
392 series_id: String,
393 event_id: Option<String>,
394 state: Option<String>,
395 limit: Option<String>,
396 before: Option<String>,
397 after: Option<String>,
398 ) -> PyResult<Bound<'py, PyAny>> {
399 let client = self.clone();
400
401 pyo3_async_runtimes::tokio::future_into_py(py, async move {
402 let events = client
403 .request_event_contract_events(GetEventContractEventsParams {
404 series_id,
405 event_id,
406 state,
407 limit,
408 before,
409 after,
410 })
411 .await
412 .map_err(to_pyvalue_err)?;
413
414 Python::attach(|py| serializable_items_to_pylist(py, events))
415 })
416 }
417
418 #[expect(clippy::too_many_arguments)]
420 #[pyo3(name = "request_event_contract_markets")]
421 #[pyo3(signature = (series_id, event_id=None, inst_id=None, state=None, limit=None, before=None, after=None))]
422 fn py_request_event_contract_markets<'py>(
423 &self,
424 py: Python<'py>,
425 series_id: String,
426 event_id: Option<String>,
427 inst_id: Option<String>,
428 state: Option<String>,
429 limit: Option<String>,
430 before: Option<String>,
431 after: Option<String>,
432 ) -> PyResult<Bound<'py, PyAny>> {
433 let client = self.clone();
434
435 pyo3_async_runtimes::tokio::future_into_py(py, async move {
436 let markets = client
437 .request_event_contract_markets(GetEventContractMarketsParams {
438 series_id,
439 event_id,
440 inst_id,
441 state,
442 limit,
443 before,
444 after,
445 })
446 .await
447 .map_err(to_pyvalue_err)?;
448
449 Python::attach(|py| serializable_items_to_pylist(py, markets))
450 })
451 }
452
453 #[pyo3(name = "request_account_state")]
455 fn py_request_account_state<'py>(
456 &self,
457 py: Python<'py>,
458 account_id: AccountId,
459 ) -> PyResult<Bound<'py, PyAny>> {
460 let client = self.clone();
461
462 pyo3_async_runtimes::tokio::future_into_py(py, async move {
463 let account_state = client
464 .request_account_state(account_id)
465 .await
466 .map_err(to_pyvalue_err)?;
467
468 Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
469 })
470 }
471
472 #[pyo3(name = "request_trades")]
474 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
475 fn py_request_trades<'py>(
476 &self,
477 py: Python<'py>,
478 instrument_id: InstrumentId,
479 start: Option<DateTime<Utc>>,
480 end: Option<DateTime<Utc>>,
481 limit: Option<u32>,
482 ) -> PyResult<Bound<'py, PyAny>> {
483 let client = self.clone();
484
485 pyo3_async_runtimes::tokio::future_into_py(py, async move {
486 let trades = client
487 .request_trades(instrument_id, start, end, limit)
488 .await
489 .map_err(to_pyvalue_err)?;
490
491 Python::attach(|py| {
492 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
493 Ok(pylist.into_py_any_unwrap(py))
494 })
495 })
496 }
497
498 #[pyo3(name = "request_bars")]
535 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
536 fn py_request_bars<'py>(
537 &self,
538 py: Python<'py>,
539 bar_type: BarType,
540 start: Option<DateTime<Utc>>,
541 end: Option<DateTime<Utc>>,
542 limit: Option<u32>,
543 ) -> PyResult<Bound<'py, PyAny>> {
544 let client = self.clone();
545
546 pyo3_async_runtimes::tokio::future_into_py(py, async move {
547 let bars = client
548 .request_bars(bar_type, start, end, limit)
549 .await
550 .map_err(to_pyvalue_err)?;
551
552 Python::attach(|py| {
553 let pylist =
554 PyList::new(py, bars.into_iter().map(|bar| bar.into_py_any_unwrap(py)))?;
555 Ok(pylist.into_py_any_unwrap(py))
556 })
557 })
558 }
559
560 #[pyo3(name = "request_orderbook_snapshot")]
562 #[pyo3(signature = (instrument_id, depth=None))]
563 fn py_request_orderbook_snapshot<'py>(
564 &self,
565 py: Python<'py>,
566 instrument_id: InstrumentId,
567 depth: 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 deltas = client
573 .request_orderbook_snapshot(instrument_id, depth)
574 .await
575 .map_err(to_pyvalue_err)?;
576
577 Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
578 })
579 }
580
581 #[pyo3(name = "request_funding_rates")]
583 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
584 fn py_request_funding_rates<'py>(
585 &self,
586 py: Python<'py>,
587 instrument_id: InstrumentId,
588 start: Option<DateTime<Utc>>,
589 end: Option<DateTime<Utc>>,
590 limit: Option<u32>,
591 ) -> PyResult<Bound<'py, PyAny>> {
592 let client = self.clone();
593
594 pyo3_async_runtimes::tokio::future_into_py(py, async move {
595 let rates = client
596 .request_funding_rates(instrument_id, start, end, limit)
597 .await
598 .map_err(to_pyvalue_err)?;
599
600 Python::attach(|py| {
601 let pylist = PyList::new(py, rates.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
602 Ok(pylist.into_py_any_unwrap(py))
603 })
604 })
605 }
606
607 #[pyo3(name = "request_forward_prices")]
609 #[pyo3(signature = (underlying, instrument_id=None))]
610 fn py_request_forward_prices<'py>(
611 &self,
612 py: Python<'py>,
613 underlying: String,
614 instrument_id: Option<InstrumentId>,
615 ) -> PyResult<Bound<'py, PyAny>> {
616 let client = self.clone();
617
618 pyo3_async_runtimes::tokio::future_into_py(py, async move {
619 let forward_prices: Vec<ForwardPrice> = client
620 .request_forward_prices(&underlying, instrument_id)
621 .await
622 .map_err(to_pyvalue_err)?;
623
624 Python::attach(|py| {
625 let pylist = PyList::new(
626 py,
627 forward_prices
628 .into_iter()
629 .map(|price| price.into_py_any_unwrap(py)),
630 )?;
631 Ok(pylist.into_py_any_unwrap(py))
632 })
633 })
634 }
635
636 #[pyo3(name = "request_mark_price")]
638 fn py_request_mark_price<'py>(
639 &self,
640 py: Python<'py>,
641 instrument_id: InstrumentId,
642 ) -> PyResult<Bound<'py, PyAny>> {
643 let client = self.clone();
644
645 pyo3_async_runtimes::tokio::future_into_py(py, async move {
646 let mark_price = client
647 .request_mark_price(instrument_id)
648 .await
649 .map_err(to_pyvalue_err)?;
650
651 Python::attach(|py| Ok(mark_price.into_py_any_unwrap(py)))
652 })
653 }
654
655 #[pyo3(name = "request_index_price")]
657 fn py_request_index_price<'py>(
658 &self,
659 py: Python<'py>,
660 instrument_id: InstrumentId,
661 ) -> PyResult<Bound<'py, PyAny>> {
662 let client = self.clone();
663
664 pyo3_async_runtimes::tokio::future_into_py(py, async move {
665 let index_price = client
666 .request_index_price(instrument_id)
667 .await
668 .map_err(to_pyvalue_err)?;
669
670 Python::attach(|py| Ok(index_price.into_py_any_unwrap(py)))
671 })
672 }
673
674 #[pyo3(name = "request_order_status_reports")]
681 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, open_only=false, limit=None))]
682 #[expect(clippy::too_many_arguments)]
683 fn py_request_order_status_reports<'py>(
684 &self,
685 py: Python<'py>,
686 account_id: AccountId,
687 instrument_type: Option<OKXInstrumentType>,
688 instrument_id: Option<InstrumentId>,
689 start: Option<DateTime<Utc>>,
690 end: Option<DateTime<Utc>>,
691 open_only: bool,
692 limit: Option<u32>,
693 ) -> PyResult<Bound<'py, PyAny>> {
694 let client = self.clone();
695
696 pyo3_async_runtimes::tokio::future_into_py(py, async move {
697 let reports = client
698 .request_order_status_reports(
699 account_id,
700 instrument_type,
701 instrument_id,
702 start,
703 end,
704 open_only,
705 limit,
706 )
707 .await
708 .map_err(to_pyvalue_err)?;
709
710 Python::attach(|py| {
711 let pylist =
712 PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
713 Ok(pylist.into_py_any_unwrap(py))
714 })
715 })
716 }
717
718 #[pyo3(name = "request_algo_order_status_reports")]
720 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, algo_id=None, algo_client_order_id=None, state=None, limit=None))]
721 #[expect(clippy::too_many_arguments)]
722 fn py_request_algo_order_status_reports<'py>(
723 &self,
724 py: Python<'py>,
725 account_id: AccountId,
726 instrument_type: Option<OKXInstrumentType>,
727 instrument_id: Option<InstrumentId>,
728 algo_id: Option<String>,
729 algo_client_order_id: Option<ClientOrderId>,
730 state: Option<OKXOrderStatus>,
731 limit: Option<u32>,
732 ) -> PyResult<Bound<'py, PyAny>> {
733 let client = self.clone();
734
735 pyo3_async_runtimes::tokio::future_into_py(py, async move {
736 let reports = client
737 .request_algo_order_status_reports(
738 account_id,
739 instrument_type,
740 instrument_id,
741 algo_id,
742 algo_client_order_id,
743 state,
744 limit,
745 )
746 .await
747 .map_err(to_pyvalue_err)?;
748
749 Python::attach(|py| {
750 let pylist =
751 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
752 Ok(pylist.into_py_any_unwrap(py))
753 })
754 })
755 }
756
757 #[pyo3(name = "request_algo_order_status_report")]
759 fn py_request_algo_order_status_report<'py>(
760 &self,
761 py: Python<'py>,
762 account_id: AccountId,
763 instrument_id: InstrumentId,
764 client_order_id: ClientOrderId,
765 ) -> PyResult<Bound<'py, PyAny>> {
766 let client = self.clone();
767
768 pyo3_async_runtimes::tokio::future_into_py(py, async move {
769 let report = client
770 .request_algo_order_status_report(account_id, instrument_id, client_order_id)
771 .await
772 .map_err(to_pyvalue_err)?;
773
774 Python::attach(|py| match report {
775 Some(report) => Ok(report.into_py_any_unwrap(py)),
776 None => Ok(py.None()),
777 })
778 })
779 }
780
781 #[pyo3(name = "request_fill_reports")]
787 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, limit=None))]
788 #[expect(clippy::too_many_arguments)]
789 fn py_request_fill_reports<'py>(
790 &self,
791 py: Python<'py>,
792 account_id: AccountId,
793 instrument_type: Option<OKXInstrumentType>,
794 instrument_id: Option<InstrumentId>,
795 start: Option<DateTime<Utc>>,
796 end: Option<DateTime<Utc>>,
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 trades = client
803 .request_fill_reports(
804 account_id,
805 instrument_type,
806 instrument_id,
807 start,
808 end,
809 limit,
810 )
811 .await
812 .map_err(to_pyvalue_err)?;
813
814 Python::attach(|py| {
815 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
816 Ok(pylist.into_py_any_unwrap(py))
817 })
818 })
819 }
820
821 #[pyo3(name = "request_position_status_reports")]
844 #[pyo3(signature = (account_id, instrument_type=None, instrument_id=None))]
845 fn py_request_position_status_reports<'py>(
846 &self,
847 py: Python<'py>,
848 account_id: AccountId,
849 instrument_type: Option<OKXInstrumentType>,
850 instrument_id: Option<InstrumentId>,
851 ) -> PyResult<Bound<'py, PyAny>> {
852 let client = self.clone();
853
854 pyo3_async_runtimes::tokio::future_into_py(py, async move {
855 let reports = client
856 .request_position_status_reports(account_id, instrument_type, instrument_id)
857 .await
858 .map_err(to_pyvalue_err)?;
859
860 Python::attach(|py| {
861 let pylist =
862 PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
863 Ok(pylist.into_py_any_unwrap(py))
864 })
865 })
866 }
867
868 #[pyo3(name = "place_order")]
874 #[pyo3(signature = (
875 trader_id,
876 strategy_id,
877 instrument_id,
878 td_mode,
879 client_order_id,
880 order_side,
881 order_type,
882 quantity,
883 time_in_force=None,
884 price=None,
885 post_only=None,
886 reduce_only=None,
887 quote_quantity=None,
888 position_side=None,
889 attach_algo_ords=None,
890 px_usd=None,
891 px_vol=None,
892 speed_bump=None,
893 outcome=None,
894 slippage_pct=None,
895 ))]
896 #[expect(clippy::too_many_arguments)]
897 fn py_place_order<'py>(
898 &self,
899 py: Python<'py>,
900 trader_id: TraderId,
901 strategy_id: StrategyId,
902 instrument_id: InstrumentId,
903 td_mode: OKXTradeMode,
904 client_order_id: ClientOrderId,
905 order_side: OrderSide,
906 order_type: OrderType,
907 quantity: Quantity,
908 time_in_force: Option<TimeInForce>,
909 price: Option<Price>,
910 post_only: Option<bool>,
911 reduce_only: Option<bool>,
912 quote_quantity: Option<bool>,
913 position_side: Option<PositionSide>,
914 attach_algo_ords: Option<Vec<Py<PyDict>>>,
915 px_usd: Option<String>,
916 px_vol: Option<String>,
917 speed_bump: Option<String>,
918 outcome: Option<String>,
919 slippage_pct: Option<String>,
920 ) -> PyResult<Bound<'py, PyAny>> {
921 let attach_algo_ords = parse_attach_algo_ords(py, attach_algo_ords)?;
922 let client = self.clone();
923
924 let _ = (trader_id, strategy_id);
925
926 pyo3_async_runtimes::tokio::future_into_py(py, async move {
927 let resp = client
928 .place_order_with_domain_types(
929 instrument_id,
930 td_mode,
931 client_order_id,
932 order_side,
933 order_type,
934 quantity,
935 time_in_force,
936 price,
937 post_only,
938 reduce_only,
939 quote_quantity,
940 position_side,
941 attach_algo_ords,
942 px_usd,
943 px_vol,
944 speed_bump,
945 outcome,
946 slippage_pct,
947 )
948 .await
949 .map_err(to_pyvalue_err)?;
950
951 Python::attach(|py| {
952 let dict = PyDict::new(py);
953
954 if let Some(ord_id) = resp.ord_id {
955 dict.set_item("ord_id", ord_id.as_str())?;
956 }
957
958 if let Some(cl_ord_id) = resp.cl_ord_id {
959 dict.set_item("cl_ord_id", cl_ord_id.as_str())?;
960 }
961
962 if let Some(s_code) = resp.s_code {
963 dict.set_item("s_code", s_code)?;
964 }
965
966 if let Some(s_msg) = resp.s_msg {
967 dict.set_item("s_msg", s_msg)?;
968 }
969
970 if let Some(sub_code) = resp.sub_code {
971 dict.set_item("sub_code", sub_code)?;
972 }
973
974 Ok(dict.into_py_any_unwrap(py))
975 })
976 })
977 }
978
979 #[pyo3(name = "place_algo_order")]
985 #[pyo3(signature = (
986 trader_id,
987 strategy_id,
988 instrument_id,
989 td_mode,
990 client_order_id,
991 order_side,
992 order_type,
993 quantity,
994 trigger_price=None,
995 trigger_type=None,
996 limit_price=None,
997 reduce_only=None,
998 close_fraction=None,
999 callback_ratio=None,
1000 callback_spread=None,
1001 activation_price=None,
1002 ))]
1003 #[expect(clippy::too_many_arguments)]
1004 fn py_place_algo_order<'py>(
1005 &self,
1006 py: Python<'py>,
1007 trader_id: TraderId,
1008 strategy_id: StrategyId,
1009 instrument_id: InstrumentId,
1010 td_mode: OKXTradeMode,
1011 client_order_id: ClientOrderId,
1012 order_side: OrderSide,
1013 order_type: OrderType,
1014 quantity: Quantity,
1015 trigger_price: Option<Price>,
1016 trigger_type: Option<TriggerType>,
1017 limit_price: Option<Price>,
1018 reduce_only: Option<bool>,
1019 close_fraction: Option<String>,
1020 callback_ratio: Option<String>,
1021 callback_spread: Option<String>,
1022 activation_price: Option<Price>,
1023 ) -> PyResult<Bound<'py, PyAny>> {
1024 let client = self.clone();
1025
1026 let _ = (trader_id, strategy_id);
1028
1029 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1030 let resp = client
1031 .place_algo_order_with_domain_types(
1032 instrument_id,
1033 td_mode,
1034 client_order_id,
1035 order_side,
1036 order_type,
1037 quantity,
1038 trigger_price,
1039 trigger_type,
1040 limit_price,
1041 reduce_only,
1042 close_fraction,
1043 callback_ratio,
1044 callback_spread,
1045 activation_price,
1046 )
1047 .await
1048 .map_err(to_pyvalue_err)?;
1049
1050 Python::attach(|py| {
1051 let dict = PyDict::new(py);
1052 dict.set_item("algo_id", resp.algo_id)?;
1053 if let Some(algo_cl_ord_id) = resp.algo_cl_ord_id {
1054 dict.set_item("algo_cl_ord_id", algo_cl_ord_id)?;
1055 }
1056
1057 if let Some(s_code) = resp.s_code {
1058 dict.set_item("s_code", s_code)?;
1059 }
1060
1061 if let Some(s_msg) = resp.s_msg {
1062 dict.set_item("s_msg", s_msg)?;
1063 }
1064
1065 if let Some(req_id) = resp.req_id {
1066 dict.set_item("req_id", req_id)?;
1067 }
1068 Ok(dict.into_py_any_unwrap(py))
1069 })
1070 })
1071 }
1072
1073 #[pyo3(name = "cancel_algo_order")]
1079 fn py_cancel_algo_order<'py>(
1080 &self,
1081 py: Python<'py>,
1082 instrument_id: InstrumentId,
1083 algo_id: String,
1084 ) -> PyResult<Bound<'py, PyAny>> {
1085 let client = self.clone();
1086
1087 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1088 let resp = client
1089 .cancel_algo_order_with_domain_types(instrument_id, algo_id)
1090 .await
1091 .map_err(to_pyvalue_err)?;
1092
1093 Python::attach(|py| {
1094 let dict = PyDict::new(py);
1095 dict.set_item("algo_id", resp.algo_id)?;
1096 if let Some(s_code) = resp.s_code {
1097 dict.set_item("s_code", s_code)?;
1098 }
1099
1100 if let Some(s_msg) = resp.s_msg {
1101 dict.set_item("s_msg", s_msg)?;
1102 }
1103 Ok(dict.into_py_any_unwrap(py))
1104 })
1105 })
1106 }
1107
1108 #[pyo3(name = "cancel_order")]
1110 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
1111 fn py_cancel_order<'py>(
1112 &self,
1113 py: Python<'py>,
1114 instrument_id: InstrumentId,
1115 client_order_id: Option<ClientOrderId>,
1116 venue_order_id: Option<VenueOrderId>,
1117 ) -> PyResult<Bound<'py, PyAny>> {
1118 let client = self.clone();
1119
1120 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1121 let resp = client
1122 .cancel_order(instrument_id, client_order_id, venue_order_id)
1123 .await
1124 .map_err(to_pyvalue_err)?;
1125
1126 Python::attach(|py| {
1127 let dict = PyDict::new(py);
1128 dict.set_item("ord_id", resp.ord_id)?;
1129
1130 if let Some(cl_ord_id) = resp.cl_ord_id {
1131 dict.set_item("cl_ord_id", cl_ord_id)?;
1132 }
1133
1134 if let Some(s_code) = resp.s_code {
1135 dict.set_item("s_code", s_code)?;
1136 }
1137
1138 if let Some(s_msg) = resp.s_msg {
1139 dict.set_item("s_msg", s_msg)?;
1140 }
1141
1142 if let Some(ts) = resp.ts {
1143 dict.set_item("ts", ts)?;
1144 }
1145
1146 Ok(dict.into_py_any_unwrap(py))
1147 })
1148 })
1149 }
1150
1151 #[pyo3(name = "cancel_all_orders")]
1153 fn py_cancel_all_orders<'py>(
1154 &self,
1155 py: Python<'py>,
1156 instrument_id: InstrumentId,
1157 ) -> PyResult<Bound<'py, PyAny>> {
1158 let client = self.clone();
1159
1160 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1161 let responses = client
1162 .cancel_all_orders(instrument_id)
1163 .await
1164 .map_err(to_pyvalue_err)?;
1165
1166 Python::attach(|py| {
1167 let results: PyResult<Vec<_>> = responses
1168 .into_iter()
1169 .map(|resp| {
1170 let dict = PyDict::new(py);
1171 dict.set_item("ord_id", resp.ord_id)?;
1172
1173 if let Some(cl_ord_id) = resp.cl_ord_id {
1174 dict.set_item("cl_ord_id", cl_ord_id)?;
1175 }
1176
1177 if let Some(s_code) = resp.s_code {
1178 dict.set_item("s_code", s_code)?;
1179 }
1180
1181 if let Some(s_msg) = resp.s_msg {
1182 dict.set_item("s_msg", s_msg)?;
1183 }
1184
1185 if let Some(ts) = resp.ts {
1186 dict.set_item("ts", ts)?;
1187 }
1188
1189 Ok(dict)
1190 })
1191 .collect();
1192 Ok(PyList::new(py, results?)?.into_py_any_unwrap(py))
1193 })
1194 })
1195 }
1196
1197 #[expect(clippy::too_many_arguments)]
1203 #[pyo3(name = "amend_algo_order")]
1204 #[pyo3(signature = (
1205 instrument_id,
1206 algo_id,
1207 new_trigger_price=None,
1208 new_limit_price=None,
1209 new_quantity=None,
1210 new_callback_ratio=None,
1211 new_callback_spread=None,
1212 new_activation_price=None,
1213 new_sl_trigger_price=None,
1214 new_tp_trigger_price=None,
1215 new_tp_order_price=None,
1216 new_tp_trigger_px_type=None,
1217 new_sl_order_price=None,
1218 new_sl_trigger_px_type=None,
1219 ))]
1220 fn py_amend_algo_order<'py>(
1221 &self,
1222 py: Python<'py>,
1223 instrument_id: InstrumentId,
1224 algo_id: String,
1225 new_trigger_price: Option<Price>,
1226 new_limit_price: Option<Price>,
1227 new_quantity: Option<Quantity>,
1228 new_callback_ratio: Option<String>,
1229 new_callback_spread: Option<String>,
1230 new_activation_price: Option<Price>,
1231 new_sl_trigger_price: Option<Price>,
1232 new_tp_trigger_price: Option<Price>,
1233 new_tp_order_price: Option<String>,
1234 new_tp_trigger_px_type: Option<String>,
1235 new_sl_order_price: Option<String>,
1236 new_sl_trigger_px_type: Option<String>,
1237 ) -> PyResult<Bound<'py, PyAny>> {
1238 let client = self.clone();
1239
1240 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1241 let resp = client
1242 .amend_algo_order_with_domain_types(
1243 instrument_id,
1244 algo_id,
1245 new_trigger_price,
1246 new_sl_trigger_price,
1247 new_limit_price,
1248 new_quantity,
1249 new_callback_ratio,
1250 new_callback_spread,
1251 new_activation_price,
1252 new_tp_trigger_price,
1253 new_tp_order_price,
1254 new_tp_trigger_px_type,
1255 new_sl_order_price,
1256 new_sl_trigger_px_type,
1257 )
1258 .await
1259 .map_err(to_pyvalue_err)?;
1260
1261 Python::attach(|py| {
1262 let dict = PyDict::new(py);
1263 dict.set_item("algo_id", resp.algo_id)?;
1264 if let Some(s_code) = resp.s_code {
1265 dict.set_item("s_code", s_code)?;
1266 }
1267
1268 if let Some(s_msg) = resp.s_msg {
1269 dict.set_item("s_msg", s_msg)?;
1270 }
1271 Ok(dict.into_py_any_unwrap(py))
1272 })
1273 })
1274 }
1275
1276 #[pyo3(name = "cancel_algo_orders")]
1285 fn py_cancel_algo_orders<'py>(
1286 &self,
1287 py: Python<'py>,
1288 orders: Vec<(InstrumentId, String)>,
1289 ) -> PyResult<Bound<'py, PyAny>> {
1290 let client = self.clone();
1291
1292 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1293 let requests: Vec<_> = orders
1294 .into_iter()
1295 .map(|(instrument_id, algo_id)| OKXCancelAlgoOrderRequest {
1296 inst_id: instrument_id.symbol.to_string(),
1297 inst_id_code: None,
1298 algo_id: Some(algo_id),
1299 algo_cl_ord_id: None,
1300 })
1301 .collect();
1302
1303 let responses = client
1304 .cancel_algo_orders(requests)
1305 .await
1306 .map_err(to_pyvalue_err)?;
1307
1308 Python::attach(|py| {
1309 let results: Vec<_> = responses
1310 .into_iter()
1311 .map(|resp| {
1312 let dict = PyDict::new(py);
1313 dict.set_item("algo_id", resp.algo_id).expect("set algo_id");
1314 if let Some(s_code) = resp.s_code {
1315 dict.set_item("s_code", s_code).expect("set s_code");
1316 }
1317
1318 if let Some(s_msg) = resp.s_msg {
1319 dict.set_item("s_msg", s_msg).expect("set s_msg");
1320 }
1321 dict
1322 })
1323 .collect();
1324 let pylist = PyList::new(py, results)?;
1325 Ok(pylist.into_py_any_unwrap(py))
1326 })
1327 })
1328 }
1329
1330 #[pyo3(name = "cancel_advance_algo_order")]
1331 fn py_cancel_advance_algo_order<'py>(
1332 &self,
1333 py: Python<'py>,
1334 instrument_id: InstrumentId,
1335 algo_id: String,
1336 ) -> PyResult<Bound<'py, PyAny>> {
1337 let client = self.clone();
1338
1339 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1340 let request = OKXCancelAlgoOrderRequest {
1341 inst_id: instrument_id.symbol.to_string(),
1342 inst_id_code: None,
1343 algo_id: Some(algo_id),
1344 algo_cl_ord_id: None,
1345 };
1346
1347 let mut responses = client
1348 .cancel_advance_algo_orders(vec![request])
1349 .await
1350 .map_err(to_pyvalue_err)?;
1351
1352 let resp = responses
1353 .pop()
1354 .ok_or_else(|| to_pyvalue_err("Empty response"))?;
1355
1356 Python::attach(|py| {
1357 let dict = PyDict::new(py);
1358 dict.set_item("algo_id", resp.algo_id)?;
1359
1360 if let Some(s_code) = resp.s_code {
1361 dict.set_item("s_code", s_code)?;
1362 }
1363
1364 if let Some(s_msg) = resp.s_msg {
1365 dict.set_item("s_msg", s_msg)?;
1366 }
1367 Ok(dict.into_py_any_unwrap(py))
1368 })
1369 })
1370 }
1371
1372 #[pyo3(name = "get_server_time")]
1380 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1381 let client = self.clone();
1382
1383 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1384 let timestamp = client.get_server_time().await.map_err(to_pyvalue_err)?;
1385
1386 Python::attach(|py| timestamp.into_py_any(py))
1387 })
1388 }
1389
1390 #[pyo3(name = "get_balance")]
1391 fn py_get_balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1392 let client = self.clone();
1393
1394 pyo3_async_runtimes::tokio::future_into_py(py, async move {
1395 let accounts = client.inner.get_balance().await.map_err(to_pyvalue_err)?;
1396
1397 let details: Vec<_> = accounts
1398 .into_iter()
1399 .flat_map(|account| account.details)
1400 .collect();
1401
1402 Python::attach(|py| {
1403 let pylist = PyList::new(py, details)?;
1404 Ok(pylist.into_py_any_unwrap(py))
1405 })
1406 })
1407 }
1408}
1409
1410impl From<OKXHttpError> for PyErr {
1411 fn from(error: OKXHttpError) -> Self {
1412 match error {
1413 OKXHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
1415 OKXHttpError::HttpClientError(e) => to_pyruntime_err(format!("Network error: {e}")),
1416 OKXHttpError::UnexpectedStatus { status, body } => {
1417 to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
1418 }
1419 OKXHttpError::MissingCredentials => {
1421 to_pyvalue_err("Missing credentials for authenticated request")
1422 }
1423 OKXHttpError::ValidationError(msg) => {
1424 to_pyvalue_err(format!("Parameter validation error: {msg}"))
1425 }
1426 OKXHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
1427 OKXHttpError::OkxError {
1428 error_code,
1429 message,
1430 } => to_pyvalue_err(format!("OKX error {error_code}: {message}")),
1431 }
1432 }
1433}