1use std::sync::Arc;
22
23use nautilus_core::UnixNanos;
24use nautilus_model::{
25 data::{HasTsInit, custom::CustomDataTrait},
26 enums::OrderSide,
27 identifiers::InstrumentId,
28 types::{Price, Quantity},
29};
30use rust_decimal::Decimal;
31use serde::{Deserialize, Serialize};
32
33#[cfg_attr(
35 feature = "python",
36 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
37)]
38#[cfg_attr(
39 feature = "python",
40 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
41)]
42#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
43pub struct BinanceFuturesOpenInterest {
44 pub instrument_id: InstrumentId,
46 pub open_interest: Decimal,
48 pub ts_event: UnixNanos,
50 pub ts_init: UnixNanos,
52}
53
54impl BinanceFuturesOpenInterest {
55 #[must_use]
57 pub fn new(
58 instrument_id: InstrumentId,
59 open_interest: Decimal,
60 ts_event: UnixNanos,
61 ts_init: UnixNanos,
62 ) -> Self {
63 Self {
64 instrument_id,
65 open_interest,
66 ts_event,
67 ts_init,
68 }
69 }
70}
71
72impl HasTsInit for BinanceFuturesOpenInterest {
73 fn ts_init(&self) -> UnixNanos {
74 self.ts_init
75 }
76}
77
78impl CustomDataTrait for BinanceFuturesOpenInterest {
79 fn type_name(&self) -> &'static str {
80 "BinanceFuturesOpenInterest"
81 }
82
83 fn as_any(&self) -> &dyn std::any::Any {
84 self
85 }
86
87 fn ts_event(&self) -> UnixNanos {
88 self.ts_event
89 }
90
91 fn to_json(&self) -> anyhow::Result<String> {
92 Ok(serde_json::to_string(self)?)
93 }
94
95 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
96 Arc::new(self.clone())
97 }
98
99 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
100 if let Some(o) = other.as_any().downcast_ref::<Self>() {
101 self == o
102 } else {
103 false
104 }
105 }
106
107 #[cfg(feature = "python")]
108 fn to_pyobject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
109 nautilus_model::data::custom::clone_pyclass_to_pyobject(self, py)
110 }
111
112 fn type_name_static() -> &'static str {
113 "BinanceFuturesOpenInterest"
114 }
115
116 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
117 let json_str = serde_json::to_string(&value)?;
118 let parsed: Self = serde_json::from_str(&json_str)?;
119 Ok(Arc::new(parsed))
120 }
121}
122
123#[cfg_attr(
125 feature = "python",
126 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
127)]
128#[cfg_attr(
129 feature = "python",
130 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
131)]
132#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
133pub struct BinanceFuturesOpenInterestHistPoint {
134 pub sum_open_interest: Decimal,
136 pub sum_open_interest_value: Decimal,
138 pub ts_event: UnixNanos,
140}
141
142impl BinanceFuturesOpenInterestHistPoint {
143 #[must_use]
145 pub fn new(
146 sum_open_interest: Decimal,
147 sum_open_interest_value: Decimal,
148 ts_event: UnixNanos,
149 ) -> Self {
150 Self {
151 sum_open_interest,
152 sum_open_interest_value,
153 ts_event,
154 }
155 }
156}
157
158#[cfg_attr(
164 feature = "python",
165 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
166)]
167#[cfg_attr(
168 feature = "python",
169 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
170)]
171#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
172pub struct BinanceFuturesOpenInterestHist {
173 pub instrument_id: InstrumentId,
175 pub period: String,
177 pub points: Vec<BinanceFuturesOpenInterestHistPoint>,
179 pub ts_event: UnixNanos,
181 pub ts_init: UnixNanos,
183}
184
185impl BinanceFuturesOpenInterestHist {
186 #[must_use]
188 pub fn new(
189 instrument_id: InstrumentId,
190 period: String,
191 points: Vec<BinanceFuturesOpenInterestHistPoint>,
192 ts_event: UnixNanos,
193 ts_init: UnixNanos,
194 ) -> Self {
195 Self {
196 instrument_id,
197 period,
198 points,
199 ts_event,
200 ts_init,
201 }
202 }
203}
204
205impl HasTsInit for BinanceFuturesOpenInterestHist {
206 fn ts_init(&self) -> UnixNanos {
207 self.ts_init
208 }
209}
210
211impl CustomDataTrait for BinanceFuturesOpenInterestHist {
212 fn type_name(&self) -> &'static str {
213 "BinanceFuturesOpenInterestHist"
214 }
215
216 fn as_any(&self) -> &dyn std::any::Any {
217 self
218 }
219
220 fn ts_event(&self) -> UnixNanos {
221 self.ts_event
222 }
223
224 fn to_json(&self) -> anyhow::Result<String> {
225 Ok(serde_json::to_string(self)?)
226 }
227
228 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
229 Arc::new(self.clone())
230 }
231
232 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
233 if let Some(o) = other.as_any().downcast_ref::<Self>() {
234 self == o
235 } else {
236 false
237 }
238 }
239
240 #[cfg(feature = "python")]
241 fn to_pyobject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
242 nautilus_model::data::custom::clone_pyclass_to_pyobject(self, py)
243 }
244
245 fn type_name_static() -> &'static str {
246 "BinanceFuturesOpenInterestHist"
247 }
248
249 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
250 let json_str = serde_json::to_string(&value)?;
251 let parsed: Self = serde_json::from_str(&json_str)?;
252 Ok(Arc::new(parsed))
253 }
254}
255
256#[cfg_attr(
258 feature = "python",
259 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
260)]
261#[cfg_attr(
262 feature = "python",
263 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
264)]
265#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
266pub struct BinanceFuturesLiquidation {
267 pub instrument_id: InstrumentId,
269 pub side: OrderSide,
271 pub price: Price,
273 pub average_price: Price,
275 pub last_filled_qty: Quantity,
277 pub accumulated_qty: Quantity,
279 pub ts_event: UnixNanos,
281 pub ts_init: UnixNanos,
283}
284
285impl BinanceFuturesLiquidation {
286 #[must_use]
288 #[expect(clippy::too_many_arguments)]
289 pub fn new(
290 instrument_id: InstrumentId,
291 side: OrderSide,
292 price: Price,
293 average_price: Price,
294 last_filled_qty: Quantity,
295 accumulated_qty: Quantity,
296 ts_event: UnixNanos,
297 ts_init: UnixNanos,
298 ) -> Self {
299 Self {
300 instrument_id,
301 side,
302 price,
303 average_price,
304 last_filled_qty,
305 accumulated_qty,
306 ts_event,
307 ts_init,
308 }
309 }
310}
311
312impl HasTsInit for BinanceFuturesLiquidation {
313 fn ts_init(&self) -> UnixNanos {
314 self.ts_init
315 }
316}
317
318impl CustomDataTrait for BinanceFuturesLiquidation {
319 fn type_name(&self) -> &'static str {
320 "BinanceFuturesLiquidation"
321 }
322
323 fn as_any(&self) -> &dyn std::any::Any {
324 self
325 }
326
327 fn ts_event(&self) -> UnixNanos {
328 self.ts_event
329 }
330
331 fn to_json(&self) -> anyhow::Result<String> {
332 Ok(serde_json::to_string(self)?)
333 }
334
335 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
336 Arc::new(self.clone())
337 }
338
339 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
340 if let Some(o) = other.as_any().downcast_ref::<Self>() {
341 self == o
342 } else {
343 false
344 }
345 }
346
347 #[cfg(feature = "python")]
348 fn to_pyobject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
349 nautilus_model::data::custom::clone_pyclass_to_pyobject(self, py)
350 }
351
352 fn type_name_static() -> &'static str {
353 "BinanceFuturesLiquidation"
354 }
355
356 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
357 let json_str = serde_json::to_string(&value)?;
358 let parsed: Self = serde_json::from_str(&json_str)?;
359 Ok(Arc::new(parsed))
360 }
361}
362
363#[cfg_attr(
365 feature = "python",
366 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
367)]
368#[cfg_attr(
369 feature = "python",
370 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
371)]
372#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
373pub struct BinanceSpotTicker {
374 pub instrument_id: InstrumentId,
376 pub price_change: Decimal,
378 pub price_change_percent: Decimal,
380 pub weighted_avg_price: Decimal,
382 pub prev_close_price: Decimal,
384 pub last_price: Decimal,
386 pub last_qty: Decimal,
388 pub bid_price: Decimal,
390 pub bid_qty: Decimal,
392 pub ask_price: Decimal,
394 pub ask_qty: Decimal,
396 pub open_price: Decimal,
398 pub high_price: Decimal,
400 pub low_price: Decimal,
402 pub volume: Decimal,
404 pub quote_volume: Decimal,
406 pub open_time: UnixNanos,
408 pub close_time: UnixNanos,
410 pub first_trade_id: i64,
412 pub last_trade_id: i64,
414 pub num_trades: i64,
416 pub ts_event: UnixNanos,
418 pub ts_init: UnixNanos,
420}
421
422impl HasTsInit for BinanceSpotTicker {
423 fn ts_init(&self) -> UnixNanos {
424 self.ts_init
425 }
426}
427
428impl CustomDataTrait for BinanceSpotTicker {
429 fn type_name(&self) -> &'static str {
430 "BinanceSpotTicker"
431 }
432
433 fn as_any(&self) -> &dyn std::any::Any {
434 self
435 }
436
437 fn ts_event(&self) -> UnixNanos {
438 self.ts_event
439 }
440
441 fn to_json(&self) -> anyhow::Result<String> {
442 Ok(serde_json::to_string(self)?)
443 }
444
445 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
446 Arc::new(self.clone())
447 }
448
449 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
450 other.as_any().downcast_ref::<Self>() == Some(self)
451 }
452
453 #[cfg(feature = "python")]
454 fn to_pyobject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
455 nautilus_model::data::custom::clone_pyclass_to_pyobject(self, py)
456 }
457
458 fn type_name_static() -> &'static str {
459 "BinanceSpotTicker"
460 }
461
462 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
463 let json_str = serde_json::to_string(&value)?;
464 Ok(Arc::new(serde_json::from_str::<Self>(&json_str)?))
465 }
466}
467
468#[cfg_attr(
470 feature = "python",
471 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
472)]
473#[cfg_attr(
474 feature = "python",
475 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
476)]
477#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
478pub struct BinanceFuturesMarkPriceUpdate {
479 pub instrument_id: InstrumentId,
481 pub mark_price: Price,
483 pub index_price: Price,
485 pub estimated_settle_price: Price,
487 pub funding_rate: Decimal,
489 pub next_funding_time: Option<UnixNanos>,
491 pub ts_event: UnixNanos,
493 pub ts_init: UnixNanos,
495}
496
497impl HasTsInit for BinanceFuturesMarkPriceUpdate {
498 fn ts_init(&self) -> UnixNanos {
499 self.ts_init
500 }
501}
502
503impl CustomDataTrait for BinanceFuturesMarkPriceUpdate {
504 fn type_name(&self) -> &'static str {
505 "BinanceFuturesMarkPriceUpdate"
506 }
507
508 fn as_any(&self) -> &dyn std::any::Any {
509 self
510 }
511
512 fn ts_event(&self) -> UnixNanos {
513 self.ts_event
514 }
515
516 fn to_json(&self) -> anyhow::Result<String> {
517 Ok(serde_json::to_string(self)?)
518 }
519
520 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
521 Arc::new(self.clone())
522 }
523
524 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
525 other.as_any().downcast_ref::<Self>() == Some(self)
526 }
527
528 #[cfg(feature = "python")]
529 fn to_pyobject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
530 nautilus_model::data::custom::clone_pyclass_to_pyobject(self, py)
531 }
532
533 fn type_name_static() -> &'static str {
534 "BinanceFuturesMarkPriceUpdate"
535 }
536
537 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
538 let json_str = serde_json::to_string(&value)?;
539 Ok(Arc::new(serde_json::from_str::<Self>(&json_str)?))
540 }
541}
542
543#[cfg_attr(
545 feature = "python",
546 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
547)]
548#[cfg_attr(
549 feature = "python",
550 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
551)]
552#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
553pub struct BinanceFuturesTicker {
554 pub instrument_id: InstrumentId,
556 pub price_change: Decimal,
558 pub price_change_percent: Decimal,
560 pub weighted_avg_price: Decimal,
562 pub last_price: Decimal,
564 pub last_qty: Decimal,
566 pub open_price: Decimal,
568 pub high_price: Decimal,
570 pub low_price: Decimal,
572 pub volume: Decimal,
574 pub quote_volume: Decimal,
576 pub open_time: UnixNanos,
578 pub close_time: UnixNanos,
580 pub first_trade_id: i64,
582 pub last_trade_id: i64,
584 pub num_trades: i64,
586 pub ts_event: UnixNanos,
588 pub ts_init: UnixNanos,
590}
591
592impl BinanceFuturesTicker {
593 #[must_use]
595 #[expect(clippy::too_many_arguments)]
596 pub fn new(
597 instrument_id: InstrumentId,
598 price_change: Decimal,
599 price_change_percent: Decimal,
600 weighted_avg_price: Decimal,
601 last_price: Decimal,
602 last_qty: Decimal,
603 open_price: Decimal,
604 high_price: Decimal,
605 low_price: Decimal,
606 volume: Decimal,
607 quote_volume: Decimal,
608 open_time: UnixNanos,
609 close_time: UnixNanos,
610 first_trade_id: i64,
611 last_trade_id: i64,
612 num_trades: i64,
613 ts_event: UnixNanos,
614 ts_init: UnixNanos,
615 ) -> Self {
616 Self {
617 instrument_id,
618 price_change,
619 price_change_percent,
620 weighted_avg_price,
621 last_price,
622 last_qty,
623 open_price,
624 high_price,
625 low_price,
626 volume,
627 quote_volume,
628 open_time,
629 close_time,
630 first_trade_id,
631 last_trade_id,
632 num_trades,
633 ts_event,
634 ts_init,
635 }
636 }
637}
638
639impl HasTsInit for BinanceFuturesTicker {
640 fn ts_init(&self) -> UnixNanos {
641 self.ts_init
642 }
643}
644
645impl CustomDataTrait for BinanceFuturesTicker {
646 fn type_name(&self) -> &'static str {
647 "BinanceFuturesTicker"
648 }
649
650 fn as_any(&self) -> &dyn std::any::Any {
651 self
652 }
653
654 fn ts_event(&self) -> UnixNanos {
655 self.ts_event
656 }
657
658 fn to_json(&self) -> anyhow::Result<String> {
659 Ok(serde_json::to_string(self)?)
660 }
661
662 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
663 Arc::new(self.clone())
664 }
665
666 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
667 if let Some(o) = other.as_any().downcast_ref::<Self>() {
668 self == o
669 } else {
670 false
671 }
672 }
673
674 #[cfg(feature = "python")]
675 fn to_pyobject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
676 nautilus_model::data::custom::clone_pyclass_to_pyobject(self, py)
677 }
678
679 fn type_name_static() -> &'static str {
680 "BinanceFuturesTicker"
681 }
682
683 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
684 let json_str = serde_json::to_string(&value)?;
685 let parsed: Self = serde_json::from_str(&json_str)?;
686 Ok(Arc::new(parsed))
687 }
688}
689
690pub fn register_binance_custom_data() {
694 let _ =
695 nautilus_model::data::ensure_custom_data_json_registered::<BinanceFuturesOpenInterest>();
696 let _ = nautilus_model::data::ensure_custom_data_json_registered::<
697 BinanceFuturesOpenInterestHist,
698 >();
699 let _ = nautilus_model::data::ensure_custom_data_json_registered::<BinanceFuturesLiquidation>();
700 let _ = nautilus_model::data::ensure_custom_data_json_registered::<BinanceFuturesTicker>();
701 let _ = nautilus_model::data::ensure_custom_data_json_registered::<BinanceSpotTicker>();
702 let _ =
703 nautilus_model::data::ensure_custom_data_json_registered::<BinanceFuturesMarkPriceUpdate>();
704}
705
706#[cfg(test)]
707mod tests {
708 #[cfg(feature = "python")]
709 use std::sync::Arc;
710
711 #[cfg(feature = "python")]
712 use nautilus_core::Params;
713 #[cfg(feature = "python")]
714 use nautilus_model::data::{CustomData, DataType};
715 #[cfg(feature = "python")]
716 use pyo3::{prelude::*, types::PyList};
717 use rstest::rstest;
718 #[cfg(feature = "python")]
719 use rust_decimal::Decimal;
720
721 use super::*;
722
723 #[rstest]
724 fn test_register_binance_custom_data_is_idempotent() {
725 register_binance_custom_data();
726 register_binance_custom_data();
727 }
728
729 #[cfg(feature = "python")]
730 #[rstest]
731 fn test_open_interest_hist_points_roundtrip_as_typed_python_list() {
732 pyo3::Python::initialize();
733 register_binance_custom_data();
734
735 Python::attach(|py| {
736 let instrument_id = InstrumentId::from("BTCUSDT-PERP.BINANCE");
737 let points = vec![
738 BinanceFuturesOpenInterestHistPoint::new(
739 Decimal::from_str_exact("100.0").unwrap(),
740 Decimal::from_str_exact("1000.0").unwrap(),
741 UnixNanos::from_millis(1_700_000_000_000),
742 ),
743 BinanceFuturesOpenInterestHistPoint::new(
744 Decimal::from_str_exact("101.0").unwrap(),
745 Decimal::from_str_exact("1005.0").unwrap(),
746 UnixNanos::from_millis(1_700_000_300_000),
747 ),
748 ];
749 let payload = BinanceFuturesOpenInterestHist::new(
750 instrument_id,
751 "5m".to_string(),
752 points,
753 UnixNanos::from_millis(1_700_000_300_000),
754 UnixNanos::from(42_u64),
755 );
756
757 let mut metadata = Params::new();
758 metadata.insert(
759 "instrument_id".to_string(),
760 serde_json::Value::String("BTCUSDT-PERP.BINANCE".to_string()),
761 );
762 metadata.insert(
763 "period".to_string(),
764 serde_json::Value::String("5m".to_string()),
765 );
766
767 let custom = CustomData::new(
768 Arc::new(payload),
769 DataType::new(
770 "BinanceFuturesOpenInterestHist",
771 Some(metadata),
772 Some("BTCUSDT-PERP.BINANCE".to_string()),
773 ),
774 );
775
776 let py_custom = Py::new(py, custom).unwrap();
777 let py_payload = py_custom.bind(py).getattr("data").unwrap();
778 let py_points = py_payload
779 .getattr("points")
780 .unwrap()
781 .cast_into::<PyList>()
782 .unwrap();
783
784 assert_eq!(py_points.len(), 2);
785 assert!(
786 py_points
787 .get_item(0)
788 .unwrap()
789 .is_instance_of::<BinanceFuturesOpenInterestHistPoint>()
790 );
791
792 let point0 = py_points
793 .get_item(0)
794 .unwrap()
795 .extract::<BinanceFuturesOpenInterestHistPoint>()
796 .unwrap();
797 let point1 = py_points
798 .get_item(1)
799 .unwrap()
800 .extract::<BinanceFuturesOpenInterestHistPoint>()
801 .unwrap();
802
803 assert_eq!(
804 point0.sum_open_interest,
805 Decimal::from_str_exact("100.0").unwrap()
806 );
807 assert_eq!(
808 point1.sum_open_interest_value,
809 Decimal::from_str_exact("1005.0").unwrap()
810 );
811 });
812 }
813}