use std::collections::HashMap;
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use crate::auth::Credentials;
use crate::config::BaseUrl;
use crate::data::corporate_actions::CorporateActions;
use crate::data::enums::CryptoFeed;
use crate::data::models::{
Bar, BarSet, MostActives, Movers, News, NewsSet, OptionsSnapshot, Orderbook, Quote, QuoteSet,
Snapshot, Trade, TradeSet, WithSymbol,
};
use crate::data::pagination::{MarketDataRequest, get_marketdata};
use crate::data::requests::{
CorporateActionsRequest, CryptoBarsRequest, CryptoLatestRequest, CryptoSnapshotRequest,
MarketMoversRequest, MostActivesRequest, NewsRequest, OptionBarsRequest, OptionChainRequest,
OptionLatestRequest, OptionSnapshotRequest, StockBarsRequest, StockLatestRequest,
StockSnapshotRequest, StockTimeseriesRequest, TimeseriesRequest,
};
use crate::error::{Error, Result};
use crate::rest::{Empty, RestClient, RestConfig};
fn into_sets<T>(merged: Map<String, Value>) -> Result<HashMap<String, Vec<T>>>
where
T: DeserializeOwned + WithSymbol,
{
let mut sets = HashMap::with_capacity(merged.len());
for (symbol, value) in merged {
let mut records: Vec<T> =
serde_json::from_value(value).map_err(|source| Error::Decode {
path: symbol.clone(),
body: String::new(),
source,
})?;
for record in &mut records {
record.set_symbol(&symbol);
}
sets.insert(symbol, records);
}
Ok(sets)
}
fn into_latest<T>(merged: Map<String, Value>) -> Result<HashMap<String, T>>
where
T: DeserializeOwned + WithSymbol,
{
let mut latest = HashMap::with_capacity(merged.len());
for (symbol, value) in merged {
let mut record: T = serde_json::from_value(value).map_err(|source| Error::Decode {
path: symbol.clone(),
body: String::new(),
source,
})?;
record.set_symbol(&symbol);
latest.insert(symbol, record);
}
Ok(latest)
}
#[derive(Debug, Clone)]
pub struct StockHistoricalDataClient {
rest: RestClient,
}
impl StockHistoricalDataClient {
pub fn new(credentials: &Credentials) -> Result<Self> {
Self::with_config(
credentials,
RestConfig::from(BaseUrl::Data).api_version("v2"),
)
}
pub fn with_config(credentials: &Credentials, config: RestConfig) -> Result<Self> {
Ok(Self {
rest: RestClient::new(credentials, config)?,
})
}
#[must_use]
pub fn rest(&self) -> &RestClient {
&self.rest
}
pub async fn get_stock_bars(&self, request: &StockBarsRequest) -> Result<BarSet> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged("/stocks/bars"),
request,
)
.await?;
into_sets(merged)
}
pub async fn get_stock_quotes(&self, request: &StockTimeseriesRequest) -> Result<QuoteSet> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged("/stocks/quotes"),
request,
)
.await?;
into_sets(merged)
}
pub async fn get_stock_trades(&self, request: &StockTimeseriesRequest) -> Result<TradeSet> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged("/stocks/trades"),
request,
)
.await?;
into_sets(merged)
}
pub async fn get_stock_latest_trade(
&self,
request: &StockLatestRequest,
) -> Result<HashMap<String, Trade>> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::latest("/stocks/trades/latest"),
request,
)
.await?;
into_latest(merged)
}
pub async fn get_stock_latest_quote(
&self,
request: &StockLatestRequest,
) -> Result<HashMap<String, Quote>> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::latest("/stocks/quotes/latest"),
request,
)
.await?;
into_latest(merged)
}
pub async fn get_stock_latest_bar(
&self,
request: &StockLatestRequest,
) -> Result<HashMap<String, Bar>> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::latest("/stocks/bars/latest"),
request,
)
.await?;
into_latest(merged)
}
pub async fn get_stock_snapshot(
&self,
request: &StockSnapshotRequest,
) -> Result<HashMap<String, Snapshot>> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::latest("/stocks/snapshots").whole_body(),
request,
)
.await?;
into_latest(merged)
}
}
#[derive(Debug, Clone)]
pub struct CryptoHistoricalDataClient {
rest: RestClient,
}
impl CryptoHistoricalDataClient {
pub fn new() -> Result<Self> {
Ok(Self {
rest: RestClient::unauthenticated(
RestConfig::from(BaseUrl::Data).api_version("v1beta3"),
)?,
})
}
pub fn with_credentials(credentials: &Credentials) -> Result<Self> {
Self::with_config(
Some(credentials),
RestConfig::from(BaseUrl::Data).api_version("v1beta3"),
)
}
pub fn with_config(credentials: Option<&Credentials>, config: RestConfig) -> Result<Self> {
let rest = match credentials {
Some(credentials) => RestClient::new(credentials, config)?,
None => RestClient::unauthenticated(config)?,
};
Ok(Self { rest })
}
#[must_use]
pub fn rest(&self) -> &RestClient {
&self.rest
}
pub async fn get_crypto_bars(
&self,
request: &CryptoBarsRequest,
feed: CryptoFeed,
) -> Result<BarSet> {
let path = format!("/crypto/{feed}/bars");
let merged = get_marketdata(&self.rest, &MarketDataRequest::paged(&path), request).await?;
into_sets(merged)
}
pub async fn get_crypto_quotes(
&self,
request: &TimeseriesRequest,
feed: CryptoFeed,
) -> Result<QuoteSet> {
let path = format!("/crypto/{feed}/quotes");
let merged = get_marketdata(&self.rest, &MarketDataRequest::paged(&path), request).await?;
into_sets(merged)
}
pub async fn get_crypto_trades(
&self,
request: &TimeseriesRequest,
feed: CryptoFeed,
) -> Result<TradeSet> {
let path = format!("/crypto/{feed}/trades");
let merged = get_marketdata(&self.rest, &MarketDataRequest::paged(&path), request).await?;
into_sets(merged)
}
pub async fn get_crypto_latest_trade(
&self,
request: &CryptoLatestRequest,
feed: CryptoFeed,
) -> Result<HashMap<String, Trade>> {
let path = format!("/crypto/{feed}/latest/trades");
let merged = get_marketdata(&self.rest, &MarketDataRequest::latest(&path), request).await?;
into_latest(merged)
}
pub async fn get_crypto_latest_quote(
&self,
request: &CryptoLatestRequest,
feed: CryptoFeed,
) -> Result<HashMap<String, Quote>> {
let path = format!("/crypto/{feed}/latest/quotes");
let merged = get_marketdata(&self.rest, &MarketDataRequest::latest(&path), request).await?;
into_latest(merged)
}
pub async fn get_crypto_latest_bar(
&self,
request: &CryptoLatestRequest,
feed: CryptoFeed,
) -> Result<HashMap<String, Bar>> {
let path = format!("/crypto/{feed}/latest/bars");
let merged = get_marketdata(&self.rest, &MarketDataRequest::latest(&path), request).await?;
into_latest(merged)
}
pub async fn get_crypto_latest_orderbook(
&self,
request: &CryptoLatestRequest,
feed: CryptoFeed,
) -> Result<HashMap<String, Orderbook>> {
let path = format!("/crypto/{feed}/latest/orderbooks");
let merged = get_marketdata(&self.rest, &MarketDataRequest::latest(&path), request).await?;
into_latest(merged)
}
pub async fn get_crypto_snapshot(
&self,
request: &CryptoSnapshotRequest,
feed: CryptoFeed,
) -> Result<HashMap<String, Snapshot>> {
let path = format!("/crypto/{feed}/snapshots");
let merged = get_marketdata(&self.rest, &MarketDataRequest::latest(&path), request).await?;
into_latest(merged)
}
}
#[derive(Debug, Clone)]
pub struct OptionHistoricalDataClient {
rest: RestClient,
}
impl OptionHistoricalDataClient {
pub fn new(credentials: &Credentials) -> Result<Self> {
Self::with_config(
credentials,
RestConfig::from(BaseUrl::Data).api_version("v1beta1"),
)
}
pub fn with_config(credentials: &Credentials, config: RestConfig) -> Result<Self> {
Ok(Self {
rest: RestClient::new(credentials, config)?,
})
}
#[must_use]
pub fn rest(&self) -> &RestClient {
&self.rest
}
pub async fn get_option_bars(&self, request: &OptionBarsRequest) -> Result<BarSet> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged("/options/bars"),
request,
)
.await?;
into_sets(merged)
}
pub async fn get_option_trades(&self, request: &TimeseriesRequest) -> Result<TradeSet> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged("/options/trades"),
request,
)
.await?;
into_sets(merged)
}
pub async fn get_option_latest_quote(
&self,
request: &OptionLatestRequest,
) -> Result<HashMap<String, Quote>> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::latest("/options/quotes/latest"),
request,
)
.await?;
into_latest(merged)
}
pub async fn get_option_latest_trade(
&self,
request: &OptionLatestRequest,
) -> Result<HashMap<String, Trade>> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::latest("/options/trades/latest"),
request,
)
.await?;
into_latest(merged)
}
pub async fn get_option_snapshot(
&self,
request: &OptionSnapshotRequest,
) -> Result<HashMap<String, OptionsSnapshot>> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged_with_limit("/options/snapshots", 1000),
request,
)
.await?;
into_latest(merged)
}
pub async fn get_option_chain(
&self,
request: &OptionChainRequest,
) -> Result<HashMap<String, OptionsSnapshot>> {
let path = format!("/options/snapshots/{}", request.underlying_symbol);
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged_with_limit(&path, 1000),
request,
)
.await?;
into_latest(merged)
}
pub async fn get_option_exchange_codes(&self) -> Result<HashMap<String, String>> {
self.rest.get("/options/meta/exchanges", &Empty).await
}
}
#[derive(Debug, Clone)]
pub struct NewsClient {
rest: RestClient,
}
impl NewsClient {
pub fn new(credentials: &Credentials) -> Result<Self> {
Self::with_config(
credentials,
RestConfig::from(BaseUrl::Data).api_version("v1beta1"),
)
}
pub fn with_config(credentials: &Credentials, config: RestConfig) -> Result<Self> {
Ok(Self {
rest: RestClient::new(credentials, config)?,
})
}
#[must_use]
pub fn rest(&self) -> &RestClient {
&self.rest
}
pub async fn get_news(&self, request: &NewsRequest) -> Result<NewsSet> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged_with_limit("/news", 50),
request,
)
.await?;
let articles: Vec<News> = merged
.get("news")
.cloned()
.map(serde_json::from_value)
.transpose()
.map_err(|source| Error::Decode {
path: "/news".to_owned(),
body: String::new(),
source,
})?
.unwrap_or_default();
Ok(NewsSet {
news: articles,
next_page_token: None,
})
}
}
#[derive(Debug, Clone)]
pub struct ScreenerClient {
rest: RestClient,
}
impl ScreenerClient {
pub fn new(credentials: &Credentials) -> Result<Self> {
Self::with_config(
credentials,
RestConfig::from(BaseUrl::Data).api_version("v1beta1"),
)
}
pub fn with_config(credentials: &Credentials, config: RestConfig) -> Result<Self> {
Ok(Self {
rest: RestClient::new(credentials, config)?,
})
}
#[must_use]
pub fn rest(&self) -> &RestClient {
&self.rest
}
pub async fn get_most_actives(&self, request: &MostActivesRequest) -> Result<MostActives> {
self.rest
.get("/screener/stocks/most-actives", request)
.await
}
pub async fn get_market_movers(&self, request: &MarketMoversRequest) -> Result<Movers> {
let path = format!("/screener/{}/movers", request.market_type.as_str());
self.rest.get(&path, request).await
}
}
#[derive(Debug, Clone)]
pub struct CorporateActionsClient {
rest: RestClient,
}
impl CorporateActionsClient {
pub fn new(credentials: &Credentials) -> Result<Self> {
Self::with_config(
credentials,
RestConfig::from(BaseUrl::Data).api_version("v1"),
)
}
pub fn with_config(credentials: &Credentials, config: RestConfig) -> Result<Self> {
Ok(Self {
rest: RestClient::new(credentials, config)?,
})
}
#[must_use]
pub fn rest(&self) -> &RestClient {
&self.rest
}
pub async fn get_corporate_actions(
&self,
request: &CorporateActionsRequest,
) -> Result<CorporateActions> {
let merged = get_marketdata(
&self.rest,
&MarketDataRequest::paged_with_limit("/corporate-actions", 1000),
request,
)
.await?;
serde_json::from_value(Value::Object(merged)).map_err(|source| Error::Decode {
path: "/corporate-actions".to_owned(),
body: String::new(),
source,
})
}
}