1use nautilus_core::UnixNanos;
22use nautilus_model::{
23 custom_data,
24 identifiers::{InstrumentId, Symbol},
25};
26#[cfg(feature = "arrow")]
27use nautilus_serialization::arrow_custom_data;
28use rust_decimal::Decimal;
29
30use crate::{common::consts::DERIBIT_VENUE, http::models::DeribitBookSummaryRaw};
31
32#[cfg_attr(
36 feature = "arrow",
37 arrow_custom_data(pyo3, stub_module = "nautilus_trader.adapters.deribit")
38)]
39#[custom_data(pyo3, stub_module = "nautilus_trader.adapters.deribit")]
40pub struct DeribitVolatilityIndex {
41 pub index_name: String,
43 pub volatility: f64,
45 pub ts_event: UnixNanos,
47 pub ts_init: UnixNanos,
49}
50
51#[cfg_attr(
57 feature = "arrow",
58 arrow_custom_data(pyo3, stub_module = "nautilus_trader.adapters.deribit")
59)]
60#[custom_data(pyo3, stub_module = "nautilus_trader.adapters.deribit")]
61pub struct DeribitBookSummary {
62 pub instrument_id: InstrumentId,
64 pub instrument_name: String,
66 #[custom_data_field(serde)]
68 pub underlying_price: Option<Decimal>,
69 #[custom_data_field(serde)]
71 pub underlying_index: Option<String>,
72 #[custom_data_field(serde)]
74 pub mark_price: Option<Decimal>,
75 #[custom_data_field(serde)]
77 pub mid_price: Option<Decimal>,
78 #[custom_data_field(serde)]
80 pub bid_price: Option<Decimal>,
81 #[custom_data_field(serde)]
83 pub ask_price: Option<Decimal>,
84 #[custom_data_field(serde)]
86 pub last_price: Option<Decimal>,
87 #[custom_data_field(serde)]
89 pub mark_iv: Option<Decimal>,
90 #[custom_data_field(serde)]
92 pub bid_iv: Option<Decimal>,
93 #[custom_data_field(serde)]
95 pub ask_iv: Option<Decimal>,
96 #[custom_data_field(serde)]
98 pub interest_rate: Option<Decimal>,
99 #[custom_data_field(serde)]
101 pub open_interest: Option<Decimal>,
102 #[custom_data_field(serde)]
104 pub open_interest_value: Option<Decimal>,
105 #[custom_data_field(serde)]
107 pub volume: Option<Decimal>,
108 #[custom_data_field(serde)]
110 pub volume_usd: Option<Decimal>,
111 #[custom_data_field(serde)]
113 pub volume_notional: Option<Decimal>,
114 #[custom_data_field(serde)]
116 pub volume_btc: Option<Decimal>,
117 #[custom_data_field(serde)]
119 pub high: Option<Decimal>,
120 #[custom_data_field(serde)]
122 pub low: Option<Decimal>,
123 #[custom_data_field(serde)]
125 pub price_change: Option<Decimal>,
126 #[custom_data_field(serde)]
128 pub estimated_delivery_price: Option<Decimal>,
129 #[custom_data_field(serde)]
131 pub delivery_price: Option<Decimal>,
132 #[custom_data_field(serde)]
134 pub base_currency: Option<String>,
135 #[custom_data_field(serde)]
137 pub quote_currency: Option<String>,
138 pub creation_timestamp: i64,
140 pub ts_event: UnixNanos,
142 pub ts_init: UnixNanos,
144}
145
146impl DeribitBookSummary {
147 #[must_use]
149 pub fn from_raw(raw: DeribitBookSummaryRaw, ts_init: UnixNanos) -> Self {
150 let instrument_id = InstrumentId::new(Symbol::new(&raw.instrument_name), *DERIBIT_VENUE);
151 let ts_event = UnixNanos::from_millis(raw.creation_timestamp as u64);
152
153 Self {
154 instrument_id,
155 instrument_name: raw.instrument_name,
156 underlying_price: raw.underlying_price,
157 underlying_index: raw.underlying_index,
158 mark_price: raw.mark_price,
159 mid_price: raw.mid_price,
160 bid_price: raw.bid_price,
161 ask_price: raw.ask_price,
162 last_price: raw.last_price,
163 mark_iv: raw.mark_iv,
164 bid_iv: raw.bid_iv,
165 ask_iv: raw.ask_iv,
166 interest_rate: raw.interest_rate,
167 open_interest: raw.open_interest,
168 open_interest_value: raw.open_interest_value,
169 volume: raw.volume,
170 volume_usd: raw.volume_usd,
171 volume_notional: raw.volume_notional,
172 volume_btc: raw.volume_btc,
173 high: raw.high,
174 low: raw.low,
175 price_change: raw.price_change,
176 estimated_delivery_price: raw.estimated_delivery_price,
177 delivery_price: raw.delivery_price,
178 base_currency: raw.base_currency,
179 quote_currency: raw.quote_currency,
180 creation_timestamp: raw.creation_timestamp,
181 ts_event,
182 ts_init,
183 }
184 }
185}
186
187pub fn register_deribit_custom_data() {
191 #[cfg(feature = "arrow")]
192 {
193 nautilus_serialization::ensure_custom_data_registered::<DeribitVolatilityIndex>();
194 nautilus_serialization::ensure_custom_data_registered::<DeribitBookSummary>();
195 }
196
197 #[cfg(not(feature = "arrow"))]
198 {
199 let _ =
200 nautilus_model::data::ensure_custom_data_json_registered::<DeribitVolatilityIndex>();
201 let _ = nautilus_model::data::ensure_custom_data_json_registered::<DeribitBookSummary>();
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use rstest::rstest;
208 use rust_decimal_macros::dec;
209
210 use super::*;
211
212 #[rstest]
213 fn test_register_deribit_custom_data_is_idempotent() {
214 register_deribit_custom_data();
215 register_deribit_custom_data();
216 }
217
218 #[rstest]
219 fn test_book_summary_from_raw_preserves_decimals_and_instrument_id() {
220 let raw = DeribitBookSummaryRaw {
221 instrument_name: "BTC-28MAR25-90000-C".to_string(),
222 underlying_price: Some(dec!(95000.5)),
223 underlying_index: Some("SYN.BTC-28MAR25".to_string()),
224 mark_price: Some(dec!(0.042)),
225 mid_price: None,
226 bid_price: Some(dec!(0.040)),
227 ask_price: Some(dec!(0.042)),
228 last_price: None,
229 mark_iv: Some(dec!(55.2)),
230 bid_iv: None,
231 ask_iv: None,
232 interest_rate: None,
233 open_interest: Some(dec!(123.5)),
234 open_interest_value: None,
235 volume: None,
236 volume_usd: None,
237 volume_notional: None,
238 volume_btc: None,
239 high: None,
240 low: None,
241 price_change: None,
242 estimated_delivery_price: None,
243 delivery_price: None,
244 base_currency: Some("BTC".to_string()),
245 quote_currency: Some("USD".to_string()),
246 creation_timestamp: 1_710_000_000_000,
247 };
248 let ts = UnixNanos::from(42_u64);
249 let summary = DeribitBookSummary::from_raw(raw, ts);
250
251 assert_eq!(
252 summary.instrument_id,
253 InstrumentId::from("BTC-28MAR25-90000-C.DERIBIT")
254 );
255 assert_eq!(summary.mark_iv, Some(dec!(55.2)));
256 assert_eq!(summary.open_interest, Some(dec!(123.5)));
257 assert_eq!(summary.ts_event, UnixNanos::from_millis(1_710_000_000_000));
258 assert_eq!(summary.ts_init, ts);
259 }
260
261 #[cfg(feature = "arrow")]
262 #[rstest]
263 fn test_deribit_volatility_index_arrow_schema() {
264 use arrow::datatypes::DataType;
265 use nautilus_serialization::arrow::{ArrowSchemaProvider, timestamp_data_type};
266
267 let schema = DeribitVolatilityIndex::get_schema(None);
268
269 assert_eq!(schema.fields().len(), 4);
270 assert_eq!(schema.field(0).name(), "index_name");
271 assert_eq!(schema.field(0).data_type(), &DataType::Utf8);
272 assert_eq!(schema.field(1).name(), "volatility");
273 assert_eq!(schema.field(1).data_type(), &DataType::Float64);
274 assert_eq!(schema.field(2).name(), "ts_event");
275 assert_eq!(schema.field(2).data_type(), ×tamp_data_type());
276 assert_eq!(schema.field(3).name(), "ts_init");
277 assert_eq!(schema.field(3).data_type(), ×tamp_data_type());
278 }
279
280 #[cfg(feature = "arrow")]
281 #[rstest]
282 fn test_book_summary_arrow_roundtrip_preserves_decimals() {
283 use arrow::datatypes::DataType;
284 use nautilus_serialization::arrow::{
285 ArrowSchemaProvider, DecodeDataFromRecordBatch, EncodeToRecordBatch,
286 };
287
288 let original = DeribitBookSummary::from_raw(
289 DeribitBookSummaryRaw {
290 instrument_name: "BTC-28MAR25-90000-C".to_string(),
291 underlying_price: Some(dec!(95000.5)),
292 underlying_index: Some("SYN.BTC-28MAR25".to_string()),
293 mark_price: Some(dec!(0.042)),
294 mid_price: None,
295 bid_price: Some(dec!(0.040)),
296 ask_price: Some(dec!(0.042)),
297 last_price: None,
298 mark_iv: Some(dec!(55.2)),
299 bid_iv: None,
300 ask_iv: None,
301 interest_rate: None,
302 open_interest: Some(dec!(123.5)),
303 open_interest_value: None,
304 volume: None,
305 volume_usd: None,
306 volume_notional: None,
307 volume_btc: None,
308 high: None,
309 low: None,
310 price_change: None,
311 estimated_delivery_price: None,
312 delivery_price: None,
313 base_currency: Some("BTC".to_string()),
314 quote_currency: Some("USD".to_string()),
315 creation_timestamp: 1_710_000_000_000,
316 },
317 UnixNanos::from(1_000_u64),
318 );
319
320 let schema = DeribitBookSummary::get_schema(None);
322 assert_eq!(
323 schema.field_with_name("mark_iv").unwrap().data_type(),
324 &DataType::Utf8
325 );
326
327 let metadata = original.metadata();
328 let batch =
329 DeribitBookSummary::encode_batch(&metadata, std::slice::from_ref(&original)).unwrap();
330 let decoded = DeribitBookSummary::decode_data_batch(&metadata, batch).unwrap();
331 let decoded = DeribitBookSummary::try_from(decoded.into_iter().next().unwrap()).unwrap();
332 assert_eq!(decoded, original);
333 }
334}