gurufocus_api/
gurus.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3
4pub use crate::strnum::FloatOrString;
5
6/// Structure holding basic data for a single Guru.
7#[derive(Deserialize, Debug)]
8#[serde(deny_unknown_fields)]
9pub struct Guru {
10    /// Unique identifier for a Guru.
11    pub id: String,
12    /// The Gurus name, could a person or an institution
13    pub name: String,
14    /// A link to the Guru's homepage, if available, otherwise None
15    pub url: Option<String>,
16    /// The organisation the Guru is working for.
17    pub company: String,
18    /// The number of stock holdings tracked by GuruFocus.
19    pub num_of_stocks: FloatOrString,
20    /// Total investment value in million US$.
21    pub value: FloatOrString,
22    /// Turnover rate in %.
23    pub turnover: FloatOrString,
24    /// Date of latest update of this data.
25    pub latest_update: String,
26}
27
28/// Container for all basic Guru data.
29#[derive(Deserialize, Debug)]
30#[serde(deny_unknown_fields)]
31pub struct Gurus {
32    /// Map holding the list of Gurus per country.
33    pub all: HashMap<String, Vec<Guru>>,
34    /// For each personal list of Gurus (e.g. default and custom list),
35    /// a vector of Guru IDs is stored.
36    pub my: HashMap<String, Vec<String>>,
37}
38
39/// Container for the guru holdings
40#[derive(Deserialize, Debug)]
41#[serde(deny_unknown_fields)]
42pub struct GuruTrades {
43    /// Array of gurus position in the stock
44    pub holdings: Vec<GuruHoldings>,
45    /// Array of recent guru picks in the stock
46    pub picks: Vec<GuruPicks2>,
47}
48
49/// Container for the guru holdings
50#[derive(Deserialize, Debug)]
51#[serde(deny_unknown_fields)]
52pub struct GuruHoldings {
53    pub change: FloatOrString,
54    pub current_shares: FloatOrString,
55    pub date: String,
56    pub guru: String,
57    pub guru_id: String,
58    pub perc_assets: FloatOrString,
59    pub perc_shares: FloatOrString,
60}
61
62/// Container for the guru holdings
63#[derive(Deserialize, Debug)]
64#[serde(deny_unknown_fields)]
65pub struct GuruPicks2 {
66    #[serde(rename = "Avg")]
67    pub avg: FloatOrString,
68    pub action: String,
69    pub comment: String,
70    pub current_shares: FloatOrString,
71    pub date: String,
72    pub guru: String,
73    pub guru_id: String,
74    pub impact: FloatOrString,
75    pub price_max: FloatOrString,
76    pub price_min: FloatOrString,
77}
78
79// #[derive(Deserialize, Debug)]
80// #[serde(deny_unknown_fields)]
81// pub struct GuruPicks {
82//     /// Portfolio of guru picks
83//     pub picks: Vec<GuruPick>,
84// }
85
86#[derive(Deserialize, Debug)]
87#[serde(deny_unknown_fields)]
88pub struct GuruPick {
89    #[serde(rename = "GuruName")]
90    pub guru_name: String,
91    #[serde(rename = "RecmAction")]
92    pub recm_action: String,
93    #[serde(rename = "RecmDate")]
94    pub recm_date: String,
95    #[serde(rename = "RecmPrice")]
96    pub recm_price: FloatOrString,
97    pub change: FloatOrString,
98    pub comment: String,
99    pub company: String,
100    pub currency: String,
101    pub currency_txt: String,
102    pub price: FloatOrString,
103    pub price_max: FloatOrString,
104    pub price_min: FloatOrString,
105    pub sector: String,
106    pub share_current: FloatOrString,
107    pub symbol: String,
108    pub symbol_ori: String,
109    pub trans_share: FloatOrString,
110    #[serde(rename = "type")]
111    pub transaction_type: String,
112    pub exchange: String,
113    pub industry: String,
114}
115
116#[derive(Deserialize, Debug)]
117#[serde(deny_unknown_fields)]
118pub struct GuruPortfolio {
119    pub summary: GuruPortSummary,
120    pub port: Vec<GuruPosition>,
121}
122
123#[derive(Deserialize, Debug)]
124#[serde(deny_unknown_fields)]
125pub struct GuruPortSummary {
126    pub country: String,
127    pub date: String,
128    pub equity: FloatOrString,
129    pub firm: String,
130    pub num_new: FloatOrString,
131    pub number_of_stocks: FloatOrString,
132    pub turnover: FloatOrString,
133}
134
135#[derive(Deserialize, Debug)]
136#[serde(deny_unknown_fields)]
137pub struct GuruPosition {
138    #[serde(rename = "13f_date")]
139    pub date_13f: String,
140    #[serde(rename = "52h")]
141    pub num_52h: FloatOrString,
142    #[serde(rename = "52l")]
143    pub num_52l: FloatOrString,
144    pub change: FloatOrString,
145    pub company: String,
146    pub currency: String,
147    pub currency_txt: String,
148    pub exchange: String,
149    pub impact: FloatOrString,
150    pub industry: String,
151    pub mktcap: FloatOrString,
152    pub pct: FloatOrString,
153    pub pe: FloatOrString,
154    pub position: FloatOrString,
155    pub price: FloatOrString,
156    pub sector: String,
157    pub share: FloatOrString,
158    pub symbol: String,
159    pub symbol_ori: String,
160    pub value: FloatOrString,
161    #[serde(rename = "yield")]
162    pub transaction_yield: FloatOrString,
163    pub class: String,
164    pub share_change_pct: FloatOrString,
165}
166
167/// Politicians
168#[derive(Deserialize, Debug)]
169#[serde(deny_unknown_fields)]
170pub struct Politician {
171    /// Unique identifier of politician
172    pub id: u32,
173    /// Full name of politician
174    pub full_name: String,
175    /// Politicians current position
176    pub position: String,
177    /// The political party the politician is a member of
178    pub party: String,
179    /// Politicians election district
180    pub district: Option<String>,
181    /// Politicians state
182    pub state: String,
183}
184
185/// Politicians transactions
186#[derive(Deserialize, Debug)]
187#[serde(deny_unknown_fields)]
188pub struct PoliticianTransaction {
189    pub symbol: String,
190    pub company: String,
191    pub exchange: String,
192    pub industry: usize,
193    pub class: AssetType,
194    pub stockid: String,
195    pub option_type: Option<String>,
196    pub strike_price: Option<FloatOrString>,
197    pub trans_type: String,
198    pub amount: String,
199    pub disclosure_date: String,
200    pub transaction_date: String,
201    pub expiration_date: Option<String>,
202    pub id: u32,
203    pub full_name: String,
204    pub official_full: Option<String>,
205    pub position: String,
206    pub state: String,
207    pub party: String,
208}
209
210/// Asset type traded by politicians
211#[derive(Deserialize, Debug)]
212pub enum AssetType {
213    #[serde(rename = "Common Stock")]
214    CommonStock,
215    Option,
216    #[serde(rename = "ETF")]
217    Etf,
218    #[serde(rename = "Preferred Stock")]
219    PreferredStock,
220    Bond,
221    Units,
222    Warrant,
223    Other,
224}
225
226/// List of politician transactions
227#[derive(Deserialize, Debug)]
228pub struct PoliticianTransactionList {
229    pub count: usize,
230    #[serde(rename = "currentPage")]
231    pub current_page: u32,
232    #[serde(rename = "lastPage")]
233    pub last_page: u32,
234    pub total: u32,
235    pub data: Vec<PoliticianTransaction>,
236}
237
238#[cfg(test)]
239mod tests {
240    use super::super::*;
241    use super::*;
242    use crate::serde_json::Error;
243    use std::{convert::TryFrom, env};
244    use time::{Date, UtcDateTime};
245
246    fn get_months_before(date: Date, months: u8) -> Date {
247        let mut day = date.day();
248        let mut month = date.month() as u8;
249        let mut year = date.year();
250
251        while month + 1 < months {
252            month += 12;
253            year -= 1;
254        }
255
256        month -= months;
257        let month: time::Month = time::Month::try_from(month).unwrap();
258        day = day.min(month.length(year));
259
260        Date::from_calendar_date(year, month, day).unwrap()
261    }
262
263    #[tokio::test]
264    async fn test_guru_trades() {
265        if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
266            if !token.is_empty() {
267                let gf_connect = GuruFocusConnector::new(token);
268                let stock = "WMT";
269                let trades = gf_connect.get_guru_trades(stock).await;
270                assert!(trades.is_ok());
271                let trades = serde_json::from_value::<HashMap<String, GuruTrades>>(trades.unwrap());
272                assert!(trades.is_ok());
273            }
274        }
275    }
276
277    #[tokio::test]
278    async fn test_guru_picks() {
279        if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
280            if !token.is_empty() {
281                let gf_connect = GuruFocusConnector::new(token);
282                // Buffett, Soros and Klarman
283                let gurus = ["7", "16", "28"];
284                let now = UtcDateTime::now().date();
285                let three_months_ago = get_months_before(now, 3);
286                let page = 1;
287                let trades = gf_connect
288                    .get_guru_picks(&gurus, three_months_ago, page)
289                    .await;
290                assert!(trades.is_ok());
291            }
292        }
293    }
294
295    #[tokio::test]
296    async fn test_gurulist() {
297        if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
298            if !token.is_empty() {
299                let gf_connect = GuruFocusConnector::new(token);
300                let guru_data = gf_connect.get_gurus().await;
301                assert!(guru_data.is_ok());
302            }
303        }
304    }
305
306    #[tokio::test]
307    async fn test_guru_portfolios() {
308        if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
309            if !token.is_empty() {
310                let gf_connect = GuruFocusConnector::new(token);
311                // Bill Ackman and David Einhorn
312                let gurus = ["47", "39"];
313                let portfolios = gf_connect.get_guru_portfolios(&gurus).await;
314                assert!(portfolios.is_ok());
315                let portfolios =
316                    serde_json::from_value::<HashMap<String, GuruPortfolio>>(portfolios.unwrap());
317                assert!(portfolios.is_ok());
318            }
319        }
320    }
321
322    #[tokio::test]
323    async fn test_politicianlist() {
324        if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
325            if !token.is_empty() {
326                let gf_connect = GuruFocusConnector::new(token);
327                let politicians = gf_connect.get_politicians().await;
328                assert!(politicians.is_ok());
329            }
330        }
331    }
332
333    #[tokio::test]
334    async fn test_politiciantransactions() {
335        if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
336            if !token.is_empty() {
337                let gf_connect = GuruFocusConnector::new(token);
338                let politician_transactions = gf_connect.get_politician_transactions(1, None).await;
339                assert!(politician_transactions.is_ok());
340                let politician_transactions: Result<PoliticianTransactionList, Error> =
341                    serde_json::from_value(politician_transactions.unwrap());
342                assert!(politician_transactions.is_ok());
343            }
344        }
345    }
346}