Skip to main content

nautilus_bybit/
data_types.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//! Bybit-specific custom data types.
17//!
18//! These types carry Bybit domain data through the Nautilus data engine as
19//! [`CustomData`](nautilus_model::data::CustomData).
20
21use nautilus_core::UnixNanos;
22use nautilus_model::{
23    custom_data,
24    enums::PositionSide,
25    identifiers::InstrumentId,
26    types::{Price, Quantity},
27};
28#[cfg(feature = "arrow")]
29use nautilus_serialization::arrow_custom_data;
30
31/// Bybit public liquidation from the `allLiquidation` WebSocket stream.
32///
33/// Bybit publishes this stream for linear and inverse contracts only.
34#[cfg_attr(
35    feature = "arrow",
36    arrow_custom_data(pyo3, stub_module = "nautilus_trader.adapters.bybit")
37)]
38#[custom_data(pyo3, stub_module = "nautilus_trader.adapters.bybit")]
39pub struct BybitLiquidation {
40    /// The instrument for this liquidation event.
41    pub instrument_id: InstrumentId,
42    /// The side of the liquidated position (Bybit `Buy` means a long position was liquidated).
43    #[custom_data_field(native_enum)]
44    pub position_side: PositionSide,
45    /// The bankruptcy price of the liquidated position.
46    pub bankruptcy_price: Price,
47    /// The executed liquidation size.
48    pub quantity: Quantity,
49    /// UNIX timestamp (nanoseconds) when the data event occurred.
50    pub ts_event: UnixNanos,
51    /// UNIX timestamp (nanoseconds) when the instance was initialized.
52    pub ts_init: UnixNanos,
53}
54
55/// Registers Bybit custom data types.
56///
57/// Safe to call multiple times (idempotent via internal `Once` guards).
58pub fn register_bybit_custom_data() {
59    #[cfg(feature = "arrow")]
60    {
61        nautilus_serialization::ensure_custom_data_registered::<BybitLiquidation>();
62    }
63
64    #[cfg(not(feature = "arrow"))]
65    {
66        let _ = nautilus_model::data::ensure_custom_data_json_registered::<BybitLiquidation>();
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use nautilus_model::data::{custom::CustomDataTrait, register_custom_data_json};
73    use rstest::rstest;
74
75    use super::*;
76
77    fn liquidation() -> BybitLiquidation {
78        BybitLiquidation::new(
79            InstrumentId::from("BTCUSDT-LINEAR.BYBIT"),
80            PositionSide::Short,
81            Price::from("97410.5"),
82            Quantity::from("1.250"),
83            UnixNanos::from(1_739_502_302_929_000_000),
84            UnixNanos::from(1_739_502_303_500_000_000),
85        )
86    }
87
88    #[rstest]
89    fn test_register_bybit_custom_data_is_idempotent() {
90        register_bybit_custom_data();
91        register_bybit_custom_data();
92    }
93
94    #[rstest]
95    fn test_register_bybit_custom_data_registers_json_deserializer() {
96        register_bybit_custom_data();
97
98        let err = register_custom_data_json::<BybitLiquidation>().unwrap_err();
99
100        assert_eq!(
101            err.to_string(),
102            "Custom data type \"BybitLiquidation\" is already registered for JSON",
103        );
104    }
105
106    #[rstest]
107    fn test_bybit_liquidation_json_round_trip() {
108        let original = liquidation();
109
110        let value: serde_json::Value = serde_json::from_str(&original.to_json().unwrap()).unwrap();
111        let restored = <BybitLiquidation as CustomDataTrait>::from_json(value).unwrap();
112        let restored = restored
113            .as_any()
114            .downcast_ref::<BybitLiquidation>()
115            .unwrap();
116
117        assert_eq!(restored, &original);
118        assert_eq!(restored.instrument_id, original.instrument_id);
119        assert_eq!(restored.position_side, PositionSide::Short);
120        assert_eq!(restored.bankruptcy_price, Price::from("97410.5"));
121        assert_eq!(restored.quantity, Quantity::from("1.250"));
122        assert_eq!(restored.ts_event, original.ts_event);
123        assert_eq!(restored.ts_init, original.ts_init);
124    }
125
126    #[cfg(feature = "arrow")]
127    #[rstest]
128    fn test_bybit_liquidation_arrow_schema_uses_native_types() {
129        use arrow::datatypes::DataType;
130        use nautilus_serialization::arrow::ArrowSchemaProvider;
131
132        let schema = BybitLiquidation::get_schema(None);
133        let names: Vec<_> = schema.fields().iter().map(|f| f.name().as_str()).collect();
134
135        assert_eq!(
136            names,
137            vec![
138                "instrument_id",
139                "position_side",
140                "bankruptcy_price",
141                "quantity",
142                "ts_event",
143                "ts_init",
144            ],
145        );
146        assert!(matches!(
147            schema.field(0).data_type(),
148            DataType::Utf8 | DataType::Utf8View
149        ));
150        assert_eq!(
151            schema.field(1).data_type(),
152            &nautilus_serialization::arrow::enum_dictionary_data_type(),
153        );
154        assert_eq!(schema.field(2).data_type(), &DataType::Decimal128(38, 16));
155        assert_eq!(schema.field(3).data_type(), &DataType::Decimal128(38, 16));
156        assert_eq!(
157            schema.field(4).data_type(),
158            &nautilus_serialization::arrow::timestamp_data_type(),
159        );
160        assert_eq!(
161            schema.field(5).data_type(),
162            &nautilus_serialization::arrow::timestamp_data_type(),
163        );
164    }
165}