Skip to main content

finance_query/models/options/
response.rs

1use super::contract::{Contracts, OptionContract};
2use crate::Provider;
3/// Options Response module
4///
5/// Handles parsing of Yahoo Finance options API responses.
6/// These types are internal implementation details and not exposed in the public API.
7use serde::{Deserialize, Serialize};
8
9/// Response wrapper for options endpoint
10///
11/// Note: While this type is public for return values, users should not manually construct it.
12/// Use `Ticker::options()` to obtain options data.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct Options {
16    /// Option chain container
17    pub(crate) option_chain: OptionChainContainer,
18
19    /// Which data provider served this data (e.g., "yahoo", "polygon").
20    #[serde(skip_serializing_if = "Option::is_none", default)]
21    pub provider_id: Option<Provider>,
22}
23
24/// Container for option chain results
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub(crate) struct OptionChainContainer {
27    /// Results array
28    pub result: Vec<OptionChainResult>,
29
30    /// Error if any
31    pub error: Option<serde_json::Value>,
32}
33
34/// Single option chain result
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub(crate) struct OptionChainResult {
38    /// Underlying symbol
39    pub underlying_symbol: Option<String>,
40
41    /// Available expiration dates (Unix timestamps)
42    pub expiration_dates: Option<Vec<i64>>,
43
44    /// Available strike prices
45    pub strikes: Option<Vec<f64>>,
46
47    /// Whether has mini options
48    pub has_mini_options: Option<bool>,
49
50    /// Quote data
51    pub quote: Option<serde_json::Value>,
52
53    /// Options data (array of option chains)
54    pub options: Vec<OptionChainData>,
55}
56
57/// Option chain data for a specific expiration
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub(crate) struct OptionChainData {
61    /// Expiration date (Unix timestamp)
62    pub expiration_date: i64,
63
64    /// Whether has mini options
65    pub has_mini_options: Option<bool>,
66
67    /// Call options
68    pub calls: Option<Vec<OptionContract>>,
69
70    /// Put options
71    pub puts: Option<Vec<OptionContract>>,
72}
73
74impl Options {
75    /// Get the first result
76    pub(crate) fn first_result(&self) -> Option<&OptionChainResult> {
77        self.option_chain.result.first()
78    }
79
80    /// Get available expiration dates
81    pub fn expiration_dates(&self) -> Vec<i64> {
82        self.first_result()
83            .and_then(|r| r.expiration_dates.clone())
84            .unwrap_or_default()
85    }
86
87    /// Get strike prices
88    pub fn strikes(&self) -> Vec<f64> {
89        self.first_result()
90            .and_then(|r| r.strikes.clone())
91            .unwrap_or_default()
92    }
93
94    /// Get all call contracts flattened across all expirations.
95    ///
96    /// Returns a `Contracts` wrapper that supports `.to_dataframe()` when
97    /// the `dataframe` feature is enabled.
98    ///
99    /// # Example
100    /// ```ignore
101    /// let options = ticker.options(None).await?;
102    /// for call in &options.calls {
103    ///     println!("{}: strike={}", call.contract_symbol, call.strike);
104    /// }
105    /// // With dataframe feature:
106    /// let df = options.calls.to_dataframe()?;
107    /// ```
108    pub fn calls(&self) -> Contracts {
109        let contracts = self
110            .first_result()
111            .map(|r| {
112                r.options
113                    .iter()
114                    .flat_map(|chain| chain.calls.as_deref().unwrap_or_default().iter())
115                    .cloned()
116                    .collect()
117            })
118            .unwrap_or_default();
119        Contracts(contracts)
120    }
121
122    /// Get all put contracts flattened across all expirations.
123    ///
124    /// Returns a `Contracts` wrapper that supports `.to_dataframe()` when
125    /// the `dataframe` feature is enabled.
126    ///
127    /// # Example
128    /// ```ignore
129    /// let options = ticker.options(None).await?;
130    /// for put in &options.puts {
131    ///     println!("{}: strike={}", put.contract_symbol, put.strike);
132    /// }
133    /// // With dataframe feature:
134    /// let df = options.puts.to_dataframe()?;
135    /// ```
136    pub fn puts(&self) -> Contracts {
137        let contracts = self
138            .first_result()
139            .map(|r| {
140                r.options
141                    .iter()
142                    .flat_map(|chain| chain.puts.as_deref().unwrap_or_default().iter())
143                    .cloned()
144                    .collect()
145            })
146            .unwrap_or_default();
147        Contracts(contracts)
148    }
149}
150
151#[cfg(feature = "dataframe")]
152impl Options {
153    /// Converts all option contracts (calls and puts) to a polars DataFrame.
154    ///
155    /// Flattens all contracts across all expiration dates into a single DataFrame
156    /// with an additional `option_type` column ("call" or "put").
157    ///
158    /// For separate DataFrames, use `options.calls.to_dataframe()` or
159    /// `options.puts.to_dataframe()`.
160    pub fn to_dataframe(&self) -> ::polars::prelude::PolarsResult<::polars::prelude::DataFrame> {
161        use polars::prelude::*;
162
163        let calls = self.calls();
164        let puts = self.puts();
165
166        let mut calls_df = calls.to_dataframe()?;
167        let mut puts_df = puts.to_dataframe()?;
168
169        // Add option_type column
170        let call_types = Series::new("option_type".into(), vec!["call"; calls.len()]);
171        let put_types = Series::new("option_type".into(), vec!["put"; puts.len()]);
172
173        calls_df.with_column(call_types.into())?;
174        puts_df.with_column(put_types.into())?;
175
176        // Combine into single DataFrame
177        calls_df.vstack(&puts_df)
178    }
179}