Skip to main content

finance_query/adapters/fmp/
advanced.rs

1//! Advanced endpoints for Financial Modeling Prep (SIC codes, COT reports).
2
3use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::build_client;
9
10/// Standard Industrial Classification code entry.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[non_exhaustive]
13pub struct SicCode {
14    /// SIC code.
15    #[serde(rename = "sicCode")]
16    pub sic_code: Option<String>,
17    /// Industry title.
18    #[serde(rename = "industryTitle")]
19    pub industry_title: Option<String>,
20}
21
22/// Detailed SIC entry for a specific code.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[non_exhaustive]
25pub struct SicEntry {
26    /// Ticker symbol.
27    pub symbol: Option<String>,
28    /// Company name.
29    pub name: Option<String>,
30    /// CIK number.
31    pub cik: Option<String>,
32    /// SIC code.
33    #[serde(rename = "sicCode")]
34    pub sic_code: Option<String>,
35    /// Industry title.
36    #[serde(rename = "industryTitle")]
37    pub industry_title: Option<String>,
38    /// Business address.
39    #[serde(rename = "businessAddress")]
40    pub business_address: Option<String>,
41    /// Phone number.
42    #[serde(rename = "phoneNumber")]
43    pub phone_number: Option<String>,
44}
45
46/// A symbol available in the Commitment of Traders report.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[non_exhaustive]
49pub struct CotSymbol {
50    /// Trading symbol.
51    #[serde(rename = "trading_symbol")]
52    pub trading_symbol: Option<String>,
53    /// Short name.
54    #[serde(rename = "short_name")]
55    pub short_name: Option<String>,
56}
57
58/// A Commitment of Traders report entry.
59#[derive(Debug, Clone, Serialize, Deserialize)]
60#[non_exhaustive]
61pub struct CotReport {
62    /// Report date.
63    pub date: Option<String>,
64    /// Symbol.
65    pub symbol: Option<String>,
66    /// Short name.
67    #[serde(rename = "short_name")]
68    pub short_name: Option<String>,
69    /// Current long (all).
70    #[serde(rename = "current_long_all")]
71    pub current_long_all: Option<f64>,
72    /// Current short (all).
73    #[serde(rename = "current_short_all")]
74    pub current_short_all: Option<f64>,
75    /// Change long (all).
76    #[serde(rename = "change_long_all")]
77    pub change_long_all: Option<f64>,
78    /// Change short (all).
79    #[serde(rename = "change_short_all")]
80    pub change_short_all: Option<f64>,
81    /// Percent long (all).
82    #[serde(rename = "pct_of_oi_long_all")]
83    pub pct_of_oi_long_all: Option<f64>,
84    /// Percent short (all).
85    #[serde(rename = "pct_of_oi_short_all")]
86    pub pct_of_oi_short_all: Option<f64>,
87}
88
89/// A Commitment of Traders analysis entry.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91#[non_exhaustive]
92pub struct CotAnalysis {
93    /// Report date.
94    pub date: Option<String>,
95    /// Symbol.
96    pub symbol: Option<String>,
97    /// Short name.
98    #[serde(rename = "short_name")]
99    pub short_name: Option<String>,
100    /// Sector.
101    pub sector: Option<String>,
102    /// Current net position.
103    #[serde(rename = "currentNetPosition")]
104    pub current_net_position: Option<f64>,
105    /// Previous net position.
106    #[serde(rename = "previousNetPosition")]
107    pub previous_net_position: Option<f64>,
108    /// Change in net position.
109    #[serde(rename = "changeInNetPosition")]
110    pub change_in_net_position: Option<f64>,
111    /// Market sentiment.
112    #[serde(rename = "marketSentiment")]
113    pub market_sentiment: Option<String>,
114    /// Reversal indicator.
115    #[serde(rename = "reversalTrend")]
116    pub reversal_trend: Option<String>,
117}
118
119/// Fetch all SIC codes.
120pub async fn sic_codes() -> Result<Vec<SicCode>> {
121    let client = build_client()?;
122    client
123        .get("/api/v4/standard_industrial_classification_list", &[])
124        .await
125}
126
127/// Fetch companies by SIC code.
128///
129/// * `code` - SIC code (e.g., `"3674"`)
130pub async fn sic_by_code(code: &str) -> Result<Vec<SicEntry>> {
131    let client = build_client()?;
132    client
133        .get(
134            "/api/v4/standard_industrial_classification",
135            &[("sicCode", code)],
136        )
137        .await
138}
139
140/// Fetch available COT report symbols.
141pub async fn cot_symbols() -> Result<Vec<CotSymbol>> {
142    let client = build_client()?;
143    client
144        .get("/api/v4/commitment_of_traders_report/list", &[])
145        .await
146}
147
148/// Fetch a Commitment of Traders report for a symbol.
149///
150/// * `symbol` - Trading symbol from the COT report list
151pub async fn cot_report(symbol: &str) -> Result<Vec<CotReport>> {
152    let client = build_client()?;
153    let path = format!(
154        "/api/v4/commitment_of_traders_report/{}",
155        encode_path_segment(symbol)
156    );
157    client.get(&path, &[]).await
158}
159
160/// Fetch a Commitment of Traders analysis for a symbol.
161///
162/// * `symbol` - Trading symbol from the COT report list
163pub async fn cot_analysis(symbol: &str) -> Result<Vec<CotAnalysis>> {
164    let client = build_client()?;
165    let path = format!(
166        "/api/v4/commitment_of_traders_report_analysis/{}",
167        encode_path_segment(symbol)
168    );
169    client.get(&path, &[]).await
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[tokio::test]
177    async fn test_sic_codes_mock() {
178        let mut server = mockito::Server::new_async().await;
179        let _mock = server
180            .mock("GET", "/api/v4/standard_industrial_classification_list")
181            .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
182                "apikey".into(),
183                "test-key".into(),
184            )]))
185            .with_status(200)
186            .with_body(
187                serde_json::json!([
188                    {
189                        "sicCode": "3674",
190                        "industryTitle": "Semiconductors and Related Devices"
191                    }
192                ])
193                .to_string(),
194            )
195            .create_async()
196            .await;
197
198        let client = super::super::build_test_client(&server.url()).unwrap();
199        let result: Vec<SicCode> = client
200            .get("/api/v4/standard_industrial_classification_list", &[])
201            .await
202            .unwrap();
203        assert_eq!(result.len(), 1);
204        assert_eq!(result[0].sic_code.as_deref(), Some("3674"));
205        assert_eq!(
206            result[0].industry_title.as_deref(),
207            Some("Semiconductors and Related Devices")
208        );
209    }
210}