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 = match self.instrument() {
78            Some(i) => i.expiration_timestamp,
79            None => return None,
80        };
81
82        if let Some(expiration_timestamp) = expiration_timestamp {
83            Utc.timestamp_millis_opt(expiration_timestamp).single()
84        } else {
85            None
86        }
87    }
88    /// Returns the first available instrument from either the call or put option
89    ///
90    /// # Returns
91    ///
92    /// * `Some(Instrument)` - The instrument data from call option if available, otherwise from put option
93    /// * `None` - If neither call nor put options are available
94    pub fn instrument(&self) -> Option<Instrument> {
95        self.call
96            .as_ref()
97            .map(|i| i.instrument.clone())
98            .or_else(|| self.put.as_ref().map(|i| i.instrument.clone()))
99    }
100    /// Returns the first available ticker data from either the call or put option
101    ///
102    /// # Returns
103    ///
104    /// * `Some(TickerData)` - The ticker data from call option if available, otherwise from put option
105    /// * `None` - If neither call nor put options are available
106    pub fn ticker(&self) -> Option<TickerData> {
107        self.call
108            .as_ref()
109            .map(|i| i.ticker.clone())
110            .or_else(|| self.put.as_ref().map(|i| i.ticker.clone()))
111    }
112    /// Calculates the total trading volume across both call and put options
113    ///
114    /// # Returns
115    ///
116    /// The sum of volumes from both call and put options. Returns 0.0 if no options are available.
117    pub fn volume(&self) -> f64 {
118        let mut volume: f64 = 0.0;
119        if let Some(call) = &self.call {
120            volume += call.ticker.stats.volume
121        }
122        if let Some(put) = &self.put {
123            volume += put.ticker.stats.volume
124        }
125        volume
126    }
127    /// Calculates the total open interest across both call and put options
128    ///
129    /// # Returns
130    ///
131    /// The sum of open interest from both call and put options. Returns 0.0 if no options are available.
132    pub fn open_interest(&self) -> f64 {
133        let mut open_interest: f64 = 0.0;
134        if let Some(call) = &self.call {
135            open_interest += call.ticker.open_interest.unwrap_or(0.0)
136        }
137        if let Some(put) = &self.put {
138            open_interest += put.ticker.open_interest.unwrap_or(0.0)
139        }
140        open_interest
141    }
142
143    /// Calculates the total interest rate across both call and put options
144    ///
145    /// # Returns
146    ///
147    /// The sum of interest rates from both call and put options. Returns 0.0 if no options are available.
148    pub fn interest_rate(&self) -> f64 {
149        let mut interest_rate: f64 = 0.0;
150        if let Some(call) = &self.call {
151            interest_rate += call.ticker.interest_rate.unwrap_or(0.0)
152        }
153        if let Some(put) = &self.put {
154            interest_rate += put.ticker.interest_rate.unwrap_or(0.0)
155        }
156        interest_rate
157    }
158
159    /// Serializes the option instrument pair to a JSON value
160    ///
161    /// # Returns
162    ///
163    /// * `Some(Value)` - The serialized JSON representation of this option pair
164    /// * `None` - If serialization fails
165    pub fn value(&self) -> Option<Value> {
166        serde_json::to_value(self).ok()
167    }
168
169    /// Calculates the bid-ask spread for the call option
170    ///
171    /// # Returns
172    ///
173    /// A `Spread` struct containing bid, ask, and mid prices for the call option.
174    /// Returns empty spread (all None values) if no call option is available.
175    pub fn call_spread(&self) -> Spread {
176        if let Some(call) = &self.call {
177            let bid = call.ticker.best_bid_price;
178            let ask = call.ticker.best_ask_price;
179            let mid = match (bid, ask) {
180                (Some(b), Some(a)) => Some((b + a) / 2.0),
181                (Some(b), None) => Some(b),
182                (None, Some(a)) => Some(a),
183                (None, None) => None,
184            };
185            Spread { bid, ask, mid }
186        } else {
187            Spread {
188                bid: None,
189                ask: None,
190                mid: None,
191            }
192        }
193    }
194
195    /// Calculates the bid-ask spread for the put option
196    ///
197    /// # Returns
198    ///
199    /// A `Spread` struct containing bid, ask, and mid prices for the put option.
200    /// Returns empty spread (all None values) if no put option is available.
201    pub fn put_spread(&self) -> Spread {
202        if let Some(put) = &self.put {
203            let bid = put.ticker.best_bid_price;
204            let ask = put.ticker.best_ask_price;
205            let mid = match (bid, ask) {
206                (Some(b), Some(a)) => Some((b + a) / 2.0),
207                (Some(b), None) => Some(b),
208                (None, Some(a)) => Some(a),
209                (None, None) => None,
210            };
211            Spread { bid, ask, mid }
212        } else {
213            Spread {
214                bid: None,
215                ask: None,
216                mid: None,
217            }
218        }
219    }
220
221    /// Returns the implied volatility for both call and put options
222    ///
223    /// # Returns
224    ///
225    /// A tuple containing `(call_iv, put_iv)` where each element is the implied volatility
226    /// for the respective option, or `None` if not available.
227    pub fn iv(&self) -> (Option<f64>, Option<f64>) {
228        let call_iv = self.call.as_ref().and_then(|c| c.ticker.mark_iv);
229        let put_iv = self.put.as_ref().and_then(|p| p.ticker.mark_iv);
230        (call_iv, put_iv)
231    }
232
233    /// Calculates and returns the basic Greeks for both call and put options
234    ///
235    /// # Returns
236    ///
237    /// A `BasicGreeks` struct containing delta values for call and put options,
238    /// and gamma value (taken from either option if available).
239    pub fn greeks(&self) -> BasicGreeks {
240        let delta_call = self
241            .call
242            .as_ref()
243            .and_then(|c| c.ticker.greeks.as_ref().and_then(|g| g.delta));
244        let delta_put = self
245            .put
246            .as_ref()
247            .and_then(|p| p.ticker.greeks.as_ref().and_then(|g| g.delta));
248        let gamma = self
249            .call
250            .as_ref()
251            .and_then(|c| c.ticker.greeks.as_ref().and_then(|g| g.gamma))
252            .or_else(|| {
253                self.put
254                    .as_ref()
255                    .and_then(|p| p.ticker.greeks.as_ref().and_then(|g| g.gamma))
256            });
257        BasicGreeks {
258            delta_call,
259            delta_put,
260            gamma,
261        }
262    }
263
264    /// Extracts and consolidates all relevant option data into a structured format
265    ///
266    /// # Returns
267    ///
268    /// A `BasicOptionData` struct containing comprehensive option information including
269    /// strike price, bid/ask prices, implied volatility, Greeks, volume, open interest,
270    /// expiration date, underlying price, risk-free rate, and additional fields.
271    pub fn data(&self) -> BasicOptionData {
272        let strike_price: f64 = match self.instrument() {
273            Some(i) => i.strike.unwrap_or(0.0),
274            None => 0.0,
275        };
276        let call_spread = self.call_spread();
277        let call_bid: Option<f64> = call_spread.bid;
278        let call_ask: Option<f64> = call_spread.ask;
279        let put_spread = self.put_spread();
280        let put_bid: Option<f64> = put_spread.bid;
281        let put_ask: Option<f64> = put_spread.ask;
282        let implied_volatility = self.iv();
283        let greeks = self.greeks();
284        let delta_call: Option<f64> = greeks.delta_call;
285        let delta_put: Option<f64> = greeks.delta_put;
286        let gamma: Option<f64> = greeks.gamma;
287        let volume = self.volume();
288        let open_interest: f64 = self.open_interest();
289        let expiration_date: Option<DateTime<Utc>> = self.expiration();
290        let underlying_price: Option<f64> = self.ticker().and_then(|t| t.underlying_price);
291        let risk_free_rate: f64 = self.interest_rate();
292        let extra_fields: Option<Value> = self.value();
293        BasicOptionData {
294            strike_price,
295            call_bid,
296            call_ask,
297            put_bid,
298            put_ask,
299            implied_volatility,
300            delta_call,
301            delta_put,
302            gamma,
303            volume,
304            open_interest,
305            expiration_date,
306            underlying_price,
307            risk_free_rate,
308            extra_fields,
309        }
310    }
311}
312
313/// Parsed option instrument with ticker data
314#[skip_serializing_none]
315#[derive(DebugPretty, DisplaySimple, Clone, Serialize)]
316pub struct ParsedOptionWithTicker {
317    /// The instrument name (e.g., "BTC-25DEC21-50000-C")
318    pub instrument_name: String,
319    /// Strike price of the option
320    pub strike: f64,
321    /// Type of option (Call or Put)
322    pub option_type: OptionType,
323    /// Expiry date string
324    pub expiry: String,
325    /// Associated ticker data
326    pub ticker: TickerData,
327}
328
329/// Sort direction options
330#[derive(Debug, Clone, Serialize, Deserialize, Default)]
331#[serde(rename_all = "lowercase")]
332pub enum SortDirection {
333    /// Ascending sort order
334    #[default]
335    Asc,
336    /// Descending sort order
337    Desc,
338    /// Default sort order (platform-specific)
339    Default,
340}
341
342impl std::fmt::Display for SortDirection {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        match self {
345            SortDirection::Asc => write!(f, "asc"),
346            SortDirection::Desc => write!(f, "desc"),
347            SortDirection::Default => write!(f, "default"),
348        }
349    }
350}