Skip to main content

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