1use chrono::{DateTime, Utc};
19use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
20use nautilus_model::{
21 data::BarType,
22 enums::{OrderSide, OrderType, TimeInForce, TriggerType},
23 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
24 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
25 types::{Price, Quantity},
26};
27use pyo3::{
28 conversion::IntoPyObjectExt,
29 prelude::*,
30 types::{PyDict, PyList},
31};
32
33use crate::{
34 common::{credential::KrakenCredential, enums::KrakenEnvironment},
35 http::KrakenFuturesHttpClient,
36};
37
38#[pymethods]
39#[pyo3_stub_gen::derive::gen_stub_pymethods]
40impl KrakenFuturesHttpClient {
41 #[new]
47 #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, timeout_secs=60, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None, proxy_url=None, max_requests_per_second=5))]
48 #[expect(clippy::too_many_arguments)]
49 fn py_new(
50 api_key: Option<String>,
51 api_secret: Option<String>,
52 base_url: Option<String>,
53 demo: bool,
54 timeout_secs: u64,
55 max_retries: Option<u32>,
56 retry_delay_ms: Option<u64>,
57 retry_delay_max_ms: Option<u64>,
58 proxy_url: Option<String>,
59 max_requests_per_second: u32,
60 ) -> PyResult<Self> {
61 let environment = if demo {
62 KrakenEnvironment::Demo
63 } else {
64 KrakenEnvironment::Mainnet
65 };
66
67 if let Some(cred) = KrakenCredential::resolve_futures(api_key, api_secret, demo) {
68 let (k, s) = cred.into_parts();
69 Self::with_credentials(
70 k,
71 s,
72 environment,
73 base_url,
74 timeout_secs,
75 max_retries,
76 retry_delay_ms,
77 retry_delay_max_ms,
78 proxy_url,
79 max_requests_per_second,
80 )
81 .map_err(to_pyvalue_err)
82 } else {
83 Self::new(
84 environment,
85 base_url,
86 timeout_secs,
87 max_retries,
88 retry_delay_ms,
89 retry_delay_max_ms,
90 proxy_url,
91 max_requests_per_second,
92 )
93 .map_err(to_pyvalue_err)
94 }
95 }
96
97 #[getter]
98 #[pyo3(name = "base_url")]
99 #[must_use]
100 pub fn py_base_url(&self) -> String {
101 self.inner.base_url().to_string()
102 }
103
104 #[getter]
105 #[pyo3(name = "api_key")]
106 #[must_use]
107 pub fn py_api_key(&self) -> Option<&str> {
108 self.inner.credential().map(|c| c.api_key())
109 }
110
111 #[getter]
112 #[pyo3(name = "api_key_masked")]
113 #[must_use]
114 pub fn py_api_key_masked(&self) -> Option<String> {
115 self.inner.credential().map(|c| c.api_key_masked())
116 }
117
118 #[pyo3(name = "cache_instrument")]
120 fn py_cache_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
121 let inst_any = pyobject_to_instrument_any(py, instrument)?;
122 self.cache_instrument(inst_any);
123 Ok(())
124 }
125
126 #[pyo3(name = "cancel_all_requests")]
128 fn py_cancel_all_requests(&self) {
129 self.cancel_all_requests();
130 }
131
132 #[pyo3(name = "request_instruments")]
134 fn py_request_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
135 let client = self.clone();
136
137 pyo3_async_runtimes::tokio::future_into_py(py, async move {
138 let instruments = client
139 .request_instruments()
140 .await
141 .map_err(to_pyruntime_err)?;
142
143 Python::attach(|py| {
144 let py_instruments: PyResult<Vec<_>> = instruments
145 .into_iter()
146 .map(|inst| instrument_any_to_pyobject(py, inst))
147 .collect();
148 let pylist = PyList::new(py, py_instruments?).unwrap();
149 Ok(pylist.unbind())
150 })
151 })
152 }
153
154 #[pyo3(name = "request_instrument_statuses")]
156 fn py_request_instrument_statuses<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
157 let client = self.clone();
158
159 pyo3_async_runtimes::tokio::future_into_py(py, async move {
160 let statuses = client
161 .request_instrument_statuses()
162 .await
163 .map_err(to_pyruntime_err)?;
164
165 Python::attach(|py| {
166 let dict = PyDict::new(py);
167 for (instrument_id, action) in statuses {
168 dict.set_item(
169 instrument_id.into_bound_py_any(py)?,
170 action.into_bound_py_any(py)?,
171 )?;
172 }
173 Ok(dict.into_any().unbind())
174 })
175 })
176 }
177
178 #[pyo3(name = "request_trades")]
179 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
180 fn py_request_trades<'py>(
181 &self,
182 py: Python<'py>,
183 instrument_id: InstrumentId,
184 start: Option<DateTime<Utc>>,
185 end: Option<DateTime<Utc>>,
186 limit: Option<u64>,
187 ) -> PyResult<Bound<'py, PyAny>> {
188 let client = self.clone();
189
190 pyo3_async_runtimes::tokio::future_into_py(py, async move {
191 let trades = client
192 .request_trades(instrument_id, start, end, limit)
193 .await
194 .map_err(to_pyruntime_err)?;
195
196 Python::attach(|py| {
197 let py_trades: PyResult<Vec<_>> = trades
198 .into_iter()
199 .map(|trade| trade.into_py_any(py))
200 .collect();
201 let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
202 Ok(pylist)
203 })
204 })
205 }
206
207 #[pyo3(name = "request_mark_price")]
209 fn py_request_mark_price<'py>(
210 &self,
211 py: Python<'py>,
212 instrument_id: InstrumentId,
213 ) -> PyResult<Bound<'py, PyAny>> {
214 let client = self.clone();
215
216 pyo3_async_runtimes::tokio::future_into_py(py, async move {
217 let mark_price = client
218 .request_mark_price(instrument_id)
219 .await
220 .map_err(to_pyruntime_err)?;
221
222 Ok(mark_price)
223 })
224 }
225
226 #[pyo3(name = "request_index_price")]
227 fn py_request_index_price<'py>(
228 &self,
229 py: Python<'py>,
230 instrument_id: InstrumentId,
231 ) -> PyResult<Bound<'py, PyAny>> {
232 let client = self.clone();
233
234 pyo3_async_runtimes::tokio::future_into_py(py, async move {
235 let index_price = client
236 .request_index_price(instrument_id)
237 .await
238 .map_err(to_pyruntime_err)?;
239
240 Ok(index_price)
241 })
242 }
243
244 #[pyo3(name = "request_book_snapshot")]
246 #[pyo3(signature = (instrument_id, depth=None))]
247 fn py_request_book_snapshot<'py>(
248 &self,
249 py: Python<'py>,
250 instrument_id: InstrumentId,
251 depth: Option<u32>,
252 ) -> PyResult<Bound<'py, PyAny>> {
253 let client = self.clone();
254
255 pyo3_async_runtimes::tokio::future_into_py(py, async move {
256 let book = client
257 .request_book_snapshot(instrument_id, depth)
258 .await
259 .map_err(to_pyruntime_err)?;
260
261 Python::attach(|py| book.into_py_any(py))
262 })
263 }
264
265 #[pyo3(name = "request_bars")]
266 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
267 fn py_request_bars<'py>(
268 &self,
269 py: Python<'py>,
270 bar_type: BarType,
271 start: Option<DateTime<Utc>>,
272 end: Option<DateTime<Utc>>,
273 limit: Option<u64>,
274 ) -> PyResult<Bound<'py, PyAny>> {
275 let client = self.clone();
276
277 pyo3_async_runtimes::tokio::future_into_py(py, async move {
278 let bars = client
279 .request_bars(bar_type, start, end, limit)
280 .await
281 .map_err(to_pyruntime_err)?;
282
283 Python::attach(|py| {
284 let py_bars: PyResult<Vec<_>> =
285 bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
286 let pylist = PyList::new(py, py_bars?).unwrap().into_any().unbind();
287 Ok(pylist)
288 })
289 })
290 }
291
292 #[pyo3(name = "request_account_state")]
297 fn py_request_account_state<'py>(
298 &self,
299 py: Python<'py>,
300 account_id: AccountId,
301 ) -> PyResult<Bound<'py, PyAny>> {
302 let client = self.clone();
303
304 pyo3_async_runtimes::tokio::future_into_py(py, async move {
305 let account_state = client
306 .request_account_state(account_id)
307 .await
308 .map_err(to_pyruntime_err)?;
309
310 Python::attach(|py| account_state.into_pyobject(py).map(|o| o.unbind()))
311 })
312 }
313
314 #[pyo3(name = "request_order_status_reports")]
315 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None, open_only=false))]
316 fn py_request_order_status_reports<'py>(
317 &self,
318 py: Python<'py>,
319 account_id: AccountId,
320 instrument_id: Option<InstrumentId>,
321 start: Option<DateTime<Utc>>,
322 end: Option<DateTime<Utc>>,
323 open_only: bool,
324 ) -> PyResult<Bound<'py, PyAny>> {
325 let client = self.clone();
326
327 pyo3_async_runtimes::tokio::future_into_py(py, async move {
328 let reports = client
329 .request_order_status_reports(account_id, instrument_id, start, end, open_only)
330 .await
331 .map_err(to_pyruntime_err)?;
332
333 Python::attach(|py| {
334 let py_reports: PyResult<Vec<_>> = reports
335 .into_iter()
336 .map(|report| report.into_py_any(py))
337 .collect();
338 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
339 Ok(pylist)
340 })
341 })
342 }
343
344 #[pyo3(name = "request_fill_reports")]
345 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None))]
346 fn py_request_fill_reports<'py>(
347 &self,
348 py: Python<'py>,
349 account_id: AccountId,
350 instrument_id: Option<InstrumentId>,
351 start: Option<DateTime<Utc>>,
352 end: Option<DateTime<Utc>>,
353 ) -> PyResult<Bound<'py, PyAny>> {
354 let client = self.clone();
355
356 pyo3_async_runtimes::tokio::future_into_py(py, async move {
357 let reports = client
358 .request_fill_reports(account_id, instrument_id, start, end)
359 .await
360 .map_err(to_pyruntime_err)?;
361
362 Python::attach(|py| {
363 let py_reports: PyResult<Vec<_>> = reports
364 .into_iter()
365 .map(|report| report.into_py_any(py))
366 .collect();
367 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
368 Ok(pylist)
369 })
370 })
371 }
372
373 #[pyo3(name = "request_position_status_reports")]
374 #[pyo3(signature = (account_id, instrument_id=None))]
375 fn py_request_position_status_reports<'py>(
376 &self,
377 py: Python<'py>,
378 account_id: AccountId,
379 instrument_id: Option<InstrumentId>,
380 ) -> PyResult<Bound<'py, PyAny>> {
381 let client = self.clone();
382
383 pyo3_async_runtimes::tokio::future_into_py(py, async move {
384 let reports = client
385 .request_position_status_reports(account_id, instrument_id)
386 .await
387 .map_err(to_pyruntime_err)?;
388
389 Python::attach(|py| {
390 let py_reports: PyResult<Vec<_>> = reports
391 .into_iter()
392 .map(|report| report.into_py_any(py))
393 .collect();
394 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
395 Ok(pylist)
396 })
397 })
398 }
399
400 #[pyo3(name = "submit_order")]
402 #[pyo3(signature = (account_id, instrument_id, client_order_id, order_side, order_type, quantity, time_in_force, price=None, trigger_price=None, trigger_type=None, reduce_only=false, post_only=false))]
403 #[expect(clippy::too_many_arguments)]
404 fn py_submit_order<'py>(
405 &self,
406 py: Python<'py>,
407 account_id: AccountId,
408 instrument_id: InstrumentId,
409 client_order_id: ClientOrderId,
410 order_side: OrderSide,
411 order_type: OrderType,
412 quantity: Quantity,
413 time_in_force: TimeInForce,
414 price: Option<Price>,
415 trigger_price: Option<Price>,
416 trigger_type: Option<TriggerType>,
417 reduce_only: bool,
418 post_only: bool,
419 ) -> PyResult<Bound<'py, PyAny>> {
420 let client = self.clone();
421
422 pyo3_async_runtimes::tokio::future_into_py(py, async move {
423 let report = client
424 .submit_order(
425 account_id,
426 instrument_id,
427 client_order_id,
428 order_side,
429 order_type,
430 quantity,
431 time_in_force,
432 price,
433 trigger_price,
434 trigger_type,
435 reduce_only,
436 post_only,
437 )
438 .await
439 .map_err(to_pyruntime_err)?;
440
441 Python::attach(|py| report.into_pyobject(py).map(|o| o.unbind()))
442 })
443 }
444
445 #[pyo3(name = "modify_order")]
449 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None, quantity=None, price=None, trigger_price=None))]
450 #[expect(clippy::too_many_arguments)]
451 fn py_modify_order<'py>(
452 &self,
453 py: Python<'py>,
454 instrument_id: InstrumentId,
455 client_order_id: Option<ClientOrderId>,
456 venue_order_id: Option<VenueOrderId>,
457 quantity: Option<Quantity>,
458 price: Option<Price>,
459 trigger_price: Option<Price>,
460 ) -> PyResult<Bound<'py, PyAny>> {
461 let client = self.clone();
462
463 pyo3_async_runtimes::tokio::future_into_py(py, async move {
464 let new_venue_order_id = client
465 .modify_order(
466 instrument_id,
467 client_order_id,
468 venue_order_id,
469 quantity,
470 price,
471 trigger_price,
472 )
473 .await
474 .map_err(to_pyruntime_err)?;
475
476 Python::attach(|py| new_venue_order_id.into_pyobject(py).map(|o| o.unbind()))
477 })
478 }
479
480 #[pyo3(name = "cancel_order")]
482 #[pyo3(signature = (account_id, instrument_id, client_order_id=None, venue_order_id=None))]
483 fn py_cancel_order<'py>(
484 &self,
485 py: Python<'py>,
486 account_id: AccountId,
487 instrument_id: InstrumentId,
488 client_order_id: Option<ClientOrderId>,
489 venue_order_id: Option<VenueOrderId>,
490 ) -> PyResult<Bound<'py, PyAny>> {
491 let client = self.clone();
492
493 pyo3_async_runtimes::tokio::future_into_py(py, async move {
494 client
495 .cancel_order(account_id, instrument_id, client_order_id, venue_order_id)
496 .await
497 .map_err(to_pyruntime_err)
498 })
499 }
500
501 #[pyo3(name = "cancel_all_orders")]
502 #[pyo3(signature = (instrument_id=None))]
503 fn py_cancel_all_orders<'py>(
504 &self,
505 py: Python<'py>,
506 instrument_id: Option<InstrumentId>,
507 ) -> PyResult<Bound<'py, PyAny>> {
508 let client = self.clone();
509
510 pyo3_async_runtimes::tokio::future_into_py(py, async move {
511 let symbol = instrument_id.map(|id| id.symbol.to_string());
512 let response = client
513 .inner
514 .cancel_all_orders(symbol)
515 .await
516 .map_err(to_pyruntime_err)?;
517
518 Ok(response.cancel_status.cancelled_orders.len())
519 })
520 }
521
522 #[pyo3(name = "cancel_orders_batch")]
532 fn py_cancel_orders_batch<'py>(
533 &self,
534 py: Python<'py>,
535 venue_order_ids: Vec<VenueOrderId>,
536 ) -> PyResult<Bound<'py, PyAny>> {
537 let client = self.clone();
538
539 pyo3_async_runtimes::tokio::future_into_py(py, async move {
540 client
541 .cancel_orders_batch(venue_order_ids)
542 .await
543 .map_err(to_pyruntime_err)
544 })
545 }
546}
547
548#[pymethods]
551impl KrakenFuturesHttpClient {
552 #[pyo3(name = "submit_orders_batch")]
557 #[expect(clippy::type_complexity)]
558 fn py_submit_orders_batch<'py>(
559 &self,
560 py: Python<'py>,
561 orders: Vec<(
562 InstrumentId,
563 ClientOrderId,
564 OrderSide,
565 OrderType,
566 Quantity,
567 TimeInForce,
568 Option<Price>,
569 Option<Price>,
570 Option<TriggerType>,
571 bool,
572 bool,
573 )>,
574 ) -> PyResult<Bound<'py, PyAny>> {
575 let client = self.clone();
576
577 pyo3_async_runtimes::tokio::future_into_py(py, async move {
578 let statuses = client
579 .submit_orders_batch(orders)
580 .await
581 .map_err(to_pyruntime_err)?;
582
583 let result: Vec<String> = statuses.into_iter().map(|s| s.status).collect();
584 Ok(result)
585 })
586 }
587
588 #[expect(clippy::type_complexity)]
590 #[pyo3(name = "edit_orders_batch")]
591 fn py_edit_orders_batch<'py>(
592 &self,
593 py: Python<'py>,
594 orders: Vec<(
595 InstrumentId,
596 Option<ClientOrderId>,
597 Option<VenueOrderId>,
598 Option<Quantity>,
599 Option<Price>,
600 Option<Price>,
601 )>,
602 ) -> PyResult<Bound<'py, PyAny>> {
603 let client = self.clone();
604
605 pyo3_async_runtimes::tokio::future_into_py(py, async move {
606 client
607 .edit_orders_batch(orders)
608 .await
609 .map_err(to_pyruntime_err)
610 })
611 }
612}