deribit_base/model/
market_data.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/7/25
5******************************************************************************/
6
7use crate::model::instrument::InstrumentKind;
8use serde::{Deserialize, Serialize};
9
10use crate::{impl_json_debug_pretty, impl_json_display};
11/// Ticker information
12#[derive(Clone, Serialize, Deserialize)]
13pub struct Ticker {
14    /// Instrument name
15    pub instrument_name: String,
16    /// Timestamp of the ticker data
17    pub timestamp: i64,
18    /// Best bid price
19    pub best_bid_price: Option<f64>,
20    /// Best bid amount
21    pub best_bid_amount: Option<f64>,
22    /// Best ask price
23    pub best_ask_price: Option<f64>,
24    /// Best ask amount
25    pub best_ask_amount: Option<f64>,
26    /// Last trade price
27    pub last_price: Option<f64>,
28    /// Mark price
29    pub mark_price: Option<f64>,
30    /// Index price
31    pub index_price: Option<f64>,
32    /// Open interest
33    pub open_interest: f64,
34    /// 24h volume
35    pub volume_24h: f64,
36    /// 24h volume in USD
37    pub volume_usd_24h: f64,
38    /// 24h price change
39    pub price_change_24h: f64,
40    /// High price in 24h
41    pub high_24h: Option<f64>,
42    /// Low price in 24h
43    pub low_24h: Option<f64>,
44    /// Underlying price (for derivatives)
45    pub underlying_price: Option<f64>,
46    /// Underlying index
47    pub underlying_index: Option<String>,
48    /// Instrument kind
49    pub instrument_kind: Option<InstrumentKind>,
50    /// Current funding rate (for perpetuals)
51    pub current_funding: Option<f64>,
52    /// Funding 8h rate
53    pub funding_8h: Option<f64>,
54    /// Implied volatility (for options)
55    pub iv: Option<f64>,
56    /// Greeks (for options)
57    pub greeks: Option<Greeks>,
58    /// Interest rate
59    pub interest_rate: Option<f64>,
60}
61
62impl Ticker {
63    /// Calculate bid-ask spread
64    pub fn spread(&self) -> Option<f64> {
65        match (self.best_ask_price, self.best_bid_price) {
66            (Some(ask), Some(bid)) => Some(ask - bid),
67            _ => None,
68        }
69    }
70
71    /// Calculate mid price
72    pub fn mid_price(&self) -> Option<f64> {
73        match (self.best_ask_price, self.best_bid_price) {
74            (Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
75            _ => None,
76        }
77    }
78
79    /// Calculate spread percentage
80    pub fn spread_percentage(&self) -> Option<f64> {
81        match (self.spread(), self.mid_price()) {
82            (Some(spread), Some(mid)) if mid != 0.0 => Some((spread / mid) * 100.0),
83            _ => None,
84        }
85    }
86
87    /// Check if there's a valid bid-ask spread
88    pub fn has_valid_spread(&self) -> bool {
89        self.best_bid_price.is_some() && self.best_ask_price.is_some()
90    }
91}
92
93/// Order book entry
94#[derive(Clone, Serialize, Deserialize)]
95pub struct OrderBookEntry {
96    /// Price level
97    pub price: f64,
98    /// Amount at this price level
99    pub amount: f64,
100}
101
102impl OrderBookEntry {
103    /// Create a new order book entry
104    pub fn new(price: f64, amount: f64) -> Self {
105        Self { price, amount }
106    }
107
108    /// Calculate notional value
109    pub fn notional(&self) -> f64 {
110        self.price * self.amount
111    }
112}
113
114/// Order book data
115#[derive(Clone, Serialize, Deserialize)]
116pub struct OrderBook {
117    /// Instrument name
118    pub instrument_name: String,
119    /// Timestamp of the order book
120    pub timestamp: i64,
121    /// Bid levels (sorted by price descending)
122    pub bids: Vec<OrderBookEntry>,
123    /// Ask levels (sorted by price ascending)
124    pub asks: Vec<OrderBookEntry>,
125    /// Change ID for incremental updates
126    pub change_id: u64,
127    /// Previous change ID
128    pub prev_change_id: Option<u64>,
129}
130
131impl OrderBook {
132    /// Create a new empty order book
133    pub fn new(instrument_name: String, timestamp: i64, change_id: u64) -> Self {
134        Self {
135            instrument_name,
136            timestamp,
137            bids: Vec::new(),
138            asks: Vec::new(),
139            change_id,
140            prev_change_id: None,
141        }
142    }
143
144    /// Get best bid price
145    pub fn best_bid(&self) -> Option<f64> {
146        self.bids.first().map(|entry| entry.price)
147    }
148
149    /// Get best ask price
150    pub fn best_ask(&self) -> Option<f64> {
151        self.asks.first().map(|entry| entry.price)
152    }
153
154    /// Get bid-ask spread
155    pub fn spread(&self) -> Option<f64> {
156        match (self.best_ask(), self.best_bid()) {
157            (Some(ask), Some(bid)) => Some(ask - bid),
158            _ => None,
159        }
160    }
161
162    /// Get mid price
163    pub fn mid_price(&self) -> Option<f64> {
164        match (self.best_ask(), self.best_bid()) {
165            (Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
166            _ => None,
167        }
168    }
169
170    /// Calculate total bid volume
171    pub fn total_bid_volume(&self) -> f64 {
172        self.bids.iter().map(|entry| entry.amount).sum()
173    }
174
175    /// Calculate total ask volume
176    pub fn total_ask_volume(&self) -> f64 {
177        self.asks.iter().map(|entry| entry.amount).sum()
178    }
179
180    /// Get volume at specific price level
181    pub fn volume_at_price(&self, price: f64, is_bid: bool) -> f64 {
182        let levels = if is_bid { &self.bids } else { &self.asks };
183        levels
184            .iter()
185            .find(|entry| (entry.price - price).abs() < f64::EPSILON)
186            .map(|entry| entry.amount)
187            .unwrap_or(0.0)
188    }
189}
190
191/// Greeks for options
192#[derive(Clone, Serialize, Deserialize)]
193pub struct Greeks {
194    /// Delta - sensitivity to underlying price changes
195    pub delta: f64,
196    /// Gamma - rate of change of delta
197    pub gamma: f64,
198    /// Theta - time decay
199    pub theta: f64,
200    /// Vega - sensitivity to volatility changes
201    pub vega: f64,
202    /// Rho - sensitivity to interest rate changes
203    pub rho: Option<f64>,
204}
205
206/// Market statistics
207#[derive(Clone, Serialize, Deserialize)]
208pub struct MarketStats {
209    /// Currency
210    pub currency: String,
211    /// Total volume in 24h
212    pub volume_24h: f64,
213    /// Volume change in 24h
214    pub volume_change_24h: f64,
215    /// Price change in 24h
216    pub price_change_24h: f64,
217    /// High price in 24h
218    pub high_24h: f64,
219    /// Low price in 24h
220    pub low_24h: f64,
221    /// Number of active instruments
222    pub active_instruments: u32,
223    /// Total open interest
224    pub total_open_interest: f64,
225}
226
227/// Candlestick/OHLCV data
228#[derive(Clone, Serialize, Deserialize)]
229pub struct Candle {
230    /// Timestamp
231    pub timestamp: i64,
232    /// Open price
233    pub open: f64,
234    /// High price
235    pub high: f64,
236    /// Low price
237    pub low: f64,
238    /// Close price
239    pub close: f64,
240    /// Volume
241    pub volume: f64,
242    /// Number of trades
243    pub trades: Option<u64>,
244}
245
246impl Candle {
247    /// Check if this is a bullish candle
248    pub fn is_bullish(&self) -> bool {
249        self.close > self.open
250    }
251
252    /// Check if this is a bearish candle
253    pub fn is_bearish(&self) -> bool {
254        self.close < self.open
255    }
256
257    /// Calculate the body size
258    pub fn body_size(&self) -> f64 {
259        (self.close - self.open).abs()
260    }
261
262    /// Calculate the upper shadow
263    pub fn upper_shadow(&self) -> f64 {
264        self.high - self.close.max(self.open)
265    }
266
267    /// Calculate the lower shadow
268    pub fn lower_shadow(&self) -> f64 {
269        self.close.min(self.open) - self.low
270    }
271
272    /// Calculate the range (high - low)
273    pub fn range(&self) -> f64 {
274        self.high - self.low
275    }
276}
277
278// Debug implementations using pretty JSON formatting
279impl_json_debug_pretty!(
280    Ticker,
281    OrderBookEntry,
282    OrderBook,
283    Greeks,
284    MarketStats,
285    Candle
286);
287
288// Display implementations using compact JSON formatting
289impl_json_display!(
290    Ticker,
291    OrderBookEntry,
292    OrderBook,
293    Greeks,
294    MarketStats,
295    Candle
296);