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}