market_data/publishers/
iexcloud.rs1use 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#[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 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.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.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.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 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 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