use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PaginationInfo {
pub limit: u64,
pub offset: u64,
pub count: u64,
pub total: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EodDataItem {
pub date: DateTime<Utc>,
pub symbol: String,
pub exchange: String,
pub split_factor: f64,
pub dividend: f64,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
pub adj_open: f64,
pub adj_high: f64,
pub adj_low: f64,
pub adj_close: f64,
pub adj_volume: f64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EodData {
pub pagination: PaginationInfo,
pub data: Vec<EodDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SplitsDataItem {
pub date: NaiveDate,
pub split_factor: f64,
pub symbol: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SplitsData {
pub pagination: PaginationInfo,
pub data: Vec<SplitsDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DividendsDataItem {
pub date: NaiveDate,
pub dividend: f64,
pub symbol: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DividendsData {
pub pagination: PaginationInfo,
pub data: Vec<DividendsDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CurrenciesDataItem {
pub code: String,
pub name: String,
pub symbol: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CurrenciesData {
pub pagination: PaginationInfo,
pub data: Vec<CurrenciesDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TimezonesDataItem {
pub timezone: String,
pub abbr: String,
pub abbr_dst: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TimezonesData {
pub pagination: PaginationInfo,
pub data: Vec<TimezonesDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StockExchange {
pub name: String,
pub acronym: String,
pub mic: String,
pub country: String,
pub country_code: String,
pub city: String,
pub website: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TickersDataItem {
pub name: String,
pub symbol: String,
pub has_intraday: bool,
pub has_eod: bool,
pub country: Option<String>,
pub stock_exchange: StockExchange,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TickerEodDataInner {
pub name: String,
pub symbol: String,
pub has_intraday: bool,
pub has_eod: bool,
pub country: Option<String>,
pub eod: Vec<EodDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TickersData {
pub pagination: PaginationInfo,
pub data: Vec<TickersDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TickersEodData {
pub pagination: PaginationInfo,
pub data: TickerEodDataInner,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExchangesDataItem {
#[serde(flatten)]
pub stock_exchange: StockExchange,
pub timezone: TimezonesDataItem,
pub currency: CurrenciesDataItem,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExchangesData {
pub pagination: PaginationInfo,
pub data: Vec<ExchangesDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExchangesEodDataInner {
pub name: String,
pub acronym: String,
pub mic: String,
pub country: String,
pub city: String,
pub website: String,
pub eod: Vec<EodDataItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExchangesEodData {
pub pagination: PaginationInfo,
pub data: ExchangesEodDataInner,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IntradayDataItem {
pub open: f64,
pub high: f64,
pub low: f64,
pub last: f64,
pub close: f64,
pub volume: f64,
pub date: DateTime<Utc>,
pub symbol: String,
pub exchange: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IntradayData {
pub pagination: PaginationInfo,
pub data: Vec<IntradayDataItem>,
}
#[cfg(test)]
mod tests {
use chrono::{Datelike, NaiveDate};
use crate::{
CurrenciesData, DividendsData, EodData, EodDataItem, ExchangesData, ExchangesEodData,
IntradayData, SplitsData, TickersData, TimezonesData,
};
#[test]
fn test_deserialize_eod() {
let json_data = r#"{
"pagination": {
"limit": 100,
"offset": 0,
"count": 100,
"total": 9944
},
"data": [
{
"open": 129.8,
"high": 133.04,
"low": 129.47,
"close": 132.995,
"volume": 106686703.0,
"adj_high": 133.04,
"adj_low": 129.47,
"adj_close": 132.995,
"adj_open": 129.8,
"adj_volume": 106686703.0,
"split_factor": 1.0,
"dividend": 0.0,
"symbol": "AAPL",
"exchange": "XNAS",
"date": "2021-04-09T00:00:00+0000"
}
]
}"#;
let eod_data: EodData = serde_json::from_str(json_data).unwrap();
assert_eq!(eod_data.data[0].open, 129.8);
assert_eq!(eod_data.data[0].symbol, "AAPL");
assert_eq!(eod_data.pagination.limit, 100);
}
#[test]
fn test_deserialize_splits() {
let json_data = r#"{
"pagination": {
"limit": 100,
"offset": 0,
"count": 5,
"total": 5
},
"data": [
{
"date": "2020-08-31",
"split_factor": 4,
"symbol": "AAPL"
},
{
"date": "2014-06-09",
"split_factor": 7,
"symbol": "AAPL"
},
{
"date": "2005-02-28",
"split_factor": 2,
"symbol": "AAPL"
},
{
"date": "2000-06-21",
"split_factor": 2,
"symbol": "AAPL"
},
{
"date": "1987-06-16",
"split_factor": 2,
"symbol": "AAPL"
}
]
}"#;
let splits_data: SplitsData = serde_json::from_str(json_data).unwrap();
assert_eq!(splits_data.data[0].split_factor, 4.0);
assert_eq!(
splits_data.data[0].date,
NaiveDate::from_ymd_opt(2020, 8, 31).unwrap()
);
assert_eq!(splits_data.data[0].symbol, "AAPL");
assert_eq!(splits_data.data[4].split_factor, 2.0);
assert_eq!(
splits_data.data[4].date,
NaiveDate::from_ymd_opt(1987, 6, 16).unwrap()
);
assert_eq!(splits_data.data[4].symbol, "AAPL");
}
#[test]
fn test_deserialize_dividends() {
let json_data = r#"{
"pagination": {
"limit": 5,
"offset": 0,
"count": 5,
"total": 68
},
"data": [
{
"date": "2023-08-11",
"dividend": 0.24,
"symbol": "AAPL"
},
{
"date": "2023-05-12",
"dividend": 0.24,
"symbol": "AAPL"
},
{
"date": "2023-02-10",
"dividend": 0.23,
"symbol": "AAPL"
},
{
"date": "2022-12-23",
"dividend": 0.17,
"symbol": "AAPL"
},
{
"date": "2022-11-04",
"dividend": 0.23,
"symbol": "AAPL"
}
]
}"#;
let dividends_data: DividendsData = serde_json::from_str(json_data).unwrap();
assert_eq!(dividends_data.pagination.limit, 5);
assert_eq!(dividends_data.data[0].dividend, 0.24);
assert_eq!(
dividends_data.data[0].date,
NaiveDate::from_ymd_opt(2023, 8, 11).unwrap()
);
}
#[test]
fn test_deserialize_currencies() {
let json_data = r#"{
"pagination": {
"limit": 3,
"offset": 0,
"count": 3,
"total": 42
},
"data": [
{
"code": "USD",
"symbol": "$",
"name": "US Dollar"
},
{
"code": "ARS",
"symbol": "AR$",
"name": "Argentine Peso"
},
{
"code": "EUR",
"symbol": "€",
"name": "Euro"
}
]
}"#;
let currencies_data: CurrenciesData = serde_json::from_str(json_data).unwrap();
assert_eq!(currencies_data.pagination.limit, 3);
assert_eq!(currencies_data.data[0].code, "USD");
assert_eq!(currencies_data.data[0].symbol, "$");
assert_eq!(currencies_data.data[0].name, "US Dollar");
}
#[test]
fn test_deserialize_timezones() {
let json_data = r#"{
"pagination": {
"limit": 3,
"offset": 0,
"count": 3,
"total": 57
},
"data": [
{
"timezone": "America/New_York",
"abbr": "EST",
"abbr_dst": "EDT"
},
{
"timezone": "America/Argentina/Buenos_Aires",
"abbr": "-03",
"abbr_dst": "-03"
},
{
"timezone": "Europe/Vienna",
"abbr": "CET",
"abbr_dst": "CEST"
}
]
}"#;
let timezones_data: TimezonesData = serde_json::from_str(json_data).unwrap();
assert_eq!(timezones_data.data[0].timezone, "America/New_York");
assert_eq!(timezones_data.data[0].abbr, "EST");
assert_eq!(timezones_data.data[0].abbr_dst, "EDT");
}
#[test]
fn test_deserialize_tickers() {
let json_data = r#"{
"pagination": {
"limit": 3,
"offset": 0,
"count": 3,
"total": 287233
},
"data": [
{
"name": "Microsoft Corporation",
"symbol": "MSFT",
"has_intraday": false,
"has_eod": true,
"country": null,
"stock_exchange": {
"name": "NASDAQ Stock Exchange",
"acronym": "NASDAQ",
"mic": "XNAS",
"country": "USA",
"country_code": "US",
"city": "New York",
"website": "www.nasdaq.com"
}
},
{
"name": "Apple Inc",
"symbol": "AAPL",
"has_intraday": false,
"has_eod": true,
"country": null,
"stock_exchange": {
"name": "NASDAQ Stock Exchange",
"acronym": "NASDAQ",
"mic": "XNAS",
"country": "USA",
"country_code": "US",
"city": "New York",
"website": "www.nasdaq.com"
}
},
{
"name": "Amazon.com Inc",
"symbol": "AMZN",
"has_intraday": false,
"has_eod": true,
"country": null,
"stock_exchange": {
"name": "NASDAQ Stock Exchange",
"acronym": "NASDAQ",
"mic": "XNAS",
"country": "USA",
"country_code": "US",
"city": "New York",
"website": "www.nasdaq.com"
}
}
]
}"#;
let tickers_data: TickersData = serde_json::from_str(json_data).unwrap();
assert_eq!(tickers_data.data[0].name, "Microsoft Corporation");
assert_eq!(tickers_data.data[0].symbol, "MSFT");
assert!(!tickers_data.data[0].has_intraday);
assert!(tickers_data.data[0].has_eod);
assert_eq!(tickers_data.data[0].country, None);
assert_eq!(
tickers_data.data[0].stock_exchange.name,
"NASDAQ Stock Exchange"
);
assert_eq!(tickers_data.data[0].stock_exchange.acronym, "NASDAQ");
assert_eq!(tickers_data.data[0].stock_exchange.mic, "XNAS");
assert_eq!(tickers_data.data[0].stock_exchange.country, "USA");
assert_eq!(tickers_data.data[0].stock_exchange.country_code, "US");
assert_eq!(tickers_data.data[0].stock_exchange.city, "New York");
assert_eq!(
tickers_data.data[0].stock_exchange.website,
"www.nasdaq.com"
);
}
#[test]
fn test_deserialize_tickers_eod_latest() {
let json_data = r#"{
"open": 166.91,
"high": 168.96,
"low": 166.83,
"close": 168.22,
"volume": 58468600,
"adj_high": 168.96,
"adj_low": 166.83,
"adj_close": 168.22,
"adj_open": 166.91,
"adj_volume": 58499129,
"split_factor": 1,
"dividend": 0,
"symbol": "AAPL",
"exchange": "XNAS",
"date": "2023-10-27T00:00:00+0000"
}"#;
let tickers_eod_data: EodDataItem = serde_json::from_str(json_data).unwrap();
assert_eq!(tickers_eod_data.open, 166.91);
assert_eq!(tickers_eod_data.symbol, "AAPL");
}
#[test]
fn test_deserialize_tickers_eod_date() {
let json_data = r#"{
"open": 166.91,
"high": 168.96,
"low": 166.83,
"close": 168.22,
"volume": 58468600,
"adj_high": 168.96,
"adj_low": 166.83,
"adj_close": 168.22,
"adj_open": 166.91,
"adj_volume": 58499129,
"split_factor": 1,
"dividend": 0,
"symbol": "AAPL",
"exchange": "XNAS",
"date": "2023-10-27T00:00:00+0000"
}"#;
let tickers_eod_data: EodDataItem = serde_json::from_str(json_data).unwrap();
assert_eq!(tickers_eod_data.open, 166.91);
assert_eq!(tickers_eod_data.date.day(), 27);
}
#[test]
fn test_deserialize_exchanges() {
let json_data = r#"{
"pagination": {
"limit": 2,
"offset": 0,
"count": 2,
"total": 69
},
"data": [
{
"name": "NASDAQ Stock Exchange",
"acronym": "NASDAQ",
"mic": "XNAS",
"country": "USA",
"country_code": "US",
"city": "New York",
"website": "www.nasdaq.com",
"timezone": {
"timezone": "America/New_York",
"abbr": "EST",
"abbr_dst": "EDT"
},
"currency": {
"code": "USD",
"symbol": "$",
"name": "US Dollar"
}
},
{
"name": "New York Stock Exchange",
"acronym": "NYSE",
"mic": "XNYS",
"country": "USA",
"country_code": "US",
"city": "New York",
"website": "www.nyse.com",
"timezone": {
"timezone": "America/New_York",
"abbr": "EST",
"abbr_dst": "EDT"
},
"currency": {
"code": "USD",
"symbol": "$",
"name": "US Dollar"
}
}
]
}"#;
let exchanges_data: ExchangesData = serde_json::from_str(json_data).unwrap();
assert_eq!(exchanges_data.pagination.limit, 2);
assert_eq!(
exchanges_data.data[0].stock_exchange.name,
"NASDAQ Stock Exchange"
);
assert_eq!(exchanges_data.data[0].stock_exchange.acronym, "NASDAQ");
assert_eq!(exchanges_data.data[0].stock_exchange.mic, "XNAS");
assert_eq!(exchanges_data.data[0].stock_exchange.country, "USA");
assert_eq!(exchanges_data.data[0].stock_exchange.country_code, "US");
assert_eq!(exchanges_data.data[0].stock_exchange.city, "New York");
assert_eq!(
exchanges_data.data[0].stock_exchange.website,
"www.nasdaq.com"
);
assert_eq!(exchanges_data.data[0].timezone.timezone, "America/New_York");
assert_eq!(exchanges_data.data[0].timezone.abbr, "EST");
assert_eq!(exchanges_data.data[0].timezone.abbr_dst, "EDT");
assert_eq!(exchanges_data.data[0].currency.code, "USD");
assert_eq!(exchanges_data.data[0].currency.symbol, "$");
assert_eq!(exchanges_data.data[0].currency.name, "US Dollar");
}
#[test]
fn test_deserialize_exchanges_eod() {
let json_data = r#"{
"pagination": {
"limit": 2,
"offset": 0,
"count": 2,
"total": 251
},
"data": {
"name": "NASDAQ Stock Exchange",
"acronym": "NASDAQ",
"mic": "XNAS",
"country": "USA",
"city": "New York",
"website": "WWW.NASDAQ.COM",
"eod": [
{
"open": 169.35,
"high": 170.9,
"low": 167.9,
"close": 170.77,
"volume": 44768914,
"adj_high": 170.9,
"adj_low": 167.9,
"adj_close": 170.77,
"adj_open": 169.35,
"adj_volume": 44846017,
"split_factor": 1,
"dividend": 0,
"symbol": "AAPL",
"exchange": "XNAS",
"date": "2023-10-31T00:00:00+0000"
},
{
"open": 169.02,
"high": 171.17,
"low": 168.87,
"close": 170.29,
"volume": 51082900,
"adj_high": 171.17,
"adj_low": 168.87,
"adj_close": 170.29,
"adj_open": 169.02,
"adj_volume": 51130955,
"split_factor": 1,
"dividend": 0,
"symbol": "AAPL",
"exchange": "XNAS",
"date": "2023-10-30T00:00:00+0000"
}
]
}
}"#;
let exchanges_eod_result: ExchangesEodData = serde_json::from_str(json_data).unwrap();
assert_eq!(exchanges_eod_result.pagination.limit, 2);
assert_eq!(exchanges_eod_result.data.name, "NASDAQ Stock Exchange");
assert_eq!(exchanges_eod_result.data.acronym, "NASDAQ");
assert_eq!(exchanges_eod_result.data.mic, "XNAS");
assert_eq!(exchanges_eod_result.data.country, "USA");
assert_eq!(exchanges_eod_result.data.city, "New York");
assert_eq!(exchanges_eod_result.data.website, "WWW.NASDAQ.COM");
assert_eq!(exchanges_eod_result.data.eod[0].open, 169.35);
}
#[test]
fn test_deserialize_intraday() {
let json_data = r#"{
"pagination": {
"limit": 2,
"offset": 0,
"count": 2,
"total": 11872
},
"data": [
{
"open": 173.75,
"high": 176.81,
"low": 173.37,
"last": 176.71,
"close": 177.57,
"volume": 1796985,
"date": "2023-11-03T20:00:00+0000",
"symbol": "AAPL",
"exchange": "IEXG"
},
{
"open": 173.75,
"high": 176.51,
"low": 173.37,
"last": 176.13,
"close": 177.57,
"volume": 1516371,
"date": "2023-11-03T19:00:00+0000",
"symbol": "AAPL",
"exchange": "IEXG"
}
]
}"#;
let intraday_data: IntradayData = serde_json::from_str(json_data).unwrap();
assert_eq!(intraday_data.pagination.limit, 2);
assert_eq!(intraday_data.data[0].open, 173.75);
assert_eq!(intraday_data.data[0].high, 176.81);
assert_eq!(intraday_data.data[0].low, 173.37);
assert_eq!(intraday_data.data[0].last, 176.71);
assert_eq!(intraday_data.data[0].close, 177.57);
assert_eq!(intraday_data.data[0].volume, 1796985.0);
let naive_date_time = NaiveDate::from_ymd_opt(2023, 11, 3)
.unwrap()
.and_hms_opt(20, 0, 0)
.unwrap()
.and_utc();
assert_eq!(intraday_data.data[0].date, naive_date_time);
assert_eq!(intraday_data.data[0].symbol, "AAPL");
assert_eq!(intraday_data.data[0].exchange, "IEXG");
}
}