use std::time::Duration;
use futures_util::Stream;
use crate::auth::Credentials;
use crate::config::BaseUrl;
use crate::data::enums::{CryptoFeed, DataFeed, OptionsFeed};
use crate::data::live::{Channel, DataStream, StreamConfig, StreamMessage, SubscriptionSet};
use crate::error::{Error, Result};
fn known_feed<T>(feed: &T) -> Result<&str>
where
T: std::fmt::Display,
T: AsUnknown,
{
if feed.is_unknown() {
return Err(Error::InvalidRequest(format!(
"unknown feed {feed}: this crate has no live stream endpoint for it"
)));
}
Ok(feed.wire())
}
trait AsUnknown {
fn is_unknown(&self) -> bool;
fn wire(&self) -> &str;
}
macro_rules! as_unknown {
($($ty:ident),+ $(,)?) => {
$(
impl AsUnknown for $ty {
fn is_unknown(&self) -> bool {
matches!(self, $ty::Unknown(_))
}
fn wire(&self) -> &str {
self.as_str()
}
}
)+
};
}
as_unknown!(CryptoFeed, OptionsFeed);
macro_rules! subscriptions {
($( $subscribe:ident / $unsubscribe:ident => $channel:expr, $what:literal ; )+) => {
$(
#[doc = concat!("Subscribes to ", $what, " for `symbols`.")]
pub fn $subscribe<I, S>(&mut self, symbols: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner.subscribe($channel, symbols);
self
}
#[doc = concat!("Stops receiving ", $what, " for `symbols`.")]
pub fn $unsubscribe<I, S>(&mut self, symbols: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.inner.unsubscribe($channel, symbols);
self
}
)+
};
}
macro_rules! common {
() => {
#[must_use]
pub fn subscriptions(&self) -> &SubscriptionSet {
self.inner.subscriptions()
}
pub fn set_data_timeout(&mut self, timeout: Duration) -> Result<&mut Self> {
self.inner.config_mut().set_data_timeout(timeout)?;
Ok(self)
}
pub fn set_backoff(&mut self, min: Duration, max: Duration) -> Result<&mut Self> {
self.inner.config_mut().set_backoff(min, max)?;
Ok(self)
}
pub fn set_stable_session(&mut self, after: Duration) -> Result<&mut Self> {
self.inner.config_mut().set_stable_session(after)?;
Ok(self)
}
pub fn run(self) -> impl Stream<Item = Result<StreamMessage>> {
self.inner.run()
}
};
}
pub struct StockDataStream {
inner: DataStream,
}
impl StockDataStream {
pub fn new(credentials: Credentials, feed: DataFeed) -> Result<Self> {
if !matches!(feed, DataFeed::Iex | DataFeed::Sip) {
return Err(Error::InvalidRequest(format!(
"only the iex and sip feeds have a live stock stream, got {feed}"
)));
}
let endpoint = format!("{}/v2/{feed}", BaseUrl::MarketDataStream);
Ok(Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
})
}
#[must_use]
pub fn with_endpoint(credentials: Credentials, endpoint: impl Into<String>) -> Self {
Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
}
}
subscriptions! {
subscribe_trades / unsubscribe_trades => Channel::Trades, "trades";
subscribe_quotes / unsubscribe_quotes => Channel::Quotes, "quotes";
subscribe_bars / unsubscribe_bars => Channel::Bars, "minute bars";
subscribe_updated_bars / unsubscribe_updated_bars => Channel::UpdatedBars, "updated bars";
subscribe_daily_bars / unsubscribe_daily_bars => Channel::DailyBars, "daily bars";
subscribe_trading_statuses / unsubscribe_trading_statuses => Channel::Statuses, "trading statuses";
subscribe_lulds / unsubscribe_lulds => Channel::Lulds, "limit up / limit down bands";
}
pub fn register_trade_corrections<I, S>(&mut self, symbols: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner.subscribe(Channel::Corrections, symbols);
self
}
pub fn register_trade_cancels<I, S>(&mut self, symbols: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner.subscribe(Channel::CancelErrors, symbols);
self
}
common!();
}
pub struct CryptoDataStream {
inner: DataStream,
}
impl CryptoDataStream {
pub fn new(credentials: Credentials, feed: CryptoFeed) -> Result<Self> {
let endpoint = format!(
"{}/v1beta3/crypto/{}",
BaseUrl::MarketDataStream,
known_feed(&feed)?
);
Ok(Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
})
}
#[must_use]
pub fn with_endpoint(credentials: Credentials, endpoint: impl Into<String>) -> Self {
Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
}
}
subscriptions! {
subscribe_trades / unsubscribe_trades => Channel::Trades, "trades";
subscribe_quotes / unsubscribe_quotes => Channel::Quotes, "quotes";
subscribe_bars / unsubscribe_bars => Channel::Bars, "minute bars";
subscribe_updated_bars / unsubscribe_updated_bars => Channel::UpdatedBars, "updated bars";
subscribe_daily_bars / unsubscribe_daily_bars => Channel::DailyBars, "daily bars";
subscribe_orderbooks / unsubscribe_orderbooks => Channel::Orderbooks, "orderbook updates";
}
common!();
}
pub struct OptionDataStream {
inner: DataStream,
}
impl OptionDataStream {
pub fn new(credentials: Credentials, feed: OptionsFeed) -> Result<Self> {
let endpoint = format!(
"{}/v1beta1/{}",
BaseUrl::MarketDataStream,
known_feed(&feed)?
);
Ok(Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
})
}
#[must_use]
pub fn with_endpoint(credentials: Credentials, endpoint: impl Into<String>) -> Self {
Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
}
}
subscriptions! {
subscribe_trades / unsubscribe_trades => Channel::Trades, "trades";
subscribe_quotes / unsubscribe_quotes => Channel::Quotes, "quotes";
}
common!();
}
pub struct NewsDataStream {
inner: DataStream,
}
impl NewsDataStream {
#[must_use]
pub fn new(credentials: Credentials) -> Self {
let endpoint = format!("{}/v1beta1/news", BaseUrl::MarketDataStream);
Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
}
}
#[must_use]
pub fn with_endpoint(credentials: Credentials, endpoint: impl Into<String>) -> Self {
Self {
inner: DataStream::new(credentials, StreamConfig::new(endpoint)),
}
}
subscriptions! {
subscribe_news / unsubscribe_news => Channel::News, "news articles";
}
common!();
}