Skip to main content

finance_query/ticker/
fmp.rs

1//! Financial Modeling Prep (FMP) adapter integration for [`Ticker`].
2//!
3//! Exposes a curated subset of the [`crate::adapters::fmp`] free
4//! functions as methods on a typed handle obtained via
5//! [`crate::Ticker::fmp`].
6//!
7//! Requires the `fmp` feature flag and a one-time call to
8//! [`crate::adapters::fmp::init`] before any method is invoked.
9//!
10//! All methods return adapter-native types unchanged (no translation,
11//! no fallback). If the adapter has not been initialized, the
12//! underlying free function returns
13//! [`crate::error::FinanceError::InvalidParameter`].
14
15use std::sync::Arc;
16
17use crate::adapters::fmp;
18use crate::error::Result;
19
20/// Valid intraday intervals for [`FmpHandle::intraday`].
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum IntradayInterval {
23    /// 1-minute bars.
24    Min1,
25    /// 5-minute bars.
26    Min5,
27    /// 15-minute bars.
28    Min15,
29    /// 30-minute bars.
30    Min30,
31    /// 1-hour bars.
32    Hour1,
33    /// 4-hour bars.
34    Hour4,
35}
36
37impl IntradayInterval {
38    fn as_str(self) -> &'static str {
39        match self {
40            Self::Min1 => "1min",
41            Self::Min5 => "5min",
42            Self::Min15 => "15min",
43            Self::Min30 => "30min",
44            Self::Hour1 => "1hour",
45            Self::Hour4 => "4hour",
46        }
47    }
48}
49
50/// Typed accessor for the FMP adapter, scoped to a single ticker.
51#[derive(Clone, Debug)]
52pub struct FmpHandle {
53    symbol: Arc<str>,
54}
55
56impl FmpHandle {
57    pub(crate) fn new(symbol: Arc<str>) -> Self {
58        Self { symbol }
59    }
60
61    pub fn symbol(&self) -> &str {
62        &self.symbol
63    }
64
65    /// Real-time quote (price, change, volume, market cap, etc.).
66    ///
67    /// Wraps [`fmp::quote`].
68    pub async fn quote(&self) -> Result<Vec<fmp::FmpQuote>> {
69        fmp::quote(&self.symbol).await
70    }
71
72    /// Historical daily OHLCV between two optional dates (`YYYY-MM-DD`).
73    ///
74    /// Wraps [`fmp::historical_price_daily`].
75    pub async fn historical(
76        &self,
77        from: Option<&str>,
78        to: Option<&str>,
79    ) -> Result<fmp::HistoricalPriceResponse> {
80        let params = fmp::HistoricalPriceParams {
81            from: from.map(str::to_owned),
82            to: to.map(str::to_owned),
83        };
84        fmp::historical_price_daily(&self.symbol, Some(params)).await
85    }
86
87    /// Intraday OHLCV bars at the given interval.
88    ///
89    /// Wraps [`fmp::historical_price_intraday`].
90    pub async fn intraday(
91        &self,
92        interval: IntradayInterval,
93        from: Option<&str>,
94        to: Option<&str>,
95    ) -> Result<Vec<fmp::IntradayPrice>> {
96        let params = if from.is_some() || to.is_some() {
97            Some(fmp::HistoricalPriceParams {
98                from: from.map(str::to_owned),
99                to: to.map(str::to_owned),
100            })
101        } else {
102            None
103        };
104        fmp::historical_price_intraday(&self.symbol, interval.as_str(), params).await
105    }
106
107    /// Income statement for the configured period.
108    ///
109    /// Wraps [`fmp::income_statement`].
110    pub async fn income_statement(
111        &self,
112        period: fmp::Period,
113        limit: Option<u32>,
114    ) -> Result<Vec<fmp::IncomeStatement>> {
115        fmp::income_statement(&self.symbol, period, limit).await
116    }
117
118    /// Balance sheet for the configured period.
119    ///
120    /// Wraps [`fmp::balance_sheet`].
121    pub async fn balance_sheet(
122        &self,
123        period: fmp::Period,
124        limit: Option<u32>,
125    ) -> Result<Vec<fmp::BalanceSheet>> {
126        fmp::balance_sheet(&self.symbol, period, limit).await
127    }
128
129    /// Cash flow statement for the configured period.
130    ///
131    /// Wraps [`fmp::cash_flow`].
132    pub async fn cash_flow(
133        &self,
134        period: fmp::Period,
135        limit: Option<u32>,
136    ) -> Result<Vec<fmp::CashFlow>> {
137        fmp::cash_flow(&self.symbol, period, limit).await
138    }
139
140    /// Key valuation, profitability, and efficiency metrics.
141    ///
142    /// Wraps [`fmp::key_metrics`].
143    pub async fn key_metrics(
144        &self,
145        period: fmp::Period,
146        limit: Option<u32>,
147    ) -> Result<Vec<fmp::KeyMetrics>> {
148        fmp::key_metrics(&self.symbol, period, limit).await
149    }
150
151    /// Financial ratios (PE, PB, debt/equity, etc.).
152    ///
153    /// Wraps [`fmp::financial_ratios`].
154    pub async fn ratios(
155        &self,
156        period: fmp::Period,
157        limit: Option<u32>,
158    ) -> Result<Vec<fmp::FinancialRatios>> {
159        fmp::financial_ratios(&self.symbol, period, limit).await
160    }
161
162    /// Company profile (description, sector, industry, executives).
163    ///
164    /// Wraps [`fmp::company_profile`].
165    pub async fn profile(&self) -> Result<Vec<fmp::CompanyProfile>> {
166        fmp::company_profile(&self.symbol).await
167    }
168
169    /// News articles for this ticker.
170    ///
171    /// Wraps [`fmp::stock_news`].
172    pub async fn news(&self, limit: u32) -> Result<Vec<fmp::StockNews>> {
173        fmp::stock_news(&self.symbol, limit).await
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    // Compile-time existence test: each method is awaited on a real handle.
182    // Body never runs (function is unused), but it MUST type-check, so a
183    // signature drift in any wrapped adapter function fails the build.
184    #[allow(dead_code)]
185    async fn _fmp_method_signatures_compile(h: &FmpHandle) {
186        let _ = h.quote().await;
187        let _ = h.historical(None, None).await;
188        let _ = h.intraday(IntradayInterval::Min1, None, None).await;
189        let _ = h.income_statement(fmp::Period::Quarter, Some(4)).await;
190        let _ = h.balance_sheet(fmp::Period::Quarter, Some(4)).await;
191        let _ = h.cash_flow(fmp::Period::Quarter, Some(4)).await;
192        let _ = h.key_metrics(fmp::Period::Quarter, Some(4)).await;
193        let _ = h.ratios(fmp::Period::Quarter, Some(4)).await;
194        let _ = h.profile().await;
195        let _ = h.news(10).await;
196    }
197
198    #[test]
199    fn handle_holds_symbol() {
200        let h = FmpHandle::new(Arc::from("AAPL"));
201        assert_eq!(h.symbol(), "AAPL");
202    }
203
204    #[test]
205    fn handle_clone_is_cheap_arc_clone() {
206        let h1 = FmpHandle::new(Arc::from("AAPL"));
207        let h2 = h1.clone();
208        assert_eq!(h1.symbol(), h2.symbol());
209    }
210}