use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
client::{MarketSeries, Series},
errors::{MarketError, MarketResult},
publishers::Publisher,
rest_call::Client,
};
const BASE_URL: &str = "https://api.iex.cloud/v1/stock/";
#[derive(Debug, Default)]
pub struct Iex {
token: String,
symbol: String,
range: String,
endpoint: Option<url::Url>,
data: Option<Vec<IexDailyPrices>>,
}
impl Iex {
pub fn new(token: String) -> Self {
Iex {
token: token,
..Default::default()
}
}
pub fn for_series(&mut self, symbol: String, range: String) -> () {
self.symbol = symbol;
self.range = range;
}
}
impl Publisher for Iex {
fn create_endpoint(&mut self) -> MarketResult<()> {
let base_url = Url::parse(BASE_URL)?;
let constructed_url = base_url.join(&format!(
"{}/chart/{}?token={}",
self.symbol, self.range, self.token
))?;
self.endpoint = Some(constructed_url);
Ok(())
}
#[cfg(feature = "use-sync")]
fn get_data(&mut self) -> MarketResult<()> {
let rest_client = Client::new(
self.endpoint
.clone()
.expect("Use create_endpoint method first to construct the URL"),
);
let response = rest_client.get_data()?;
let body = response.into_string()?;
let prices: Vec<IexDailyPrices> = serde_json::from_str(&body)?;
self.data = Some(prices);
Ok(())
}
#[cfg(feature = "use-async")]
async fn get_data(&mut self) -> MarketResult<()> {
let client = Client::new(
self.endpoint
.clone()
.expect("Use create_endpoint method first to construct the URL"),
);
let response = client.get_data().await?;
let body = response.text().await?;
let prices: Vec<IexDailyPrices> = serde_json::from_str(&body)?;
self.data = Some(prices);
Ok(())
}
fn to_writer(&self, writer: impl std::io::Write) -> MarketResult<()> {
if let Some(data) = &self.data {
serde_json::to_writer(writer, data).map_err(|err| {
MarketError::ToWriter(format!("Unable to write to writer, got the error: {}", err))
})?;
}
Ok(())
}
fn transform_data(&self) -> MarketResult<MarketSeries> {
if let Some(data) = self.data.as_ref() {
let mut data_series: Vec<Series> = Vec::with_capacity(data.len());
for series in data.iter() {
let date: NaiveDate =
NaiveDate::parse_from_str(&series.date, "%Y-%m-%d").map_err(|e| {
MarketError::ParsingError(format!("Unable to parse Date field: {}", e))
})?;
data_series.push(Series {
date: date,
open: series.open,
close: series.close,
high: series.high,
low: series.low,
volume: series.volume as f32,
})
}
data_series.sort_by_key(|item| item.date);
Ok(MarketSeries {
symbol: self.symbol.clone(),
data: data_series,
})
} else {
Err(MarketError::DownloadedData(
"No data downloaded".to_string(),
))
}
}
}
#[derive(Debug, Deserialize, Serialize)]
struct IexDailyPrices {
close: f32,
high: f32,
low: f32,
open: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "priceDate"))]
price_date: String,
#[allow(dead_code)]
symbol: String,
volume: u64,
#[allow(dead_code)]
id: String,
#[allow(dead_code)]
key: String,
#[allow(dead_code)]
subkey: String,
date: String,
#[allow(dead_code)]
updated: u64,
#[allow(dead_code)]
#[serde(rename(deserialize = "changeOverTime"))]
change_over_time: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "marketChangeOverTime"))]
market_change_over_time: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "uOpen"))]
u_open: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "uClose"))]
u_close: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "uHigh"))]
u_high: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "uLow"))]
u_low: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "uVolume"))]
u_volume: u64,
#[allow(dead_code)]
#[serde(rename(deserialize = "fOpen"))]
f_open: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "fClose"))]
f_close: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "fHigh"))]
f_high: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "fLow"))]
f_low: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "fVolume"))]
f_volume: u64,
#[allow(dead_code)]
label: String,
#[allow(dead_code)]
change: f32,
#[allow(dead_code)]
#[serde(rename(deserialize = "changePercent"))]
change_percent: f32,
}