use std::collections::HashMap;
use serde::Deserialize;
use super::YahooError;
#[derive(Deserialize, Debug)]
pub struct YResponse {
pub chart: YChart,
}
impl YResponse {
fn check_consistency(&self) -> Result<(), YahooError> {
for stock in &self.chart.result {
let n = stock.timestamp.len();
if n == 0 {
return Err(YahooError::EmptyDataSet);
}
let quote = &stock.indicators.quote[0];
if quote.open.len() != n
|| quote.high.len() != n
|| quote.low.len() != n
|| quote.volume.len() != n
|| quote.close.len() != n
{
return Err(YahooError::DataInconsistency);
}
if let Some(ref adjclose) = stock.indicators.adjclose {
if adjclose[0].adjclose.len() != n {
return Err(YahooError::DataInconsistency);
}
}
}
Ok(())
}
pub fn from_json(json: serde_json::Value) -> Result<YResponse, YahooError> {
Ok(serde_json::from_value(json)?)
}
pub fn last_quote(&self) -> Result<Quote, YahooError> {
self.check_consistency()?;
let stock = &self.chart.result[0];
let n = stock.timestamp.len();
for i in (0..n).rev() {
let quote = stock.indicators.get_ith_quote(stock.timestamp[i], i);
if quote.is_ok() {
return quote;
}
}
Err(YahooError::EmptyDataSet)
}
pub fn quotes(&self) -> Result<Vec<Quote>, YahooError> {
self.check_consistency()?;
let stock: &YQuoteBlock = &self.chart.result[0];
let mut quotes = Vec::new();
let n = stock.timestamp.len();
for i in 0..n {
let timestamp = stock.timestamp[i];
let quote = stock.indicators.get_ith_quote(timestamp, i);
if let Ok(q) = quote {
quotes.push(q);
}
}
Ok(quotes)
}
pub fn metadata(&self) -> Result<YMetaData, YahooError> {
self.check_consistency()?;
let stock = &self.chart.result[0];
Ok( stock.meta.to_owned())
}
pub fn splits(&self) -> Result<Vec<Split>, YahooError> {
self.check_consistency()?;
let stock = &self.chart.result[0];
if let Some(events) = &stock.events {
if let Some(splits) = &events.splits {
let mut data = splits.values().cloned().collect::<Vec<Split>>();
data.sort_unstable_by_key(|d| d.date);
return Ok(data);
}
}
Ok(vec![])
}
pub fn dividends(&self) -> Result<Vec<Dividend>, YahooError> {
self.check_consistency()?;
let stock = &self.chart.result[0];
if let Some(events) = &stock.events {
if let Some(dividends) = &events.dividends {
let mut data = dividends.values().cloned().collect::<Vec<Dividend>>();
data.sort_unstable_by_key(|d| d.date);
return Ok(data);
}
}
Ok(vec![])
}
pub fn capital_gains(&self) -> Result<Vec<CapitalGain>, YahooError> {
self.check_consistency()?;
let stock = &self.chart.result[0];
if let Some(events) = &stock.events {
if let Some(capital_gain) = &events.capital_gains {
let mut data = capital_gain.values().cloned().collect::<Vec<CapitalGain>>();
data.sort_unstable_by_key(|d| d.date);
return Ok(data);
}
}
Ok(vec![])
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Quote {
pub timestamp: u64,
pub open: f64,
pub high: f64,
pub low: f64,
pub volume: u64,
pub close: f64,
pub adjclose: f64,
}
#[derive(Deserialize, Debug)]
pub struct YChart {
pub result: Vec<YQuoteBlock>,
pub error: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct YQuoteBlock {
pub meta: YMetaData,
pub timestamp: Vec<u64>,
pub events: Option<EventsBlock>,
pub indicators: QuoteBlock,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct YMetaData {
pub currency: String,
pub symbol: String,
pub exchange_name: String,
pub instrument_type: String,
#[serde(default)]
pub first_trade_date: Option<i32>,
pub regular_market_time: u32,
pub gmtoffset: i32,
pub timezone: String,
pub exchange_timezone_name: String,
pub regular_market_price: f64,
pub chart_previous_close: f64,
#[serde(default)]
pub previous_close: Option<f64>,
#[serde(default)]
pub scale: Option<i32>,
pub price_hint: i32,
pub current_trading_period: TradingPeriod,
#[serde(default)]
pub trading_periods: Option<Vec<Vec<PeriodInfo>>>,
pub data_granularity: String,
pub range: String,
pub valid_ranges: Vec<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct TradingPeriod {
pub pre: PeriodInfo,
pub regular: PeriodInfo,
pub post: PeriodInfo,
}
#[derive(Deserialize, Debug, Clone)]
pub struct PeriodInfo {
pub timezone: String,
pub start: u32,
pub end: u32,
pub gmtoffset: i32,
}
#[derive(Deserialize, Debug)]
pub struct QuoteBlock {
quote: Vec<QuoteList>,
#[serde(default)]
adjclose: Option<Vec<AdjClose>>,
}
impl QuoteBlock {
fn get_ith_quote(&self, timestamp: u64, i: usize) -> Result<Quote, YahooError> {
let adjclose = match &self.adjclose {
Some(adjclose) => adjclose[0].adjclose[i],
None => None,
};
let quote = &self.quote[0];
if quote.close[i].is_none() {
return Err(YahooError::EmptyDataSet);
}
Ok(Quote {
timestamp,
open: quote.open[i].unwrap_or(0.0),
high: quote.high[i].unwrap_or(0.0),
low: quote.low[i].unwrap_or(0.0),
volume: quote.volume[i].unwrap_or(0),
close: quote.close[i].unwrap(),
adjclose: adjclose.unwrap_or(0.0),
})
}
}
#[derive(Deserialize, Debug)]
pub struct AdjClose {
adjclose: Vec<Option<f64>>,
}
#[derive(Deserialize, Debug)]
pub struct QuoteList {
pub volume: Vec<Option<u64>>,
pub high: Vec<Option<f64>>,
pub close: Vec<Option<f64>>,
pub low: Vec<Option<f64>>,
pub open: Vec<Option<f64>>,
}
#[derive(Deserialize, Debug)]
pub struct EventsBlock {
pub splits: Option<HashMap<u64, Split>>,
pub dividends: Option<HashMap<u64, Dividend>>,
#[serde(rename = "capitalGains")]
pub capital_gains: Option<HashMap<u64, CapitalGain>>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Split {
pub date: u64,
pub numerator: f64,
pub denominator: f64,
#[serde(rename = "splitRatio")]
pub split_ratio: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Dividend {
pub amount: f64,
pub date: u64,
}
#[derive(Deserialize, Debug, Clone)]
pub struct CapitalGain {
pub amount: f64,
pub date: u64,
}