#![cfg(feature = "data")]
use std::time::Duration;
use crate::common::credentials;
use alpaca_sdk::Credentials;
use alpaca_sdk::data::{
Channel, CryptoDataStream, CryptoFeed, DataFeed, NewsDataStream, OptionDataStream, OptionsFeed,
StockDataStream,
};
#[test]
fn a_stock_stream_offers_every_equity_channel() {
let mut stream = StockDataStream::new(credentials(), DataFeed::Iex).unwrap();
stream.subscribe_trades(["AAPL"]);
stream.subscribe_quotes(["AAPL"]);
stream.subscribe_bars(["AAPL"]);
stream.subscribe_updated_bars(["AAPL"]);
stream.subscribe_daily_bars(["AAPL"]);
stream.subscribe_trading_statuses(["AAPL"]);
stream.subscribe_lulds(["AAPL"]);
let set = stream.subscriptions();
for channel in [
Channel::Trades,
Channel::Quotes,
Channel::Bars,
Channel::UpdatedBars,
Channel::DailyBars,
Channel::Statuses,
Channel::Lulds,
] {
assert_eq!(set.symbols(channel), ["AAPL"], "{channel:?}");
}
}
#[test]
fn unsubscribing_removes_only_the_symbols_named() {
let mut stream = StockDataStream::new(credentials(), DataFeed::Sip).unwrap();
stream.subscribe_trades(["AAPL", "MSFT", "SPY"]);
stream.unsubscribe_trades(["MSFT"]);
assert_eq!(
stream.subscriptions().symbols(Channel::Trades),
["AAPL", "SPY"]
);
stream.unsubscribe_quotes(["AAPL"]);
assert!(stream.subscriptions().symbols(Channel::Quotes).is_empty());
}
#[test]
fn registering_for_corrections_does_not_count_as_a_subscription() {
let mut stream = StockDataStream::new(credentials(), DataFeed::Iex).unwrap();
stream.register_trade_corrections(["AAPL"]);
stream.register_trade_cancels(["AAPL"]);
assert_eq!(
stream.subscriptions().symbols(Channel::Corrections),
["AAPL"]
);
assert_eq!(
stream.subscriptions().symbols(Channel::CancelErrors),
["AAPL"]
);
assert!(
stream.subscriptions().is_empty(),
"neither channel is subscribable, so nothing is subscribed yet"
);
stream.subscribe_trades(["AAPL"]);
assert!(!stream.subscriptions().is_empty());
}
#[test]
fn only_the_two_feeds_that_carry_a_live_stock_stream_are_accepted() {
assert!(StockDataStream::new(credentials(), DataFeed::Iex).is_ok());
assert!(StockDataStream::new(credentials(), DataFeed::Sip).is_ok());
assert!(StockDataStream::new(credentials(), DataFeed::Otc).is_err());
assert!(StockDataStream::new(credentials(), DataFeed::DelayedSip).is_err());
}
#[test]
fn a_crypto_stream_carries_orderbooks_and_no_trading_statuses() {
let mut stream = CryptoDataStream::new(credentials(), CryptoFeed::Us).unwrap();
stream.subscribe_trades(["BTC/USD"]);
stream.subscribe_quotes(["BTC/USD"]);
stream.subscribe_bars(["BTC/USD"]);
stream.subscribe_updated_bars(["BTC/USD"]);
stream.subscribe_daily_bars(["BTC/USD"]);
stream.subscribe_orderbooks(["BTC/USD"]);
let set = stream.subscriptions();
assert_eq!(set.symbols(Channel::Orderbooks), ["BTC/USD"]);
assert_eq!(set.symbols(Channel::Trades), ["BTC/USD"]);
assert!(set.symbols(Channel::Statuses).is_empty());
stream.unsubscribe_orderbooks(["BTC/USD"]);
assert!(
stream
.subscriptions()
.symbols(Channel::Orderbooks)
.is_empty()
);
}
#[test]
fn an_option_stream_carries_trades_and_quotes_only() {
let mut stream = OptionDataStream::new(credentials(), OptionsFeed::Opra).unwrap();
stream.subscribe_trades(["AAPL240119C00150000"]);
stream.subscribe_quotes(["AAPL240119C00150000"]);
let set = stream.subscriptions();
assert_eq!(set.symbols(Channel::Trades), ["AAPL240119C00150000"]);
assert_eq!(set.symbols(Channel::Quotes), ["AAPL240119C00150000"]);
assert!(set.symbols(Channel::Bars).is_empty());
stream.unsubscribe_trades(["AAPL240119C00150000"]);
assert!(stream.subscriptions().symbols(Channel::Trades).is_empty());
}
#[test]
fn a_news_stream_carries_one_channel() {
let mut stream = NewsDataStream::new(credentials());
stream.subscribe_news(["*"]);
assert_eq!(stream.subscriptions().symbols(Channel::News), ["*"]);
stream.unsubscribe_news(["*"]);
assert!(stream.subscriptions().is_empty());
}
#[test]
fn a_staleness_timeout_must_be_positive() {
let mut stream = StockDataStream::new(credentials(), DataFeed::Iex).unwrap();
assert!(stream.set_data_timeout(Duration::from_secs(30)).is_ok());
assert!(stream.set_data_timeout(Duration::ZERO).is_err());
}
#[test]
fn every_stream_can_be_pointed_somewhere_else() {
let endpoint = "ws://127.0.0.1:1";
let mut stock = StockDataStream::with_endpoint(credentials(), endpoint);
stock.subscribe_trades(["AAPL"]);
assert!(!stock.subscriptions().is_empty());
let mut crypto = CryptoDataStream::with_endpoint(credentials(), endpoint);
crypto.subscribe_trades(["BTC/USD"]);
assert!(!crypto.subscriptions().is_empty());
let mut options = OptionDataStream::with_endpoint(credentials(), endpoint);
options.subscribe_quotes(["AAPL240119C00150000"]);
assert!(!options.subscriptions().is_empty());
let mut news = NewsDataStream::with_endpoint(credentials(), endpoint);
news.subscribe_news(["*"]);
assert!(!news.subscriptions().is_empty());
}
#[test]
fn an_unknown_feed_is_refused_rather_than_put_in_the_endpoint() {
let creds = || Credentials::new("key", "secret").unwrap();
let crypto = CryptoDataStream::new(creds(), CryptoFeed::from("../../v2/account"));
assert!(
matches!(crypto, Err(alpaca_sdk::Error::InvalidRequest(_))),
"an unknown crypto feed must not reach the endpoint URL"
);
let options = OptionDataStream::new(creds(), OptionsFeed::from("../../v2/account"));
assert!(
matches!(options, Err(alpaca_sdk::Error::InvalidRequest(_))),
"an unknown options feed must not reach the endpoint URL"
);
assert!(StockDataStream::new(creds(), DataFeed::Otc).is_err());
}
#[test]
fn the_known_feeds_still_construct() {
let creds = || Credentials::new("key", "secret").unwrap();
assert!(CryptoDataStream::new(creds(), CryptoFeed::Us).is_ok());
assert!(OptionDataStream::new(creds(), OptionsFeed::Opra).is_ok());
assert!(OptionDataStream::new(creds(), OptionsFeed::Indicative).is_ok());
assert!(StockDataStream::new(creds(), DataFeed::Iex).is_ok());
assert!(StockDataStream::new(creds(), DataFeed::Sip).is_ok());
}