use crate::types::{Balance, Candle, Interval, Market, Order, OrderBook, Ticker, Trade};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Feed {
Trades,
OrderBook,
Ticker,
Candles(Interval),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Subscription {
markets: Vec<Market>,
feeds: Vec<Feed>,
}
impl Subscription {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn market(mut self, market: Market) -> Self {
if !self.markets.contains(&market) {
self.markets.push(market);
}
self
}
#[must_use]
pub fn markets_iter(mut self, markets: impl IntoIterator<Item = Market>) -> Self {
for market in markets {
self = self.market(market);
}
self
}
#[must_use]
pub fn feed(mut self, feed: Feed) -> Self {
if !self.feeds.contains(&feed) {
self.feeds.push(feed);
}
self
}
pub fn markets(&self) -> &[Market] {
&self.markets
}
pub fn feeds(&self) -> &[Feed] {
&self.feeds
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum MarketEvent {
Trade(Trade),
OrderBook(OrderBook),
Ticker(Ticker),
Candle(Candle),
Reconnected,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccountEvent {
Balance(Balance),
Order(Order),
Reconnected,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamConfig {
pub max_reconnect_attempts: Option<u32>,
pub initial_reconnect_delay_ms: u64,
pub max_reconnect_delay_ms: u64,
pub idle_timeout_ms: u64,
pub buffer_size: usize,
pub overflow: Overflow,
}
impl Default for StreamConfig {
fn default() -> Self {
Self {
max_reconnect_attempts: None,
initial_reconnect_delay_ms: 1_000,
max_reconnect_delay_ms: 30_000,
idle_timeout_ms: 30_000,
buffer_size: 4_096,
overflow: Overflow::Backpressure,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Overflow {
Backpressure,
DropNewest,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Exchange;
#[test]
fn duplicate_markets_and_feeds_collapse() {
let market = Market::spot(Exchange::Upbit, "BTC", "KRW");
let subscription = Subscription::new()
.market(market.clone())
.market(market)
.feed(Feed::Trades)
.feed(Feed::Trades);
assert_eq!(subscription.markets().len(), 1);
assert_eq!(subscription.feeds().len(), 1);
}
#[test]
fn candle_feeds_at_different_intervals_are_different_feeds() {
let subscription = Subscription::new()
.feed(Feed::Candles(Interval::Min1))
.feed(Feed::Candles(Interval::Hour1));
assert_eq!(subscription.feeds().len(), 2);
}
#[test]
fn insertion_order_is_preserved() {
let btc = Market::spot(Exchange::Upbit, "BTC", "KRW");
let eth = Market::spot(Exchange::Upbit, "ETH", "KRW");
let subscription = Subscription::new()
.markets_iter([btc.clone(), eth.clone()])
.feed(Feed::Ticker)
.feed(Feed::Trades);
assert_eq!(subscription.markets(), [btc, eth]);
assert_eq!(subscription.feeds(), [Feed::Ticker, Feed::Trades]);
}
#[test]
fn defaults_reconnect_forever_and_lose_nothing() {
let config = StreamConfig::default();
assert_eq!(config.max_reconnect_attempts, None);
assert_eq!(config.overflow, Overflow::Backpressure);
}
}