Skip to main content

deribit_http/model/
other.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 15/9/25
5******************************************************************************/
6use crate::model::instrument::Instrument;
7use crate::model::ticker::TickerData;
8use crate::model::{BasicGreeks, BasicOptionData, OptionType, Spread};
9use chrono::{DateTime, TimeZone, Utc};
10use pretty_simple_display::{DebugPretty, DisplaySimple};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use serde_with::skip_serializing_none;
14
15/// Delivery price data
16#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
17pub struct DeliveryPriceData {
18    /// Date of the delivery price
19    pub date: String,
20    /// Delivery price value
21    pub delivery_price: f64,
22}
23
24/// Greeks sub-structure for options
25#[skip_serializing_none]
26#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
27pub struct Greeks {
28    /// Delta value
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub delta: Option<f64>,
31    /// Gamma value
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub gamma: Option<f64>,
34    /// Vega value
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub vega: Option<f64>,
37    /// Theta value
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub theta: Option<f64>,
40    /// Rho value
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub rho: Option<f64>,
43}
44
45/// Combined option instrument data with ticker information
46#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
47pub struct OptionInstrument {
48    /// The instrument details
49    pub instrument: Instrument,
50    /// Real-time ticker data for the option
51    pub ticker: TickerData,
52}
53
54/// A pair of option instruments representing both call and put options for the same underlying asset
55///
56/// This structure groups together the call and put options for a specific underlying asset,
57/// allowing for easy access to both sides of an option strategy. Both options are optional,
58/// meaning you can have just a call, just a put, or both.
59///
60#[skip_serializing_none]
61#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
62pub struct OptionInstrumentPair {
63    /// Call option instrument data, if available
64    pub call: Option<OptionInstrument>,
65    /// Put option instrument data, if available  
66    pub put: Option<OptionInstrument>,
67}
68
69impl OptionInstrumentPair {
70    /// Returns the expiration date and time of the option instrument
71    ///
72    /// # Returns
73    ///
74    /// * `Some(DateTime<Utc>)` - The expiration timestamp if available
75    /// * `None` - If no instrument is available or expiration timestamp is not set
76    pub fn expiration(&self) -> Option<DateTime<Utc>> {
77        let expiration_timestamp = self.instrument()?.expiration_timestamp;
78
79        if let Some(expiration_timestamp) = expiration_timestamp {
80            Utc.timestamp_millis_opt(expiration_timestamp).single()
81        } else {
82            None
83        }
84    }
85    /// Returns the first available instrument from either the call or put option
86    ///
87    /// # Returns
88    ///
89    /// * `Some(Instrument)` - The instrument data from call option if available, otherwise from put option
90    /// * `None` - If neither call nor put options are available
91    pub fn instrument(&self) -> Option<Instrument> {
92        self.call
93            .as_ref()
94            .map(|i| i.instrument.clone())
95            .or_else(|| self.put.as_ref().map(|i| i.instrument.clone()))
96    }
97    /// Returns the first available ticker data from either the call or put option
98    ///
99    /// # Returns
100    ///
101    /// * `Some(TickerData)` - The ticker data from call option if available, otherwise from put option
102    /// * `None` - If neither call nor put options are available
103    pub fn ticker(&self) -> Option<TickerData> {
104        self.call
105            .as_ref()
106            .map(|i| i.ticker.clone())
107            .or_else(|| self.put.as_ref().map(|i| i.ticker.clone()))
108    }
109    /// Calculates the total trading volume across both call and put options
110    ///
111    /// # Returns
112    ///
113    /// The sum of volumes from both call and put options. Returns 0.0 if no options are available.
114    pub fn volume(&self) -> f64 {
115        let mut volume: f64 = 0.0;
116        if let Some(call) = &self.call {
117            volume += call.ticker.stats.volume
118        }
119        if let Some(put) = &self.put {
120            volume += put.ticker.stats.volume
121        }
122        volume
123    }
124    /// Calculates the total open interest across both call and put options
125    ///
126    /// # Returns
127    ///
128    /// The sum of open interest from both call and put options. Returns 0.0 if no options are available.
129    pub fn open_interest(&self) -> f64 {
130        let mut open_interest: f64 = 0.0;
131        if let Some(call) = &self.call {
132            open_interest += call.ticker.open_interest.unwrap_or(0.0)
133        }
134        if let Some(put) = &self.put {
135            open_interest += put.ticker.open_interest.unwrap_or(0.0)
136        }
137        open_interest
138    }
139
140    /// Calculates the total interest rate across both call and put options
141    ///
142    /// # Returns
143    ///
144    /// The sum of interest rates from both call and put options. Returns 0.0 if no options are available.
145    pub fn interest_rate(&self) -> f64 {
146        let mut interest_rate: f64 = 0.0;
147        if let Some(call) = &self.call {
148            interest_rate += call.ticker.interest_rate.unwrap_or(0.0)
149        }
150        if let Some(put) = &self.put {
151            interest_rate += put.ticker.interest_rate.unwrap_or(0.0)
152        }
153        interest_rate
154    }
155
156    /// Serializes the option instrument pair to a JSON value
157    ///
158    /// # Returns
159    ///
160    /// * `Some(Value)` - The serialized JSON representation of this option pair
161    /// * `None` - If serialization fails
162    pub fn value(&self) -> Option<Value> {
163        serde_json::to_value(self).ok()
164    }
165
166    /// Calculates the bid-ask spread for the call option
167    ///
168    /// # Returns
169    ///
170    /// A `Spread` struct containing bid, ask, and mid prices for the call option.
171    /// Returns empty spread (all None values) if no call option is available.
172    pub fn call_spread(&self) -> Spread {
173        if let Some(call) = &self.call {
174            let bid = call.ticker.best_bid_price;
175            let ask = call.ticker.best_ask_price;
176            let mid = match (bid, ask) {
177                (Some(b), Some(a)) => Some((b + a) / 2.0),
178                (Some(b), None) => Some(b),
179                (None, Some(a)) => Some(a),
180                (None, None) => None,
181            };
182            Spread { bid, ask, mid }
183        } else {
184            Spread {
185                bid: None,
186                ask: None,
187                mid: None,
188            }
189        }
190    }
191
192    /// Calculates the bid-ask spread for the put option
193    ///
194    /// # Returns
195    ///
196    /// A `Spread` struct containing bid, ask, and mid prices for the put option.
197    /// Returns empty spread (all None values) if no put option is available.
198    pub fn put_spread(&self) -> Spread {
199        if let Some(put) = &self.put {
200            let bid = put.ticker.best_bid_price;
201            let ask = put.ticker.best_ask_price;
202            let mid = match (bid, ask) {
203                (Some(b), Some(a)) => Some((b + a) / 2.0),
204                (Some(b), None) => Some(b),
205                (None, Some(a)) => Some(a),
206                (None, None) => None,
207            };
208            Spread { bid, ask, mid }
209        } else {
210            Spread {
211                bid: None,
212                ask: None,
213                mid: None,
214            }
215        }
216    }
217
218    /// Returns the implied volatility for both call and put options
219    ///
220    /// # Returns
221    ///
222    /// A tuple containing `(call_iv, put_iv)` where each element is the implied volatility
223    /// for the respective option, or `None` if not available.
224    pub fn iv(&self) -> (Option<f64>, Option<f64>) {
225        let call_iv = self.call.as_ref().and_then(|c| c.ticker.mark_iv);
226        let put_iv = self.put.as_ref().and_then(|p| p.ticker.mark_iv);
227        (call_iv, put_iv)
228    }
229
230    /// Calculates and returns the basic Greeks for both call and put options
231    ///
232    /// # Returns
233    ///
234    /// A `BasicGreeks` struct containing delta values for call and put options,
235    /// and gamma value (taken from either option if available).
236    pub fn greeks(&self) -> BasicGreeks {
237        let delta_call = self
238            .call
239            .as_ref()
240            .and_then(|c| c.ticker.greeks.as_ref().and_then(|g| g.delta));
241        let delta_put = self
242            .put
243            .as_ref()
244            .and_then(|p| p.ticker.greeks.as_ref().and_then(|g| g.delta));
245        let gamma = self
246            .call
247            .as_ref()
248            .and_then(|c| c.ticker.greeks.as_ref().and_then(|g| g.gamma))
249            .or_else(|| {
250                self.put
251                    .as_ref()
252                    .and_then(|p| p.ticker.greeks.as_ref().and_then(|g| g.gamma))
253            });
254        BasicGreeks {
255            delta_call,
256            delta_put,
257            gamma,
258        }
259    }
260
261    /// Extracts and consolidates all relevant option data into a structured format
262    ///
263    /// # Returns
264    ///
265    /// A `BasicOptionData` struct containing comprehensive option information including
266    /// strike price, bid/ask prices, implied volatility, Greeks, volume, open interest,
267    /// expiration date, underlying price, risk-free rate, and additional fields.
268    pub fn data(&self) -> BasicOptionData {
269        let strike_price: f64 = match self.instrument() {
270            Some(i) => i.strike.unwrap_or(0.0),
271            None => 0.0,
272        };
273        let call_spread = self.call_spread();
274        let call_bid: Option<f64> = call_spread.bid;
275        let call_ask: Option<f64> = call_spread.ask;
276        let put_spread = self.put_spread();
277        let put_bid: Option<f64> = put_spread.bid;
278        let put_ask: Option<f64> = put_spread.ask;
279        let implied_volatility = self.iv();
280        let greeks = self.greeks();
281        let delta_call: Option<f64> = greeks.delta_call;
282        let delta_put: Option<f64> = greeks.delta_put;
283        let gamma: Option<f64> = greeks.gamma;
284        let volume = self.volume();
285        let open_interest: f64 = self.open_interest();
286        let expiration_date: Option<DateTime<Utc>> = self.expiration();
287        let underlying_price: Option<f64> = self.ticker().and_then(|t| t.underlying_price);
288        let risk_free_rate: f64 = self.interest_rate();
289        let extra_fields: Option<Value> = self.value();
290        BasicOptionData {
291            strike_price,
292            call_bid,
293            call_ask,
294            put_bid,
295            put_ask,
296            implied_volatility,
297            delta_call,
298            delta_put,
299            gamma,
300            volume,
301            open_interest,
302            expiration_date,
303            underlying_price,
304            risk_free_rate,
305            extra_fields,
306        }
307    }
308}
309
310/// Parsed option instrument with ticker data
311#[skip_serializing_none]
312#[derive(DebugPretty, DisplaySimple, Clone, Serialize)]
313pub struct ParsedOptionWithTicker {
314    /// The instrument name (e.g., "BTC-25DEC21-50000-C")
315    pub instrument_name: String,
316    /// Strike price of the option
317    pub strike: f64,
318    /// Type of option (Call or Put)
319    pub option_type: OptionType,
320    /// Expiry date string
321    pub expiry: String,
322    /// Associated ticker data
323    pub ticker: TickerData,
324}
325
326/// Sort direction options
327#[derive(Debug, Clone, Serialize, Deserialize, Default)]
328#[serde(rename_all = "lowercase")]
329pub enum SortDirection {
330    /// Ascending sort order
331    #[default]
332    Asc,
333    /// Descending sort order
334    Desc,
335    /// Default sort order (platform-specific)
336    Default,
337}
338
339impl std::fmt::Display for SortDirection {
340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        match self {
342            SortDirection::Asc => write!(f, "asc"),
343            SortDirection::Desc => write!(f, "desc"),
344            SortDirection::Default => write!(f, "default"),
345        }
346    }
347}