Add calendar

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2024-02-17 20:36:02 +00:00
parent 152a0b4682
commit 4f73058792
7 changed files with 117 additions and 1 deletions

11
Cargo.lock generated
View File

@@ -1482,6 +1482,15 @@ dependencies = [
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "number_prefix"
version = "0.4.0"
@@ -2394,7 +2403,9 @@ checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",

View File

@@ -46,9 +46,11 @@ uuid = { version = "1.6.1", features = [
] }
time = { version = "0.3.31", features = [
"serde",
"serde-well-known",
"serde-human-readable",
"formatting",
"macros",
"serde-well-known",
"local-offset",
] }
backoff = { version = "0.4.0", features = [
"tokio",

View File

@@ -0,0 +1,55 @@
use crate::{config::ALPACA_API_URL, types::alpaca::api::outgoing, utils::de};
use backoff::{future::retry_notify, ExponentialBackoff};
use governor::DefaultDirectRateLimiter;
use log::warn;
use reqwest::{Client, Error};
use serde::Deserialize;
use std::time::Duration;
use time::{Date, Time};
#[derive(Deserialize)]
pub struct Calendar {
pub date: Date,
#[serde(deserialize_with = "de::human_time_hh_mm")]
pub open: Time,
#[serde(deserialize_with = "de::human_time_hh_mm")]
pub close: Time,
pub settlement_date: Date,
}
pub async fn get(
alpaca_client: &Client,
alpaca_rate_limiter: &DefaultDirectRateLimiter,
query: &outgoing::calendar::Calendar,
backoff: Option<ExponentialBackoff>,
) -> Result<Vec<Calendar>, Error> {
retry_notify(
backoff.unwrap_or_default(),
|| async {
alpaca_rate_limiter.until_ready().await;
alpaca_client
.get(&format!("{}/calendar", *ALPACA_API_URL))
.query(query)
.send()
.await?
.error_for_status()
.map_err(|e| match e.status() {
Some(reqwest::StatusCode::BAD_REQUEST | reqwest::StatusCode::FORBIDDEN) => {
backoff::Error::Permanent(e)
}
_ => e.into(),
})?
.json::<Vec<Calendar>>()
.await
.map_err(backoff::Error::Permanent)
},
|e, duration: Duration| {
warn!(
"Failed to get calendar, will retry in {} seconds: {}",
duration.as_secs(),
e
);
},
)
.await
}

View File

@@ -1,6 +1,7 @@
pub mod account;
pub mod asset;
pub mod bar;
pub mod calendar;
pub mod clock;
pub mod news;
pub mod order;

View File

@@ -0,0 +1,20 @@
use serde::Serialize;
use time::OffsetDateTime;
#[derive(Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[allow(dead_code)]
pub enum DateType {
Trading,
Settlement,
}
#[derive(Serialize)]
pub struct Calendar {
#[serde(with = "time::serde::rfc3339")]
pub start: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub end: OffsetDateTime,
#[serde(rename = "date")]
pub date_type: DateType,
}

View File

@@ -1,3 +1,4 @@
pub mod bar;
pub mod calendar;
pub mod news;
pub mod order;

View File

@@ -5,9 +5,11 @@ use serde::{
Deserializer,
};
use std::fmt;
use time::{format_description::OwnedFormatItem, macros::format_description, Time};
lazy_static! {
static ref RE_SLASH: Regex = Regex::new(r"^(.+)(BTC|USD.?)$").unwrap();
static ref FMT_HH_MM: OwnedFormatItem = format_description!("[hour]:[minute]").into();
}
fn add_slash(pair: &str) -> String {
@@ -75,3 +77,27 @@ where
deserializer.deserialize_seq(VecStringVisitor)
}
pub fn human_time_hh_mm<'de, D>(deserializer: D) -> Result<Time, D::Error>
where
D: Deserializer<'de>,
{
struct TimeVisitor;
impl<'de> Visitor<'de> for TimeVisitor {
type Value = time::Time;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string in the format HH:MM")
}
fn visit_str<E>(self, time: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Time::parse(time, &FMT_HH_MM).map_err(|e| de::Error::custom(e.to_string()))
}
}
deserializer.deserialize_str(TimeVisitor)
}