1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
//! # GuruFocus API //! //! This project provides a set of functions to receive data from the //! the guru focus website via the [GuruFocus API](https://www.gurufocus.com/api.php). //! //! # Usage //! Please note that you need at least a premium account to use this API. There a couple of //! examples demonstrating how to use the API in your own rust projects. To run this example, //! you first need to define an environment variable holding the user Token you got from //! GuruFocus: //! ```bash //! export GURUFOCUS_TOKEN='<your user token>' //! ``` //! //! The examples can be executed via the command //! ```dummy //! cargo test --example <name of example> //! ``` //! Here, `<name of example>` could be the name of any of the files in the examples folder //! without the `.rs` extension //! Please note that running any of the examples increases your API access counter by at least 1. //! //! The GuruFocus API provides all data in JSON format, and the basic API functions currently //! will just return these JSON structures as `serde_json::Value` types without any further //! processing. The `serde_json::Value` types can be deserialized //! into more meaningful data structures, as is demonstrated in the `gurulist` example. //! //! The GuruFocus API returns numbers sometimes as numbers, sometimes as strings. This is dealt //! with by introducing a new struct `FloatOrString` containing a float value, but which can //! be read from either a string or float automatically. The drawback is that `.0` as to be //! added to the variable name of a specific data structure. I.e., to access the quoted price //! in a variable of type Quote, i.e. `q: Quote`, the price can be accessed via `q.price.0` instead //! of `q.price`. In a few cases, the string contains not a number, but an error message, like //! "Negative Tangible Equity". In such cases, if the string can not be parsed to a number, the //! value is set to `NAN`. //! //! Since version 0.4, all requests using the ```async``` attribute, returning a Future instead of //! waiting for the response and returning the result. To get the actual results, ```.await``` or ```block_on``` //! or something similar needs to be used. The examples demonstrate how the library could be used. //! //! Please note that the library is not yet stable and that the user interface is still subject to change. //! However, feedback regarding the usability and suggestions for improving the interface are welcome. use chrono; use reqwest::{self, StatusCode}; use serde_json::{self, Value}; use tokio_compat_02::FutureExt; /// Special types for dealing with Gurus. pub mod gurus; pub use gurus::*; /// Special types for dealing with stocks. pub mod stock; pub use stock::*; /// Special types for dealing with financial data. pub mod financials; pub use financials::*; /// Special types for key ratios. pub mod keyratios; pub use keyratios::*; /// Special types for insider tradingey ratios. pub mod insiders; pub use insiders::*; /// Special types for user portfolio. pub mod portfolio; pub use portfolio::*; /// Module for special string / number derserializer pub mod strnum; /// Module for special hex num derserializer pub mod hexnum; /// Container for connection parameters to gurufocus server. pub struct GuruFocusConnector { url: &'static str, user_token: String, } impl GuruFocusConnector { /// Constructor for a new instance of GuruFocusConnector. /// token is the user token you get from gurufocus if you subscribe for /// a premium or premium plus account. pub fn new(token: String) -> GuruFocusConnector { GuruFocusConnector { url: "https://api.gurufocus.com/public/user/", user_token: token, } } /// Returns the full history of financial data for stock symbol given as argument pub async fn get_financials(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/financials", stock); self.send_request(args.as_str()).await } /// Returns the current key statistic figures for stock symbol given as argument pub async fn get_key_ratios(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/keyratios", stock); self.send_request(args.as_str()).await } /// Returns the current quote data of a comma separated list of symbols given as argument pub async fn get_quotes(&self, stocks: &[&str]) -> Result<Value, String> { let args = format!("stock/{}/quote", compact_list(&stocks)); self.send_request(args.as_str()).await } /// Returns the history of (adjusted) quoted prices for symbol given as argument pub async fn get_price_hist(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/price", stock); self.send_request(args.as_str()).await } /// Returns the history of (unadjusted) quoted prices for symbol given as argument pub async fn get_unadj_price_hist(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/unadjusted_price", stock); self.send_request(args.as_str()).await } /// Returns companies current price, valuation rations and ranks for symbol given as argument pub async fn get_stock_summary(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/summary", stock); self.send_request(args.as_str()).await } /// Returns real-time guru trades and holding data for symbol given as argument pub async fn get_guru_trades(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/gurus", stock); self.send_request(args.as_str()).await } /// Returns real-time insider trades for symbol given as argument pub async fn get_insider_trades(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/insider", stock); self.send_request(args.as_str()).await } /// Returns lists of all and personalized gurus pub async fn get_gurus(&self) -> Result<Value, String> { self.send_request("gurulist").await } /// Returns list of gurus stock picks using list of guru ids since a given start date. pub async fn get_guru_picks( &self, gurus: &[&str], start_date: chrono::NaiveDate, ) -> Result<Value, String> { let args = format!( "guru/{}/picks/{}", compact_list(&gurus), start_date.format("%F") ); self.send_request(args.as_str()).await } /// Returns list of aggregated guru portfolios given a slice of guru ids pub async fn get_guru_portfolios(&self, gurus: &[&str]) -> Result<Value, String> { let args = format!("guru/{}/aggregated", compact_list(&gurus)); self.send_request(args.as_str()).await } /// Returns list of supported exchanges pub async fn get_exchanges(&self) -> Result<Value, String> { self.send_request("exchange_list").await } /// Returns list of all stocks of a particular exchange pub async fn get_listed_stocks(&self, exchange: &str) -> Result<Value, String> { let args = format!("exchange_stocks/{}", exchange); self.send_request(args.as_str()).await } /// Returns list of latest insider trades ordered by insider transctions time pub async fn get_insider_updates(&self) -> Result<Value, String> { self.send_request("insider_updates").await } /// Returns 30 years dividend history data of a stock pub async fn get_dividend_history(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/dividend", stock); self.send_request(args.as_str()).await } /// Returns analyst estimate data of a stock pub async fn get_analyst_estimate(&self, stock: &str) -> Result<Value, String> { let args = format!("stock/{}/analyst_estimate ", stock); self.send_request(args.as_str()).await } /// Returns list of personal portfolios pub async fn get_personal_portfolio(&self) -> Result<Value, String> { self.send_request("portfolio/my_portfolios").await } /// Returns list of all stocks with updated fundamental data within a week of the given date pub async fn get_updated_stocks(&self, date: chrono::NaiveDate) -> Result<Value, String> { let args = format!("funda_updated/{}", date); self.send_request(args.as_str()).await } /// Send request to gurufocus server and transform response to JSON value async fn send_request(&self, args: &str) -> Result<Value, String> { let url: String = format!("{}{}/{}", self.url, self.user_token, args); println!("{}", url); let resp = reqwest::get(url.as_str()).compat().await; if resp.is_err() { return Err(String::from("Connection to server failed.")); } let resp = resp.unwrap(); match resp.status() { StatusCode::OK => match resp.json().await { Ok(json) => Ok(json), err => Err(format!("Parsing json failed: {:?}", err)), }, StatusCode::FORBIDDEN => match resp.json().await { Ok(json) => Err(format!("Access forbidden, {}.", get_error(json))), _ => Err(format!("Access forbidden.")), }, StatusCode::NOT_FOUND => match resp.json().await { Ok(json) => Err(format!("Not found, {}.", get_error(json))), _ => Err(format!("Access forbidden.")), }, err => match resp.json().await { Ok(json) => Err(format!("Unspecified error, {}.", get_error(json))), _ => Err(format!("Received bad response from server: {}", err)), }, } } } /// Extract error message from JSON returned by the GuruFocus server fn get_error(err: serde_json::Value) -> String { match err { Value::Object(map) => match &map["error"] { Value::String(msg) => msg.to_string(), val => format!("error was '{}'.", val), }, val => format!("response was '{}'", val), } } /// Compact list as input to url fn compact_list(a: &[&str]) -> String { if a.len() == 0 { return String::new(); } let mut it = a.iter(); let mut res = format!("{}", it.next().unwrap()); for n in it { res.push_str(&format!(",{}", n)); } res } #[cfg(test)] mod tests { use super::*; use serde::Deserialize; #[test] fn test_compact_list() { assert_eq!(compact_list(&["1", "2", "3"]), "1,2,3"); assert_eq!(compact_list(&[]), ""); assert_eq!(compact_list(&["3"]), "3"); } #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] struct SimpleStruct { value: i32, } #[test] fn deserialize_extra_fields() { let data = r#" { "value": 42, "text": "bla" }"#; let s: serde_json::Result<SimpleStruct> = serde_json::from_str(data); // Fails if json has extra fields assert!(s.is_err()); } }