finance_query/adapters/fmp/
institutional.rs1use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::build_client;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
16#[non_exhaustive]
17pub struct InstitutionalHolder {
18 pub holder: Option<String>,
20 pub shares: Option<f64>,
22 #[serde(rename = "dateReported")]
24 pub date_reported: Option<String>,
25 pub change: Option<f64>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31#[non_exhaustive]
32pub struct EtfHolder {
33 pub asset: Option<String>,
35 #[serde(rename = "sharesNumber")]
37 pub shares_number: Option<f64>,
38 #[serde(rename = "weightPercentage")]
40 pub weight_percentage: Option<f64>,
41 #[serde(rename = "marketValue")]
43 pub market_value: Option<f64>,
44 pub updated: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[non_exhaustive]
51pub struct MutualFundHolder {
52 pub holder: Option<String>,
54 pub shares: Option<f64>,
56 #[serde(rename = "dateReported")]
58 pub date_reported: Option<String>,
59 pub change: Option<f64>,
61 #[serde(rename = "weightPercentage")]
63 pub weight_percentage: Option<f64>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68#[non_exhaustive]
69pub struct Form13F {
70 pub date: Option<String>,
72 #[serde(rename = "fillingDate")]
74 pub filling_date: Option<String>,
75 #[serde(rename = "acceptedDate")]
77 pub accepted_date: Option<String>,
78 pub cik: Option<String>,
80 pub cusip: Option<String>,
82 #[serde(rename = "tickercusip")]
84 pub ticker_cusip: Option<String>,
85 #[serde(rename = "nameOfIssuer")]
87 pub name_of_issuer: Option<String>,
88 pub shares: Option<f64>,
90 pub value: Option<f64>,
92 pub link: Option<String>,
94}
95
96pub async fn institutional_holders(symbol: &str) -> Result<Vec<InstitutionalHolder>> {
102 let client = build_client()?;
103 let path = format!(
104 "/api/v3/institutional-holder/{}",
105 encode_path_segment(symbol)
106 );
107 client.get(&path, &[]).await
108}
109
110pub async fn etf_holders(symbol: &str) -> Result<Vec<EtfHolder>> {
112 let client = build_client()?;
113 let path = format!("/api/v3/etf-holder/{}", encode_path_segment(symbol));
114 client.get(&path, &[]).await
115}
116
117pub async fn mutual_fund_holders(symbol: &str) -> Result<Vec<MutualFundHolder>> {
119 let client = build_client()?;
120 let path = format!("/api/v3/mutual-fund-holder/{}", encode_path_segment(symbol));
121 client.get(&path, &[]).await
122}
123
124pub async fn form_13f(cik: &str, date: &str) -> Result<Vec<Form13F>> {
129 let client = build_client()?;
130 let path = format!("/api/v3/form-thirteen/{}", encode_path_segment(cik));
131 client.get(&path, &[("date", date)]).await
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[tokio::test]
139 async fn test_institutional_holders_mock() {
140 let mut server = mockito::Server::new_async().await;
141 let _mock = server
142 .mock("GET", "/api/v3/institutional-holder/AAPL")
143 .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
144 "apikey".into(),
145 "test-key".into(),
146 )]))
147 .with_status(200)
148 .with_body(
149 serde_json::json!([
150 {
151 "holder": "Vanguard Group Inc",
152 "shares": 1300000000.0,
153 "dateReported": "2024-01-15",
154 "change": 5000000.0
155 }
156 ])
157 .to_string(),
158 )
159 .create_async()
160 .await;
161
162 let client = super::super::build_test_client(&server.url()).unwrap();
163 let resp: Vec<InstitutionalHolder> = client
164 .get("/api/v3/institutional-holder/AAPL", &[])
165 .await
166 .unwrap();
167 assert_eq!(resp.len(), 1);
168 assert_eq!(resp[0].holder.as_deref(), Some("Vanguard Group Inc"));
169 }
170
171 #[tokio::test]
172 async fn test_etf_holders_mock() {
173 let mut server = mockito::Server::new_async().await;
174 let _mock = server
175 .mock("GET", "/api/v3/etf-holder/SPY")
176 .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
177 "apikey".into(),
178 "test-key".into(),
179 )]))
180 .with_status(200)
181 .with_body(
182 serde_json::json!([
183 {
184 "asset": "AAPL",
185 "sharesNumber": 170000000.0,
186 "weightPercentage": 7.2,
187 "marketValue": 31450000000.0,
188 "updated": "2024-01-15"
189 }
190 ])
191 .to_string(),
192 )
193 .create_async()
194 .await;
195
196 let client = super::super::build_test_client(&server.url()).unwrap();
197 let resp: Vec<EtfHolder> = client.get("/api/v3/etf-holder/SPY", &[]).await.unwrap();
198 assert_eq!(resp.len(), 1);
199 assert_eq!(resp[0].asset.as_deref(), Some("AAPL"));
200 assert!((resp[0].weight_percentage.unwrap() - 7.2).abs() < 0.01);
201 }
202}