Skip to main content

finance_query/models/quote/
price.rs

1//! Price Module
2//!
3//! Contains detailed pricing data for a stock including pre/post market data,
4//! exchange information, and market state.
5
6use crate::models::format::{Both, Format};
7use finance_query_derive::FormatConvert;
8use serde::{Deserialize, Serialize};
9
10/// Detailed pricing data for a stock
11///
12/// Includes current price, pre/post market data, volume, market cap, and exchange information.
13///
14/// The type parameter `F` controls how numeric fields are represented:
15/// - `Price` / `Price<Both>` — **default**; fields hold `FormattedValue<T>`
16/// - `Price<Raw>` — fields hold `T` directly (e.g. `Option<f64>`)
17/// - `Price<Pretty>` — fields hold `Option<String>` (human-readable)
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, FormatConvert, Default)]
19#[serde(rename_all = "camelCase", bound = "")]
20#[non_exhaustive]
21pub struct Price<F: Format = Both> {
22    /// Maximum age of the data in seconds
23    #[serde(default)]
24    pub max_age: Option<i64>,
25
26    /// Pre-market change percentage
27    #[serde(default)]
28    pub pre_market_change_percent: Option<F::Value<f64>>,
29
30    /// Pre-market change value
31    #[serde(default)]
32    pub pre_market_change: Option<F::Value<f64>>,
33
34    /// Pre-market time as Unix timestamp
35    #[serde(default)]
36    pub pre_market_time: Option<i64>,
37
38    /// Pre-market price
39    #[serde(default)]
40    pub pre_market_price: Option<F::Value<f64>>,
41
42    /// Pre-market data source
43    #[serde(default)]
44    pub pre_market_source: Option<String>,
45
46    /// Post-market change percentage
47    #[serde(default)]
48    pub post_market_change_percent: Option<F::Value<f64>>,
49
50    /// Post-market change value
51    #[serde(default)]
52    pub post_market_change: Option<F::Value<f64>>,
53
54    /// Post-market time as Unix timestamp
55    #[serde(default)]
56    pub post_market_time: Option<i64>,
57
58    /// Post-market price
59    #[serde(default)]
60    pub post_market_price: Option<F::Value<f64>>,
61
62    /// Post-market data source
63    #[serde(default)]
64    pub post_market_source: Option<String>,
65
66    /// Regular market change percentage
67    #[serde(default)]
68    pub regular_market_change_percent: Option<F::Value<f64>>,
69
70    /// Regular market change value
71    #[serde(default)]
72    pub regular_market_change: Option<F::Value<f64>>,
73
74    /// Regular market time as Unix timestamp
75    #[serde(default)]
76    pub regular_market_time: Option<i64>,
77
78    /// Price hint for decimal places
79    #[serde(default)]
80    pub price_hint: Option<F::Value<i64>>,
81
82    /// Current regular market price
83    #[serde(default)]
84    pub regular_market_price: Option<F::Value<f64>>,
85
86    /// Regular market day high
87    #[serde(default)]
88    pub regular_market_day_high: Option<F::Value<f64>>,
89
90    /// Regular market day low
91    #[serde(default)]
92    pub regular_market_day_low: Option<F::Value<f64>>,
93
94    /// Regular market volume
95    #[serde(default)]
96    pub regular_market_volume: Option<F::Value<i64>>,
97
98    /// Average daily volume over 10 days
99    #[serde(default)]
100    pub average_daily_volume10_day: Option<F::Value<i64>>,
101
102    /// Average daily volume over 3 months
103    #[serde(default)]
104    pub average_daily_volume3_month: Option<F::Value<i64>>,
105
106    /// Regular market previous close
107    #[serde(default)]
108    pub regular_market_previous_close: Option<F::Value<f64>>,
109
110    /// Regular market data source
111    #[serde(default)]
112    pub regular_market_source: Option<String>,
113
114    /// Regular market open price
115    #[serde(default)]
116    pub regular_market_open: Option<F::Value<f64>>,
117
118    /// Exchange code (e.g., "NMS" for NASDAQ)
119    #[serde(default)]
120    pub exchange: Option<String>,
121
122    /// Exchange name (e.g., "NasdaqGS")
123    #[serde(default)]
124    pub exchange_name: Option<String>,
125
126    /// Exchange data delay in seconds
127    #[serde(default)]
128    pub exchange_data_delayed_by: Option<i32>,
129
130    /// Current market state (e.g., "REGULAR", "POST", "PRE")
131    #[serde(default)]
132    pub market_state: Option<String>,
133
134    /// Quote type (e.g., "EQUITY", "ETF", "MUTUALFUND")
135    #[serde(default)]
136    pub quote_type: Option<String>,
137
138    /// Stock symbol
139    #[serde(default)]
140    pub symbol: Option<String>,
141
142    /// Underlying symbol (for derivatives)
143    #[serde(default)]
144    pub underlying_symbol: Option<String>,
145
146    /// Short name of the security
147    #[serde(default)]
148    pub short_name: Option<String>,
149
150    /// Long name of the security
151    #[serde(default)]
152    pub long_name: Option<String>,
153
154    /// Currency code (e.g., "USD")
155    #[serde(default)]
156    pub currency: Option<String>,
157
158    /// Quote source name
159    #[serde(default)]
160    pub quote_source_name: Option<String>,
161
162    /// Currency symbol (e.g., "$")
163    #[serde(default)]
164    pub currency_symbol: Option<String>,
165
166    /// From currency (for currency pairs)
167    #[serde(default)]
168    pub from_currency: Option<String>,
169
170    /// To currency (for currency pairs)
171    #[serde(default)]
172    pub to_currency: Option<String>,
173
174    /// Last market
175    #[serde(default)]
176    pub last_market: Option<String>,
177
178    /// Market capitalization
179    #[serde(default)]
180    pub market_cap: Option<F::Value<i64>>,
181}
182
183impl Price<Both> {
184    /// Returns the current price (regular market price)
185    pub fn current_price(&self) -> Option<f64> {
186        self.regular_market_price.as_ref()?.raw
187    }
188
189    /// Returns the day's change in price
190    pub fn day_change(&self) -> Option<f64> {
191        self.regular_market_change.as_ref()?.raw
192    }
193
194    /// Returns the day's change as a percentage
195    pub fn day_change_percent(&self) -> Option<f64> {
196        self.regular_market_change_percent.as_ref()?.raw
197    }
198
199    /// Returns the day's trading range as (low, high)
200    pub fn day_range(&self) -> Option<(f64, f64)> {
201        let low = self.regular_market_day_low.as_ref()?.raw?;
202        let high = self.regular_market_day_high.as_ref()?.raw?;
203        Some((low, high))
204    }
205
206    /// Returns whether the market is currently open
207    pub fn is_market_open(&self) -> bool {
208        self.market_state.as_deref() == Some("REGULAR")
209    }
210
211    /// Returns whether this is in pre-market trading
212    pub fn is_pre_market(&self) -> bool {
213        self.market_state.as_deref() == Some("PRE")
214    }
215
216    /// Returns whether this is in post-market trading
217    pub fn is_post_market(&self) -> bool {
218        self.market_state.as_deref() == Some("POST")
219    }
220
221    /// Returns the most relevant current price based on market state
222    ///
223    /// Returns post-market price if in post-market, pre-market price if in pre-market,
224    /// otherwise regular market price.
225    pub fn live_price(&self) -> Option<f64> {
226        if self.is_post_market() {
227            self.post_market_price
228                .as_ref()
229                .and_then(|p| p.raw)
230                .or_else(|| self.regular_market_price.as_ref()?.raw)
231        } else if self.is_pre_market() {
232            self.pre_market_price
233                .as_ref()
234                .and_then(|p| p.raw)
235                .or_else(|| self.regular_market_price.as_ref()?.raw)
236        } else {
237            self.regular_market_price.as_ref()?.raw
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use serde_json::json;
246
247    #[test]
248    fn test_price_deserialize() {
249        let json = json!({
250            "maxAge": 1,
251            "regularMarketPrice": {"raw": 150.25, "fmt": "150.25"},
252            "regularMarketChange": {"raw": 2.50, "fmt": "2.50"},
253            "regularMarketChangePercent": {"raw": 0.0169, "fmt": "1.69%"},
254            "regularMarketDayHigh": {"raw": 151.00, "fmt": "151.00"},
255            "regularMarketDayLow": {"raw": 148.50, "fmt": "148.50"},
256            "regularMarketVolume": {"raw": 50000000, "fmt": "50M"},
257            "marketCap": {"raw": 2500000000000i64, "fmt": "2.5T"},
258            "symbol": "AAPL",
259            "shortName": "Apple Inc.",
260            "longName": "Apple Inc.",
261            "currency": "USD",
262            "exchange": "NMS",
263            "marketState": "REGULAR"
264        });
265
266        let price: Price = serde_json::from_value(json).unwrap();
267        assert_eq!(
268            price.regular_market_price.as_ref().and_then(|v| v.raw),
269            Some(150.25)
270        );
271        assert_eq!(price.symbol, Some("AAPL".to_string()));
272        assert_eq!(price.current_price(), Some(150.25));
273        assert_eq!(price.day_change(), Some(2.50));
274        assert!(price.is_market_open());
275    }
276
277    #[test]
278    fn test_price_helpers() {
279        use super::super::FormattedValue;
280
281        let price = Price {
282            regular_market_price: Some(FormattedValue::new(100.0)),
283            regular_market_day_low: Some(FormattedValue::new(98.0)),
284            regular_market_day_high: Some(FormattedValue::new(102.0)),
285            market_state: Some("REGULAR".to_string()),
286            post_market_price: Some(FormattedValue::new(101.0)),
287            pre_market_price: Some(FormattedValue::new(99.0)),
288            ..Default::default()
289        };
290
291        assert_eq!(price.day_range(), Some((98.0, 102.0)));
292        assert_eq!(price.live_price(), Some(100.0));
293        assert!(price.is_market_open());
294    }
295
296    #[test]
297    fn test_live_price_post_market() {
298        use super::super::FormattedValue;
299
300        let price = Price {
301            regular_market_price: Some(FormattedValue::new(100.0)),
302            post_market_price: Some(FormattedValue::new(101.0)),
303            market_state: Some("POST".to_string()),
304            ..Default::default()
305        };
306
307        assert_eq!(price.live_price(), Some(101.0));
308        assert!(price.is_post_market());
309    }
310
311    #[test]
312    fn test_into_raw() {
313        use super::super::FormattedValue;
314
315        let price = Price {
316            regular_market_price: Some(FormattedValue::new(100.0)),
317            market_state: Some("REGULAR".to_string()),
318            ..Default::default()
319        };
320
321        let raw = price.into_raw();
322        assert_eq!(raw.regular_market_price, Some(100.0));
323        assert_eq!(raw.market_state.as_deref(), Some("REGULAR"));
324    }
325}