Skip to main content

nautilus_binance/python/
http_futures.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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 Binance Futures HTTP client.
17
18use chrono::{TimeZone, Utc};
19use nautilus_core::python::{IntoPyObjectNautilusExt, 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,
25    types::{Price, Quantity},
26};
27use pyo3::{
28    IntoPyObjectExt,
29    prelude::*,
30    types::{PyDict, PyList},
31};
32
33use crate::{
34    common::enums::{BinanceEnvironment, BinancePositionSide, BinanceProductType},
35    futures::http::{
36        client::BinanceFuturesHttpClient,
37        models::BatchOrderResult,
38        query::{BatchCancelItem, BatchModifyItem, BatchOrderItem},
39    },
40};
41
42#[pymethods]
43impl BinanceFuturesHttpClient {
44    #[new]
45    #[pyo3(signature = (
46        product_type,
47        environment=BinanceEnvironment::Mainnet,
48        api_key=None,
49        api_secret=None,
50        base_url=None,
51        recv_window=None,
52        timeout_secs=None,
53        proxy_url=None,
54    ))]
55    #[allow(clippy::too_many_arguments)]
56    fn py_new(
57        product_type: BinanceProductType,
58        environment: BinanceEnvironment,
59        api_key: Option<String>,
60        api_secret: Option<String>,
61        base_url: Option<String>,
62        recv_window: Option<u64>,
63        timeout_secs: Option<u64>,
64        proxy_url: Option<String>,
65    ) -> PyResult<Self> {
66        Self::new(
67            product_type,
68            environment,
69            api_key,
70            api_secret,
71            base_url,
72            recv_window,
73            timeout_secs,
74            proxy_url,
75        )
76        .map_err(to_pyvalue_err)
77    }
78
79    #[getter]
80    #[pyo3(name = "product_type")]
81    #[must_use]
82    pub fn py_product_type(&self) -> BinanceProductType {
83        self.product_type()
84    }
85
86    #[pyo3(name = "server_time")]
87    fn py_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
88        let client = self.clone();
89        pyo3_async_runtimes::tokio::future_into_py(py, async move {
90            let timestamp = client.server_time().await.map_err(to_pyvalue_err)?;
91            Python::attach(|py| Ok(timestamp.server_time.into_pyobject(py)?.into_any().unbind()))
92        })
93    }
94
95    #[pyo3(name = "query_hedge_mode")]
96    fn py_query_hedge_mode<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
97        let client = self.clone();
98
99        pyo3_async_runtimes::tokio::future_into_py(py, async move {
100            let response = client.query_hedge_mode().await.map_err(to_pyvalue_err)?;
101            Python::attach(|py| {
102                Ok(response
103                    .dual_side_position
104                    .into_pyobject(py)?
105                    .to_owned()
106                    .into_any()
107                    .unbind())
108            })
109        })
110    }
111
112    #[pyo3(name = "create_listen_key")]
113    fn py_create_listen_key<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
114        let client = self.clone();
115
116        pyo3_async_runtimes::tokio::future_into_py(py, async move {
117            let response = client.create_listen_key().await.map_err(to_pyvalue_err)?;
118            Python::attach(|py| Ok(response.listen_key.into_pyobject(py)?.into_any().unbind()))
119        })
120    }
121
122    #[pyo3(name = "keepalive_listen_key")]
123    fn py_keepalive_listen_key<'py>(
124        &self,
125        py: Python<'py>,
126        listen_key: String,
127    ) -> PyResult<Bound<'py, PyAny>> {
128        let client = self.clone();
129
130        pyo3_async_runtimes::tokio::future_into_py(py, async move {
131            client
132                .keepalive_listen_key(&listen_key)
133                .await
134                .map_err(to_pyvalue_err)?;
135            Python::attach(|py| Ok(py.None()))
136        })
137    }
138
139    #[pyo3(name = "close_listen_key")]
140    fn py_close_listen_key<'py>(
141        &self,
142        py: Python<'py>,
143        listen_key: String,
144    ) -> PyResult<Bound<'py, PyAny>> {
145        let client = self.clone();
146
147        pyo3_async_runtimes::tokio::future_into_py(py, async move {
148            client
149                .close_listen_key(&listen_key)
150                .await
151                .map_err(to_pyvalue_err)?;
152            Python::attach(|py| Ok(py.None()))
153        })
154    }
155
156    #[pyo3(name = "request_instruments")]
157    fn py_request_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
158        let client = self.clone();
159
160        pyo3_async_runtimes::tokio::future_into_py(py, async move {
161            let instruments = client.request_instruments().await.map_err(to_pyvalue_err)?;
162
163            Python::attach(|py| {
164                let py_instruments: PyResult<Vec<_>> = instruments
165                    .into_iter()
166                    .map(|inst| instrument_any_to_pyobject(py, inst))
167                    .collect();
168                let pylist = PyList::new(py, py_instruments?)?.into_any().unbind();
169                Ok(pylist)
170            })
171        })
172    }
173
174    #[pyo3(name = "request_trades")]
175    #[pyo3(signature = (instrument_id, limit=None))]
176    fn py_request_trades<'py>(
177        &self,
178        py: Python<'py>,
179        instrument_id: InstrumentId,
180        limit: Option<u32>,
181    ) -> PyResult<Bound<'py, PyAny>> {
182        let client = self.clone();
183
184        pyo3_async_runtimes::tokio::future_into_py(py, async move {
185            let trades = client
186                .request_trades(instrument_id, limit)
187                .await
188                .map_err(to_pyvalue_err)?;
189
190            Python::attach(|py| {
191                let py_trades: PyResult<Vec<_>> = trades
192                    .into_iter()
193                    .map(|t| Ok(t.into_py_any_unwrap(py)))
194                    .collect();
195                let pylist = PyList::new(py, py_trades?)?.into_any().unbind();
196                Ok(pylist)
197            })
198        })
199    }
200
201    #[pyo3(name = "request_bars")]
202    #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
203    fn py_request_bars<'py>(
204        &self,
205        py: Python<'py>,
206        bar_type: BarType,
207        start: Option<i64>,
208        end: Option<i64>,
209        limit: Option<u32>,
210    ) -> PyResult<Bound<'py, PyAny>> {
211        let client = self.clone();
212
213        let start_dt = start
214            .map(|ts| {
215                Utc.timestamp_millis_opt(ts)
216                    .single()
217                    .ok_or_else(|| to_pyvalue_err(format!("Invalid start timestamp: {ts}")))
218            })
219            .transpose()?;
220
221        let end_dt = end
222            .map(|ts| {
223                Utc.timestamp_millis_opt(ts)
224                    .single()
225                    .ok_or_else(|| to_pyvalue_err(format!("Invalid end timestamp: {ts}")))
226            })
227            .transpose()?;
228
229        pyo3_async_runtimes::tokio::future_into_py(py, async move {
230            let bars = client
231                .request_bars(bar_type, start_dt, end_dt, limit)
232                .await
233                .map_err(to_pyvalue_err)?;
234
235            Python::attach(|py| {
236                let py_bars: PyResult<Vec<_>> = bars
237                    .into_iter()
238                    .map(|b| Ok(b.into_py_any_unwrap(py)))
239                    .collect();
240                let pylist = PyList::new(py, py_bars?)?.into_any().unbind();
241                Ok(pylist)
242            })
243        })
244    }
245
246    #[pyo3(name = "request_account_state")]
247    fn py_request_account_state<'py>(
248        &self,
249        py: Python<'py>,
250        account_id: AccountId,
251    ) -> PyResult<Bound<'py, PyAny>> {
252        let client = self.clone();
253
254        pyo3_async_runtimes::tokio::future_into_py(py, async move {
255            let account_state = client
256                .request_account_state(account_id)
257                .await
258                .map_err(to_pyvalue_err)?;
259
260            Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
261        })
262    }
263
264    #[pyo3(name = "request_order_status_report")]
265    #[pyo3(signature = (account_id, instrument_id, venue_order_id=None, client_order_id=None))]
266    fn py_request_order_status_report<'py>(
267        &self,
268        py: Python<'py>,
269        account_id: AccountId,
270        instrument_id: InstrumentId,
271        venue_order_id: Option<VenueOrderId>,
272        client_order_id: Option<ClientOrderId>,
273    ) -> PyResult<Bound<'py, PyAny>> {
274        let client = self.clone();
275
276        pyo3_async_runtimes::tokio::future_into_py(py, async move {
277            let report = client
278                .request_order_status_report(
279                    account_id,
280                    instrument_id,
281                    venue_order_id,
282                    client_order_id,
283                )
284                .await
285                .map_err(to_pyvalue_err)?;
286
287            Python::attach(|py| report.into_py_any(py))
288        })
289    }
290
291    #[pyo3(name = "request_order_status_reports")]
292    #[pyo3(signature = (account_id, instrument_id=None, open_only=true))]
293    fn py_request_order_status_reports<'py>(
294        &self,
295        py: Python<'py>,
296        account_id: AccountId,
297        instrument_id: Option<InstrumentId>,
298        open_only: bool,
299    ) -> PyResult<Bound<'py, PyAny>> {
300        let client = self.clone();
301
302        pyo3_async_runtimes::tokio::future_into_py(py, async move {
303            let reports = client
304                .request_order_status_reports(account_id, instrument_id, open_only)
305                .await
306                .map_err(to_pyvalue_err)?;
307
308            Python::attach(|py| {
309                let py_reports: PyResult<Vec<_>> =
310                    reports.into_iter().map(|r| r.into_py_any(py)).collect();
311                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
312                Ok(pylist)
313            })
314        })
315    }
316
317    #[pyo3(name = "request_fill_reports")]
318    #[pyo3(signature = (account_id, instrument_id, venue_order_id=None, start=None, end=None, limit=None))]
319    #[allow(clippy::too_many_arguments)]
320    fn py_request_fill_reports<'py>(
321        &self,
322        py: Python<'py>,
323        account_id: AccountId,
324        instrument_id: InstrumentId,
325        venue_order_id: Option<VenueOrderId>,
326        start: Option<i64>,
327        end: Option<i64>,
328        limit: Option<u32>,
329    ) -> PyResult<Bound<'py, PyAny>> {
330        let client = self.clone();
331
332        pyo3_async_runtimes::tokio::future_into_py(py, async move {
333            let reports = client
334                .request_fill_reports(account_id, instrument_id, venue_order_id, start, end, limit)
335                .await
336                .map_err(to_pyvalue_err)?;
337
338            Python::attach(|py| {
339                let py_reports: PyResult<Vec<_>> =
340                    reports.into_iter().map(|r| r.into_py_any(py)).collect();
341                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
342                Ok(pylist)
343            })
344        })
345    }
346
347    #[pyo3(name = "submit_order")]
348    #[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, position_side=None))]
349    #[allow(clippy::too_many_arguments)]
350    fn py_submit_order<'py>(
351        &self,
352        py: Python<'py>,
353        account_id: AccountId,
354        instrument_id: InstrumentId,
355        client_order_id: ClientOrderId,
356        order_side: OrderSide,
357        order_type: OrderType,
358        quantity: Quantity,
359        time_in_force: TimeInForce,
360        price: Option<Price>,
361        trigger_price: Option<Price>,
362        reduce_only: bool,
363        position_side: Option<BinancePositionSide>,
364    ) -> PyResult<Bound<'py, PyAny>> {
365        let client = self.clone();
366
367        pyo3_async_runtimes::tokio::future_into_py(py, async move {
368            let report = client
369                .submit_order(
370                    account_id,
371                    instrument_id,
372                    client_order_id,
373                    order_side,
374                    order_type,
375                    quantity,
376                    time_in_force,
377                    price,
378                    trigger_price,
379                    reduce_only,
380                    position_side,
381                )
382                .await
383                .map_err(to_pyvalue_err)?;
384            Python::attach(|py| report.into_py_any(py))
385        })
386    }
387
388    #[pyo3(name = "modify_order")]
389    #[pyo3(signature = (account_id, instrument_id, order_side, quantity, price, venue_order_id=None, client_order_id=None))]
390    #[allow(clippy::too_many_arguments)]
391    fn py_modify_order<'py>(
392        &self,
393        py: Python<'py>,
394        account_id: AccountId,
395        instrument_id: InstrumentId,
396        order_side: OrderSide,
397        quantity: Quantity,
398        price: Price,
399        venue_order_id: Option<VenueOrderId>,
400        client_order_id: Option<ClientOrderId>,
401    ) -> PyResult<Bound<'py, PyAny>> {
402        let client = self.clone();
403
404        pyo3_async_runtimes::tokio::future_into_py(py, async move {
405            let report = client
406                .modify_order(
407                    account_id,
408                    instrument_id,
409                    venue_order_id,
410                    client_order_id,
411                    order_side,
412                    quantity,
413                    price,
414                )
415                .await
416                .map_err(to_pyvalue_err)?;
417            Python::attach(|py| report.into_py_any(py))
418        })
419    }
420
421    #[pyo3(name = "cancel_order")]
422    #[pyo3(signature = (instrument_id, venue_order_id=None, client_order_id=None))]
423    fn py_cancel_order<'py>(
424        &self,
425        py: Python<'py>,
426        instrument_id: InstrumentId,
427        venue_order_id: Option<VenueOrderId>,
428        client_order_id: Option<ClientOrderId>,
429    ) -> PyResult<Bound<'py, PyAny>> {
430        let client = self.clone();
431
432        pyo3_async_runtimes::tokio::future_into_py(py, async move {
433            let order_id = client
434                .cancel_order(instrument_id, venue_order_id, client_order_id)
435                .await
436                .map_err(to_pyvalue_err)?;
437            Python::attach(|py| order_id.into_py_any(py))
438        })
439    }
440
441    #[pyo3(name = "cancel_all_orders")]
442    fn py_cancel_all_orders<'py>(
443        &self,
444        py: Python<'py>,
445        instrument_id: InstrumentId,
446    ) -> PyResult<Bound<'py, PyAny>> {
447        let client = self.clone();
448
449        pyo3_async_runtimes::tokio::future_into_py(py, async move {
450            let order_ids = client
451                .cancel_all_orders(instrument_id)
452                .await
453                .map_err(to_pyvalue_err)?;
454            Python::attach(|py| {
455                let py_ids: PyResult<Vec<_>> =
456                    order_ids.into_iter().map(|id| id.into_py_any(py)).collect();
457                let pylist = PyList::new(py, py_ids?)?.into_any().unbind();
458                Ok(pylist)
459            })
460        })
461    }
462
463    #[pyo3(name = "batch_submit_orders")]
464    fn py_batch_submit_orders<'py>(
465        &self,
466        py: Python<'py>,
467        orders: Vec<BatchOrderItem>,
468    ) -> PyResult<Bound<'py, PyAny>> {
469        let client = self.clone();
470
471        pyo3_async_runtimes::tokio::future_into_py(py, async move {
472            let results = client
473                .submit_order_list(&orders)
474                .await
475                .map_err(to_pyvalue_err)?;
476
477            Python::attach(|py| {
478                let py_results: Vec<_> = results
479                    .into_iter()
480                    .map(|r| match r {
481                        BatchOrderResult::Success(order) => {
482                            let dict = PyDict::new(py);
483                            dict.set_item("success", true).ok();
484                            dict.set_item("order_id", order.order_id).ok();
485                            dict.set_item("client_order_id", &order.client_order_id)
486                                .ok();
487                            dict.set_item("symbol", order.symbol.as_str()).ok();
488                            dict.into_any().unbind()
489                        }
490                        BatchOrderResult::Error(err) => {
491                            let dict = PyDict::new(py);
492                            dict.set_item("success", false).ok();
493                            dict.set_item("code", err.code).ok();
494                            dict.set_item("msg", &err.msg).ok();
495                            dict.into_any().unbind()
496                        }
497                    })
498                    .collect();
499                let pylist = PyList::new(py, py_results)?.into_any().unbind();
500                Ok(pylist)
501            })
502        })
503    }
504
505    #[pyo3(name = "batch_modify_orders")]
506    fn py_batch_modify_orders<'py>(
507        &self,
508        py: Python<'py>,
509        modifies: Vec<BatchModifyItem>,
510    ) -> PyResult<Bound<'py, PyAny>> {
511        let client = self.clone();
512
513        pyo3_async_runtimes::tokio::future_into_py(py, async move {
514            let results = client
515                .batch_modify_orders(&modifies)
516                .await
517                .map_err(to_pyvalue_err)?;
518
519            Python::attach(|py| {
520                let py_results: Vec<_> = results
521                    .into_iter()
522                    .map(|r| match r {
523                        BatchOrderResult::Success(order) => {
524                            let dict = PyDict::new(py);
525                            dict.set_item("success", true).ok();
526                            dict.set_item("order_id", order.order_id).ok();
527                            dict.set_item("client_order_id", &order.client_order_id)
528                                .ok();
529                            dict.set_item("symbol", order.symbol.as_str()).ok();
530                            dict.into_any().unbind()
531                        }
532                        BatchOrderResult::Error(err) => {
533                            let dict = PyDict::new(py);
534                            dict.set_item("success", false).ok();
535                            dict.set_item("code", err.code).ok();
536                            dict.set_item("msg", &err.msg).ok();
537                            dict.into_any().unbind()
538                        }
539                    })
540                    .collect();
541                let pylist = PyList::new(py, py_results)?.into_any().unbind();
542                Ok(pylist)
543            })
544        })
545    }
546
547    #[pyo3(name = "batch_cancel_orders")]
548    fn py_batch_cancel_orders<'py>(
549        &self,
550        py: Python<'py>,
551        cancels: Vec<BatchCancelItem>,
552    ) -> PyResult<Bound<'py, PyAny>> {
553        let client = self.clone();
554
555        pyo3_async_runtimes::tokio::future_into_py(py, async move {
556            let results = client
557                .batch_cancel_orders(&cancels)
558                .await
559                .map_err(to_pyvalue_err)?;
560
561            Python::attach(|py| {
562                let py_results: Vec<_> = results
563                    .into_iter()
564                    .map(|r| match r {
565                        BatchOrderResult::Success(order) => {
566                            let dict = PyDict::new(py);
567                            dict.set_item("success", true).ok();
568                            dict.set_item("order_id", order.order_id).ok();
569                            dict.set_item("client_order_id", &order.client_order_id)
570                                .ok();
571                            dict.set_item("symbol", order.symbol.as_str()).ok();
572                            dict.into_any().unbind()
573                        }
574                        BatchOrderResult::Error(err) => {
575                            let dict = PyDict::new(py);
576                            dict.set_item("success", false).ok();
577                            dict.set_item("code", err.code).ok();
578                            dict.set_item("msg", &err.msg).ok();
579                            dict.into_any().unbind()
580                        }
581                    })
582                    .collect();
583                let pylist = PyList::new(py, py_results)?.into_any().unbind();
584                Ok(pylist)
585            })
586        })
587    }
588}