nautilus_binance/python/
http.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 nautilus_core::python::to_pyvalue_err;
19use nautilus_model::{
20    enums::{OrderSide, OrderType, TimeInForce},
21    identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
22    python::instruments::instrument_any_to_pyobject,
23    types::{Price, Quantity},
24};
25use pyo3::{IntoPyObjectExt, prelude::*, types::PyList};
26
27use crate::{
28    common::enums::BinanceEnvironment,
29    spot::http::{
30        client::BinanceSpotHttpClient,
31        query::{AccountTradesParams, AllOrdersParams, OpenOrdersParams, QueryOrderParams},
32    },
33};
34
35#[pymethods]
36impl BinanceSpotHttpClient {
37    /// Creates a new Binance Spot HTTP client.
38    #[new]
39    #[pyo3(signature = (environment=BinanceEnvironment::Mainnet, api_key=None, api_secret=None, base_url=None, recv_window=None, timeout_secs=None, proxy_url=None))]
40    #[allow(clippy::too_many_arguments)]
41    fn py_new(
42        environment: BinanceEnvironment,
43        api_key: Option<String>,
44        api_secret: Option<String>,
45        base_url: Option<String>,
46        recv_window: Option<u64>,
47        timeout_secs: Option<u64>,
48        proxy_url: Option<String>,
49    ) -> PyResult<Self> {
50        Self::new(
51            environment,
52            api_key,
53            api_secret,
54            base_url,
55            recv_window,
56            timeout_secs,
57            proxy_url,
58        )
59        .map_err(to_pyvalue_err)
60    }
61
62    /// Returns the SBE schema ID.
63    #[getter]
64    #[pyo3(name = "schema_id")]
65    #[must_use]
66    pub fn py_schema_id(&self) -> u16 {
67        Self::schema_id()
68    }
69
70    /// Returns the SBE schema version.
71    #[getter]
72    #[pyo3(name = "schema_version")]
73    #[must_use]
74    pub fn py_schema_version(&self) -> u16 {
75        Self::schema_version()
76    }
77
78    /// Tests connectivity to the API.
79    #[pyo3(name = "ping")]
80    fn py_ping<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
81        let client = self.clone();
82        pyo3_async_runtimes::tokio::future_into_py(py, async move {
83            client.ping().await.map_err(to_pyvalue_err)?;
84            Python::attach(|py| Ok(py.None()))
85        })
86    }
87
88    /// Returns the server time in microseconds since epoch.
89    #[pyo3(name = "server_time")]
90    fn py_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
91        let client = self.clone();
92        pyo3_async_runtimes::tokio::future_into_py(py, async move {
93            let timestamp = client.server_time().await.map_err(to_pyvalue_err)?;
94            Python::attach(|py| Ok(timestamp.into_pyobject(py)?.into_any().unbind()))
95        })
96    }
97
98    /// Requests Nautilus instruments for all trading symbols.
99    #[pyo3(name = "request_instruments")]
100    fn py_request_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
101        let client = self.clone();
102        pyo3_async_runtimes::tokio::future_into_py(py, async move {
103            let instruments = client.request_instruments().await.map_err(to_pyvalue_err)?;
104
105            Python::attach(|py| {
106                let py_instruments: PyResult<Vec<_>> = instruments
107                    .into_iter()
108                    .map(|inst| instrument_any_to_pyobject(py, inst))
109                    .collect();
110                let pylist = PyList::new(py, py_instruments?)?.into_any().unbind();
111                Ok(pylist)
112            })
113        })
114    }
115
116    /// Requests recent trades for an instrument.
117    #[pyo3(name = "request_trades", signature = (instrument_id, limit=None))]
118    fn py_request_trades<'py>(
119        &self,
120        py: Python<'py>,
121        instrument_id: InstrumentId,
122        limit: Option<u32>,
123    ) -> PyResult<Bound<'py, PyAny>> {
124        let client = self.clone();
125        pyo3_async_runtimes::tokio::future_into_py(py, async move {
126            let trades = client
127                .request_trades(instrument_id, limit)
128                .await
129                .map_err(to_pyvalue_err)?;
130
131            Python::attach(|py| {
132                let py_trades: PyResult<Vec<_>> = trades
133                    .into_iter()
134                    .map(|tick| tick.into_py_any(py))
135                    .collect();
136                let pylist = PyList::new(py, py_trades?)?.into_any().unbind();
137                Ok(pylist)
138            })
139        })
140    }
141
142    /// Requests the status of a specific order.
143    ///
144    /// Returns an OrderStatusReport.
145    #[pyo3(name = "request_order_status", signature = (account_id, instrument_id, symbol, order_id=None, client_order_id=None))]
146    fn py_request_order_status<'py>(
147        &self,
148        py: Python<'py>,
149        account_id: AccountId,
150        instrument_id: InstrumentId,
151        symbol: String,
152        order_id: Option<i64>,
153        client_order_id: Option<String>,
154    ) -> PyResult<Bound<'py, PyAny>> {
155        let client = self.clone();
156        let params = QueryOrderParams {
157            symbol,
158            order_id,
159            orig_client_order_id: client_order_id,
160        };
161        pyo3_async_runtimes::tokio::future_into_py(py, async move {
162            let report = client
163                .request_order_status(account_id, instrument_id, &params)
164                .await
165                .map_err(to_pyvalue_err)?;
166            Python::attach(|py| report.into_py_any(py))
167        })
168    }
169
170    /// Requests all open orders for a symbol or all symbols.
171    ///
172    /// Returns a list of OrderStatusReport.
173    #[pyo3(name = "request_open_orders", signature = (account_id, symbol=None))]
174    fn py_request_open_orders<'py>(
175        &self,
176        py: Python<'py>,
177        account_id: AccountId,
178        symbol: Option<String>,
179    ) -> PyResult<Bound<'py, PyAny>> {
180        let client = self.clone();
181        let params = OpenOrdersParams { symbol };
182        pyo3_async_runtimes::tokio::future_into_py(py, async move {
183            let reports = client
184                .request_open_orders(account_id, &params)
185                .await
186                .map_err(to_pyvalue_err)?;
187            Python::attach(|py| {
188                let py_reports: PyResult<Vec<_>> =
189                    reports.into_iter().map(|r| r.into_py_any(py)).collect();
190                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
191                Ok(pylist)
192            })
193        })
194    }
195
196    /// Requests order history (including closed orders) for a symbol.
197    ///
198    /// Returns a list of OrderStatusReport.
199    #[pyo3(name = "request_order_history", signature = (account_id, symbol, order_id=None, start_time=None, end_time=None, limit=None))]
200    #[allow(clippy::too_many_arguments)]
201    fn py_request_order_history<'py>(
202        &self,
203        py: Python<'py>,
204        account_id: AccountId,
205        symbol: String,
206        order_id: Option<i64>,
207        start_time: Option<i64>,
208        end_time: Option<i64>,
209        limit: Option<u32>,
210    ) -> PyResult<Bound<'py, PyAny>> {
211        let client = self.clone();
212        let params = AllOrdersParams {
213            symbol,
214            order_id,
215            start_time,
216            end_time,
217            limit,
218        };
219        pyo3_async_runtimes::tokio::future_into_py(py, async move {
220            let reports = client
221                .request_order_history(account_id, &params)
222                .await
223                .map_err(to_pyvalue_err)?;
224            Python::attach(|py| {
225                let py_reports: PyResult<Vec<_>> =
226                    reports.into_iter().map(|r| r.into_py_any(py)).collect();
227                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
228                Ok(pylist)
229            })
230        })
231    }
232
233    /// Requests fill reports (trade history) for a symbol.
234    ///
235    /// Returns a list of FillReport.
236    #[pyo3(name = "request_fill_reports", signature = (account_id, symbol, order_id=None, start_time=None, end_time=None, from_id=None, limit=None))]
237    #[allow(clippy::too_many_arguments)]
238    fn py_request_fill_reports<'py>(
239        &self,
240        py: Python<'py>,
241        account_id: AccountId,
242        symbol: String,
243        order_id: Option<i64>,
244        start_time: Option<i64>,
245        end_time: Option<i64>,
246        from_id: Option<i64>,
247        limit: Option<u32>,
248    ) -> PyResult<Bound<'py, PyAny>> {
249        let client = self.clone();
250        let params = AccountTradesParams {
251            symbol,
252            order_id,
253            start_time,
254            end_time,
255            from_id,
256            limit,
257        };
258        pyo3_async_runtimes::tokio::future_into_py(py, async move {
259            let reports = client
260                .request_fill_reports(account_id, &params)
261                .await
262                .map_err(to_pyvalue_err)?;
263            Python::attach(|py| {
264                let py_reports: PyResult<Vec<_>> =
265                    reports.into_iter().map(|r| r.into_py_any(py)).collect();
266                let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
267                Ok(pylist)
268            })
269        })
270    }
271
272    /// Submits a new order to the venue.
273    ///
274    /// Returns an `OrderStatusReport`.
275    #[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))]
276    #[allow(clippy::too_many_arguments)]
277    fn py_submit_order<'py>(
278        &self,
279        py: Python<'py>,
280        account_id: AccountId,
281        instrument_id: InstrumentId,
282        client_order_id: ClientOrderId,
283        order_side: OrderSide,
284        order_type: OrderType,
285        quantity: Quantity,
286        time_in_force: TimeInForce,
287        price: Option<Price>,
288        trigger_price: Option<Price>,
289        post_only: bool,
290    ) -> PyResult<Bound<'py, PyAny>> {
291        let client = self.clone();
292        pyo3_async_runtimes::tokio::future_into_py(py, async move {
293            let report = client
294                .submit_order(
295                    account_id,
296                    instrument_id,
297                    client_order_id,
298                    order_side,
299                    order_type,
300                    quantity,
301                    time_in_force,
302                    price,
303                    trigger_price,
304                    post_only,
305                )
306                .await
307                .map_err(to_pyvalue_err)?;
308            Python::attach(|py| report.into_py_any(py))
309        })
310    }
311
312    /// Cancels an existing order on the venue by venue order ID.
313    ///
314    /// Returns the venue order ID of the canceled order.
315    #[pyo3(name = "cancel_order")]
316    fn py_cancel_order<'py>(
317        &self,
318        py: Python<'py>,
319        instrument_id: InstrumentId,
320        venue_order_id: VenueOrderId,
321    ) -> PyResult<Bound<'py, PyAny>> {
322        let client = self.clone();
323        pyo3_async_runtimes::tokio::future_into_py(py, async move {
324            let order_id = client
325                .cancel_order(instrument_id, venue_order_id)
326                .await
327                .map_err(to_pyvalue_err)?;
328            Python::attach(|py| order_id.into_py_any(py))
329        })
330    }
331
332    /// Cancels an existing order on the venue by client order ID.
333    ///
334    /// Returns the venue order ID of the canceled order.
335    #[pyo3(name = "cancel_order_by_client_id")]
336    fn py_cancel_order_by_client_id<'py>(
337        &self,
338        py: Python<'py>,
339        instrument_id: InstrumentId,
340        client_order_id: ClientOrderId,
341    ) -> PyResult<Bound<'py, PyAny>> {
342        let client = self.clone();
343        pyo3_async_runtimes::tokio::future_into_py(py, async move {
344            let order_id = client
345                .cancel_order_by_client_id(instrument_id, client_order_id)
346                .await
347                .map_err(to_pyvalue_err)?;
348            Python::attach(|py| order_id.into_py_any(py))
349        })
350    }
351
352    /// Cancels all open orders for a symbol.
353    ///
354    /// Returns a list of venue order IDs for all canceled orders.
355    #[pyo3(name = "cancel_all_orders")]
356    fn py_cancel_all_orders<'py>(
357        &self,
358        py: Python<'py>,
359        instrument_id: InstrumentId,
360    ) -> PyResult<Bound<'py, PyAny>> {
361        let client = self.clone();
362        pyo3_async_runtimes::tokio::future_into_py(py, async move {
363            let order_ids = client
364                .cancel_all_orders(instrument_id)
365                .await
366                .map_err(to_pyvalue_err)?;
367            Python::attach(|py| {
368                let py_ids: PyResult<Vec<_>> =
369                    order_ids.into_iter().map(|id| id.into_py_any(py)).collect();
370                let pylist = PyList::new(py, py_ids?)?.into_any().unbind();
371                Ok(pylist)
372            })
373        })
374    }
375
376    /// Modifies an existing order (cancel and replace atomically).
377    ///
378    /// Returns an `OrderStatusReport` for the new order.
379    #[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))]
380    #[allow(clippy::too_many_arguments)]
381    fn py_modify_order<'py>(
382        &self,
383        py: Python<'py>,
384        account_id: AccountId,
385        instrument_id: InstrumentId,
386        venue_order_id: VenueOrderId,
387        client_order_id: ClientOrderId,
388        order_side: OrderSide,
389        order_type: OrderType,
390        quantity: Quantity,
391        time_in_force: TimeInForce,
392        price: Option<Price>,
393    ) -> PyResult<Bound<'py, PyAny>> {
394        let client = self.clone();
395        pyo3_async_runtimes::tokio::future_into_py(py, async move {
396            let report = client
397                .modify_order(
398                    account_id,
399                    instrument_id,
400                    venue_order_id,
401                    client_order_id,
402                    order_side,
403                    order_type,
404                    quantity,
405                    time_in_force,
406                    price,
407                )
408                .await
409                .map_err(to_pyvalue_err)?;
410            Python::attach(|py| report.into_py_any(py))
411        })
412    }
413}