1use chrono::{DateTime, Utc};
19use nautilus_core::{datetime::datetime_to_unix_nanos, python::to_pyvalue_err};
20use nautilus_model::{
21 data::BarType,
22 enums::{OrderSide, OrderType, TimeInForce},
23 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
24 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
25 types::{Price, Quantity},
26};
27use pyo3::{IntoPyObjectExt, prelude::*, types::PyList};
28use rust_decimal::Decimal;
29
30use crate::{
31 common::{
32 enums::{AxCandleWidth, AxOrderSide},
33 parse::quantity_to_contracts,
34 },
35 http::{client::AxHttpClient, error::AxHttpError, models::PreviewAggressiveLimitOrderRequest},
36};
37
38#[pymethods]
39#[pyo3_stub_gen::derive::gen_stub_pymethods]
40impl AxHttpClient {
41 #[new]
46 #[pyo3(signature = (
47 base_url=None,
48 orders_base_url=None,
49 timeout_secs=60,
50 max_retries=3,
51 retry_delay_ms=1000,
52 retry_delay_max_ms=10_000,
53 proxy_url=None,
54 ))]
55 fn py_new(
56 base_url: Option<String>,
57 orders_base_url: Option<String>,
58 timeout_secs: u64,
59 max_retries: u32,
60 retry_delay_ms: u64,
61 retry_delay_max_ms: u64,
62 proxy_url: Option<String>,
63 ) -> PyResult<Self> {
64 Self::new(
65 base_url,
66 orders_base_url,
67 timeout_secs,
68 max_retries,
69 retry_delay_ms,
70 retry_delay_max_ms,
71 proxy_url,
72 )
73 .map_err(to_pyvalue_err)
74 }
75
76 #[staticmethod]
78 #[pyo3(name = "with_credentials")]
79 #[pyo3(signature = (
80 api_key,
81 api_secret,
82 base_url=None,
83 orders_base_url=None,
84 timeout_secs=60,
85 max_retries=3,
86 retry_delay_ms=1000,
87 retry_delay_max_ms=10_000,
88 proxy_url=None,
89 ))]
90 #[expect(clippy::too_many_arguments)]
91 fn py_with_credentials(
92 api_key: String,
93 api_secret: String,
94 base_url: Option<String>,
95 orders_base_url: Option<String>,
96 timeout_secs: u64,
97 max_retries: u32,
98 retry_delay_ms: u64,
99 retry_delay_max_ms: u64,
100 proxy_url: Option<String>,
101 ) -> PyResult<Self> {
102 Self::with_credentials(
103 api_key,
104 api_secret,
105 base_url,
106 orders_base_url,
107 timeout_secs,
108 max_retries,
109 retry_delay_ms,
110 retry_delay_max_ms,
111 proxy_url,
112 )
113 .map_err(to_pyvalue_err)
114 }
115
116 #[getter]
118 #[pyo3(name = "base_url")]
119 #[must_use]
120 pub fn py_base_url(&self) -> &str {
121 self.base_url()
122 }
123
124 #[getter]
126 #[pyo3(name = "api_key_masked")]
127 #[must_use]
128 pub fn py_api_key_masked(&self) -> String {
129 self.api_key_masked()
130 }
131
132 #[pyo3(name = "cancel_all_requests")]
134 pub fn py_cancel_all_requests(&self) {
135 self.cancel_all_requests();
136 }
137
138 #[pyo3(name = "cancel_all_orders")]
140 pub fn py_cancel_all_orders<'py>(
141 &self,
142 py: Python<'py>,
143 instrument_id: InstrumentId,
144 ) -> PyResult<Bound<'py, PyAny>> {
145 let client = self.clone();
146 pyo3_async_runtimes::tokio::future_into_py(py, async move {
147 client
148 .cancel_all_orders(instrument_id)
149 .await
150 .map_err(to_pyvalue_err)
151 })
152 }
153
154 #[pyo3(name = "cache_instrument")]
158 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
159 self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
160 Ok(())
161 }
162
163 #[pyo3(name = "authenticate")]
167 #[pyo3(signature = (api_key, api_secret, expiration_seconds=86400))]
168 fn py_authenticate<'py>(
169 &self,
170 py: Python<'py>,
171 api_key: String,
172 api_secret: String,
173 expiration_seconds: i32,
174 ) -> PyResult<Bound<'py, PyAny>> {
175 let client = self.clone();
176
177 pyo3_async_runtimes::tokio::future_into_py(py, async move {
178 client
179 .authenticate(&api_key, &api_secret, expiration_seconds)
180 .await
181 .map_err(to_pyvalue_err)
182 })
183 }
184
185 #[pyo3(name = "authenticate_auto")]
195 #[pyo3(signature = (expiration_seconds=86400))]
196 fn py_authenticate_auto<'py>(
197 &self,
198 py: Python<'py>,
199 expiration_seconds: i32,
200 ) -> PyResult<Bound<'py, PyAny>> {
201 let client = self.clone();
202
203 pyo3_async_runtimes::tokio::future_into_py(py, async move {
204 client
205 .authenticate_auto(expiration_seconds)
206 .await
207 .map_err(to_pyvalue_err)
208 })
209 }
210
211 #[pyo3(name = "request_instruments")]
213 #[pyo3(signature = (maker_fee=None, taker_fee=None))]
214 fn py_request_instruments<'py>(
215 &self,
216 py: Python<'py>,
217 maker_fee: Option<Decimal>,
218 taker_fee: Option<Decimal>,
219 ) -> PyResult<Bound<'py, PyAny>> {
220 let client = self.clone();
221
222 pyo3_async_runtimes::tokio::future_into_py(py, async move {
223 let instruments = client
224 .request_instruments(maker_fee, taker_fee)
225 .await
226 .map_err(to_pyvalue_err)?;
227
228 Python::attach(|py| {
229 let py_instruments: PyResult<Vec<_>> = instruments
230 .into_iter()
231 .map(|inst| instrument_any_to_pyobject(py, inst))
232 .collect();
233 let pylist = PyList::new(py, py_instruments?)?.into_any().unbind();
234 Ok(pylist)
235 })
236 })
237 }
238
239 #[pyo3(name = "request_trade_ticks")]
246 #[pyo3(signature = (instrument_id, limit=None, start=None, end=None))]
247 fn py_request_trade_ticks<'py>(
248 &self,
249 py: Python<'py>,
250 instrument_id: InstrumentId,
251 limit: Option<i32>,
252 start: Option<DateTime<Utc>>,
253 end: Option<DateTime<Utc>>,
254 ) -> PyResult<Bound<'py, PyAny>> {
255 let client = self.clone();
256 let symbol = instrument_id.symbol.inner();
257 let start_nanos = datetime_to_unix_nanos(start);
258 let end_nanos = datetime_to_unix_nanos(end);
259
260 pyo3_async_runtimes::tokio::future_into_py(py, async move {
261 let trades = client
262 .request_trade_ticks(symbol, limit, start_nanos, end_nanos)
263 .await
264 .map_err(to_pyvalue_err)?;
265
266 Python::attach(|py| {
267 let py_trades: PyResult<Vec<_>> = trades
268 .into_iter()
269 .map(|trade| trade.into_py_any(py))
270 .collect();
271 let pylist = PyList::new(py, py_trades?)?.into_any().unbind();
272 Ok(pylist)
273 })
274 })
275 }
276
277 #[pyo3(name = "request_bars")]
281 #[pyo3(signature = (bar_type, start=None, end=None))]
282 fn py_request_bars<'py>(
283 &self,
284 py: Python<'py>,
285 bar_type: BarType,
286 start: Option<DateTime<Utc>>,
287 end: Option<DateTime<Utc>>,
288 ) -> PyResult<Bound<'py, PyAny>> {
289 let client = self.clone();
290 let symbol = bar_type.instrument_id().symbol.inner();
291 let width = AxCandleWidth::try_from(&bar_type.spec()).map_err(to_pyvalue_err)?;
292
293 pyo3_async_runtimes::tokio::future_into_py(py, async move {
294 let bars = client
295 .request_bars(symbol, start, end, width)
296 .await
297 .map_err(to_pyvalue_err)?;
298
299 Python::attach(|py| {
300 let py_bars: PyResult<Vec<_>> =
301 bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
302 let pylist = PyList::new(py, py_bars?)?.into_any().unbind();
303 Ok(pylist)
304 })
305 })
306 }
307
308 #[pyo3(name = "request_funding_rates")]
310 #[pyo3(signature = (instrument_id, start=None, end=None))]
311 fn py_request_funding_rates<'py>(
312 &self,
313 py: Python<'py>,
314 instrument_id: InstrumentId,
315 start: Option<DateTime<Utc>>,
316 end: Option<DateTime<Utc>>,
317 ) -> PyResult<Bound<'py, PyAny>> {
318 let client = self.clone();
319
320 pyo3_async_runtimes::tokio::future_into_py(py, async move {
321 let funding_rates = client
322 .request_funding_rates(instrument_id, start, end)
323 .await
324 .map_err(to_pyvalue_err)?;
325
326 Python::attach(|py| {
327 let py_rates: PyResult<Vec<_>> = funding_rates
328 .into_iter()
329 .map(|rate| rate.into_py_any(py))
330 .collect();
331 let pylist = PyList::new(py, py_rates?)?.into_any().unbind();
332 Ok(pylist)
333 })
334 })
335 }
336
337 #[pyo3(name = "request_account_state")]
339 fn py_request_account_state<'py>(
340 &self,
341 py: Python<'py>,
342 account_id: AccountId,
343 ) -> PyResult<Bound<'py, PyAny>> {
344 let client = self.clone();
345
346 pyo3_async_runtimes::tokio::future_into_py(py, async move {
347 let account_state = client
348 .request_account_state(account_id)
349 .await
350 .map_err(to_pyvalue_err)?;
351
352 Python::attach(|py| account_state.into_py_any(py))
353 })
354 }
355
356 #[pyo3(name = "request_order_status")]
362 #[pyo3(signature = (
363 account_id,
364 instrument_id,
365 order_side,
366 order_type,
367 time_in_force,
368 client_order_id=None,
369 venue_order_id=None,
370 ))]
371 #[expect(clippy::too_many_arguments)]
372 fn py_request_order_status<'py>(
373 &self,
374 py: Python<'py>,
375 account_id: AccountId,
376 instrument_id: InstrumentId,
377 order_side: OrderSide,
378 order_type: OrderType,
379 time_in_force: TimeInForce,
380 client_order_id: Option<ClientOrderId>,
381 venue_order_id: Option<VenueOrderId>,
382 ) -> PyResult<Bound<'py, PyAny>> {
383 let client = self.clone();
384
385 pyo3_async_runtimes::tokio::future_into_py(py, async move {
386 let report = client
387 .request_order_status(
388 account_id,
389 instrument_id,
390 client_order_id,
391 venue_order_id,
392 order_side,
393 order_type,
394 time_in_force,
395 )
396 .await
397 .map_err(to_pyvalue_err)?;
398
399 Python::attach(|py| report.into_py_any(py))
400 })
401 }
402
403 #[pyo3(name = "request_order_status_reports")]
410 fn py_request_order_status_reports<'py>(
411 &self,
412 py: Python<'py>,
413 account_id: AccountId,
414 ) -> PyResult<Bound<'py, PyAny>> {
415 let client = self.clone();
416
417 pyo3_async_runtimes::tokio::future_into_py(py, async move {
418 let reports = client
419 .request_order_status_reports(account_id, None::<fn(u64) -> Option<ClientOrderId>>)
420 .await
421 .map_err(to_pyvalue_err)?;
422
423 Python::attach(|py| {
424 let py_reports: PyResult<Vec<_>> = reports
425 .into_iter()
426 .map(|report| report.into_py_any(py))
427 .collect();
428 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
429 Ok(pylist)
430 })
431 })
432 }
433
434 #[pyo3(name = "request_fill_reports")]
438 fn py_request_fill_reports<'py>(
439 &self,
440 py: Python<'py>,
441 account_id: AccountId,
442 ) -> PyResult<Bound<'py, PyAny>> {
443 let client = self.clone();
444
445 pyo3_async_runtimes::tokio::future_into_py(py, async move {
446 let reports = client
447 .request_fill_reports(account_id)
448 .await
449 .map_err(to_pyvalue_err)?;
450
451 Python::attach(|py| {
452 let py_reports: PyResult<Vec<_>> = reports
453 .into_iter()
454 .map(|report| report.into_py_any(py))
455 .collect();
456 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
457 Ok(pylist)
458 })
459 })
460 }
461
462 #[pyo3(name = "request_position_reports")]
466 fn py_request_position_reports<'py>(
467 &self,
468 py: Python<'py>,
469 account_id: AccountId,
470 ) -> PyResult<Bound<'py, PyAny>> {
471 let client = self.clone();
472
473 pyo3_async_runtimes::tokio::future_into_py(py, async move {
474 let reports = client
475 .request_position_reports(account_id)
476 .await
477 .map_err(to_pyvalue_err)?;
478
479 Python::attach(|py| {
480 let py_reports: PyResult<Vec<_>> = reports
481 .into_iter()
482 .map(|report| report.into_py_any(py))
483 .collect();
484 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
485 Ok(pylist)
486 })
487 })
488 }
489
490 #[pyo3(name = "preview_aggressive_limit_order")]
491 fn py_preview_aggressive_limit_order<'py>(
492 &self,
493 py: Python<'py>,
494 instrument_id: InstrumentId,
495 quantity: Quantity,
496 side: OrderSide,
497 ) -> PyResult<Bound<'py, PyAny>> {
498 let symbol = instrument_id.symbol.inner();
499 let ax_side = AxOrderSide::try_from(side).map_err(to_pyvalue_err)?;
500 let qty_contracts = quantity_to_contracts(quantity).map_err(to_pyvalue_err)?;
501
502 let client = self.clone();
503
504 pyo3_async_runtimes::tokio::future_into_py(py, async move {
505 let request = PreviewAggressiveLimitOrderRequest::new(symbol, qty_contracts, ax_side);
506 let response = client
507 .inner
508 .preview_aggressive_limit_order(&request)
509 .await
510 .map_err(to_pyvalue_err)?;
511
512 let price = response
513 .limit_price
514 .map(|p| Price::from(p.to_string().as_str()));
515
516 Python::attach(|py| price.into_py_any(py))
517 })
518 }
519}
520
521impl From<AxHttpError> for PyErr {
522 fn from(error: AxHttpError) -> Self {
523 to_pyvalue_err(error)
524 }
525}