use chrono::{DateTime, Utc};
use nautilus_core::python::{
IntoPyObjectNautilusExt, params::value_to_pyobject, to_pyruntime_err, to_pyvalue_err,
};
use nautilus_model::{
data::{BarType, forward::ForwardPrice},
enums::{OrderSide, OrderType, PositionSide, TimeInForce, TriggerType},
identifiers::{AccountId, ClientOrderId, InstrumentId, StrategyId, TraderId, VenueOrderId},
python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
types::{Price, Quantity},
};
use pyo3::{
conversion::IntoPyObjectExt,
prelude::*,
types::{PyDict, PyList, PyTuple},
};
use super::{extract_optional_string, extract_optional_trigger_type};
use crate::{
common::enums::{
OKXEnvironment, OKXInstrumentType, OKXOrderStatus, OKXPositionMode, OKXTradeMode,
},
http::{
client::OKXHttpClient,
error::OKXHttpError,
models::{OKXAttachAlgoOrdRequest, OKXCancelAlgoOrderRequest},
query::{
GetEventContractEventsParams, GetEventContractMarketsParams,
GetEventContractSeriesParams, GetSpreadsParams,
},
},
};
fn serializable_items_to_pylist<T>(py: Python<'_>, items: Vec<T>) -> PyResult<Py<PyAny>>
where
T: serde::Serialize,
{
let py_items: PyResult<Vec<_>> = items
.into_iter()
.map(|item| {
let value = serde_json::to_value(item).map_err(to_pyvalue_err)?;
value_to_pyobject(py, &value)
})
.collect();
Ok(PyList::new(py, py_items?)?.into_py_any_unwrap(py))
}
fn parse_attach_algo_ords(
py: Python<'_>,
attach_algo_ords: Option<Vec<Py<PyDict>>>,
) -> PyResult<Option<Vec<OKXAttachAlgoOrdRequest>>> {
attach_algo_ords
.map(|items| {
items
.into_iter()
.map(|item| {
let dict = item.bind(py);
Ok(OKXAttachAlgoOrdRequest {
attach_algo_cl_ord_id: extract_optional_string(
dict,
"attach_algo_cl_ord_id",
)?,
sl_trigger_px: extract_optional_string(dict, "sl_trigger_px")?,
sl_ord_px: extract_optional_string(dict, "sl_ord_px")?,
sl_trigger_px_type: extract_optional_trigger_type(
dict,
"sl_trigger_px_type",
)?,
tp_trigger_px: extract_optional_string(dict, "tp_trigger_px")?,
tp_ord_px: extract_optional_string(dict, "tp_ord_px")?,
tp_trigger_px_type: extract_optional_trigger_type(
dict,
"tp_trigger_px_type",
)?,
callback_ratio: extract_optional_string(dict, "callback_ratio")?,
callback_spread: extract_optional_string(dict, "callback_spread")?,
active_px: extract_optional_string(dict, "active_px")?,
new_callback_ratio: extract_optional_string(dict, "new_callback_ratio")?,
new_callback_spread: extract_optional_string(dict, "new_callback_spread")?,
new_active_px: extract_optional_string(dict, "new_active_px")?,
})
})
.collect::<PyResult<Vec<_>>>()
})
.transpose()
}
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl OKXHttpClient {
#[new]
#[pyo3(signature = (
api_key=None,
api_secret=None,
api_passphrase=None,
base_url=None,
timeout_secs=60,
max_retries=3,
retry_delay_ms=1_000,
retry_delay_max_ms=10_000,
environment=OKXEnvironment::Live,
proxy_url=None,
))]
#[expect(clippy::too_many_arguments)]
fn py_new(
api_key: Option<String>,
api_secret: Option<String>,
api_passphrase: Option<String>,
base_url: Option<String>,
timeout_secs: u64,
max_retries: u32,
retry_delay_ms: u64,
retry_delay_max_ms: u64,
environment: OKXEnvironment,
proxy_url: Option<String>,
) -> PyResult<Self> {
Self::with_credentials(
api_key,
api_secret,
api_passphrase,
base_url,
timeout_secs,
max_retries,
retry_delay_ms,
retry_delay_max_ms,
environment,
proxy_url,
)
.map_err(to_pyvalue_err)
}
#[staticmethod]
#[pyo3(name = "from_env")]
fn py_from_env() -> PyResult<Self> {
Self::from_env().map_err(to_pyvalue_err)
}
#[getter]
#[pyo3(name = "base_url")]
#[must_use]
pub fn py_base_url(&self) -> &str {
self.base_url()
}
#[getter]
#[pyo3(name = "api_key")]
#[must_use]
pub fn py_api_key(&self) -> Option<&str> {
self.api_key()
}
#[getter]
#[pyo3(name = "api_key_masked")]
#[must_use]
pub fn py_api_key_masked(&self) -> Option<String> {
self.api_key_masked()
}
#[pyo3(name = "is_initialized")]
#[must_use]
pub fn py_is_initialized(&self) -> bool {
self.is_initialized()
}
#[pyo3(name = "get_cached_symbols")]
#[must_use]
pub fn py_get_cached_symbols(&self) -> Vec<String> {
self.get_cached_symbols()
}
#[pyo3(name = "cancel_all_requests")]
pub fn py_cancel_all_requests(&self) {
self.cancel_all_requests();
}
#[pyo3(name = "cache_instruments")]
pub fn py_cache_instruments(
&self,
py: Python<'_>,
instruments: Vec<Py<PyAny>>,
) -> PyResult<()> {
let instruments: Result<Vec<_>, _> = instruments
.into_iter()
.map(|inst| pyobject_to_instrument_any(py, inst))
.collect();
self.cache_instruments(&instruments?);
Ok(())
}
#[pyo3(name = "cache_instrument")]
pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
Ok(())
}
#[pyo3(name = "set_position_mode")]
fn py_set_position_mode<'py>(
&self,
py: Python<'py>,
position_mode: OKXPositionMode,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.set_position_mode(position_mode)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(py.None()))
})
}
#[pyo3(name = "request_instruments")]
#[pyo3(signature = (instrument_type, instrument_family=None))]
fn py_request_instruments<'py>(
&self,
py: Python<'py>,
instrument_type: OKXInstrumentType,
instrument_family: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let (instruments, inst_id_codes) = client
.request_instruments(instrument_type, instrument_family)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_instruments: PyResult<Vec<_>> = instruments
.into_iter()
.map(|inst| instrument_any_to_pyobject(py, inst))
.collect();
let instruments_list = PyList::new(py, py_instruments?).unwrap();
let py_codes: Vec<_> = inst_id_codes
.into_iter()
.map(|(inst_id, code)| (inst_id.to_string(), code))
.collect();
let codes_list = PyList::new(py, py_codes).unwrap();
let result = PyTuple::new(py, [instruments_list.as_any(), codes_list.as_any()])
.unwrap()
.into_any()
.unbind();
Ok(result)
})
})
}
#[pyo3(name = "request_spread_instruments")]
#[pyo3(signature = (base_currency=None, instrument_id=None, spread_id=None, state=None))]
fn py_request_spread_instruments<'py>(
&self,
py: Python<'py>,
base_currency: Option<String>,
instrument_id: Option<InstrumentId>,
spread_id: Option<String>,
state: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let instruments = client
.request_spread_instruments(GetSpreadsParams {
base_ccy: base_currency,
inst_id: instrument_id.map(|id| id.symbol.to_string()),
sprd_id: spread_id,
state,
})
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_instruments: PyResult<Vec<_>> = instruments
.into_iter()
.map(|inst| instrument_any_to_pyobject(py, inst))
.collect();
Ok(PyList::new(py, py_instruments?)?.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_instrument")]
fn py_request_instrument<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let instrument = client
.request_instrument(instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| instrument_any_to_pyobject(py, instrument))
})
}
#[pyo3(name = "request_event_contract_series")]
#[pyo3(signature = (series_id=None))]
fn py_request_event_contract_series<'py>(
&self,
py: Python<'py>,
series_id: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let series = client
.request_event_contract_series(GetEventContractSeriesParams { series_id })
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| serializable_items_to_pylist(py, series))
})
}
#[expect(clippy::too_many_arguments)]
#[pyo3(name = "request_event_contract_events")]
#[pyo3(signature = (series_id, event_id=None, state=None, limit=None, before=None, after=None))]
fn py_request_event_contract_events<'py>(
&self,
py: Python<'py>,
series_id: String,
event_id: Option<String>,
state: Option<String>,
limit: Option<String>,
before: Option<String>,
after: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let events = client
.request_event_contract_events(GetEventContractEventsParams {
series_id,
event_id,
state,
limit,
before,
after,
})
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| serializable_items_to_pylist(py, events))
})
}
#[expect(clippy::too_many_arguments)]
#[pyo3(name = "request_event_contract_markets")]
#[pyo3(signature = (series_id, event_id=None, inst_id=None, state=None, limit=None, before=None, after=None))]
fn py_request_event_contract_markets<'py>(
&self,
py: Python<'py>,
series_id: String,
event_id: Option<String>,
inst_id: Option<String>,
state: Option<String>,
limit: Option<String>,
before: Option<String>,
after: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let markets = client
.request_event_contract_markets(GetEventContractMarketsParams {
series_id,
event_id,
inst_id,
state,
limit,
before,
after,
})
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| serializable_items_to_pylist(py, markets))
})
}
#[pyo3(name = "request_account_state")]
fn py_request_account_state<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let account_state = client
.request_account_state(account_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
})
}
#[pyo3(name = "request_trades")]
#[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
fn py_request_trades<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let trades = client
.request_trades(instrument_id, start, end, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_bars")]
#[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
fn py_request_bars<'py>(
&self,
py: Python<'py>,
bar_type: BarType,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let bars = client
.request_bars(bar_type, start, end, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, bars.into_iter().map(|bar| bar.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_orderbook_snapshot")]
#[pyo3(signature = (instrument_id, depth=None))]
fn py_request_orderbook_snapshot<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
depth: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let deltas = client
.request_orderbook_snapshot(instrument_id, depth)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
})
}
#[pyo3(name = "request_funding_rates")]
#[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
fn py_request_funding_rates<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let rates = client
.request_funding_rates(instrument_id, start, end, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist = PyList::new(py, rates.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_forward_prices")]
#[pyo3(signature = (underlying, instrument_id=None))]
fn py_request_forward_prices<'py>(
&self,
py: Python<'py>,
underlying: String,
instrument_id: Option<InstrumentId>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let forward_prices: Vec<ForwardPrice> = client
.request_forward_prices(&underlying, instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist = PyList::new(
py,
forward_prices
.into_iter()
.map(|price| price.into_py_any_unwrap(py)),
)?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_mark_price")]
fn py_request_mark_price<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let mark_price = client
.request_mark_price(instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(mark_price.into_py_any_unwrap(py)))
})
}
#[pyo3(name = "request_index_price")]
fn py_request_index_price<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let index_price = client
.request_index_price(instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(index_price.into_py_any_unwrap(py)))
})
}
#[pyo3(name = "request_order_status_reports")]
#[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, open_only=false, limit=None))]
#[expect(clippy::too_many_arguments)]
fn py_request_order_status_reports<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
instrument_type: Option<OKXInstrumentType>,
instrument_id: Option<InstrumentId>,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
open_only: bool,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let reports = client
.request_order_status_reports(
account_id,
instrument_type,
instrument_id,
start,
end,
open_only,
limit,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_algo_order_status_reports")]
#[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, algo_id=None, algo_client_order_id=None, state=None, limit=None))]
#[expect(clippy::too_many_arguments)]
fn py_request_algo_order_status_reports<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
instrument_type: Option<OKXInstrumentType>,
instrument_id: Option<InstrumentId>,
algo_id: Option<String>,
algo_client_order_id: Option<ClientOrderId>,
state: Option<OKXOrderStatus>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let reports = client
.request_algo_order_status_reports(
account_id,
instrument_type,
instrument_id,
algo_id,
algo_client_order_id,
state,
limit,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_algo_order_status_report")]
fn py_request_algo_order_status_report<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
instrument_id: InstrumentId,
client_order_id: ClientOrderId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let report = client
.request_algo_order_status_report(account_id, instrument_id, client_order_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| match report {
Some(report) => Ok(report.into_py_any_unwrap(py)),
None => Ok(py.None()),
})
})
}
#[pyo3(name = "request_fill_reports")]
#[pyo3(signature = (account_id, instrument_type=None, instrument_id=None, start=None, end=None, limit=None))]
#[expect(clippy::too_many_arguments)]
fn py_request_fill_reports<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
instrument_type: Option<OKXInstrumentType>,
instrument_id: Option<InstrumentId>,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let trades = client
.request_fill_reports(
account_id,
instrument_type,
instrument_id,
start,
end,
limit,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_position_status_reports")]
#[pyo3(signature = (account_id, instrument_type=None, instrument_id=None))]
fn py_request_position_status_reports<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
instrument_type: Option<OKXInstrumentType>,
instrument_id: Option<InstrumentId>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let reports = client
.request_position_status_reports(account_id, instrument_type, instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "place_order")]
#[pyo3(signature = (
trader_id,
strategy_id,
instrument_id,
td_mode,
client_order_id,
order_side,
order_type,
quantity,
time_in_force=None,
price=None,
post_only=None,
reduce_only=None,
quote_quantity=None,
position_side=None,
attach_algo_ords=None,
px_usd=None,
px_vol=None,
speed_bump=None,
outcome=None,
slippage_pct=None,
))]
#[expect(clippy::too_many_arguments)]
fn py_place_order<'py>(
&self,
py: Python<'py>,
trader_id: TraderId,
strategy_id: StrategyId,
instrument_id: InstrumentId,
td_mode: OKXTradeMode,
client_order_id: ClientOrderId,
order_side: OrderSide,
order_type: OrderType,
quantity: Quantity,
time_in_force: Option<TimeInForce>,
price: Option<Price>,
post_only: Option<bool>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
position_side: Option<PositionSide>,
attach_algo_ords: Option<Vec<Py<PyDict>>>,
px_usd: Option<String>,
px_vol: Option<String>,
speed_bump: Option<String>,
outcome: Option<String>,
slippage_pct: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let attach_algo_ords = parse_attach_algo_ords(py, attach_algo_ords)?;
let client = self.clone();
let _ = (trader_id, strategy_id);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let resp = client
.place_order_with_domain_types(
instrument_id,
td_mode,
client_order_id,
order_side,
order_type,
quantity,
time_in_force,
price,
post_only,
reduce_only,
quote_quantity,
position_side,
attach_algo_ords,
px_usd,
px_vol,
speed_bump,
outcome,
slippage_pct,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let dict = PyDict::new(py);
if let Some(ord_id) = resp.ord_id {
dict.set_item("ord_id", ord_id.as_str())?;
}
if let Some(cl_ord_id) = resp.cl_ord_id {
dict.set_item("cl_ord_id", cl_ord_id.as_str())?;
}
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code)?;
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg)?;
}
if let Some(sub_code) = resp.sub_code {
dict.set_item("sub_code", sub_code)?;
}
Ok(dict.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "place_algo_order")]
#[pyo3(signature = (
trader_id,
strategy_id,
instrument_id,
td_mode,
client_order_id,
order_side,
order_type,
quantity,
trigger_price=None,
trigger_type=None,
limit_price=None,
reduce_only=None,
close_fraction=None,
callback_ratio=None,
callback_spread=None,
activation_price=None,
))]
#[expect(clippy::too_many_arguments)]
fn py_place_algo_order<'py>(
&self,
py: Python<'py>,
trader_id: TraderId,
strategy_id: StrategyId,
instrument_id: InstrumentId,
td_mode: OKXTradeMode,
client_order_id: ClientOrderId,
order_side: OrderSide,
order_type: OrderType,
quantity: Quantity,
trigger_price: Option<Price>,
trigger_type: Option<TriggerType>,
limit_price: Option<Price>,
reduce_only: Option<bool>,
close_fraction: Option<String>,
callback_ratio: Option<String>,
callback_spread: Option<String>,
activation_price: Option<Price>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let _ = (trader_id, strategy_id);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let resp = client
.place_algo_order_with_domain_types(
instrument_id,
td_mode,
client_order_id,
order_side,
order_type,
quantity,
trigger_price,
trigger_type,
limit_price,
reduce_only,
close_fraction,
callback_ratio,
callback_spread,
activation_price,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("algo_id", resp.algo_id)?;
if let Some(algo_cl_ord_id) = resp.algo_cl_ord_id {
dict.set_item("algo_cl_ord_id", algo_cl_ord_id)?;
}
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code)?;
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg)?;
}
if let Some(req_id) = resp.req_id {
dict.set_item("req_id", req_id)?;
}
Ok(dict.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "cancel_algo_order")]
fn py_cancel_algo_order<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
algo_id: String,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let resp = client
.cancel_algo_order_with_domain_types(instrument_id, algo_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("algo_id", resp.algo_id)?;
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code)?;
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg)?;
}
Ok(dict.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "cancel_order")]
#[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None))]
fn py_cancel_order<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
client_order_id: Option<ClientOrderId>,
venue_order_id: Option<VenueOrderId>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let resp = client
.cancel_order(instrument_id, client_order_id, venue_order_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("ord_id", resp.ord_id)?;
if let Some(cl_ord_id) = resp.cl_ord_id {
dict.set_item("cl_ord_id", cl_ord_id)?;
}
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code)?;
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg)?;
}
if let Some(ts) = resp.ts {
dict.set_item("ts", ts)?;
}
Ok(dict.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "cancel_all_orders")]
fn py_cancel_all_orders<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let responses = client
.cancel_all_orders(instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let results: PyResult<Vec<_>> = responses
.into_iter()
.map(|resp| {
let dict = PyDict::new(py);
dict.set_item("ord_id", resp.ord_id)?;
if let Some(cl_ord_id) = resp.cl_ord_id {
dict.set_item("cl_ord_id", cl_ord_id)?;
}
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code)?;
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg)?;
}
if let Some(ts) = resp.ts {
dict.set_item("ts", ts)?;
}
Ok(dict)
})
.collect();
Ok(PyList::new(py, results?)?.into_py_any_unwrap(py))
})
})
}
#[expect(clippy::too_many_arguments)]
#[pyo3(name = "amend_algo_order")]
#[pyo3(signature = (
instrument_id,
algo_id,
new_trigger_price=None,
new_limit_price=None,
new_quantity=None,
new_callback_ratio=None,
new_callback_spread=None,
new_activation_price=None,
new_sl_trigger_price=None,
new_tp_trigger_price=None,
new_tp_order_price=None,
new_tp_trigger_px_type=None,
new_sl_order_price=None,
new_sl_trigger_px_type=None,
))]
fn py_amend_algo_order<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
algo_id: String,
new_trigger_price: Option<Price>,
new_limit_price: Option<Price>,
new_quantity: Option<Quantity>,
new_callback_ratio: Option<String>,
new_callback_spread: Option<String>,
new_activation_price: Option<Price>,
new_sl_trigger_price: Option<Price>,
new_tp_trigger_price: Option<Price>,
new_tp_order_price: Option<String>,
new_tp_trigger_px_type: Option<String>,
new_sl_order_price: Option<String>,
new_sl_trigger_px_type: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let resp = client
.amend_algo_order_with_domain_types(
instrument_id,
algo_id,
new_trigger_price,
new_sl_trigger_price,
new_limit_price,
new_quantity,
new_callback_ratio,
new_callback_spread,
new_activation_price,
new_tp_trigger_price,
new_tp_order_price,
new_tp_trigger_px_type,
new_sl_order_price,
new_sl_trigger_px_type,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("algo_id", resp.algo_id)?;
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code)?;
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg)?;
}
Ok(dict.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "cancel_algo_orders")]
fn py_cancel_algo_orders<'py>(
&self,
py: Python<'py>,
orders: Vec<(InstrumentId, String)>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let requests: Vec<_> = orders
.into_iter()
.map(|(instrument_id, algo_id)| OKXCancelAlgoOrderRequest {
inst_id: instrument_id.symbol.to_string(),
inst_id_code: None,
algo_id: Some(algo_id),
algo_cl_ord_id: None,
})
.collect();
let responses = client
.cancel_algo_orders(requests)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let results: Vec<_> = responses
.into_iter()
.map(|resp| {
let dict = PyDict::new(py);
dict.set_item("algo_id", resp.algo_id).expect("set algo_id");
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code).expect("set s_code");
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg).expect("set s_msg");
}
dict
})
.collect();
let pylist = PyList::new(py, results)?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "cancel_advance_algo_order")]
fn py_cancel_advance_algo_order<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
algo_id: String,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let request = OKXCancelAlgoOrderRequest {
inst_id: instrument_id.symbol.to_string(),
inst_id_code: None,
algo_id: Some(algo_id),
algo_cl_ord_id: None,
};
let mut responses = client
.cancel_advance_algo_orders(vec![request])
.await
.map_err(to_pyvalue_err)?;
let resp = responses
.pop()
.ok_or_else(|| to_pyvalue_err("Empty response"))?;
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("algo_id", resp.algo_id)?;
if let Some(s_code) = resp.s_code {
dict.set_item("s_code", s_code)?;
}
if let Some(s_msg) = resp.s_msg {
dict.set_item("s_msg", s_msg)?;
}
Ok(dict.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "get_server_time")]
fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let timestamp = client.get_server_time().await.map_err(to_pyvalue_err)?;
Python::attach(|py| timestamp.into_py_any(py))
})
}
#[pyo3(name = "get_balance")]
fn py_get_balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let accounts = client.inner.get_balance().await.map_err(to_pyvalue_err)?;
let details: Vec<_> = accounts
.into_iter()
.flat_map(|account| account.details)
.collect();
Python::attach(|py| {
let pylist = PyList::new(py, details)?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
}
impl From<OKXHttpError> for PyErr {
fn from(error: OKXHttpError) -> Self {
match error {
OKXHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
OKXHttpError::HttpClientError(e) => to_pyruntime_err(format!("Network error: {e}")),
OKXHttpError::UnexpectedStatus { status, body } => {
to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
}
OKXHttpError::MissingCredentials => {
to_pyvalue_err("Missing credentials for authenticated request")
}
OKXHttpError::ValidationError(msg) => {
to_pyvalue_err(format!("Parameter validation error: {msg}"))
}
OKXHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
OKXHttpError::OkxError {
error_code,
message,
} => to_pyvalue_err(format!("OKX error {error_code}: {message}")),
}
}
}