Skip to main content

finance_query/adapters/alphavantage/
models.rs

1//! Shared types and deserialization helpers for Alpha Vantage responses.
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5/// Deserialize a string value to `Option<f64>`.
6///
7/// Alpha Vantage returns numeric values as strings, and uses `"None"` or `"."`
8/// for missing data.
9#[allow(dead_code)]
10pub(crate) fn deserialize_optional_f64<'de, D>(
11    deserializer: D,
12) -> std::result::Result<Option<f64>, D::Error>
13where
14    D: Deserializer<'de>,
15{
16    let s: Option<String> = Option::deserialize(deserializer)?;
17    match s.as_deref() {
18        Some("None") | Some(".") | Some("-") | Some("") | None => Ok(None),
19        Some(v) => v.parse::<f64>().ok().map_or(Ok(None), |n| Ok(Some(n))),
20    }
21}
22
23/// Deserialize a string to `f64`, defaulting to `0.0` on failure.
24#[allow(dead_code)]
25pub(crate) fn deserialize_f64_from_str<'de, D>(
26    deserializer: D,
27) -> std::result::Result<f64, D::Error>
28where
29    D: Deserializer<'de>,
30{
31    let s = String::deserialize(deserializer)?;
32    Ok(s.parse::<f64>().unwrap_or(0.0))
33}
34
35// ============================================================================
36// Interval and SeriesType enums for Alpha Vantage API parameters
37// ============================================================================
38
39/// Time interval for Alpha Vantage time series and indicator requests.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum AvInterval {
42    /// 1-minute intervals
43    OneMin,
44    /// 5-minute intervals
45    FiveMin,
46    /// 15-minute intervals
47    FifteenMin,
48    /// 30-minute intervals
49    ThirtyMin,
50    /// 60-minute intervals
51    SixtyMin,
52    /// Daily intervals
53    Daily,
54    /// Weekly intervals
55    Weekly,
56    /// Monthly intervals
57    Monthly,
58}
59
60impl AvInterval {
61    /// Convert to the Alpha Vantage API parameter string.
62    pub fn as_str(&self) -> &'static str {
63        match self {
64            Self::OneMin => "1min",
65            Self::FiveMin => "5min",
66            Self::FifteenMin => "15min",
67            Self::ThirtyMin => "30min",
68            Self::SixtyMin => "60min",
69            Self::Daily => "daily",
70            Self::Weekly => "weekly",
71            Self::Monthly => "monthly",
72        }
73    }
74}
75
76/// Price series type for technical indicator calculations.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum SeriesType {
79    /// Close price
80    Close,
81    /// Open price
82    Open,
83    /// High price
84    High,
85    /// Low price
86    Low,
87}
88
89impl SeriesType {
90    /// Convert to the Alpha Vantage API parameter string.
91    pub fn as_str(&self) -> &'static str {
92        match self {
93            Self::Close => "close",
94            Self::Open => "open",
95            Self::High => "high",
96            Self::Low => "low",
97        }
98    }
99}
100
101/// Output size for time series requests.
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum OutputSize {
104    /// Returns the latest 100 data points (default).
105    Compact,
106    /// Returns up to 20+ years of historical data.
107    Full,
108}
109
110impl OutputSize {
111    /// Convert to the Alpha Vantage API parameter string.
112    pub fn as_str(&self) -> &'static str {
113        match self {
114            Self::Compact => "compact",
115            Self::Full => "full",
116        }
117    }
118}
119
120// ============================================================================
121// Core time series response types
122// ============================================================================
123
124/// A single OHLCV data point from a time series response.
125#[derive(Debug, Clone, Serialize, Deserialize)]
126#[non_exhaustive]
127pub struct TimeSeriesEntry {
128    /// Timestamp or date string
129    pub timestamp: String,
130    /// Opening price
131    pub open: f64,
132    /// Highest price
133    pub high: f64,
134    /// Lowest price
135    pub low: f64,
136    /// Closing price
137    pub close: f64,
138    /// Trading volume
139    pub volume: f64,
140}
141
142/// A single adjusted OHLCV data point including dividend and split information.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[non_exhaustive]
145pub struct AdjustedTimeSeriesEntry {
146    /// Timestamp or date string
147    pub timestamp: String,
148    /// Opening price
149    pub open: f64,
150    /// Highest price
151    pub high: f64,
152    /// Lowest price
153    pub low: f64,
154    /// Closing price
155    pub close: f64,
156    /// Adjusted closing price
157    pub adjusted_close: f64,
158    /// Trading volume
159    pub volume: f64,
160    /// Dividend amount
161    pub dividend_amount: f64,
162    /// Split coefficient (only in daily adjusted)
163    pub split_coefficient: Option<f64>,
164}
165
166/// Time series response containing metadata and OHLCV entries.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168#[non_exhaustive]
169pub struct TimeSeries {
170    /// Symbol (e.g., `"AAPL"`)
171    pub symbol: String,
172    /// Last refreshed timestamp
173    pub last_refreshed: String,
174    /// Time series data points
175    pub entries: Vec<TimeSeriesEntry>,
176}
177
178/// Adjusted time series response.
179#[derive(Debug, Clone, Serialize, Deserialize)]
180#[non_exhaustive]
181pub struct AdjustedTimeSeries {
182    /// Symbol (e.g., `"AAPL"`)
183    pub symbol: String,
184    /// Last refreshed timestamp
185    pub last_refreshed: String,
186    /// Adjusted time series data points
187    pub entries: Vec<AdjustedTimeSeriesEntry>,
188}
189
190// ============================================================================
191// Global quote
192// ============================================================================
193
194/// Real-time quote for a single ticker.
195#[derive(Debug, Clone, Serialize, Deserialize)]
196#[non_exhaustive]
197pub struct GlobalQuote {
198    /// Ticker symbol
199    pub symbol: String,
200    /// Open price
201    pub open: f64,
202    /// High price
203    pub high: f64,
204    /// Low price
205    pub low: f64,
206    /// Current/last price
207    pub price: f64,
208    /// Trading volume
209    pub volume: f64,
210    /// Latest trading day
211    pub latest_trading_day: String,
212    /// Previous close
213    pub previous_close: f64,
214    /// Price change
215    pub change: f64,
216    /// Percent change (as string, e.g. `"1.23%"`)
217    pub change_percent: String,
218}
219
220/// A single quote within a bulk quotes response.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222#[non_exhaustive]
223pub struct BulkQuote {
224    /// Ticker symbol
225    pub symbol: String,
226    /// Open price
227    pub open: Option<f64>,
228    /// High price
229    pub high: Option<f64>,
230    /// Low price
231    pub low: Option<f64>,
232    /// Current/last price
233    pub price: Option<f64>,
234    /// Trading volume
235    pub volume: Option<f64>,
236    /// Latest trading day
237    pub latest_trading_day: Option<String>,
238    /// Previous close
239    pub previous_close: Option<f64>,
240    /// Price change
241    pub change: Option<f64>,
242    /// Percent change
243    pub change_percent: Option<String>,
244}
245
246// ============================================================================
247// Symbol search
248// ============================================================================
249
250/// A single match result from a symbol search.
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[non_exhaustive]
253pub struct SymbolMatch {
254    /// Ticker symbol
255    pub symbol: String,
256    /// Company/security name
257    pub name: String,
258    /// Asset type (e.g., `"Equity"`, `"ETF"`)
259    pub asset_type: String,
260    /// Stock exchange region
261    pub region: String,
262    /// Market open time
263    pub market_open: String,
264    /// Market close time
265    pub market_close: String,
266    /// Timezone
267    pub timezone: String,
268    /// Currency
269    pub currency: String,
270    /// Match score (0.0 to 1.0)
271    pub match_score: f64,
272}
273
274// ============================================================================
275// Market status
276// ============================================================================
277
278/// Status of a single market/exchange.
279#[derive(Debug, Clone, Serialize, Deserialize)]
280#[non_exhaustive]
281pub struct MarketStatus {
282    /// Market type (e.g., `"Equity"`, `"Forex"`)
283    pub market_type: String,
284    /// Region name
285    pub region: String,
286    /// Primary exchanges
287    pub primary_exchanges: String,
288    /// Local open time
289    pub local_open: String,
290    /// Local close time
291    pub local_close: String,
292    /// Current status (e.g., `"open"`, `"closed"`)
293    pub current_status: String,
294    /// Notes
295    pub notes: String,
296}
297
298// ============================================================================
299// Options
300// ============================================================================
301
302/// A single options contract.
303#[derive(Debug, Clone, Serialize, Deserialize)]
304#[non_exhaustive]
305pub struct OptionContract {
306    /// Contract ID
307    pub contractid: String,
308    /// Underlying symbol
309    pub symbol: String,
310    /// Expiration date
311    pub expiration: String,
312    /// Strike price
313    pub strike: f64,
314    /// Option type: `"call"` or `"put"`
315    pub option_type: String,
316    /// Last traded price
317    pub last: Option<f64>,
318    /// Current mark/mid price
319    pub mark: Option<f64>,
320    /// Bid price
321    pub bid: Option<f64>,
322    /// Bid size
323    pub bid_size: Option<f64>,
324    /// Ask price
325    pub ask: Option<f64>,
326    /// Ask size
327    pub ask_size: Option<f64>,
328    /// Trading volume
329    pub volume: Option<f64>,
330    /// Open interest
331    pub open_interest: Option<f64>,
332    /// Implied volatility
333    pub implied_volatility: Option<f64>,
334    /// Delta
335    pub delta: Option<f64>,
336    /// Gamma
337    pub gamma: Option<f64>,
338    /// Theta
339    pub theta: Option<f64>,
340    /// Vega
341    pub vega: Option<f64>,
342    /// Rho
343    pub rho: Option<f64>,
344}
345
346/// Options chain response.
347#[derive(Debug, Clone, Serialize, Deserialize)]
348#[non_exhaustive]
349pub struct OptionsChain {
350    /// Underlying symbol
351    pub symbol: String,
352    /// Option contracts
353    pub contracts: Vec<OptionContract>,
354}
355
356// ============================================================================
357// Alpha Intelligence
358// ============================================================================
359
360/// A single news article with sentiment data.
361#[derive(Debug, Clone, Serialize, Deserialize)]
362#[non_exhaustive]
363pub struct NewsArticle {
364    /// Article title
365    pub title: String,
366    /// Article URL
367    pub url: String,
368    /// Published timestamp
369    pub time_published: String,
370    /// Source name
371    pub source: String,
372    /// Summary text
373    pub summary: String,
374    /// Overall sentiment score (-1.0 to 1.0)
375    pub overall_sentiment_score: Option<f64>,
376    /// Overall sentiment label
377    pub overall_sentiment_label: Option<String>,
378    /// Per-ticker sentiment
379    pub ticker_sentiment: Vec<TickerSentiment>,
380}
381
382/// Sentiment data for a specific ticker within a news article.
383#[derive(Debug, Clone, Serialize, Deserialize)]
384#[non_exhaustive]
385pub struct TickerSentiment {
386    /// Ticker symbol
387    pub ticker: String,
388    /// Relevance score (0.0 to 1.0)
389    pub relevance_score: Option<f64>,
390    /// Sentiment score (-1.0 to 1.0)
391    pub ticker_sentiment_score: Option<f64>,
392    /// Sentiment label
393    pub ticker_sentiment_label: Option<String>,
394}
395
396/// Earnings call transcript.
397#[derive(Debug, Clone, Serialize, Deserialize)]
398#[non_exhaustive]
399pub struct EarningsCallTranscript {
400    /// Ticker symbol
401    pub symbol: String,
402    /// Quarter identifier (e.g., `"2024Q1"`)
403    pub quarter: String,
404    /// Transcript text
405    pub transcript: String,
406}
407
408/// A top gainer, loser, or most actively traded ticker.
409#[derive(Debug, Clone, Serialize, Deserialize)]
410#[non_exhaustive]
411pub struct TopMoverTicker {
412    /// Ticker symbol
413    pub ticker: String,
414    /// Current price
415    pub price: String,
416    /// Absolute change
417    pub change_amount: String,
418    /// Percentage change
419    pub change_percentage: String,
420    /// Trading volume
421    pub volume: String,
422}
423
424/// Top gainers, losers, and most actively traded tickers.
425#[derive(Debug, Clone, Serialize, Deserialize)]
426#[non_exhaustive]
427pub struct TopMovers {
428    /// Last updated timestamp
429    pub last_updated: String,
430    /// Top gaining tickers
431    pub top_gainers: Vec<TopMoverTicker>,
432    /// Top losing tickers
433    pub top_losers: Vec<TopMoverTicker>,
434    /// Most actively traded tickers
435    pub most_actively_traded: Vec<TopMoverTicker>,
436}
437
438// ============================================================================
439// Fundamental data
440// ============================================================================
441
442/// Company overview / profile.
443#[derive(Debug, Clone, Serialize, Deserialize)]
444#[non_exhaustive]
445pub struct CompanyOverview {
446    /// Ticker symbol
447    pub symbol: String,
448    /// Asset type
449    pub asset_type: Option<String>,
450    /// Company name
451    pub name: Option<String>,
452    /// Company description
453    pub description: Option<String>,
454    /// Exchange
455    pub exchange: Option<String>,
456    /// Currency
457    pub currency: Option<String>,
458    /// Country
459    pub country: Option<String>,
460    /// Sector
461    pub sector: Option<String>,
462    /// Industry
463    pub industry: Option<String>,
464    /// Market capitalization
465    pub market_capitalization: Option<f64>,
466    /// Price-to-earnings ratio (trailing)
467    pub pe_ratio: Option<f64>,
468    /// Price-to-earnings-growth ratio
469    pub peg_ratio: Option<f64>,
470    /// Book value per share
471    pub book_value: Option<f64>,
472    /// Dividend per share
473    pub dividend_per_share: Option<f64>,
474    /// Dividend yield
475    pub dividend_yield: Option<f64>,
476    /// Earnings per share
477    pub eps: Option<f64>,
478    /// Revenue per share (TTM)
479    pub revenue_per_share_ttm: Option<f64>,
480    /// Profit margin
481    pub profit_margin: Option<f64>,
482    /// Operating margin (TTM)
483    pub operating_margin_ttm: Option<f64>,
484    /// Return on assets (TTM)
485    pub return_on_assets_ttm: Option<f64>,
486    /// Return on equity (TTM)
487    pub return_on_equity_ttm: Option<f64>,
488    /// Revenue (TTM)
489    pub revenue_ttm: Option<f64>,
490    /// Gross profit (TTM)
491    pub gross_profit_ttm: Option<f64>,
492    /// EBITDA
493    pub ebitda: Option<f64>,
494    /// 52-week high
495    pub week_52_high: Option<f64>,
496    /// 52-week low
497    pub week_52_low: Option<f64>,
498    /// 50-day moving average
499    pub moving_average_50day: Option<f64>,
500    /// 200-day moving average
501    pub moving_average_200day: Option<f64>,
502    /// Shares outstanding
503    pub shares_outstanding: Option<f64>,
504    /// Beta
505    pub beta: Option<f64>,
506    /// Forward PE
507    pub forward_pe: Option<f64>,
508    /// Price-to-sales ratio (TTM)
509    pub price_to_sales_ratio_ttm: Option<f64>,
510    /// Price-to-book ratio
511    pub price_to_book_ratio: Option<f64>,
512    /// Analyst target price
513    pub analyst_target_price: Option<f64>,
514    /// Analyst rating: strong buy count
515    pub analyst_rating_strong_buy: Option<u32>,
516    /// Analyst rating: buy count
517    pub analyst_rating_buy: Option<u32>,
518    /// Analyst rating: hold count
519    pub analyst_rating_hold: Option<u32>,
520    /// Analyst rating: sell count
521    pub analyst_rating_sell: Option<u32>,
522    /// Analyst rating: strong sell count
523    pub analyst_rating_strong_sell: Option<u32>,
524}
525
526/// ETF profile and holdings.
527#[derive(Debug, Clone, Serialize, Deserialize)]
528#[non_exhaustive]
529pub struct EtfProfile {
530    /// ETF symbol
531    pub symbol: String,
532    /// ETF name
533    pub name: Option<String>,
534    /// Asset type
535    pub asset_type: Option<String>,
536    /// Net assets
537    pub net_assets: Option<f64>,
538    /// Expense ratio
539    pub net_expense_ratio: Option<f64>,
540    /// Turnover ratio
541    pub portfolio_turnover: Option<f64>,
542    /// Dividend yield
543    pub dividend_yield: Option<f64>,
544    /// Inception date
545    pub inception_date: Option<String>,
546    /// Top holdings
547    pub holdings: Vec<EtfHolding>,
548}
549
550/// A single ETF holding.
551#[derive(Debug, Clone, Serialize, Deserialize)]
552#[non_exhaustive]
553pub struct EtfHolding {
554    /// Symbol of the held security
555    pub symbol: Option<String>,
556    /// Description
557    pub description: Option<String>,
558    /// Weight in the ETF portfolio (as percentage)
559    pub weight: Option<f64>,
560}
561
562/// A single row in an income statement, balance sheet, or cash flow statement.
563#[derive(Debug, Clone, Serialize, Deserialize)]
564#[non_exhaustive]
565pub struct FinancialReport {
566    /// Fiscal date ending
567    pub fiscal_date_ending: String,
568    /// Reported currency
569    pub reported_currency: String,
570    /// All fields as key-value pairs (field names vary by statement type)
571    #[serde(flatten)]
572    pub fields: std::collections::HashMap<String, serde_json::Value>,
573}
574
575/// Financial statements (income statement, balance sheet, or cash flow).
576#[derive(Debug, Clone, Serialize, Deserialize)]
577#[non_exhaustive]
578pub struct FinancialStatements {
579    /// Ticker symbol
580    pub symbol: String,
581    /// Annual reports
582    pub annual_reports: Vec<FinancialReport>,
583    /// Quarterly reports
584    pub quarterly_reports: Vec<FinancialReport>,
585}
586
587/// A single dividend payment event.
588#[derive(Debug, Clone, Serialize, Deserialize)]
589#[non_exhaustive]
590pub struct DividendEvent {
591    /// Ex-dividend date
592    pub ex_dividend_date: Option<String>,
593    /// Declaration date
594    pub declaration_date: Option<String>,
595    /// Record date
596    pub record_date: Option<String>,
597    /// Payment date
598    pub payment_date: Option<String>,
599    /// Dividend amount
600    pub amount: Option<f64>,
601}
602
603/// A single stock split event.
604#[derive(Debug, Clone, Serialize, Deserialize)]
605#[non_exhaustive]
606pub struct SplitEvent {
607    /// Effective date
608    pub effective_date: Option<String>,
609    /// Split ratio (e.g., `"4:1"`)
610    pub split_ratio: Option<String>,
611}
612
613/// Earnings data for a single quarter.
614#[derive(Debug, Clone, Serialize, Deserialize)]
615#[non_exhaustive]
616pub struct EarningsData {
617    /// Fiscal date ending
618    pub fiscal_date_ending: Option<String>,
619    /// Reported date
620    pub reported_date: Option<String>,
621    /// Reported EPS
622    pub reported_eps: Option<f64>,
623    /// Estimated EPS
624    pub estimated_eps: Option<f64>,
625    /// Surprise
626    pub surprise: Option<f64>,
627    /// Surprise percentage
628    pub surprise_percentage: Option<f64>,
629}
630
631/// Earnings history with annual and quarterly data.
632#[derive(Debug, Clone, Serialize, Deserialize)]
633#[non_exhaustive]
634pub struct EarningsHistory {
635    /// Ticker symbol
636    pub symbol: String,
637    /// Annual earnings
638    pub annual_earnings: Vec<EarningsData>,
639    /// Quarterly earnings
640    pub quarterly_earnings: Vec<EarningsData>,
641}
642
643/// A single earnings calendar entry.
644#[derive(Debug, Clone, Serialize, Deserialize)]
645#[non_exhaustive]
646pub struct EarningsCalendarEntry {
647    /// Ticker symbol
648    pub symbol: String,
649    /// Company name
650    pub name: Option<String>,
651    /// Report date
652    pub report_date: Option<String>,
653    /// Fiscal date ending
654    pub fiscal_date_ending: Option<String>,
655    /// Estimated EPS
656    pub estimate: Option<f64>,
657    /// Currency
658    pub currency: Option<String>,
659}
660
661/// A single IPO calendar entry.
662#[derive(Debug, Clone, Serialize, Deserialize)]
663#[non_exhaustive]
664pub struct IpoCalendarEntry {
665    /// Ticker symbol
666    pub symbol: Option<String>,
667    /// Company name
668    pub name: Option<String>,
669    /// IPO date
670    pub ipo_date: Option<String>,
671    /// Price range (e.g., `"$15-$17"`)
672    pub price_range: Option<String>,
673    /// Exchange
674    pub exchange: Option<String>,
675}
676
677/// Listing status entry.
678#[derive(Debug, Clone, Serialize, Deserialize)]
679#[non_exhaustive]
680pub struct ListingEntry {
681    /// Ticker symbol
682    pub symbol: String,
683    /// Security name
684    pub name: Option<String>,
685    /// Exchange
686    pub exchange: Option<String>,
687    /// Asset type
688    pub asset_type: Option<String>,
689    /// IPO date
690    pub ipo_date: Option<String>,
691    /// Delisting date (if applicable)
692    pub delisting_date: Option<String>,
693    /// Status (`"Active"` or `"Delisted"`)
694    pub status: Option<String>,
695}
696
697// ============================================================================
698// Forex
699// ============================================================================
700
701/// Real-time exchange rate between two currencies.
702#[derive(Debug, Clone, Serialize, Deserialize)]
703#[non_exhaustive]
704pub struct ExchangeRate {
705    /// Source currency code
706    pub from_currency_code: String,
707    /// Source currency name
708    pub from_currency_name: String,
709    /// Target currency code
710    pub to_currency_code: String,
711    /// Target currency name
712    pub to_currency_name: String,
713    /// Exchange rate
714    pub exchange_rate: f64,
715    /// Last refreshed timestamp
716    pub last_refreshed: String,
717    /// Bid price
718    pub bid_price: f64,
719    /// Ask price
720    pub ask_price: f64,
721}
722
723/// A single forex time series data point.
724#[derive(Debug, Clone, Serialize, Deserialize)]
725#[non_exhaustive]
726pub struct ForexEntry {
727    /// Timestamp or date string
728    pub timestamp: String,
729    /// Opening price
730    pub open: f64,
731    /// Highest price
732    pub high: f64,
733    /// Lowest price
734    pub low: f64,
735    /// Closing price
736    pub close: f64,
737}
738
739/// Forex time series response.
740#[derive(Debug, Clone, Serialize, Deserialize)]
741#[non_exhaustive]
742pub struct ForexTimeSeries {
743    /// Source currency code
744    pub from_symbol: String,
745    /// Target currency code
746    pub to_symbol: String,
747    /// Last refreshed timestamp
748    pub last_refreshed: String,
749    /// Data entries
750    pub entries: Vec<ForexEntry>,
751}
752
753// ============================================================================
754// Crypto
755// ============================================================================
756
757/// A single cryptocurrency time series data point.
758#[derive(Debug, Clone, Serialize, Deserialize)]
759#[non_exhaustive]
760pub struct CryptoEntry {
761    /// Timestamp or date string
762    pub timestamp: String,
763    /// Opening price
764    pub open: f64,
765    /// Highest price
766    pub high: f64,
767    /// Lowest price
768    pub low: f64,
769    /// Closing price
770    pub close: f64,
771    /// Trading volume
772    pub volume: f64,
773}
774
775/// Cryptocurrency time series response.
776#[derive(Debug, Clone, Serialize, Deserialize)]
777#[non_exhaustive]
778pub struct CryptoTimeSeries {
779    /// Cryptocurrency symbol (e.g., `"BTC"`)
780    pub symbol: String,
781    /// Market/exchange currency (e.g., `"USD"`)
782    pub market: String,
783    /// Last refreshed timestamp
784    pub last_refreshed: String,
785    /// Data entries
786    pub entries: Vec<CryptoEntry>,
787}
788
789// ============================================================================
790// Commodities
791// ============================================================================
792
793/// A single commodity data point.
794#[derive(Debug, Clone, Serialize, Deserialize)]
795#[non_exhaustive]
796pub struct CommodityDataPoint {
797    /// Date string (YYYY-MM-DD)
798    pub date: String,
799    /// Value
800    pub value: Option<f64>,
801}
802
803/// Commodity time series response.
804#[derive(Debug, Clone, Serialize, Deserialize)]
805#[non_exhaustive]
806pub struct CommoditySeries {
807    /// Commodity name/identifier
808    pub name: String,
809    /// Interval
810    pub interval: String,
811    /// Unit
812    pub unit: String,
813    /// Data points
814    pub data: Vec<CommodityDataPoint>,
815}
816
817// ============================================================================
818// Economic indicators
819// ============================================================================
820
821/// A single economic indicator data point.
822#[derive(Debug, Clone, Serialize, Deserialize)]
823#[non_exhaustive]
824pub struct EconomicDataPoint {
825    /// Date string (YYYY-MM-DD)
826    pub date: String,
827    /// Value
828    pub value: Option<f64>,
829}
830
831/// Economic indicator time series response.
832#[derive(Debug, Clone, Serialize, Deserialize)]
833#[non_exhaustive]
834pub struct EconomicSeries {
835    /// Indicator name
836    pub name: String,
837    /// Interval
838    pub interval: String,
839    /// Unit
840    pub unit: String,
841    /// Data points
842    pub data: Vec<EconomicDataPoint>,
843}
844
845// ============================================================================
846// Technical indicators
847// ============================================================================
848
849/// A single technical indicator data point.
850#[derive(Debug, Clone, Serialize, Deserialize)]
851#[non_exhaustive]
852pub struct IndicatorDataPoint {
853    /// Timestamp or date string
854    pub timestamp: String,
855    /// Indicator values as key-value pairs (keys vary by indicator).
856    /// e.g., for SMA: `{"SMA": 150.5}`, for BBANDS: `{"Real Upper Band": ..., "Real Middle Band": ..., "Real Lower Band": ...}`
857    pub values: std::collections::HashMap<String, f64>,
858}
859
860/// Technical indicator response.
861#[derive(Debug, Clone, Serialize, Deserialize)]
862#[non_exhaustive]
863pub struct TechnicalIndicator {
864    /// Indicator function name (e.g., `"SMA"`, `"RSI"`, `"MACD"`)
865    pub indicator: String,
866    /// Symbol
867    pub symbol: String,
868    /// Last refreshed timestamp
869    pub last_refreshed: String,
870    /// Interval used
871    pub interval: String,
872    /// Data points
873    pub data: Vec<IndicatorDataPoint>,
874}