Skip to main content

finance_query/adapters/fmp/
market_performance.rs

1//! Market performance endpoints: sector/industry PE, sector performance, gainers/losers/actives.
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::Result;
6
7use super::build_client;
8
9// ============================================================================
10// Response types
11// ============================================================================
12
13/// Sector price-to-earnings ratio entry.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[non_exhaustive]
16pub struct SectorPe {
17    /// Date.
18    pub date: Option<String>,
19    /// Sector name.
20    pub sector: Option<String>,
21    /// Exchange.
22    pub exchange: Option<String>,
23    /// PE ratio.
24    pub pe: Option<f64>,
25}
26
27/// Industry price-to-earnings ratio entry.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[non_exhaustive]
30pub struct IndustryPe {
31    /// Date.
32    pub date: Option<String>,
33    /// Industry name.
34    pub industry: Option<String>,
35    /// Exchange.
36    pub exchange: Option<String>,
37    /// PE ratio.
38    pub pe: Option<f64>,
39}
40
41/// Sector performance entry.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43#[non_exhaustive]
44pub struct SectorPerformance {
45    /// Sector name.
46    pub sector: Option<String>,
47    /// Changes percentage.
48    #[serde(rename = "changesPercentage")]
49    pub changes_percentage: Option<String>,
50}
51
52/// Historical sector performance entry.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[non_exhaustive]
55pub struct HistoricalSectorPerformance {
56    /// Date.
57    pub date: Option<String>,
58    /// Utilities sector performance.
59    #[serde(rename = "utilitiesChangesPercentage")]
60    pub utilities_changes_percentage: Option<f64>,
61    /// Basic materials sector performance.
62    #[serde(rename = "basicMaterialsChangesPercentage")]
63    pub basic_materials_changes_percentage: Option<f64>,
64    /// Communication services sector performance.
65    #[serde(rename = "communicationServicesChangesPercentage")]
66    pub communication_services_changes_percentage: Option<f64>,
67    /// Consumer cyclical sector performance.
68    #[serde(rename = "consumerCyclicalChangesPercentage")]
69    pub consumer_cyclical_changes_percentage: Option<f64>,
70    /// Consumer defensive sector performance.
71    #[serde(rename = "consumerDefensiveChangesPercentage")]
72    pub consumer_defensive_changes_percentage: Option<f64>,
73    /// Energy sector performance.
74    #[serde(rename = "energyChangesPercentage")]
75    pub energy_changes_percentage: Option<f64>,
76    /// Financial services sector performance.
77    #[serde(rename = "financialServicesChangesPercentage")]
78    pub financial_services_changes_percentage: Option<f64>,
79    /// Healthcare sector performance.
80    #[serde(rename = "healthcareChangesPercentage")]
81    pub healthcare_changes_percentage: Option<f64>,
82    /// Industrials sector performance.
83    #[serde(rename = "industrialsChangesPercentage")]
84    pub industrials_changes_percentage: Option<f64>,
85    /// Real estate sector performance.
86    #[serde(rename = "realEstateChangesPercentage")]
87    pub real_estate_changes_percentage: Option<f64>,
88    /// Technology sector performance.
89    #[serde(rename = "technologyChangesPercentage")]
90    pub technology_changes_percentage: Option<f64>,
91}
92
93/// Market mover entry (gainers, losers, most active).
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[non_exhaustive]
96pub struct MarketMover {
97    /// Ticker symbol.
98    pub symbol: Option<String>,
99    /// Company name.
100    pub name: Option<String>,
101    /// Price change.
102    pub change: Option<f64>,
103    /// Price.
104    pub price: Option<f64>,
105    /// Change percentage.
106    #[serde(rename = "changesPercentage")]
107    pub changes_percentage: Option<f64>,
108}
109
110// ============================================================================
111// Public API
112// ============================================================================
113
114/// Fetch sector PE ratios.
115pub async fn sectors_pe() -> Result<Vec<SectorPe>> {
116    let client = build_client()?;
117    client.get("/api/v4/sector_price_earning_ratio", &[]).await
118}
119
120/// Fetch industry PE ratios.
121pub async fn industries_pe() -> Result<Vec<IndustryPe>> {
122    let client = build_client()?;
123    client
124        .get("/api/v4/industry_price_earning_ratio", &[])
125        .await
126}
127
128/// Fetch current sector performance.
129pub async fn sector_performance() -> Result<Vec<SectorPerformance>> {
130    let client = build_client()?;
131    client.get("/api/v3/sector-performance", &[]).await
132}
133
134/// Fetch historical sector performance.
135pub async fn historical_sector_performance(limit: u32) -> Result<Vec<HistoricalSectorPerformance>> {
136    let client = build_client()?;
137    let limit_str = limit.to_string();
138    client
139        .get(
140            "/api/v3/historical-sectors-performance",
141            &[("limit", &*limit_str)],
142        )
143        .await
144}
145
146/// Fetch top stock market gainers.
147pub async fn stock_market_gainers() -> Result<Vec<MarketMover>> {
148    let client = build_client()?;
149    client.get("/api/v3/stock_market/gainers", &[]).await
150}
151
152/// Fetch top stock market losers.
153pub async fn stock_market_losers() -> Result<Vec<MarketMover>> {
154    let client = build_client()?;
155    client.get("/api/v3/stock_market/losers", &[]).await
156}
157
158/// Fetch most active stocks.
159pub async fn stock_market_most_active() -> Result<Vec<MarketMover>> {
160    let client = build_client()?;
161    client.get("/api/v3/stock_market/actives", &[]).await
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[tokio::test]
169    async fn test_sector_performance_mock() {
170        let mut server = mockito::Server::new_async().await;
171        let _mock = server
172            .mock("GET", "/api/v3/sector-performance")
173            .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
174                "apikey".into(),
175                "test-key".into(),
176            )]))
177            .with_status(200)
178            .with_body(
179                serde_json::json!([
180                    {
181                        "sector": "Technology",
182                        "changesPercentage": "1.25%"
183                    },
184                    {
185                        "sector": "Healthcare",
186                        "changesPercentage": "-0.45%"
187                    }
188                ])
189                .to_string(),
190            )
191            .create_async()
192            .await;
193
194        let client = super::super::build_test_client(&server.url()).unwrap();
195        let resp: Vec<SectorPerformance> =
196            client.get("/api/v3/sector-performance", &[]).await.unwrap();
197        assert_eq!(resp.len(), 2);
198        assert_eq!(resp[0].sector.as_deref(), Some("Technology"));
199        assert_eq!(resp[0].changes_percentage.as_deref(), Some("1.25%"));
200    }
201
202    #[tokio::test]
203    async fn test_stock_market_gainers_mock() {
204        let mut server = mockito::Server::new_async().await;
205        let _mock = server
206            .mock("GET", "/api/v3/stock_market/gainers")
207            .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
208                "apikey".into(),
209                "test-key".into(),
210            )]))
211            .with_status(200)
212            .with_body(
213                serde_json::json!([
214                    {
215                        "symbol": "XYZ",
216                        "name": "XYZ Corp",
217                        "change": 5.20,
218                        "price": 42.50,
219                        "changesPercentage": 13.93
220                    }
221                ])
222                .to_string(),
223            )
224            .create_async()
225            .await;
226
227        let client = super::super::build_test_client(&server.url()).unwrap();
228        let resp: Vec<MarketMover> = client
229            .get("/api/v3/stock_market/gainers", &[])
230            .await
231            .unwrap();
232        assert_eq!(resp.len(), 1);
233        assert_eq!(resp[0].symbol.as_deref(), Some("XYZ"));
234        assert!((resp[0].change.unwrap() - 5.20).abs() < 0.01);
235    }
236}