nautilus_kraken/python/
http_futures.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for the Kraken Futures HTTP client.
17
18use chrono::{DateTime, Utc};
19use nautilus_core::python::{to_pyruntime_err, 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::{conversion::IntoPyObjectExt, prelude::*, types::PyList};
28
29use crate::{
30    common::{credential::KrakenCredential, enums::KrakenEnvironment},
31    http::KrakenFuturesHttpClient,
32};
33
34#[pymethods]
35impl KrakenFuturesHttpClient {
36    #[new]
37    #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, timeout_secs=None, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None, proxy_url=None, max_requests_per_second=None))]
38    #[allow(clippy::too_many_arguments)]
39    fn py_new(
40        api_key: Option<String>,
41        api_secret: Option<String>,
42        base_url: Option<String>,
43        demo: bool,
44        timeout_secs: Option<u64>,
45        max_retries: Option<u32>,
46        retry_delay_ms: Option<u64>,
47        retry_delay_max_ms: Option<u64>,
48        proxy_url: Option<String>,
49        max_requests_per_second: Option<u32>,
50    ) -> PyResult<Self> {
51        let timeout = timeout_secs.or(Some(60));
52
53        let environment = if demo {
54            KrakenEnvironment::Demo
55        } else {
56            KrakenEnvironment::Mainnet
57        };
58
59        if let Some(cred) = KrakenCredential::resolve_futures(api_key, api_secret, demo) {
60            let (k, s) = cred.into_parts();
61            Self::with_credentials(
62                k,
63                s,
64                environment,
65                base_url,
66                timeout,
67                max_retries,
68                retry_delay_ms,
69                retry_delay_max_ms,
70                proxy_url,
71                max_requests_per_second,
72            )
73            .map_err(to_pyvalue_err)
74        } else {
75            Self::new(
76                environment,
77                base_url,
78                timeout,
79                max_retries,
80                retry_delay_ms,
81                retry_delay_max_ms,
82                proxy_url,
83                max_requests_per_second,
84            )
85            .map_err(to_pyvalue_err)
86        }
87    }
88
89    #[getter]
90    #[pyo3(name = "base_url")]
91    #[must_use]
92    pub fn py_base_url(&self) -> String {
93        self.inner.base_url().to_string()
94    }
95
96    #[getter]
97    #[pyo3(name = "api_key")]
98    #[must_use]
99    pub fn py_api_key(&self) -> Option<&str> {
100        self.inner.credential().map(|c| c.api_key())
101    }
102
103    #[getter]
104    #[pyo3(name = "api_key_masked")]
105    #[must_use]
106    pub fn py_api_key_masked(&self) -> Option<String> {
107        self.inner.credential().map(|c| c.api_key_masked())
108    }
109
110    #[pyo3(name = "cache_instrument")]
111    fn py_cache_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
112        let inst_any = pyobject_to_instrument_any(py, instrument)?;
113        self.cache_instrument(inst_any);
114        Ok(())
115    }
116
117    #[pyo3(name = "cancel_all_requests")]
118    fn py_cancel_all_requests(&self) {
119        self.cancel_all_requests();
120    }
121
122    #[pyo3(name = "request_instruments")]
123    fn py_request_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
124        let client = self.clone();
125
126        pyo3_async_runtimes::tokio::future_into_py(py, async move {
127            let instruments = client
128                .request_instruments()
129                .await
130                .map_err(to_pyruntime_err)?;
131
132            Python::attach(|py| {
133                let py_instruments: PyResult<Vec<_>> = instruments
134                    .into_iter()
135                    .map(|inst| instrument_any_to_pyobject(py, inst))
136                    .collect();
137                let pylist = PyList::new(py, py_instruments?).unwrap();
138                Ok(pylist.unbind())
139            })
140        })
141    }
142
143    #[pyo3(name = "request_trades")]
144    #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
145    fn py_request_trades<'py>(
146        &self,
147        py: Python<'py>,
148        instrument_id: InstrumentId,
149        start: Option<DateTime<Utc>>,
150        end: Option<DateTime<Utc>>,
151        limit: Option<u64>,
152    ) -> PyResult<Bound<'py, PyAny>> {
153        let client = self.clone();
154
155        pyo3_async_runtimes::tokio::future_into_py(py, async move {
156            let trades = client
157                .request_trades(instrument_id, start, end, limit)
158                .await
159                .map_err(to_pyruntime_err)?;
160
161            Python::attach(|py| {
162                let py_trades: PyResult<Vec<_>> = trades
163                    .into_iter()
164                    .map(|trade| trade.into_py_any(py))
165                    .collect();
166                let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
167                Ok(pylist)
168            })
169        })
170    }
171
172    #[pyo3(name = "request_mark_price")]
173    fn py_request_mark_price<'py>(
174        &self,
175        py: Python<'py>,
176        instrument_id: InstrumentId,
177    ) -> PyResult<Bound<'py, PyAny>> {
178        let client = self.clone();
179
180        pyo3_async_runtimes::tokio::future_into_py(py, async move {
181            let mark_price = client
182                .request_mark_price(instrument_id)
183                .await
184                .map_err(to_pyruntime_err)?;
185
186            Ok(mark_price)
187        })
188    }
189
190    #[pyo3(name = "request_index_price")]
191    fn py_request_index_price<'py>(
192        &self,
193        py: Python<'py>,
194        instrument_id: InstrumentId,
195    ) -> PyResult<Bound<'py, PyAny>> {
196        let client = self.clone();
197
198        pyo3_async_runtimes::tokio::future_into_py(py, async move {
199            let index_price = client
200                .request_index_price(instrument_id)
201                .await
202                .map_err(to_pyruntime_err)?;
203
204            Ok(index_price)
205        })
206    }
207
208    #[pyo3(name = "request_bars")]
209    #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
210    fn py_request_bars<'py>(
211        &self,
212        py: Python<'py>,
213        bar_type: BarType,
214        start: Option<DateTime<Utc>>,
215        end: Option<DateTime<Utc>>,
216        limit: Option<u64>,
217    ) -> PyResult<Bound<'py, PyAny>> {
218        let client = self.clone();
219
220        pyo3_async_runtimes::tokio::future_into_py(py, async move {
221            let bars = client
222                .request_bars(bar_type, start, end, limit)
223                .await
224                .map_err(to_pyruntime_err)?;
225
226            Python::attach(|py| {
227                let py_bars: PyResult<Vec<_>> =
228                    bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
229                let pylist = PyList::new(py, py_bars?).unwrap().into_any().unbind();
230                Ok(pylist)
231            })
232        })
233    }
234
235    #[pyo3(name = "request_account_state")]
236    fn py_request_account_state<'py>(
237        &self,
238        py: Python<'py>,
239        account_id: AccountId,
240    ) -> PyResult<Bound<'py, PyAny>> {
241        let client = self.clone();
242
243        pyo3_async_runtimes::tokio::future_into_py(py, async move {
244            let account_state = client
245                .request_account_state(account_id)
246                .await
247                .map_err(to_pyruntime_err)?;
248
249            Python::attach(|py| account_state.into_pyobject(py).map(|o| o.unbind()))
250        })
251    }
252
253    #[pyo3(name = "request_order_status_reports")]
254    #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None, open_only=false))]
255    fn py_request_order_status_reports<'py>(
256        &self,
257        py: Python<'py>,
258        account_id: AccountId,
259        instrument_id: Option<InstrumentId>,
260        start: Option<DateTime<Utc>>,
261        end: Option<DateTime<Utc>>,
262        open_only: bool,
263    ) -> PyResult<Bound<'py, PyAny>> {
264        let client = self.clone();
265
266        pyo3_async_runtimes::tokio::future_into_py(py, async move {
267            let reports = client
268                .request_order_status_reports(account_id, instrument_id, start, end, open_only)
269                .await
270                .map_err(to_pyruntime_err)?;
271
272            Python::attach(|py| {
273                let py_reports: PyResult<Vec<_>> = reports
274                    .into_iter()
275                    .map(|report| report.into_py_any(py))
276                    .collect();
277                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
278                Ok(pylist)
279            })
280        })
281    }
282
283    #[pyo3(name = "request_fill_reports")]
284    #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None))]
285    fn py_request_fill_reports<'py>(
286        &self,
287        py: Python<'py>,
288        account_id: AccountId,
289        instrument_id: Option<InstrumentId>,
290        start: Option<DateTime<Utc>>,
291        end: Option<DateTime<Utc>>,
292    ) -> PyResult<Bound<'py, PyAny>> {
293        let client = self.clone();
294
295        pyo3_async_runtimes::tokio::future_into_py(py, async move {
296            let reports = client
297                .request_fill_reports(account_id, instrument_id, start, end)
298                .await
299                .map_err(to_pyruntime_err)?;
300
301            Python::attach(|py| {
302                let py_reports: PyResult<Vec<_>> = reports
303                    .into_iter()
304                    .map(|report| report.into_py_any(py))
305                    .collect();
306                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
307                Ok(pylist)
308            })
309        })
310    }
311
312    #[pyo3(name = "request_position_status_reports")]
313    #[pyo3(signature = (account_id, instrument_id=None))]
314    fn py_request_position_status_reports<'py>(
315        &self,
316        py: Python<'py>,
317        account_id: AccountId,
318        instrument_id: Option<InstrumentId>,
319    ) -> PyResult<Bound<'py, PyAny>> {
320        let client = self.clone();
321
322        pyo3_async_runtimes::tokio::future_into_py(py, async move {
323            let reports = client
324                .request_position_status_reports(account_id, instrument_id)
325                .await
326                .map_err(to_pyruntime_err)?;
327
328            Python::attach(|py| {
329                let py_reports: PyResult<Vec<_>> = reports
330                    .into_iter()
331                    .map(|report| report.into_py_any(py))
332                    .collect();
333                let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
334                Ok(pylist)
335            })
336        })
337    }
338
339    #[pyo3(name = "submit_order")]
340    #[pyo3(signature = (account_id, instrument_id, client_order_id, order_side, order_type, quantity, time_in_force, price=None, trigger_price=None, reduce_only=false, post_only=false))]
341    #[allow(clippy::too_many_arguments)]
342    fn py_submit_order<'py>(
343        &self,
344        py: Python<'py>,
345        account_id: AccountId,
346        instrument_id: InstrumentId,
347        client_order_id: ClientOrderId,
348        order_side: OrderSide,
349        order_type: OrderType,
350        quantity: Quantity,
351        time_in_force: TimeInForce,
352        price: Option<Price>,
353        trigger_price: Option<Price>,
354        reduce_only: bool,
355        post_only: bool,
356    ) -> PyResult<Bound<'py, PyAny>> {
357        let client = self.clone();
358
359        pyo3_async_runtimes::tokio::future_into_py(py, async move {
360            let report = client
361                .submit_order(
362                    account_id,
363                    instrument_id,
364                    client_order_id,
365                    order_side,
366                    order_type,
367                    quantity,
368                    time_in_force,
369                    price,
370                    trigger_price,
371                    reduce_only,
372                    post_only,
373                )
374                .await
375                .map_err(to_pyruntime_err)?;
376
377            Python::attach(|py| report.into_pyobject(py).map(|o| o.unbind()))
378        })
379    }
380
381    #[pyo3(name = "modify_order")]
382    #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None, quantity=None, price=None, trigger_price=None))]
383    #[allow(clippy::too_many_arguments)]
384    fn py_modify_order<'py>(
385        &self,
386        py: Python<'py>,
387        instrument_id: InstrumentId,
388        client_order_id: Option<ClientOrderId>,
389        venue_order_id: Option<VenueOrderId>,
390        quantity: Option<Quantity>,
391        price: Option<Price>,
392        trigger_price: Option<Price>,
393    ) -> PyResult<Bound<'py, PyAny>> {
394        let client = self.clone();
395
396        pyo3_async_runtimes::tokio::future_into_py(py, async move {
397            let new_venue_order_id = client
398                .modify_order(
399                    instrument_id,
400                    client_order_id,
401                    venue_order_id,
402                    quantity,
403                    price,
404                    trigger_price,
405                )
406                .await
407                .map_err(to_pyruntime_err)?;
408
409            Python::attach(|py| new_venue_order_id.into_pyobject(py).map(|o| o.unbind()))
410        })
411    }
412
413    #[pyo3(name = "cancel_order")]
414    #[pyo3(signature = (account_id, instrument_id, client_order_id=None, venue_order_id=None))]
415    fn py_cancel_order<'py>(
416        &self,
417        py: Python<'py>,
418        account_id: AccountId,
419        instrument_id: InstrumentId,
420        client_order_id: Option<ClientOrderId>,
421        venue_order_id: Option<VenueOrderId>,
422    ) -> PyResult<Bound<'py, PyAny>> {
423        let client = self.clone();
424
425        pyo3_async_runtimes::tokio::future_into_py(py, async move {
426            client
427                .cancel_order(account_id, instrument_id, client_order_id, venue_order_id)
428                .await
429                .map_err(to_pyruntime_err)
430        })
431    }
432
433    #[pyo3(name = "cancel_all_orders")]
434    #[pyo3(signature = (instrument_id=None))]
435    fn py_cancel_all_orders<'py>(
436        &self,
437        py: Python<'py>,
438        instrument_id: Option<InstrumentId>,
439    ) -> PyResult<Bound<'py, PyAny>> {
440        let client = self.clone();
441
442        pyo3_async_runtimes::tokio::future_into_py(py, async move {
443            let symbol = instrument_id.map(|id| id.symbol.to_string());
444            let response = client
445                .inner
446                .cancel_all_orders(symbol)
447                .await
448                .map_err(to_pyruntime_err)?;
449
450            Ok(response.cancel_status.cancelled_orders.len())
451        })
452    }
453
454    #[pyo3(name = "cancel_orders_batch")]
455    fn py_cancel_orders_batch<'py>(
456        &self,
457        py: Python<'py>,
458        venue_order_ids: Vec<VenueOrderId>,
459    ) -> PyResult<Bound<'py, PyAny>> {
460        let client = self.clone();
461
462        pyo3_async_runtimes::tokio::future_into_py(py, async move {
463            client
464                .cancel_orders_batch(venue_order_ids)
465                .await
466                .map_err(to_pyruntime_err)
467        })
468    }
469}