From 080b989ff55f09dcf25811e42fe8bfaeecbff299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Roll=C3=A9n?= <38324289+SebRollen@users.noreply.github.com> Date: Mon, 27 Dec 2021 13:40:29 +0100 Subject: [PATCH] SR/updated time handling (#42) * fix: Use Eastern dates in pagination * feat: More endpoints and examples * doc: add more examples --- Cargo.toml | 12 +- examples/aggregates.rs | 39 +-- examples/quotes.rs | 1 + examples/stock_dividends.rs | 14 ++ examples/stock_splits.rs | 14 ++ src/lib.rs | 2 + src/rest/date_utils.rs | 238 +++++++++++------- src/rest/reference/market_holidays.rs | 55 ++++ .../market_status.rs} | 50 +--- src/rest/reference/mod.rs | 13 + src/rest/reference/stock_dividends.rs | 59 +++++ src/rest/reference/stock_splits.rs | 61 +++++ src/rest/reference/ticker_details.rs | 83 ++++++ src/rest/reference/ticker_types.rs | 51 ++++ src/rest/reference/tickers.rs | 1 + src/rest/stocks.rs | 61 +++-- src/ws/mod.rs | 3 +- 17 files changed, 569 insertions(+), 188 deletions(-) create mode 100644 examples/stock_dividends.rs create mode 100644 examples/stock_splits.rs create mode 100644 src/rest/reference/market_holidays.rs rename src/rest/{reference.rs => reference/market_status.rs} (56%) create mode 100644 src/rest/reference/mod.rs create mode 100644 src/rest/reference/stock_dividends.rs create mode 100644 src/rest/reference/stock_splits.rs create mode 100644 src/rest/reference/ticker_details.rs create mode 100644 src/rest/reference/ticker_types.rs create mode 100644 src/rest/reference/tickers.rs diff --git a/Cargo.toml b/Cargo.toml index 14832b6..724b71d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] chrono = { version = "0.4", features = ["serde"] } +chrono-tz = { version = "0.6.0", features = ["serde"] } futures = { version = "0.3"} itertools = "0.10" rust_decimal = { version = "1.11", features = ["serde-float"] } @@ -18,7 +19,8 @@ thiserror = "1.0" tokio-tungstenite = { version = "0.15", features = ["stream", "rustls-tls"], optional = true } tokio = { version = "1.0", default-features = false, features = ["net"], optional = true} tracing = "0.1" -vila = { version = "2.0", optional = true } +vila = { version = "2.1", optional = true, features = ["progress"] } +uuid = { version = "0.8.2", features = ["serde"] } [dev-dependencies] anyhow = "1.0.45" @@ -42,3 +44,11 @@ required-features = ["rest"] [[example]] name = "quotes" required-features = ["rest"] + +[[example]] +name = "stock_dividends" +required-features = ["rest"] + +[[example]] +name = "stock_splits" +required-features = ["rest"] diff --git a/examples/aggregates.rs b/examples/aggregates.rs index e9ddbe7..9d67898 100644 --- a/examples/aggregates.rs +++ b/examples/aggregates.rs @@ -1,6 +1,6 @@ -use chrono::{NaiveDate, TimeZone, Utc}; +use chrono::NaiveDate; use futures::{StreamExt, TryStreamExt}; -use polygon::rest::{client, GetAggregate, Timespan}; +use polygon::rest::{client, GetAggregate}; use std::env; use stream_flatten_iters::TryStreamExt as _; @@ -9,20 +9,29 @@ async fn main() { env_logger::init(); let key = env::var("POLYGON_TOKEN").unwrap(); let client = client(&key); - let req = GetAggregate::new( + let req1 = GetAggregate::new( "GE", - Utc.from_utc_datetime(&NaiveDate::from_ymd(2011, 11, 5).and_hms(0, 0, 0)), - Utc.from_utc_datetime(&NaiveDate::from_ymd(2021, 11, 5).and_hms(0, 0, 0)), + NaiveDate::from_ymd(2011, 11, 5).and_hms(0, 0, 0), + NaiveDate::from_ymd(2021, 11, 5).and_hms(0, 0, 0), ) - .multiplier(1) - .timespan(Timespan::Minute) - .limit(50000); - log::debug!("{:?}", req); + .limit(1); + let req2 = GetAggregate::new( + "AAPL", + NaiveDate::from_ymd(2011, 11, 5).and_hms(0, 0, 0), + NaiveDate::from_ymd(2021, 11, 5).and_hms(0, 0, 0), + ) + .limit(1); + let reqs = [req1, req2]; - client - .send_paginated(&req) - .map_ok(|x| x.results) - .try_flatten_iters() - .for_each(|x| async move { println!("{:?}", x.unwrap()) }) - .await; + futures::stream::select_all(client.send_all_paginated(&reqs).map(|stream| { + stream + .map_ok(|x| { + let ticker = x.ticker.clone(); + x.results.into_iter().map(move |r| (ticker.clone(), r)) + }) + .try_flatten_iters() + .take(10) + })) + .for_each_concurrent(None, |x| async move { println!("{:?}", x) }) + .await; } diff --git a/examples/quotes.rs b/examples/quotes.rs index dbf402a..1fc370f 100644 --- a/examples/quotes.rs +++ b/examples/quotes.rs @@ -14,6 +14,7 @@ async fn main() { .send_paginated(&req) .map_ok(|x| x.results) .try_flatten_iters() + .take(10) .for_each(|x| async move { println!("{:?}", x) }) .await; } diff --git a/examples/stock_dividends.rs b/examples/stock_dividends.rs new file mode 100644 index 0000000..6ca7e56 --- /dev/null +++ b/examples/stock_dividends.rs @@ -0,0 +1,14 @@ +use polygon::rest::{client, GetStockDividends}; +use std::env; + +#[tokio::main] +async fn main() { + env_logger::init(); + let key = env::var("POLYGON_TOKEN").unwrap(); + let client = client(&key); + let req = GetStockDividends { + stocks_ticker: "AAPL".to_string(), + }; + + println!("{:#?}", client.send(&req).await); +} diff --git a/examples/stock_splits.rs b/examples/stock_splits.rs new file mode 100644 index 0000000..4142d79 --- /dev/null +++ b/examples/stock_splits.rs @@ -0,0 +1,14 @@ +use polygon::rest::{client, GetStockSplits}; +use std::env; + +#[tokio::main] +async fn main() { + env_logger::init(); + let key = env::var("POLYGON_TOKEN").unwrap(); + let client = client(&key); + let req = GetStockSplits { + stocks_ticker: "AAPL".to_string(), + }; + + println!("{:#?}", client.send(&req).await); +} diff --git a/src/lib.rs b/src/lib.rs index 6a5a315..9f8bb4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +extern crate chrono; +extern crate chrono_tz; pub mod errors; #[cfg(feature = "rest")] pub mod rest; diff --git a/src/rest/date_utils.rs b/src/rest/date_utils.rs index 6ee203f..00a4ca2 100644 --- a/src/rest/date_utils.rs +++ b/src/rest/date_utils.rs @@ -1,7 +1,49 @@ use super::stocks::Timespan; -use chrono::{DateTime, Datelike, Duration, DurationRound, TimeZone, Utc}; +use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, RoundingError}; -fn snap_backward(start: DateTime, timespan: Timespan) -> DateTime { +const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036; + +// TODO: This is a workaround since chrono has not released updated code where duration_trunc is +// implemented for NaiveDateTime yet, even though it is merged into the trunk. Once a new release +// of chrono is cut, we should be able to remove this +trait NaiveDateTimeExt { + fn duration_trunc(self, duration: Duration) -> Result + where + Self: Sized; +} + +impl NaiveDateTimeExt for NaiveDateTime { + fn duration_trunc(self, duration: Duration) -> Result { + if let Some(span) = duration.num_nanoseconds() { + if self.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { + return Err(RoundingError::TimestampExceedsLimit); + } + let stamp = self.timestamp_nanos(); + if span > stamp.abs() { + return Err(RoundingError::DurationExceedsTimestamp); + } + let delta_down = stamp % span; + if delta_down == 0 { + Ok(self) + } else { + let (delta_up, delta_down) = if delta_down < 0 { + (delta_down.abs(), span - delta_down.abs()) + } else { + (span - delta_down, delta_down) + }; + if delta_up <= delta_down { + Ok(self + Duration::nanoseconds(delta_up)) + } else { + Ok(self - Duration::nanoseconds(delta_down)) + } + } + } else { + Err(RoundingError::DurationExceedsLimit) + } + } +} + +fn snap_backward(start: NaiveDateTime, timespan: Timespan) -> NaiveDateTime { match timespan { Timespan::Minute => start.duration_trunc(Duration::minutes(1)).unwrap(), Timespan::Hour => start.duration_trunc(Duration::hours(1)).unwrap(), @@ -10,15 +52,15 @@ fn snap_backward(start: DateTime, timespan: Timespan) -> DateTime { let start = start.duration_trunc(Duration::days(1)).unwrap(); start - Duration::days(start.weekday().num_days_from_sunday().into()) } - Timespan::Month => Utc.ymd(start.year(), start.month(), 1).and_hms(0, 0, 0), - Timespan::Quarter => Utc - .ymd(start.year(), 3 * ((start.month() - 1) / 3) + 1, 1) - .and_hms(0, 0, 0), - Timespan::Year => Utc.ymd(start.year(), 1, 1).and_hms(0, 0, 0), + Timespan::Month => NaiveDate::from_ymd(start.year(), start.month(), 1).and_hms(0, 0, 0), + Timespan::Quarter => { + NaiveDate::from_ymd(start.year(), 3 * ((start.month() - 1) / 3) + 1, 1).and_hms(0, 0, 0) + } + Timespan::Year => NaiveDate::from_ymd(start.year(), 1, 1).and_hms(0, 0, 0), } } -pub(crate) fn snap_forward(start: DateTime, timespan: Timespan) -> DateTime { +pub(crate) fn snap_forward(start: NaiveDateTime, timespan: Timespan) -> NaiveDateTime { match timespan { Timespan::Minute => { snap_backward(start, timespan) + Duration::minutes(1) - Duration::milliseconds(1) @@ -34,30 +76,32 @@ pub(crate) fn snap_forward(start: DateTime, timespan: Timespan) -> DateTime } Timespan::Month => { if start.month() == 12 { - Utc.ymd(start.year() + 1, 1, 1).and_hms(0, 0, 0) - Duration::milliseconds(1) + NaiveDate::from_ymd(start.year() + 1, 1, 1).and_hms(0, 0, 0) + - Duration::milliseconds(1) } else { - Utc.ymd(start.year(), start.month() + 1, 1).and_hms(0, 0, 0) + NaiveDate::from_ymd(start.year(), start.month() + 1, 1).and_hms(0, 0, 0) - Duration::milliseconds(1) } } Timespan::Quarter => { if [10, 11, 12].contains(&start.month()) { - Utc.ymd(start.year() + 1, 1, 1).and_hms(0, 0, 0) - Duration::milliseconds(1) + NaiveDate::from_ymd(start.year() + 1, 1, 1).and_hms(0, 0, 0) + - Duration::milliseconds(1) } else { - Utc.ymd(start.year(), 3 * ((start.month() - 1) / 3) + 4, 1) + NaiveDate::from_ymd(start.year(), 3 * ((start.month() - 1) / 3) + 4, 1) .and_hms(0, 0, 0) - Duration::milliseconds(1) } } Timespan::Year => { - Utc.ymd(start.year() + 1, 1, 1).and_hms(0, 0, 0) - Duration::milliseconds(1) + NaiveDate::from_ymd(start.year() + 1, 1, 1).and_hms(0, 0, 0) - Duration::milliseconds(1) } } } fn is_multiple( - date: DateTime, - base: DateTime, + date: NaiveDateTime, + base: NaiveDateTime, multiplier: u32, timespan: Timespan, ) -> bool { @@ -86,11 +130,11 @@ fn is_multiple( } pub(crate) fn adjust_timeperiods( - from: DateTime, - to: DateTime, + from: NaiveDateTime, + to: NaiveDateTime, multiplier: u32, timespan: Timespan, -) -> (DateTime, DateTime) { +) -> (NaiveDateTime, NaiveDateTime) { let from = snap_backward(from, timespan); let mut to = snap_forward(to, timespan); while !is_multiple(to, from, multiplier, timespan) { @@ -100,12 +144,12 @@ pub(crate) fn adjust_timeperiods( } pub(crate) fn next_pagination_date( - from: DateTime, - to: DateTime, + from: NaiveDateTime, + to: NaiveDateTime, limit: u32, multiplier: u32, timespan: Timespan, -) -> DateTime { +) -> NaiveDateTime { let (max_periods, periods) = match timespan { Timespan::Minute => (limit, (to - from + Duration::microseconds(1)).num_minutes()), Timespan::Hour => ( @@ -163,190 +207,190 @@ mod test { #[test] fn test_snap_period() { - let start = Utc.ymd(2021, 5, 14).and_hms(1, 2, 3); + let start = NaiveDate::from_ymd(2021, 5, 14).and_hms(1, 2, 3); assert_eq!( snap_backward(start, Timespan::Minute), - Utc.ymd(2021, 5, 14).and_hms(1, 2, 0) + NaiveDate::from_ymd(2021, 5, 14).and_hms(1, 2, 0) ); assert_eq!( snap_forward(start, Timespan::Minute), - Utc.ymd(2021, 5, 14).and_hms_milli(1, 2, 59, 999) + NaiveDate::from_ymd(2021, 5, 14).and_hms_milli(1, 2, 59, 999) ); assert_eq!( snap_backward(start, Timespan::Hour), - Utc.ymd(2021, 5, 14).and_hms(1, 0, 0) + NaiveDate::from_ymd(2021, 5, 14).and_hms(1, 0, 0) ); assert_eq!( snap_forward(start, Timespan::Hour), - Utc.ymd(2021, 5, 14).and_hms_milli(1, 59, 59, 999) + NaiveDate::from_ymd(2021, 5, 14).and_hms_milli(1, 59, 59, 999) ); assert_eq!( snap_backward(start, Timespan::Day), - Utc.ymd(2021, 5, 14).and_hms(0, 0, 0) + NaiveDate::from_ymd(2021, 5, 14).and_hms(0, 0, 0) ); assert_eq!( snap_forward(start, Timespan::Day), - Utc.ymd(2021, 5, 14).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 5, 14).and_hms_milli(23, 59, 59, 999) ); assert_eq!( snap_backward(start, Timespan::Week), - Utc.ymd(2021, 5, 9).and_hms(0, 0, 0) + NaiveDate::from_ymd(2021, 5, 9).and_hms(0, 0, 0) ); assert_eq!( snap_forward(start, Timespan::Week), - Utc.ymd(2021, 5, 15).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 5, 15).and_hms_milli(23, 59, 59, 999) ); assert_eq!( snap_backward(start, Timespan::Month), - Utc.ymd(2021, 5, 1).and_hms(0, 0, 0) + NaiveDate::from_ymd(2021, 5, 1).and_hms(0, 0, 0) ); assert_eq!( snap_forward(start, Timespan::Month), - Utc.ymd(2021, 5, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 5, 31).and_hms_milli(23, 59, 59, 999) ); assert_eq!( snap_backward(start, Timespan::Quarter), - Utc.ymd(2021, 4, 1).and_hms(0, 0, 0) + NaiveDate::from_ymd(2021, 4, 1).and_hms(0, 0, 0) ); assert_eq!( snap_forward(start, Timespan::Quarter), - Utc.ymd(2021, 6, 30).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 6, 30).and_hms_milli(23, 59, 59, 999) ); assert_eq!( snap_backward(start, Timespan::Year), - Utc.ymd(2021, 1, 1).and_hms(0, 0, 0) + NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0) ); assert_eq!( snap_forward(start, Timespan::Year), - Utc.ymd(2021, 12, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 12, 31).and_hms_milli(23, 59, 59, 999) ); } #[test] fn test_is_multiple() { - let base = Utc.ymd(2021, 1, 1).and_hms(0, 0, 0); + let base = NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0); assert!(is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(0, 0, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(0, 0, 59, 999), base, 1, Timespan::Minute )); assert!(!is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(0, 0, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(0, 0, 59, 999), base, 2, Timespan::Minute )); assert!(is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(0, 1, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(0, 1, 59, 999), base, 2, Timespan::Minute )); assert!(is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(0, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(0, 59, 59, 999), base, 1, Timespan::Hour )); assert!(!is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(0, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(0, 59, 59, 999), base, 3, Timespan::Hour )); assert!(is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(2, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(2, 59, 59, 999), base, 3, Timespan::Hour )); assert!(is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(23, 59, 59, 999), base, 1, Timespan::Day )); assert!(!is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(23, 59, 59, 999), base, 4, Timespan::Day )); assert!(is_multiple( - Utc.ymd(2021, 1, 4).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 4).and_hms_milli(23, 59, 59, 999), base, 4, Timespan::Day )); assert!(is_multiple( - Utc.ymd(2021, 1, 7).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 7).and_hms_milli(23, 59, 59, 999), base, 1, Timespan::Week )); assert!(!is_multiple( - Utc.ymd(2021, 1, 1).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(23, 59, 59, 999), base, 5, Timespan::Week )); assert!(is_multiple( - Utc.ymd(2021, 2, 4).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 2, 4).and_hms_milli(23, 59, 59, 999), base, 5, Timespan::Week )); assert!(is_multiple( - Utc.ymd(2021, 1, 31).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 31).and_hms_milli(23, 59, 59, 999), base, 1, Timespan::Month )); assert!(!is_multiple( - Utc.ymd(2021, 1, 31).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 1, 31).and_hms_milli(23, 59, 59, 999), base, 6, Timespan::Month )); assert!(is_multiple( - Utc.ymd(2021, 6, 30).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 6, 30).and_hms_milli(23, 59, 59, 999), base, 6, Timespan::Month )); assert!(is_multiple( - Utc.ymd(2021, 3, 31).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 3, 31).and_hms_milli(23, 59, 59, 999), base, 1, Timespan::Quarter )); assert!(!is_multiple( - Utc.ymd(2021, 3, 31).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 3, 31).and_hms_milli(23, 59, 59, 999), base, 7, Timespan::Quarter )); assert!(is_multiple( - Utc.ymd(2022, 9, 30).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2022, 9, 30).and_hms_milli(23, 59, 59, 999), base, 7, Timespan::Quarter )); assert!(is_multiple( - Utc.ymd(2021, 12, 31).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 12, 31).and_hms_milli(23, 59, 59, 999), base, 1, Timespan::Year )); assert!(!is_multiple( - Utc.ymd(2021, 12, 31).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2021, 12, 31).and_hms_milli(23, 59, 59, 999), base, 8, Timespan::Year )); assert!(is_multiple( - Utc.ymd(2028, 12, 31).and_hms_milli(23, 59, 59, 999), + NaiveDate::from_ymd(2028, 12, 31).and_hms_milli(23, 59, 59, 999), base, 8, Timespan::Year @@ -355,74 +399,74 @@ mod test { #[test] fn adjust_time_periods() { - let start = Utc.ymd(2021, 1, 1).and_hms(0, 0, 0); - let end = Utc.ymd(2022, 1, 1).and_hms(0, 0, 0); + let start = NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0); + let end = NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0); assert_eq!( adjust_timeperiods(start, end, 1, Timespan::Minute), ( - Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), - Utc.ymd(2022, 1, 1).and_hms_milli(0, 0, 59, 999) + NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd(2022, 1, 1).and_hms_milli(0, 0, 59, 999) ) ); assert_eq!( adjust_timeperiods(start, end, 2, Timespan::Hour), ( - Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), - Utc.ymd(2022, 1, 1).and_hms_milli(1, 59, 59, 999) + NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd(2022, 1, 1).and_hms_milli(1, 59, 59, 999) ) ); assert_eq!( adjust_timeperiods(start, end, 3, Timespan::Day), ( - Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), - Utc.ymd(2022, 1, 1).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd(2022, 1, 1).and_hms_milli(23, 59, 59, 999) ) ); assert_eq!( adjust_timeperiods(start, end, 4, Timespan::Week), ( - Utc.ymd(2020, 12, 27).and_hms(0, 0, 0), - Utc.ymd(2022, 1, 22).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2020, 12, 27).and_hms(0, 0, 0), + NaiveDate::from_ymd(2022, 1, 22).and_hms_milli(23, 59, 59, 999) ) ); assert_eq!( adjust_timeperiods(start, end, 5, Timespan::Month), ( - Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), - Utc.ymd(2022, 3, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd(2022, 3, 31).and_hms_milli(23, 59, 59, 999) ) ); assert_eq!( adjust_timeperiods(start, end, 6, Timespan::Quarter), ( - Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), - Utc.ymd(2022, 6, 30).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd(2022, 6, 30).and_hms_milli(23, 59, 59, 999) ) ); assert_eq!( adjust_timeperiods(start, end, 7, Timespan::Year), ( - Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), - Utc.ymd(2027, 12, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd(2027, 12, 31).and_hms_milli(23, 59, 59, 999) ) ); } #[test] fn test_next_pagination_date() { - let from = Utc.ymd(2023, 1, 1).and_hms(0, 0, 0); - let to = Utc.ymd(2032, 12, 31).and_hms_milli(23, 59, 59, 999); + let from = NaiveDate::from_ymd(2023, 1, 1).and_hms(0, 0, 0); + let to = NaiveDate::from_ymd(2032, 12, 31).and_hms_milli(23, 59, 59, 999); assert_eq!( next_pagination_date(from, to, 2, 1, Timespan::Minute), - Utc.ymd(2023, 1, 1).and_hms_milli(0, 1, 59, 999) + NaiveDate::from_ymd(2023, 1, 1).and_hms_milli(0, 1, 59, 999) ); assert_eq!( next_pagination_date(from, to, 2, 2, Timespan::Minute), - Utc.ymd(2023, 1, 1).and_hms_milli(0, 1, 59, 999) + NaiveDate::from_ymd(2023, 1, 1).and_hms_milli(0, 1, 59, 999) ); assert_eq!( next_pagination_date(from, to, 7, 5, Timespan::Minute), - Utc.ymd(2023, 1, 1).and_hms_milli(0, 4, 59, 999) + NaiveDate::from_ymd(2023, 1, 1).and_hms_milli(0, 4, 59, 999) ); assert_eq!( next_pagination_date(from, to, 5270400, 1, Timespan::Minute), @@ -430,15 +474,15 @@ mod test { ); assert_eq!( next_pagination_date(from, to, 120, 1, Timespan::Hour), - Utc.ymd(2023, 1, 1).and_hms_milli(1, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 1).and_hms_milli(1, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 120, 2, Timespan::Hour), - Utc.ymd(2023, 1, 1).and_hms_milli(1, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 1).and_hms_milli(1, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 420, 5, Timespan::Hour), - Utc.ymd(2023, 1, 1).and_hms_milli(4, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 1).and_hms_milli(4, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 5270400, 1, Timespan::Hour), @@ -446,54 +490,54 @@ mod test { ); assert_eq!( next_pagination_date(from, to, 2, 1, Timespan::Day), - Utc.ymd(2023, 1, 2).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 2).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 2, 2, Timespan::Day), - Utc.ymd(2023, 1, 2).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 2).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 7, 5, Timespan::Day), - Utc.ymd(2023, 1, 5).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 5).and_hms_milli(23, 59, 59, 999) ); assert_eq!(next_pagination_date(from, to, 3660, 1, Timespan::Day), to); assert_eq!( next_pagination_date(from, to, 14, 1, Timespan::Week), - Utc.ymd(2023, 1, 14).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 14).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 14, 2, Timespan::Week), - Utc.ymd(2023, 1, 14).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 1, 14).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 49, 5, Timespan::Week), - Utc.ymd(2023, 2, 4).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 2, 4).and_hms_milli(23, 59, 59, 999) ); assert_eq!(next_pagination_date(from, to, 3660, 1, Timespan::Week), to); assert_eq!( next_pagination_date(from, to, 62, 1, Timespan::Month), - Utc.ymd(2023, 2, 28).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 2, 28).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 62, 2, Timespan::Month), - Utc.ymd(2023, 2, 28).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 2, 28).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 217, 5, Timespan::Month), - Utc.ymd(2023, 5, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 5, 31).and_hms_milli(23, 59, 59, 999) ); assert_eq!(next_pagination_date(from, to, 3660, 1, Timespan::Month), to); assert_eq!( next_pagination_date(from, to, 186, 1, Timespan::Quarter), - Utc.ymd(2023, 6, 30).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 6, 30).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 186, 2, Timespan::Quarter), - Utc.ymd(2023, 6, 30).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 6, 30).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 366, 3, Timespan::Quarter), - Utc.ymd(2023, 9, 30).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2023, 9, 30).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 3660, 1, Timespan::Quarter), @@ -501,15 +545,15 @@ mod test { ); assert_eq!( next_pagination_date(from, to, 732, 1, Timespan::Year), - Utc.ymd(2024, 12, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2024, 12, 31).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 732, 2, Timespan::Year), - Utc.ymd(2024, 12, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2024, 12, 31).and_hms_milli(23, 59, 59, 999) ); assert_eq!( next_pagination_date(from, to, 2562, 5, Timespan::Year), - Utc.ymd(2027, 12, 31).and_hms_milli(23, 59, 59, 999) + NaiveDate::from_ymd(2027, 12, 31).and_hms_milli(23, 59, 59, 999) ); assert_eq!(next_pagination_date(from, to, 3660, 1, Timespan::Year), to); } diff --git a/src/rest/reference/market_holidays.rs b/src/rest/reference/market_holidays.rs new file mode 100644 index 0000000..0353cba --- /dev/null +++ b/src/rest/reference/market_holidays.rs @@ -0,0 +1,55 @@ +use chrono::{DateTime, NaiveDate, Utc}; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use vila::Request; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(tag = "status")] +pub enum MarketHolidayStatus { + #[serde(rename = "closed")] + Closed, + #[serde(rename = "early-close")] + EarlyClose { + open: DateTime, + close: DateTime, + }, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct MarketHoliday { + pub exchange: String, + pub name: String, + #[serde(flatten)] + pub status: MarketHolidayStatus, + pub date: NaiveDate, +} + +pub struct GetMarketHolidays; + +impl Request for GetMarketHolidays { + type Data = (); + type Response = Vec; + + fn endpoint(&self) -> Cow { + "/v1/marketstatus/upcoming".into() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::rest::client_with_url; + use mockito::{mock, Matcher}; + + #[tokio::test] + async fn get_market_holidays() { + let _m = mock("GET", "/v1/marketstatus/upcoming") + .match_query(Matcher::UrlEncoded("apiKey".into(), "TOKEN".into())) + .with_body(r#"[{"exchange":"NYSE","name":"Thanksgiving","date":"2020-11-26","status":"closed"},{"exchange":"NASDAQ","name":"Thanksgiving","date":"2020-11-26","status":"closed"}]"#).create(); + let url = mockito::server_url(); + + let client = client_with_url(&url, "TOKEN"); + let req = GetMarketHolidays; + client.send(&req).await.unwrap(); + } +} diff --git a/src/rest/reference.rs b/src/rest/reference/market_status.rs similarity index 56% rename from src/rest/reference.rs rename to src/rest/reference/market_status.rs index c718967..bec7d88 100644 --- a/src/rest/reference.rs +++ b/src/rest/reference/market_status.rs @@ -1,44 +1,8 @@ -use chrono::{DateTime, NaiveDate, Utc}; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use vila::Request; -// Market holidays - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(tag = "status")] -pub enum MarketHolidayStatus { - #[serde(rename = "closed")] - Closed, - #[serde(rename = "early-close")] - EarlyClose { - open: DateTime, - close: DateTime, - }, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct MarketHoliday { - pub exchange: String, - pub name: String, - #[serde(flatten)] - pub status: MarketHolidayStatus, - pub date: NaiveDate, -} - -pub struct GetMarketHolidays; - -impl Request for GetMarketHolidays { - type Data = (); - type Response = Vec; - - fn endpoint(&self) -> Cow { - "/v1/marketstatus/upcoming".into() - } -} - -// Market Status - #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum Status { #[serde(rename = "open")] @@ -88,18 +52,6 @@ mod test { use crate::rest::client_with_url; use mockito::{mock, Matcher}; - #[tokio::test] - async fn get_market_holidays() { - let _m = mock("GET", "/v1/marketstatus/upcoming") - .match_query(Matcher::UrlEncoded("apiKey".into(), "TOKEN".into())) - .with_body(r#"[{"exchange":"NYSE","name":"Thanksgiving","date":"2020-11-26","status":"closed"},{"exchange":"NASDAQ","name":"Thanksgiving","date":"2020-11-26","status":"closed"}]"#).create(); - let url = mockito::server_url(); - - let client = client_with_url(&url, "TOKEN"); - let req = GetMarketHolidays; - client.send(&req).await.unwrap(); - } - #[tokio::test] async fn get_market_status() { let _m = mock("GET", "/v1/marketstatus/now") diff --git a/src/rest/reference/mod.rs b/src/rest/reference/mod.rs new file mode 100644 index 0000000..74ee312 --- /dev/null +++ b/src/rest/reference/mod.rs @@ -0,0 +1,13 @@ +mod market_holidays; +mod market_status; +mod stock_dividends; +mod stock_splits; +mod ticker_details; +mod ticker_types; + +pub use market_holidays::*; +pub use market_status::*; +pub use stock_dividends::*; +pub use stock_splits::*; +pub use ticker_details::*; +pub use ticker_types::*; diff --git a/src/rest/reference/stock_dividends.rs b/src/rest/reference/stock_dividends.rs new file mode 100644 index 0000000..b845747 --- /dev/null +++ b/src/rest/reference/stock_dividends.rs @@ -0,0 +1,59 @@ +use chrono::NaiveDate; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use vila::Request; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct StockDividend { + pub amount: f64, + pub ex_date: NaiveDate, + pub payment_date: NaiveDate, + pub record_date: NaiveDate, + pub ticker: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct StockDividendsWrapper { + pub count: usize, + pub status: String, + pub results: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct GetStockDividends { + #[serde(rename = "stocksTicker")] + pub stocks_ticker: String, +} + +impl Request for GetStockDividends { + type Data = (); + type Response = StockDividendsWrapper; + + fn endpoint(&self) -> Cow { + format!("/v2/reference/dividends/{}", self.stocks_ticker).into() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::rest::client_with_url; + use mockito::{mock, Matcher}; + + #[tokio::test] + async fn get_stock_dividends() { + let _m = mock("GET", "/v2/reference/dividends/AAPL") + .match_query(Matcher::UrlEncoded("apiKey".into(), "TOKEN".into())) + // The format of dates here is different than that which appears in the Polygon docs. + // But the endpoint definitely returns dates, not datetimes. + .with_body(r#"{"count":2,"results":[{"amount":0.82,"exDate":"2020-05-08","paymentDate":"2020-05-14","recordDate":"2020-05-11","ticker":"AAPL"},{"amount":0.77,"exDate":"2020-02-07","paymentDate":"2020-02-13","recordDate":"2020-02-10","ticker":"AAPL"}],"status":"OK"}"#).create(); + let url = mockito::server_url(); + + let client = client_with_url(&url, "TOKEN"); + let req = GetStockDividends { + stocks_ticker: "AAPL".to_string(), + }; + client.send(&req).await.unwrap(); + } +} diff --git a/src/rest/reference/stock_splits.rs b/src/rest/reference/stock_splits.rs new file mode 100644 index 0000000..3753e25 --- /dev/null +++ b/src/rest/reference/stock_splits.rs @@ -0,0 +1,61 @@ +use chrono::NaiveDate; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use vila::Request; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct StockSplit { + pub forfactor: Option, + pub tofactor: Option, + pub ratio: f64, + pub declared_date: Option, + pub ex_date: NaiveDate, + pub payment_date: NaiveDate, + pub ticker: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct StockSplitsWrapper { + pub count: usize, + pub status: String, + pub results: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct GetStockSplits { + #[serde(rename = "stocksTicker")] + pub stocks_ticker: String, +} + +impl Request for GetStockSplits { + type Data = (); + type Response = StockSplitsWrapper; + + fn endpoint(&self) -> Cow { + format!("/v2/reference/splits/{}", self.stocks_ticker).into() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::rest::client_with_url; + use mockito::{mock, Matcher}; + + #[tokio::test] + async fn get_stock_splits() { + let _m = mock("GET", "/v2/reference/splits/AAPL") + .match_query(Matcher::UrlEncoded("apiKey".into(), "TOKEN".into())) + // The format of dates here is different than that which appears in the Polygon docs. + // But the endpoint definitely returns dates, not datetimes. + .with_body(r#"{"count":4,"results":[{"declaredDate":"2020-07-30","exDate":"2020-08-31","forfactor":4,"paymentDate":"2020-08-28","ratio":0.25,"ticker":"AAPL","tofactor":1},{"exDate":"2014-06-09","forfactor":7,"paymentDate":"2014-06-10","ratio":0.14285714285714285,"ticker":"AAPL","tofactor":1},{"exDate":"2005-02-28","paymentDate":"2005-02-28","ratio":0.5,"ticker":"AAPL"},{"exDate":"2000-06-21","paymentDate":"2000-06-21","ratio":0.5,"ticker":"AAPL"}],"status":"OK"}"#).create(); + let url = mockito::server_url(); + + let client = client_with_url(&url, "TOKEN"); + let req = GetStockSplits { + stocks_ticker: "AAPL".to_string(), + }; + client.send(&req).await.unwrap(); + } +} diff --git a/src/rest/reference/ticker_details.rs b/src/rest/reference/ticker_details.rs new file mode 100644 index 0000000..704d676 --- /dev/null +++ b/src/rest/reference/ticker_details.rs @@ -0,0 +1,83 @@ +use super::Locale; +use chrono::{DateTime, NaiveDate, Utc}; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use uuid::Uuid; +use vila::{Request, RequestData}; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum Market { + Stocks, + Crypto, + Fx, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct TickerDetails { + pub cik: String, + pub composite_figi: String, + pub currency_name: String, + pub last_updated_utc: DateTime, + pub locale: Locale, + pub market: Market, + pub market_cap: usize, + pub name: String, + pub outstanding_shares: usize, + pub phone_number: String, + pub primary_exchange: String, + pub share_class_figi: String, + pub sic_code: String, + pub sic_description: String, + pub ticker: String, + pub r#type: String, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct TickerDetailsWrapper { + pub count: usize, + pub request_id: Uuid, + pub results: TickerDetails, + pub status: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetTickerDetails { + ticker: String, + date: Option, +} + +impl Request for GetTickerDetails { + type Data = Self; + type Response = TickerDetailsWrapper; + + fn endpoint(&self) -> Cow { + format!("/vX/reference/tickers/{}", self.ticker).into() + } + + fn data(&self) -> RequestData<&Self> { + RequestData::Query(self) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::rest::client_with_url; + use mockito::{mock, Matcher}; + + #[tokio::test] + async fn get_ticker_details() { + let _m = mock("GET", "/vX/reference/tickers/AAPL") + .match_query(Matcher::UrlEncoded("apiKey".into(), "TOKEN".into())) + .with_body(r#"{"count":1,"request_id":"31d59dda-80e5-4721-8496-d0d32a654afe","results":{"active":true,"address":{"address1":"One Apple Park Way","city":"Cupertino","state":"CA"},"cik":"0000320193","composite_figi":"BBG000B9XRY4","currency_name":"usd","last_updated_utc":"2020-12-27T00:00:00Z","locale":"us","market":"stocks","market_cap":2082042128180,"name":"Apple Inc.","outstanding_shares":17001800000,"phone_number":"(408) 996-1010","primary_exchange":"XNAS","share_class_figi":"BBG001S5N8V8","sic_code":"3571","sic_description":"ELECTRONIC COMPUTERS","ticker":"AAPL","type":"CS"},"status":"OK"}"#).create(); + let url = mockito::server_url(); + + let client = client_with_url(&url, "TOKEN"); + let req = GetTickerDetails { + ticker: "AAPL".to_string(), + date: None, + }; + client.send(&req).await.unwrap(); + } +} diff --git a/src/rest/reference/ticker_types.rs b/src/rest/reference/ticker_types.rs new file mode 100644 index 0000000..9220f42 --- /dev/null +++ b/src/rest/reference/ticker_types.rs @@ -0,0 +1,51 @@ +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use uuid::Uuid; +use vila::Request; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum AssetClass { + Stocks, + Options, + Crypto, + Fx, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum Locale { + Us, + Global, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TickerType { + pub asset_class: AssetClass, + pub code: String, + pub description: String, + pub locale: Locale, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TickerTypesWrapper { + pub count: usize, + pub request_id: Uuid, + pub results: Vec, + pub status: String, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +pub struct GetTickerTypes { + asset_class: AssetClass, + locale: Locale, +} + +impl Request for GetTickerTypes { + type Data = (); + type Response = TickerTypesWrapper; + + fn endpoint(&self) -> Cow { + "/v3/reference/tickers/types".into() + } +} diff --git a/src/rest/reference/tickers.rs b/src/rest/reference/tickers.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/rest/reference/tickers.rs @@ -0,0 +1 @@ + diff --git a/src/rest/stocks.rs b/src/rest/stocks.rs index 6c687db..b2de984 100644 --- a/src/rest/stocks.rs +++ b/src/rest/stocks.rs @@ -1,8 +1,9 @@ use super::date_utils::*; use chrono::{ serde::{ts_milliseconds, ts_nanoseconds, ts_nanoseconds_option}, - DateTime, Duration, NaiveDate, TimeZone, Utc, + DateTime, Duration, NaiveDate, NaiveDateTime, TimeZone, Utc, }; +use chrono_tz::US::Eastern; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -208,28 +209,28 @@ pub struct AggregateWrapper { pub results: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Debug, Clone)] /// Request aggregate bars. /// Note that Polygon performs time-snapping and stretching of the `from` and `to` parameters to /// ensure whole bars of data are returned. In order to reduce confusion, this library performs the /// same time-snapping and stretching before sending the raw requests. /// /// For more details, see [this Polygon blogpost](https://polygon.io/blog/aggs-api-updates/) -pub struct GetAggregate<'a> { +pub struct GetAggregate { #[serde(rename = "stocksTicker")] - ticker: &'a str, + ticker: String, multiplier: u32, timespan: Timespan, - from: DateTime, - to: DateTime, + from: NaiveDateTime, + to: NaiveDateTime, query: GetAggregateQuery, } -impl<'a> GetAggregate<'a> { - pub fn new(ticker: &'a str, from: DateTime, to: DateTime) -> Self { +impl GetAggregate { + pub fn new(ticker: T, from: NaiveDateTime, to: NaiveDateTime) -> Self { let (from, to) = adjust_timeperiods(from, to, 1, Timespan::Day); Self { - ticker, + ticker: ticker.to_string(), multiplier: 1, timespan: Timespan::Day, from, @@ -281,18 +282,22 @@ pub struct GetAggregateQuery { limit: u32, } -impl<'a> Request for GetAggregate<'a> { +impl Request for GetAggregate { type Response = AggregateWrapper; type Data = GetAggregateQuery; fn endpoint(&self) -> Cow { + let from = Eastern + .from_local_datetime(&self.from) + .unwrap() + .timestamp_millis(); + let to = Eastern + .from_local_datetime(&self.to) + .unwrap() + .timestamp_millis(); format!( "v2/aggs/ticker/{}/range/{}/{}/{}/{}", - self.ticker, - self.multiplier, - self.timespan, - self.from.timestamp_millis(), - self.to.timestamp_millis() + self.ticker, self.multiplier, self.timespan, from, to ) .into() } @@ -304,20 +309,28 @@ impl<'a> Request for GetAggregate<'a> { #[derive(Clone)] pub struct AggregatePaginationData { - from: DateTime, - to: DateTime, + from: NaiveDateTime, + to: NaiveDateTime, } impl From for PathModifier { fn from(d: AggregatePaginationData) -> PathModifier { + let from = Eastern + .from_local_datetime(&d.from) + .unwrap() + .timestamp_millis(); + let to = Eastern + .from_local_datetime(&d.to) + .unwrap() + .timestamp_millis(); let mut data = HashMap::new(); - data.insert(7, d.from.timestamp_millis().to_string()); - data.insert(8, d.to.timestamp_millis().to_string()); + data.insert(7, from.to_string()); + data.insert(8, to.to_string()); PathModifier { data } } } -impl<'a> PaginatedRequest for GetAggregate<'a> { +impl PaginatedRequest for GetAggregate { type Data = AggregatePaginationData; type Paginator = PathPaginator; fn initial_page(&self) -> Option { @@ -495,7 +508,7 @@ mod test { #[tokio::test] async fn get_aggregate() { - let _aggs_mock = mock("GET", "/v2/aggs/ticker/AAPL/range/1/day/1614556800000/1614643199999") + let _aggs_mock = mock("GET", "/v2/aggs/ticker/AAPL/range/1/day/1614574800000/1614661199999") .match_query(Matcher::AllOf(vec![ Matcher::UrlEncoded("apiKey".into(), "TOKEN".into()), Matcher::UrlEncoded("unadjusted".into(), "false".into()), @@ -509,8 +522,8 @@ mod test { let client = client_with_url(&url, "TOKEN"); let req = GetAggregate::new( "AAPL", - Utc.from_utc_datetime(&NaiveDate::from_ymd(2021, 3, 1).and_hms(0, 0, 0)), - Utc.from_utc_datetime(&NaiveDate::from_ymd(2021, 3, 1).and_hms(0, 0, 0)), + NaiveDate::from_ymd(2021, 3, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd(2021, 3, 1).and_hms(0, 0, 0), ); client.send(&req).await.unwrap(); } @@ -533,7 +546,7 @@ mod test { // use futures::StreamExt; // let _m = mock("GET", "/v2/ticks/stocks/nbbo/AAPL/2021-03-01") // .match_query(Matcher::UrlEncoded("apiKey".into(), "TOKEN".into())) - // .with_body(r#"{"ticker":"AAPL","success":true,"results_count":2,"db_latency":43,"results":[{"t":1517562000065700400,"y":1517562000065321200,"q":2060,"c":[1],"z":3,"p":102.7,"s":60,"x":11,"P":0,"S":0,"X":0}]}"#).create(); + // .with_body(r#"{"ticker":"AAPL","success":true,"results_count":2,"db_latency":43,"results":[{"t":1517562000065700400,"y":1517562000065321200,"q":2060,"c":[1],"z":3,"p":102.7,"s":60,"x":11,"P":0,"S":0,"X":0},{"t":1517562000065700400,"y":1517562000065321200,"q":2060,"c":[1],"z":3,"p":102.7,"s":60,"x":11,"P":0,"S":0,"X":0}]}"#).create(); // let url = mockito::server_url(); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 34128cd..3725a6c 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -23,8 +23,7 @@ impl + Unpin> Stream for WebSocket { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - if !self.buffer.is_empty() { - let message = self.buffer.pop_front().expect("Guaranteed to be non-empty"); + if let Some(message) = self.buffer.pop_front() { return Poll::Ready(Some(Ok(message))); } match ready!(Pin::new(&mut self.inner).poll_next(cx)) {