use std::cmp;
use std::collections::HashMap;
use std::str::FromStr;
use serde::Deserialize;
use crate::api::{ApiClient, OutputSize, TimeSeriesInterval};
use crate::deserialize::from_str;
use crate::error::{Error, Result};
use crate::vec_trait::FindData;
#[derive(Debug, Clone, Default)]
pub struct MetaData {
information: String,
symbol: String,
last_refreshed: String,
interval: Option<String>,
output_size: Option<String>,
time_zone: String,
}
#[derive(Default, Debug, Clone)]
pub struct Data {
time: String,
open: f64,
high: f64,
low: f64,
close: f64,
adjusted_close: Option<f64>,
volume: u64,
dividend_amount: Option<f64>,
split_coefficient: Option<f64>,
}
impl Data {
#[must_use]
pub fn time(&self) -> &str {
&self.time
}
#[must_use]
pub fn open(&self) -> f64 {
self.open
}
#[must_use]
pub fn high(&self) -> f64 {
self.high
}
#[must_use]
pub fn low(&self) -> f64 {
self.low
}
#[must_use]
pub fn close(&self) -> f64 {
self.close
}
#[must_use]
pub fn adjusted(&self) -> Option<f64> {
self.adjusted_close
}
#[must_use]
pub fn volume(&self) -> u64 {
self.volume
}
#[must_use]
pub fn dividend(&self) -> Option<f64> {
self.dividend_amount
}
#[must_use]
pub fn split(&self) -> Option<f64> {
self.split_coefficient
}
}
#[derive(Debug, Default)]
pub struct TimeSeries {
meta_data: MetaData,
data: Vec<Data>,
}
impl TimeSeries {
#[must_use]
pub fn information(&self) -> &str {
self.return_meta_string("information")
}
#[must_use]
pub fn symbol(&self) -> &str {
self.return_meta_string("symbol")
}
#[must_use]
pub fn last_refreshed(&self) -> &str {
self.return_meta_string("last refreshed")
}
#[must_use]
pub fn time_zone(&self) -> &str {
self.return_meta_string("time zone")
}
#[must_use]
pub fn interval(&self) -> Option<&str> {
self.operate_option_meta_value("interval")
}
#[must_use]
pub fn output_size(&self) -> Option<&str> {
self.operate_option_meta_value("output size")
}
#[must_use]
pub fn data(&self) -> &Vec<Data> {
&self.data
}
fn return_meta_string(&self, which_val: &str) -> &str {
match which_val {
"information" => &self.meta_data.information,
"symbol" => &self.meta_data.symbol,
"time zone" => &self.meta_data.time_zone,
"last refreshed" => &self.meta_data.last_refreshed,
_ => "",
}
}
fn operate_option_meta_value(&self, which_val: &str) -> Option<&str> {
let value = match which_val {
"interval" => &self.meta_data.interval,
"output size" => &self.meta_data.output_size,
_ => &None,
};
value.as_deref()
}
}
#[derive(Clone, Deserialize)]
struct DataHelper {
#[serde(rename = "1. open", deserialize_with = "from_str")]
open: f64,
#[serde(rename = "2. high", deserialize_with = "from_str")]
high: f64,
#[serde(rename = "3. low", deserialize_with = "from_str")]
low: f64,
#[serde(rename = "4. close", deserialize_with = "from_str")]
close: f64,
#[serde(rename = "5. volume", deserialize_with = "from_str")]
volume: u64,
}
#[derive(Deserialize, Clone)]
struct AdjustedHelper {
#[serde(rename = "1. open", deserialize_with = "from_str")]
open: f64,
#[serde(rename = "2. high", deserialize_with = "from_str")]
high: f64,
#[serde(rename = "3. low", deserialize_with = "from_str")]
low: f64,
#[serde(rename = "4. close", deserialize_with = "from_str")]
close: f64,
#[serde(rename = "5. adjusted close")]
adjusted_close: Option<String>,
#[serde(rename = "6. volume", deserialize_with = "from_str")]
volume: u64,
#[serde(rename = "7. dividend amount")]
dividend_amount: Option<String>,
#[serde(rename = "8. split coefficient")]
split_coefficient: Option<String>,
}
#[derive(Deserialize)]
pub(crate) struct TimeSeriesHelper {
#[serde(rename = "Meta Data")]
meta_data: Option<HashMap<String, String>>,
#[serde(flatten)]
time_series: Option<HashMap<String, HashMap<String, DataHelper>>>,
#[serde(flatten)]
adjusted_series: Option<HashMap<String, HashMap<String, AdjustedHelper>>>,
}
impl TimeSeriesHelper {
fn convert(self) -> Result<TimeSeries> {
if self.meta_data.is_none()
|| (self.time_series.is_none() && self.adjusted_series.is_none())
{
return Err(Error::EmptyResponse);
}
let meta_data = self.meta_data.unwrap();
let information = &meta_data["1. Information"];
let symbol = &meta_data["2. Symbol"];
let last_refreshed = &meta_data["3. Last Refreshed"];
let interval = meta_data.get("4. Interval");
let mut output_size = meta_data.get("4. Output Size");
if output_size.is_none() {
output_size = meta_data.get("5. Output Size");
}
let time_zone = meta_data.get("4. Time Zone").unwrap_or_else(|| {
meta_data.get("5. Time Zone").unwrap_or_else(|| {
meta_data
.get("6. Time Zone")
.expect("time zone value is None")
})
});
let meta_data = MetaData {
information: information.to_string(),
symbol: symbol.to_string(),
last_refreshed: last_refreshed.to_string(),
interval: interval.map(ToString::to_string),
output_size: output_size.map(ToString::to_string),
time_zone: time_zone.to_string(),
};
let mut data_value: Vec<Data> = Vec::new();
if let Some(time_series) = self.time_series {
for hash in time_series.values() {
for val in hash.keys() {
let data_helper = hash
.get(val)
.expect("failed to get value from stock time hashmap");
data_value.push(Data {
time: val.to_string(),
open: data_helper.open,
high: data_helper.high,
low: data_helper.low,
close: data_helper.close,
volume: data_helper.volume,
..Data::default()
});
}
}
}
if let Some(adjusted_series) = self.adjusted_series {
for hash in adjusted_series.values() {
for val in hash.keys() {
let data_helper = hash
.get(val)
.expect("failed to get value from adjusted series");
data_value.push(Data {
time: val.to_string(),
open: data_helper.open,
high: data_helper.high,
low: data_helper.low,
close: data_helper.close,
volume: data_helper.volume,
adjusted_close: option_from_str(&data_helper.adjusted_close),
split_coefficient: option_from_str(&data_helper.split_coefficient),
dividend_amount: option_from_str(&data_helper.dividend_amount),
});
}
}
}
Ok(TimeSeries {
data: data_value,
meta_data,
})
}
}
impl FindData for Vec<Data> {
#[must_use]
fn find(&self, time: &str) -> Option<&<Self as IntoIterator>::Item> {
self.iter().find(|&data| data.time == time)
}
#[must_use]
fn latest(&self) -> <Self as IntoIterator>::Item {
let mut latest = &Data::default();
for data in self {
if latest.time < data.time {
latest = data;
}
}
latest.clone()
}
fn latest_n(&self, n: usize) -> Result<Vec<&<Self as IntoIterator>::Item>> {
let mut time_list = self.iter().map(|data| &data.time).collect::<Vec<_>>();
time_list.sort_by_key(|w| cmp::Reverse(*w));
if n > time_list.len() {
return Err(Error::DesiredNumberOfDataNotPresent(time_list.len()));
}
let mut full_list = Vec::<&Data>::new();
for time in &time_list[0..n] {
full_list.push(self.find(time).unwrap());
}
Ok(full_list)
}
}
fn option_from_str<T>(val: &Option<String>) -> Option<T>
where
T: FromStr,
T::Err: std::error::Error,
{
val.as_ref().map(|s| T::from_str(s).unwrap())
}
pub struct TimeSeriesBuilder<'a> {
api_client: &'a ApiClient,
function: StockFunction,
symbol: &'a str,
interval: Option<TimeSeriesInterval>,
output_size: Option<OutputSize>,
adjusted: Option<bool>,
}
impl<'a> TimeSeriesBuilder<'a> {
crate::json_data_struct!(TimeSeries, TimeSeriesHelper);
#[must_use]
pub fn new(api_client: &'a ApiClient, function: StockFunction, symbol: &'a str) -> Self {
Self {
api_client,
function,
symbol,
interval: None,
output_size: None,
adjusted: None,
}
}
#[must_use]
pub fn interval(mut self, interval: TimeSeriesInterval) -> Self {
self.interval = Some(interval);
self
}
#[must_use]
pub fn output_size(mut self, output_size: OutputSize) -> Self {
self.output_size = Some(output_size);
self
}
#[must_use]
pub fn adjusted(mut self, adjusted: bool) -> Self {
self.adjusted = Some(adjusted);
self
}
fn create_url(&self) -> String {
let function = match self.function {
StockFunction::IntraDay => "TIME_SERIES_INTRADAY",
StockFunction::Daily => "TIME_SERIES_DAILY",
StockFunction::DailyAdjusted => "TIME_SERIES_DAILY_ADJUSTED",
StockFunction::Weekly => "TIME_SERIES_WEEKLY",
StockFunction::WeeklyAdjusted => "TIME_SERIES_WEEKLY_ADJUSTED",
StockFunction::Monthly => "TIME_SERIES_MONTHLY",
StockFunction::MonthlyAdjusted => "TIME_SERIES_MONTHLY_ADJUSTED",
};
let mut url = format!("query?function={function}&symbol={}", self.symbol);
if let Some(stock_time_interval) = &self.interval {
let interval = match stock_time_interval {
TimeSeriesInterval::OneMin => "1min",
TimeSeriesInterval::FiveMin => "5min",
TimeSeriesInterval::FifteenMin => "15min",
TimeSeriesInterval::ThirtyMin => "30min",
TimeSeriesInterval::SixtyMin => "60min",
};
url.push_str(&format!("&interval={interval}"));
};
if let Some(stock_time_output_size) = &self.output_size {
let size = match stock_time_output_size {
OutputSize::Full => "full",
OutputSize::Compact => "compact",
};
url.push_str(&format!("&outputsize={size}"));
}
if let Some(adjusted) = self.adjusted {
if adjusted {
url.push_str("&adjusted=true");
} else {
url.push_str("&adjusted=false");
}
};
url
}
}
#[derive(Clone)]
pub enum StockFunction {
IntraDay,
Daily,
DailyAdjusted,
Weekly,
WeeklyAdjusted,
Monthly,
MonthlyAdjusted,
}