finance_query/models/options/
response.rs

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