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}