1use 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}