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