mod parse;
mod wire;
use crate::client::Network;
use crate::live::market::{Resolution, UpDownResolution};
use crate::utils::{AccountIndex, MarketId};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Subscription {
OrderBook {
market: MarketId,
},
Ticker {
market: MarketId,
},
MarketStats {
market: Option<MarketId>,
},
SpotMarketStats {
market: Option<MarketId>,
},
BoMarketStats {
market: Option<MarketId>,
},
Trade {
market: MarketId,
},
Candle {
market: MarketId,
resolution: Resolution,
},
MarkPriceCandle {
market: MarketId,
resolution: Resolution,
},
UpDownCandle {
symbol: String,
duration: String,
resolution: UpDownResolution,
},
Height,
AccountAll {
account: AccountIndex,
},
AccountMarket {
market: MarketId,
account: AccountIndex,
},
UserStats {
account: AccountIndex,
},
AccountTx {
account: AccountIndex,
},
AccountAllOrders {
account: AccountIndex,
},
PoolData {
account: AccountIndex,
},
PoolInfo {
account: AccountIndex,
},
Notification {
account: AccountIndex,
},
AccountOrders {
market: MarketId,
account: AccountIndex,
},
AccountAllTrades {
account: AccountIndex,
},
AccountAllPositions {
account: AccountIndex,
},
AccountAllAssets {
account: AccountIndex,
},
AccountSpotAvgEntryPrices {
account: AccountIndex,
},
Rfq,
LivePoints {
account: AccountIndex,
},
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BadSubscription {
#[error("`{0}` is not a channel the venue serves")]
Unknown(String),
#[error("`{kind}` needs its {part}")]
Missing { kind: &'static str, part: &'static str },
#[error("the {part} of `{kind}` must be {expected}")]
Wrong { kind: &'static str, part: &'static str, expected: &'static str },
#[error("`{0}` has more parts than its channel takes")]
TooLong(String),
#[error("`{kind}` is not served on {network}")]
OtherNetwork { kind: &'static str, network: Network },
}
impl Subscription {
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Subscription::OrderBook { .. } => "order_book",
Subscription::Ticker { .. } => "ticker",
Subscription::MarketStats { .. } => "market_stats",
Subscription::SpotMarketStats { .. } => "spot_market_stats",
Subscription::BoMarketStats { .. } => "bo_market_stats",
Subscription::Trade { .. } => "trade",
Subscription::Candle { .. } => "candle",
Subscription::MarkPriceCandle { .. } => "mark_price_candle",
Subscription::UpDownCandle { .. } => "up_down_candle",
Subscription::Height => "height",
Subscription::AccountAll { .. } => "account_all",
Subscription::AccountMarket { .. } => "account_market",
Subscription::UserStats { .. } => "user_stats",
Subscription::AccountTx { .. } => "account_tx",
Subscription::AccountAllOrders { .. } => "account_all_orders",
Subscription::PoolData { .. } => "pool_data",
Subscription::PoolInfo { .. } => "pool_info",
Subscription::Notification { .. } => "notification",
Subscription::AccountOrders { .. } => "account_orders",
Subscription::AccountAllTrades { .. } => "account_all_trades",
Subscription::AccountAllPositions { .. } => "account_all_positions",
Subscription::AccountAllAssets { .. } => "account_all_assets",
Subscription::AccountSpotAvgEntryPrices { .. } => "account_spot_avg_entry_prices",
Subscription::Rfq => "rfq",
Subscription::LivePoints { .. } => "live_points",
}
}
#[must_use]
pub const fn needs_auth(&self) -> bool {
matches!(
self,
Subscription::AccountMarket { .. }
| Subscription::AccountTx { .. }
| Subscription::AccountAllOrders { .. }
| Subscription::Notification { .. }
| Subscription::AccountOrders { .. }
| Subscription::AccountAllAssets { .. }
| Subscription::AccountSpotAvgEntryPrices { .. }
| Subscription::Rfq
| Subscription::LivePoints { .. }
)
}
#[must_use]
pub const fn takes_auth(&self) -> bool {
self.needs_auth() || matches!(self, Subscription::PoolData { .. } | Subscription::PoolInfo { .. })
}
#[must_use]
pub const fn account(&self) -> Option<AccountIndex> {
match self {
Subscription::AccountAll { account }
| Subscription::AccountMarket { account, .. }
| Subscription::UserStats { account }
| Subscription::AccountTx { account }
| Subscription::AccountAllOrders { account }
| Subscription::PoolData { account }
| Subscription::PoolInfo { account }
| Subscription::Notification { account }
| Subscription::AccountOrders { account, .. }
| Subscription::AccountAllTrades { account }
| Subscription::AccountAllPositions { account }
| Subscription::AccountAllAssets { account }
| Subscription::AccountSpotAvgEntryPrices { account }
| Subscription::LivePoints { account } => Some(*account),
_ => None,
}
}
#[must_use]
pub const fn market(&self) -> Option<MarketId> {
match self {
Subscription::OrderBook { market }
| Subscription::Ticker { market }
| Subscription::Trade { market }
| Subscription::Candle { market, .. }
| Subscription::MarkPriceCandle { market, .. }
| Subscription::AccountMarket { market, .. }
| Subscription::AccountOrders { market, .. } => Some(*market),
Subscription::MarketStats { market }
| Subscription::SpotMarketStats { market }
| Subscription::BoMarketStats { market } => *market,
_ => None,
}
}
#[must_use]
pub const fn serves(&self, network: Network) -> bool {
match self {
Subscription::BoMarketStats { .. } | Subscription::UpDownCandle { .. } => network.has_binary_options(),
Subscription::LivePoints { .. } => matches!(network, Network::Robinhood),
_ => true,
}
}
pub fn check(&self, network: Network) -> Result<(), BadSubscription> {
if !self.serves(network) {
return Err(BadSubscription::OtherNetwork { kind: self.kind(), network });
}
if let Subscription::UpDownCandle { symbol, duration, .. } = self {
let plain = |part: &str| !part.is_empty() && part.bytes().all(|byte| byte.is_ascii_alphanumeric());
if !plain(symbol) {
return Err(BadSubscription::Wrong { kind: self.kind(), part: "symbol", expected: "one plain word" });
}
if !plain(duration) {
return Err(BadSubscription::Wrong { kind: self.kind(), part: "duration", expected: "one plain word" });
}
}
Ok(())
}
}
#[cfg(test)]
#[path = "tests/subscription.rs"]
mod tests;