1use nautilus_core::UnixNanos;
22use nautilus_model::identifiers::{InstrumentId, Symbol};
23use nautilus_persistence_macros::custom_data;
24use rust_decimal::Decimal;
25
26use crate::{common::consts::DERIBIT_VENUE, http::models::DeribitBookSummaryRaw};
27
28#[cfg_attr(
32 feature = "arrow",
33 custom_data(pyo3, stub_module = "nautilus_trader.adapters.deribit")
34)]
35#[cfg_attr(
36 not(feature = "arrow"),
37 custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.deribit")
38)]
39pub struct DeribitVolatilityIndex {
40 pub index_name: String,
42 pub volatility: f64,
44 pub ts_event: UnixNanos,
46 pub ts_init: UnixNanos,
48}
49
50#[cfg_attr(
56 feature = "arrow",
57 custom_data(pyo3, stub_module = "nautilus_trader.adapters.deribit")
58)]
59#[cfg_attr(
60 not(feature = "arrow"),
61 custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.deribit")
62)]
63pub struct DeribitBookSummary {
64 pub instrument_id: InstrumentId,
66 pub instrument_name: String,
68 #[custom_data_field(serde)]
70 pub underlying_price: Option<Decimal>,
71 #[custom_data_field(serde)]
73 pub underlying_index: Option<String>,
74 #[custom_data_field(serde)]
76 pub mark_price: Option<Decimal>,
77 #[custom_data_field(serde)]
79 pub mid_price: Option<Decimal>,
80 #[custom_data_field(serde)]
82 pub bid_price: Option<Decimal>,
83 #[custom_data_field(serde)]
85 pub ask_price: Option<Decimal>,
86 #[custom_data_field(serde)]
88 pub last_price: Option<Decimal>,
89 #[custom_data_field(serde)]
91 pub mark_iv: Option<Decimal>,
92 #[custom_data_field(serde)]
94 pub bid_iv: Option<Decimal>,
95 #[custom_data_field(serde)]
97 pub ask_iv: Option<Decimal>,
98 #[custom_data_field(serde)]
100 pub interest_rate: Option<Decimal>,
101 #[custom_data_field(serde)]
103 pub open_interest: Option<Decimal>,
104 #[custom_data_field(serde)]
106 pub open_interest_value: Option<Decimal>,
107 #[custom_data_field(serde)]
109 pub volume: Option<Decimal>,
110 #[custom_data_field(serde)]
112 pub volume_usd: Option<Decimal>,
113 #[custom_data_field(serde)]
115 pub volume_notional: Option<Decimal>,
116 #[custom_data_field(serde)]
118 pub volume_btc: Option<Decimal>,
119 #[custom_data_field(serde)]
121 pub high: Option<Decimal>,
122 #[custom_data_field(serde)]
124 pub low: Option<Decimal>,
125 #[custom_data_field(serde)]
127 pub price_change: Option<Decimal>,
128 #[custom_data_field(serde)]
130 pub estimated_delivery_price: Option<Decimal>,
131 #[custom_data_field(serde)]
133 pub delivery_price: Option<Decimal>,
134 #[custom_data_field(serde)]
136 pub base_currency: Option<String>,
137 #[custom_data_field(serde)]
139 pub quote_currency: Option<String>,
140 pub creation_timestamp: i64,
142 pub ts_event: UnixNanos,
144 pub ts_init: UnixNanos,
146}
147
148impl DeribitBookSummary {
149 #[must_use]
151 pub fn from_raw(raw: DeribitBookSummaryRaw, ts: UnixNanos) -> Self {
152 let instrument_id = InstrumentId::new(Symbol::new(&raw.instrument_name), *DERIBIT_VENUE);
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: ts,
182 ts_init: ts,
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, ts);
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;
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(), &DataType::UInt64);
276 assert_eq!(schema.field(3).name(), "ts_init");
277 assert_eq!(schema.field(3).data_type(), &DataType::UInt64);
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}