1use ahash::AHashMap;
19use jiff::Timestamp;
20use nautilus_core::{datetime::datetime_to_unix_nanos, python::to_pyvalue_err};
21use nautilus_model::{
22 data::BarType,
23 enums::{OrderSide, OrderType, TimeInForce},
24 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
25 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
26 types::{Price, Quantity},
27};
28use pyo3::{IntoPyObjectExt, prelude::*, types::PyList};
29use rust_decimal::Decimal;
30
31use crate::{
32 common::{
33 enums::{AxCandleWidth, AxOrderSide},
34 parse::{client_order_id_to_cid, quantity_to_contracts},
35 },
36 http::{client::AxHttpClient, error::AxHttpError, models::PreviewAggressiveLimitOrderRequest},
37};
38
39#[pymethods]
40#[pyo3_stub_gen::derive::gen_stub_pymethods]
41impl AxHttpClient {
42 #[new]
47 #[pyo3(signature = (
48 base_url=None,
49 orders_base_url=None,
50 timeout_secs=60,
51 max_retries=3,
52 retry_delay_ms=1000,
53 retry_delay_max_ms=10_000,
54 proxy_url=None,
55 ))]
56 fn py_new(
57 base_url: Option<String>,
58 orders_base_url: Option<String>,
59 timeout_secs: u64,
60 max_retries: u32,
61 retry_delay_ms: u64,
62 retry_delay_max_ms: u64,
63 proxy_url: Option<String>,
64 ) -> PyResult<Self> {
65 Self::new(
66 base_url,
67 orders_base_url,
68 timeout_secs,
69 max_retries,
70 retry_delay_ms,
71 retry_delay_max_ms,
72 proxy_url,
73 )
74 .map_err(to_pyvalue_err)
75 }
76
77 #[staticmethod]
83 #[pyo3(name = "with_credentials")]
84 #[pyo3(signature = (
85 api_key,
86 api_secret,
87 base_url=None,
88 orders_base_url=None,
89 timeout_secs=60,
90 max_retries=3,
91 retry_delay_ms=1000,
92 retry_delay_max_ms=10_000,
93 proxy_url=None,
94 ))]
95 #[expect(clippy::too_many_arguments)]
96 fn py_with_credentials(
97 api_key: String,
98 api_secret: String,
99 base_url: Option<String>,
100 orders_base_url: Option<String>,
101 timeout_secs: u64,
102 max_retries: u32,
103 retry_delay_ms: u64,
104 retry_delay_max_ms: u64,
105 proxy_url: Option<String>,
106 ) -> PyResult<Self> {
107 Self::with_credentials(
108 api_key,
109 api_secret,
110 base_url,
111 orders_base_url,
112 timeout_secs,
113 max_retries,
114 retry_delay_ms,
115 retry_delay_max_ms,
116 proxy_url,
117 )
118 .map_err(to_pyvalue_err)
119 }
120
121 #[getter]
123 #[pyo3(name = "base_url")]
124 #[must_use]
125 pub fn py_base_url(&self) -> &str {
126 self.base_url()
127 }
128
129 #[getter]
131 #[pyo3(name = "api_key_masked")]
132 #[must_use]
133 pub fn py_api_key_masked(&self) -> String {
134 self.api_key_masked()
135 }
136
137 #[pyo3(name = "cancel_all_requests")]
139 pub fn py_cancel_all_requests(&self) {
140 self.cancel_all_requests();
141 }
142
143 #[pyo3(name = "cancel_all_orders")]
149 pub fn py_cancel_all_orders<'py>(
150 &self,
151 py: Python<'py>,
152 instrument_id: InstrumentId,
153 ) -> PyResult<Bound<'py, PyAny>> {
154 let client = self.clone();
155 pyo3_async_runtimes::tokio::future_into_py(py, async move {
156 client
157 .cancel_all_orders(instrument_id)
158 .await
159 .map_err(to_pyvalue_err)
160 })
161 }
162
163 #[pyo3(name = "cache_instrument")]
167 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
168 self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
169 Ok(())
170 }
171
172 #[pyo3(name = "authenticate")]
180 #[pyo3(signature = (api_key, api_secret, expiration_seconds=86400))]
181 fn py_authenticate<'py>(
182 &self,
183 py: Python<'py>,
184 api_key: String,
185 api_secret: String,
186 expiration_seconds: i32,
187 ) -> PyResult<Bound<'py, PyAny>> {
188 let client = self.clone();
189
190 pyo3_async_runtimes::tokio::future_into_py(py, async move {
191 client
192 .authenticate(&api_key, &api_secret, expiration_seconds)
193 .await
194 .map_err(to_pyvalue_err)
195 })
196 }
197
198 #[pyo3(name = "authenticate_auto")]
215 #[pyo3(signature = (expiration_seconds=86400))]
216 fn py_authenticate_auto<'py>(
217 &self,
218 py: Python<'py>,
219 expiration_seconds: i32,
220 ) -> PyResult<Bound<'py, PyAny>> {
221 let client = self.clone();
222
223 pyo3_async_runtimes::tokio::future_into_py(py, async move {
224 client
225 .authenticate_auto(expiration_seconds)
226 .await
227 .map_err(to_pyvalue_err)
228 })
229 }
230
231 #[pyo3(name = "request_instruments")]
240 #[pyo3(signature = (maker_fee=None, taker_fee=None))]
241 fn py_request_instruments<'py>(
242 &self,
243 py: Python<'py>,
244 maker_fee: Option<Decimal>,
245 taker_fee: Option<Decimal>,
246 ) -> PyResult<Bound<'py, PyAny>> {
247 let client = self.clone();
248
249 pyo3_async_runtimes::tokio::future_into_py(py, async move {
250 let instruments = client
251 .request_instruments(maker_fee, taker_fee)
252 .await
253 .map_err(to_pyvalue_err)?;
254
255 Python::attach(|py| {
256 let py_instruments: PyResult<Vec<_>> = instruments
257 .into_iter()
258 .map(|inst| instrument_any_to_pyobject(py, inst))
259 .collect();
260 let pylist = PyList::new(py, py_instruments?)?.into_any().unbind();
261 Ok(pylist)
262 })
263 })
264 }
265
266 #[pyo3(name = "request_trade_ticks")]
280 #[pyo3(signature = (instrument_id, limit=None, start=None, end=None))]
281 fn py_request_trade_ticks<'py>(
282 &self,
283 py: Python<'py>,
284 instrument_id: InstrumentId,
285 limit: Option<i32>,
286 start: Option<Timestamp>,
287 end: Option<Timestamp>,
288 ) -> PyResult<Bound<'py, PyAny>> {
289 let client = self.clone();
290 let symbol = instrument_id.symbol.inner();
291 let start_nanos = datetime_to_unix_nanos(start);
292 let end_nanos = datetime_to_unix_nanos(end);
293
294 pyo3_async_runtimes::tokio::future_into_py(py, async move {
295 let trades = client
296 .request_trade_ticks(symbol, limit, start_nanos, end_nanos)
297 .await
298 .map_err(to_pyvalue_err)?;
299
300 Python::attach(|py| {
301 let py_trades: PyResult<Vec<_>> = trades
302 .into_iter()
303 .map(|trade| trade.into_py_any(py))
304 .collect();
305 let pylist = PyList::new(py, py_trades?)?.into_any().unbind();
306 Ok(pylist)
307 })
308 })
309 }
310
311 #[pyo3(name = "request_bars")]
322 #[pyo3(signature = (bar_type, start=None, end=None))]
323 fn py_request_bars<'py>(
324 &self,
325 py: Python<'py>,
326 bar_type: BarType,
327 start: Option<Timestamp>,
328 end: Option<Timestamp>,
329 ) -> PyResult<Bound<'py, PyAny>> {
330 let client = self.clone();
331 let symbol = bar_type.instrument_id().symbol.inner();
332 let width = AxCandleWidth::try_from(&bar_type.spec()).map_err(to_pyvalue_err)?;
333
334 pyo3_async_runtimes::tokio::future_into_py(py, async move {
335 let bars = client
336 .request_bars(symbol, start, end, width)
337 .await
338 .map_err(to_pyvalue_err)?;
339
340 Python::attach(|py| {
341 let py_bars: PyResult<Vec<_>> =
342 bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
343 let pylist = PyList::new(py, py_bars?)?.into_any().unbind();
344 Ok(pylist)
345 })
346 })
347 }
348
349 #[pyo3(name = "request_book_snapshot")]
359 #[pyo3(signature = (instrument_id, depth=None))]
360 fn py_request_book_snapshot<'py>(
361 &self,
362 py: Python<'py>,
363 instrument_id: InstrumentId,
364 depth: Option<u32>,
365 ) -> PyResult<Bound<'py, PyAny>> {
366 let client = self.clone();
367 let symbol = instrument_id.symbol.inner();
368
369 pyo3_async_runtimes::tokio::future_into_py(py, async move {
370 let book = client
371 .request_book_snapshot(symbol, depth.map(|value| value as usize))
372 .await
373 .map_err(to_pyvalue_err)?;
374
375 Python::attach(|py| book.into_py_any(py))
376 })
377 }
378
379 #[pyo3(name = "request_funding_rates")]
388 #[pyo3(signature = (instrument_id, start=None, end=None))]
389 fn py_request_funding_rates<'py>(
390 &self,
391 py: Python<'py>,
392 instrument_id: InstrumentId,
393 start: Option<Timestamp>,
394 end: Option<Timestamp>,
395 ) -> PyResult<Bound<'py, PyAny>> {
396 let client = self.clone();
397
398 pyo3_async_runtimes::tokio::future_into_py(py, async move {
399 let funding_rates = client
400 .request_funding_rates(instrument_id, start, end)
401 .await
402 .map_err(to_pyvalue_err)?;
403
404 Python::attach(|py| {
405 let py_rates: PyResult<Vec<_>> = funding_rates
406 .into_iter()
407 .map(|rate| rate.into_py_any(py))
408 .collect();
409 let pylist = PyList::new(py, py_rates?)?.into_any().unbind();
410 Ok(pylist)
411 })
412 })
413 }
414
415 #[pyo3(name = "request_account_state")]
421 fn py_request_account_state<'py>(
422 &self,
423 py: Python<'py>,
424 account_id: AccountId,
425 ) -> PyResult<Bound<'py, PyAny>> {
426 let client = self.clone();
427
428 pyo3_async_runtimes::tokio::future_into_py(py, async move {
429 let account_state = client
430 .request_account_state(account_id)
431 .await
432 .map_err(to_pyvalue_err)?;
433
434 Python::attach(|py| account_state.into_py_any(py))
435 })
436 }
437
438 #[pyo3(name = "request_order_status")]
450 #[pyo3(signature = (
451 account_id,
452 instrument_id,
453 order_side,
454 order_type,
455 time_in_force,
456 client_order_id=None,
457 venue_order_id=None,
458 ))]
459 #[expect(clippy::too_many_arguments)]
460 fn py_request_order_status<'py>(
461 &self,
462 py: Python<'py>,
463 account_id: AccountId,
464 instrument_id: InstrumentId,
465 order_side: OrderSide,
466 order_type: OrderType,
467 time_in_force: TimeInForce,
468 client_order_id: Option<ClientOrderId>,
469 venue_order_id: Option<VenueOrderId>,
470 ) -> PyResult<Bound<'py, PyAny>> {
471 let client = self.clone();
472
473 pyo3_async_runtimes::tokio::future_into_py(py, async move {
474 let report = client
475 .request_order_status(
476 account_id,
477 instrument_id,
478 client_order_id,
479 venue_order_id,
480 Some(order_side),
481 order_type,
482 time_in_force,
483 )
484 .await
485 .map_err(to_pyvalue_err)?;
486
487 Python::attach(|py| report.into_py_any(py))
488 })
489 }
490
491 #[pyo3(name = "request_order_status_reports", signature = (account_id, client_order_ids=None))]
508 fn py_request_order_status_reports<'py>(
509 &self,
510 py: Python<'py>,
511 account_id: AccountId,
512 client_order_ids: Option<Vec<ClientOrderId>>,
513 ) -> PyResult<Bound<'py, PyAny>> {
514 let client = self.clone();
515 let cid_map = client_order_ids
516 .unwrap_or_default()
517 .into_iter()
518 .map(|client_order_id| (client_order_id_to_cid(&client_order_id), client_order_id))
519 .collect::<AHashMap<_, _>>();
520
521 pyo3_async_runtimes::tokio::future_into_py(py, async move {
522 let cid_resolver = move |cid: u64| cid_map.get(&cid).copied();
523 let reports = client
524 .request_order_status_reports(account_id, Some(cid_resolver))
525 .await
526 .map_err(to_pyvalue_err)?;
527
528 Python::attach(|py| {
529 let py_reports: PyResult<Vec<_>> = reports
530 .into_iter()
531 .map(|report| report.into_py_any(py))
532 .collect();
533 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
534 Ok(pylist)
535 })
536 })
537 }
538
539 #[pyo3(name = "request_fill_reports")]
552 fn py_request_fill_reports<'py>(
553 &self,
554 py: Python<'py>,
555 account_id: AccountId,
556 ) -> PyResult<Bound<'py, PyAny>> {
557 let client = self.clone();
558
559 pyo3_async_runtimes::tokio::future_into_py(py, async move {
560 let reports = client
561 .request_fill_reports(account_id, None, None)
562 .await
563 .map_err(to_pyvalue_err)?;
564
565 Python::attach(|py| {
566 let py_reports: PyResult<Vec<_>> = reports
567 .into_iter()
568 .map(|report| report.into_py_any(py))
569 .collect();
570 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
571 Ok(pylist)
572 })
573 })
574 }
575
576 #[pyo3(name = "request_position_reports")]
590 fn py_request_position_reports<'py>(
591 &self,
592 py: Python<'py>,
593 account_id: AccountId,
594 ) -> PyResult<Bound<'py, PyAny>> {
595 let client = self.clone();
596
597 pyo3_async_runtimes::tokio::future_into_py(py, async move {
598 let reports = client
599 .request_position_reports(account_id)
600 .await
601 .map_err(to_pyvalue_err)?;
602
603 Python::attach(|py| {
604 let py_reports: PyResult<Vec<_>> = reports
605 .into_iter()
606 .map(|report| report.into_py_any(py))
607 .collect();
608 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
609 Ok(pylist)
610 })
611 })
612 }
613
614 #[pyo3(name = "preview_aggressive_limit_order")]
615 fn py_preview_aggressive_limit_order<'py>(
616 &self,
617 py: Python<'py>,
618 instrument_id: InstrumentId,
619 quantity: Quantity,
620 side: OrderSide,
621 ) -> PyResult<Bound<'py, PyAny>> {
622 let symbol = instrument_id.symbol.inner();
623 let ax_side = AxOrderSide::from(side);
624 let qty_contracts = quantity_to_contracts(quantity).map_err(to_pyvalue_err)?;
625
626 let client = self.clone();
627
628 pyo3_async_runtimes::tokio::future_into_py(py, async move {
629 let request = PreviewAggressiveLimitOrderRequest::new(symbol, qty_contracts, ax_side);
630 let response = client
631 .inner
632 .preview_aggressive_limit_order(&request)
633 .await
634 .map_err(to_pyvalue_err)?;
635
636 let price = response
637 .limit_price
638 .map(|p| Price::from(p.to_string().as_str()));
639
640 Python::attach(|py| price.into_py_any(py))
641 })
642 }
643}
644
645impl From<AxHttpError> for PyErr {
646 fn from(error: AxHttpError) -> Self {
647 to_pyvalue_err(error)
648 }
649}