eodhistoricaldata_api/
lib.rs1use reqwest::StatusCode;
16use serde::Deserialize;
17use serde_json::Value;
18use thiserror::Error;
19use time::Date;
20
21#[derive(Deserialize, Debug)]
22pub struct RealTimeQuote {
23 pub code: String,
25 pub timestamp: u64,
27 pub gmtoffset: i32,
28 pub open: f64,
29 pub high: f64,
30 pub low: f64,
31 pub close: f64,
32 pub volume: usize,
33 #[serde(rename = "previousClose")]
34 pub previous_close: f64,
35 pub change: f64,
36 pub change_p: f64,
37}
38
39#[derive(Deserialize, Debug)]
40pub struct HistoricQuote {
41 pub date: String,
43 pub open: Option<f64>,
44 pub high: Option<f64>,
45 pub low: Option<f64>,
46 pub close: Option<f64>,
47 pub adjusted_close: f64,
48 pub volume: Option<usize>,
49}
50
51#[derive(Deserialize, Debug)]
52#[serde(rename_all = "camelCase")]
53pub struct Dividend {
54 pub currency: String,
55 pub date: String,
57 pub declaration_date: Option<String>,
58 pub payment_date: String,
59 pub period: String,
60 pub record_date: String,
61 pub unadjusted_value: f64,
62 pub value: f64,
63}
64
65#[derive(Error, Debug)]
66pub enum EodHistDataError {
67 #[error("fetching the data from eodhistoricaldata failed with status code {0}")]
68 FetchFailed(StatusCode),
69 #[error("deserializing response from eodhistoricaldata failed")]
70 DeserializeFailed(#[from] reqwest::Error),
71 #[error("connection to eodhistoricaldata server failed")]
72 ConnectionFailed(#[from] serde_json::Error),
73}
74
75pub struct EodHistConnector {
76 url: &'static str,
77 api_token: String,
78}
79
80impl EodHistConnector {
81 pub fn new(token: String) -> EodHistConnector {
84 EodHistConnector {
85 url: "https://eodhistoricaldata.com/api",
86 api_token: token,
87 }
88 }
89
90 pub async fn get_latest_quote(&self, ticker: &str) -> Result<RealTimeQuote, EodHistDataError> {
92 let url: String = format!(
93 "{}/real-time/{}?api_token={}&fmt=json",
94 self.url, ticker, self.api_token
95 );
96 let resp = self.send_request(&url).await?;
97 let quote: RealTimeQuote = serde_json::from_value(resp)?;
98 Ok(quote)
99 }
100
101 pub async fn get_quote_history(
103 &self,
104 ticker: &str,
105 start: Date,
106 end: Date,
107 ) -> Result<Vec<HistoricQuote>, EodHistDataError> {
108 let url: String = format!(
109 "{}/eod/{}?from={}&to={}&api_token={}&period=d&fmt=json",
110 self.url, ticker, start, end, self.api_token
111 );
112 let resp = self.send_request(&url).await?;
113 let quotes: Vec<HistoricQuote> = serde_json::from_value(resp)?;
114 Ok(quotes)
115 }
116
117 pub async fn get_dividend_history(
119 &self,
120 ticker: &str,
121 start: Date,
122 ) -> Result<Vec<Dividend>, EodHistDataError> {
123 let url: String = format!(
124 "{}/div/{}?from={}&api_token={}&fmt=json",
125 self.url, ticker, start, self.api_token
126 );
127 let resp = self.send_request(&url).await?;
128 let dividends: Vec<Dividend> = serde_json::from_value(resp)?;
129 Ok(dividends)
130 }
131
132 async fn send_request(&self, url: &str) -> Result<Value, EodHistDataError> {
134 let resp = reqwest::get(url).await?;
135 match resp.status() {
136 StatusCode::OK => Ok(resp.json().await?),
137 status => Err(EodHistDataError::FetchFailed(status)),
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use tokio_test;
146
147 #[test]
148 fn test_get_single_quote() {
149 let token = "OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX".to_string();
151 let provider = EodHistConnector::new(token);
152 let quote = tokio_test::block_on(provider.get_latest_quote("AAPL.US")).unwrap();
153
154 assert_eq!("e.code, "AAPL.US");
155 }
156
157 #[test]
158 fn test_get_quote_history() {
159 let token = "OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX".to_string();
161 let provider = EodHistConnector::new(token);
162 let start = Date::from_calendar_date(2020, time::Month::January, 1).unwrap();
163 let end = Date::from_calendar_date(2020, time::Month::January, 31).unwrap();
164 let quotes =
165 tokio_test::block_on(provider.get_quote_history("AAPL.US", start, end)).unwrap();
166
167 assert_eq!(quotes.len(), 21);
168 assert_eq!(quotes[0].date, "2020-01-02");
169 assert_eq!(quotes[quotes.len() - 1].date, "2020-01-31");
170 }
171
172 #[test]
173 fn test_get_dividend_history() {
174 let token = "OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX".to_string();
176 let provider = EodHistConnector::new(token);
177 let start = Date::from_calendar_date(2020, time::Month::January, 1).unwrap();
178 let dividends =
179 tokio_test::block_on(provider.get_dividend_history("AAPL.US", start)).unwrap();
180
181 assert!(dividends.len() >= 4);
182 }
183}