market_data/publishers/
iexcloud.rs

1//! Fetch time series stock data from [IEX](https://iexcloud.io/docs/), implements Publisher trait
2///
3/// Example - retrieves historical data for Apple's daily stock price:
4/// Reference https://iexcloud.io/docs/api/#historical-prices
5/// https://api.iex.cloud/v1/stock/AAPL/chart/2y?token=YOUR-TOKEN-HERE
6///
7/// Reference https://iexcloud.io/docs/core/HISTORICAL_PRICES
8/// https://api.iex.cloud/v1/data/core/historical_prices/aapl?range=2y&token=YOUR-TOKEN-HERE
9use chrono::NaiveDate;
10use serde::{Deserialize, Serialize};
11use url::Url;
12
13use crate::{
14    client::{Interval, MarketSeries, Series},
15    errors::{MarketError, MarketResult},
16    publishers::Publisher,
17    rest_call::Client,
18};
19
20const BASE_URL: &str = "https://api.iex.cloud/v1/stock/";
21
22/// Fetch time series stock data from [IEX](https://iexcloud.io/docs/), implements Publisher trait
23#[derive(Debug, Default)]
24pub struct Iex {
25    token: String,
26    requests: Vec<IexRequest>,
27    endpoints: Vec<url::Url>,
28    data: Vec<Vec<IexDailyPrices>>,
29}
30
31#[derive(Debug, Default)]
32pub struct IexRequest {
33    symbol: String,
34    range: String,
35}
36
37impl Iex {
38    pub fn new(token: impl Into<String>) -> Self {
39        Iex {
40            token: token.into(),
41            ..Default::default()
42        }
43    }
44
45    /// Request for daily series
46    pub fn daily_series(&mut self, symbol: impl Into<String>, range: impl Into<String>) -> () {
47        self.requests.push(IexRequest {
48            symbol: symbol.into(),
49            range: range.into(),
50        });
51    }
52}
53
54impl Publisher for Iex {
55    fn create_endpoint(&mut self) -> MarketResult<()> {
56        let base_url = Url::parse(BASE_URL)?;
57        self.endpoints = self
58            .requests
59            .iter()
60            .map(|request| {
61                let constructed_url = base_url
62                    .join(&format!(
63                        "{}/chart/{}?token={}",
64                        request.symbol, request.range, self.token
65                    ))
66                    .unwrap();
67                constructed_url
68            })
69            .collect();
70
71        // self.requests have to be consumed once used for creating the endpoints
72        self.requests.clear();
73
74        Ok(())
75    }
76
77    #[cfg(feature = "use-sync")]
78    fn get_data(&mut self) -> MarketResult<()> {
79        let rest_client = Client::new();
80        for endpoint in &self.endpoints {
81            let response = rest_client.get_data(endpoint)?;
82            let body = response.into_string()?;
83
84            let prices: Vec<IexDailyPrices> = serde_json::from_str(&body)?;
85            self.data.push(prices);
86        }
87
88        // self.endpoints have to be consumed once the data was downloaded for requested URL
89        self.endpoints.clear();
90
91        Ok(())
92    }
93
94    #[cfg(feature = "use-async")]
95    async fn get_data(&mut self) -> MarketResult<()> {
96        let client = Client::new();
97        for endpoint in &self.endpoints {
98            let response = client.get_data(endpoint).await?;
99            let body = response.text().await?;
100
101            let prices: Vec<IexDailyPrices> = serde_json::from_str(&body)?;
102            self.data.push(prices);
103        }
104
105        // self.endpoints have to be consumed once the data was downloaded for requested URL
106        self.endpoints.clear();
107
108        Ok(())
109    }
110
111    fn to_writer(&self, writer: impl std::io::Write) -> MarketResult<()> {
112        serde_json::to_writer(writer, &self.data).map_err(|err| {
113            MarketError::ToWriter(format!("Unable to write to writer, got the error: {}", err))
114        })?;
115
116        Ok(())
117    }
118
119    fn transform_data(&mut self) -> Vec<MarketResult<MarketSeries>> {
120        let mut result: Vec<MarketResult<MarketSeries>> = Vec::new();
121        let mut symbol = String::new();
122        for data in self.data.iter() {
123            let mut data_series: Vec<Series> = Vec::with_capacity(data.len());
124            for series in data.iter() {
125                let date: NaiveDate = match NaiveDate::parse_from_str(&series.date, "%Y-%m-%d") {
126                    //.map_err(|e| {MarketError::ParsingError(format!("Unable to parse Date field: {}", e))
127                    Ok(date) => date,
128                    Err(err) => {
129                        result.push(Err(MarketError::ParsingError(format!(
130                            "Unable to parse Date field: {}",
131                            err
132                        ))));
133                        break;
134                    }
135                };
136                data_series.push(Series {
137                    date: date,
138                    open: series.open,
139                    close: series.close,
140                    high: series.high,
141                    low: series.low,
142                    volume: series.volume as f32,
143                });
144                symbol = series.symbol.clone();
145            }
146
147            // sort the series by date
148            data_series.sort_by_key(|item| item.date);
149
150            result.push(Ok(MarketSeries {
151                symbol: symbol.clone(),
152                interval: Interval::Daily,
153                data: data_series,
154            }))
155        }
156        result
157    }
158}
159
160#[derive(Debug, Deserialize, Serialize)]
161struct IexDailyPrices {
162    close: f32,
163    high: f32,
164    low: f32,
165    open: f32,
166    #[allow(dead_code)]
167    #[serde(rename(deserialize = "priceDate"))]
168    price_date: String,
169    #[allow(dead_code)]
170    symbol: String,
171    volume: u64,
172    #[allow(dead_code)]
173    id: String,
174    #[allow(dead_code)]
175    key: String,
176    #[allow(dead_code)]
177    subkey: String,
178    date: String,
179    #[allow(dead_code)]
180    updated: u64,
181    #[allow(dead_code)]
182    #[serde(rename(deserialize = "changeOverTime"))]
183    change_over_time: f32,
184    #[allow(dead_code)]
185    #[serde(rename(deserialize = "marketChangeOverTime"))]
186    market_change_over_time: f32,
187    #[allow(dead_code)]
188    #[serde(rename(deserialize = "uOpen"))]
189    u_open: f32,
190    #[allow(dead_code)]
191    #[serde(rename(deserialize = "uClose"))]
192    u_close: f32,
193    #[allow(dead_code)]
194    #[serde(rename(deserialize = "uHigh"))]
195    u_high: f32,
196    #[allow(dead_code)]
197    #[serde(rename(deserialize = "uLow"))]
198    u_low: f32,
199    #[allow(dead_code)]
200    #[serde(rename(deserialize = "uVolume"))]
201    u_volume: u64,
202    #[allow(dead_code)]
203    #[serde(rename(deserialize = "fOpen"))]
204    f_open: f32,
205    #[allow(dead_code)]
206    #[serde(rename(deserialize = "fClose"))]
207    f_close: f32,
208    #[allow(dead_code)]
209    #[serde(rename(deserialize = "fHigh"))]
210    f_high: f32,
211    #[allow(dead_code)]
212    #[serde(rename(deserialize = "fLow"))]
213    f_low: f32,
214    #[allow(dead_code)]
215    #[serde(rename(deserialize = "fVolume"))]
216    f_volume: u64,
217    #[allow(dead_code)]
218    label: String,
219    #[allow(dead_code)]
220    change: f32,
221    #[allow(dead_code)]
222    #[serde(rename(deserialize = "changePercent"))]
223    change_percent: f32,
224}
225
226// HistoricalPrice struct fields:
227// close    number  Adjusted data for historical dates. Split adjusted only.
228// high     number	Adjusted data for historical dates. Split adjusted only.
229// low	    number	Adjusted data for historical dates. Split adjusted only.
230// open	    number	Adjusted data for historical dates. Split adjusted only.
231// symbol	string	Associated symbol or ticker
232// volume	number	Adjusted data for historical dates. Split adjusted only.
233// changeOverTime	number	Percent change of each interval relative to first value. Useful for comparing multiple stocks.
234// marketChangeOverTime	number	Percent change of each interval relative to first value. 15 minute delayed consolidated data.
235// uOpen	number	Unadjusted data for historical dates.
236// uClose	number	Unadjusted data for historical dates.
237// uHigh	number	Unadjusted data for historical dates.
238// uLow	    number	Unadjusted data for historical dates.
239// uVolume	number	Unadjusted data for historical dates.
240// fOpen	number	Fully adjusted for historical dates.
241// fClose	number	Fully adjusted for historical dates.
242// fHigh	number	Fully adjusted for historical dates.
243// fLow	    number	Fully adjusted for historical dates.
244// fVolume	number	Fully adjusted for historical dates.
245// label	number	A human readable format of the date depending on the range.
246// change	number	Change from previous trading day.
247// changePercent	number	Change percent from previous trading day.