use crate::indexer::types::{
CandleResponse as CandlesInitialMessageContents, CandleResponseObject as Candle,
HeightResponse as BlockHeightInitialMessageContents,
OrderBookResponseObject as OrdersInitialMessageContents,
OrderResponseObject as OrderMessageObject,
ParentSubaccountResponseObject as ParentSubaccountMessageObject,
PerpetualMarketResponse as MarketsInitialMessageContents,
SubaccountResponseObject as SubaccountMessageObject,
TradeResponse as TradesInitialMessageContents, *,
};
use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc};
use serde::Deserialize;
use serde_json::{json, Value};
use std::collections::HashMap;
use tokio_tungstenite::tungstenite::protocol::Message;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum Subscription {
Subaccounts(Subaccount),
Orders(Ticker),
Trades(Ticker),
Markets,
Candles(Ticker, CandleResolution),
ParentSubaccounts(ParentSubaccount),
BlockHeight,
}
impl Subscription {
pub(crate) fn sub_message(&self, batched: bool) -> Message {
match self {
Self::Subaccounts(ref subacc) => subaccounts::sub_message(subacc, batched),
Self::Markets => markets::sub_message(batched),
Self::Orders(ref ticker) => orders::sub_message(ticker, batched),
Self::Trades(ref ticker) => trades::sub_message(ticker, batched),
Self::Candles(ref ticker, ref res) => candles::sub_message(ticker, res, batched),
Self::ParentSubaccounts(ref subacc) => parent_subaccounts::sub_message(subacc, batched),
Self::BlockHeight => block_height::sub_message(batched),
}
}
pub(crate) fn unsub_message(&self) -> Message {
match self {
Self::Subaccounts(ref subacc) => subaccounts::unsub_message(subacc),
Self::Markets => markets::unsub_message(),
Self::Orders(ref ticker) => orders::unsub_message(ticker),
Self::Trades(ref ticker) => trades::unsub_message(ticker),
Self::Candles(ref ticker, ref res) => candles::unsub_message(ticker, res),
Self::ParentSubaccounts(ref subacc) => parent_subaccounts::unsub_message(subacc),
Self::BlockHeight => block_height::unsub_message(),
}
}
}
struct MessageFormatter {}
impl MessageFormatter {
pub(crate) fn sub_message(channel: &str, fields: Value) -> Message {
let message = json!({
"type": "subscribe",
"channel": channel,
});
Self::message(fields, message)
}
pub(crate) fn unsub_message(channel: &str, fields: Value) -> Message {
let message = json!({
"type": "unsubscribe",
"channel": channel,
});
Self::message(fields, message)
}
fn message(mut message: Value, fields: Value) -> Message {
if let Value::Object(ref mut map) = message {
if let Value::Object(fields) = fields {
map.extend(fields);
}
}
Message::Text(message.to_string().into())
}
}
pub(crate) mod subaccounts {
use super::{json, Message, MessageFormatter, Subaccount};
pub(crate) const CHANNEL: &str = "v4_subaccounts";
pub(crate) fn sub_message(subacc: &Subaccount, batched: bool) -> Message {
let address = &subacc.address;
let number = &subacc.number;
MessageFormatter::sub_message(
CHANNEL,
json!({"id": format!("{address}/{number}"), "batched": batched}),
)
}
pub(crate) fn unsub_message(subacc: &Subaccount) -> Message {
let address = &subacc.address;
let number = &subacc.number;
MessageFormatter::unsub_message(CHANNEL, json!({"id": format!("{address}/{number}")}))
}
}
pub(crate) mod parent_subaccounts {
use super::{json, Message, MessageFormatter, ParentSubaccount};
pub(crate) const CHANNEL: &str = "v4_parent_subaccounts";
pub(crate) fn sub_message(subacc: &ParentSubaccount, batched: bool) -> Message {
let address = &subacc.address;
let number = &subacc.number;
MessageFormatter::sub_message(
CHANNEL,
json!({"id": format!("{address}/{number}"), "batched": batched}),
)
}
pub(crate) fn unsub_message(subacc: &ParentSubaccount) -> Message {
let address = &subacc.address;
let number = &subacc.number;
MessageFormatter::unsub_message(CHANNEL, json!({"id": format!("{address}/{number}")}))
}
}
pub(crate) mod orders {
use super::{json, Message, MessageFormatter, Ticker};
pub(crate) const CHANNEL: &str = "v4_orderbook";
pub(crate) fn sub_message(id: &Ticker, batched: bool) -> Message {
MessageFormatter::sub_message(CHANNEL, json!({"id": id, "batched": batched}))
}
pub(crate) fn unsub_message(id: &Ticker) -> Message {
MessageFormatter::unsub_message(CHANNEL, json!({"id": id}))
}
}
pub(crate) mod trades {
use super::{json, Message, MessageFormatter, Ticker};
pub(crate) const CHANNEL: &str = "v4_trades";
pub(crate) fn sub_message(id: &Ticker, batched: bool) -> Message {
MessageFormatter::sub_message(CHANNEL, json!({"id": id, "batched": batched}))
}
pub(crate) fn unsub_message(id: &Ticker) -> Message {
MessageFormatter::unsub_message(CHANNEL, json!({"id": id}))
}
}
pub(crate) mod markets {
use super::{json, Message, MessageFormatter};
pub const CHANNEL: &str = "v4_markets";
pub(crate) fn sub_message(batched: bool) -> Message {
MessageFormatter::sub_message(CHANNEL, json!({"batched": batched}))
}
pub(crate) fn unsub_message() -> Message {
MessageFormatter::unsub_message(CHANNEL, json!({}))
}
}
pub(crate) mod candles {
use super::{json, CandleResolution, Message, MessageFormatter, Ticker};
pub(crate) const CHANNEL: &str = "v4_candles";
pub(crate) fn sub_message(
id: &Ticker,
resolution: &CandleResolution,
batched: bool,
) -> Message {
let resolution_str = serde_json::to_string(resolution).unwrap_or_default();
let resolution_str = resolution_str.trim_matches('"');
MessageFormatter::sub_message(
CHANNEL,
json!({"id": format!("{id}/{resolution_str}"), "batched": batched}),
)
}
pub(crate) fn unsub_message(id: &Ticker, resolution: &CandleResolution) -> Message {
let resolution_str = serde_json::to_string(resolution).unwrap_or_default();
let resolution_str = resolution_str.trim_matches('"');
MessageFormatter::unsub_message(CHANNEL, json!({"id": format!("{id}/{resolution_str}")}))
}
}
pub(crate) mod block_height {
use super::{json, Message, MessageFormatter};
pub const CHANNEL: &str = "v4_block_height";
pub(crate) fn sub_message(batched: bool) -> Message {
MessageFormatter::sub_message(CHANNEL, json!({"batched": batched}))
}
pub(crate) fn unsub_message() -> Message {
MessageFormatter::unsub_message(CHANNEL, json!({}))
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub(crate) enum WsMessage {
#[serde(rename = "connected")]
Setup(StatusConnectedMessage),
#[serde(rename = "error")]
Error(StatusErrorMessage),
#[serde(rename = "unsubscribed")]
Unsub(StatusUnsubMessage),
#[serde(untagged)]
Data(FeedMessage),
}
#[derive(Debug, Deserialize)]
pub(crate) struct StatusConnectedMessage {
pub(crate) connection_id: String,
#[allow(dead_code)] pub(crate) message_id: u64,
}
#[derive(Debug, Deserialize)]
pub(crate) struct StatusErrorMessage {
pub(crate) message: String,
#[allow(dead_code)] pub(crate) connection_id: String,
#[allow(dead_code)] pub(crate) message_id: u64,
}
#[derive(Debug, Deserialize)]
pub(crate) struct StatusUnsubMessage {
#[allow(dead_code)] pub(crate) connection_id: String,
#[allow(dead_code)] pub(crate) message_id: u64,
pub(crate) channel: String,
pub(crate) id: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "channel")]
pub enum FeedMessage {
#[serde(rename = "v4_subaccounts")]
Subaccounts(SubaccountsMessage),
#[serde(rename = "v4_orderbook")]
Orders(OrdersMessage),
#[serde(rename = "v4_trades")]
Trades(TradesMessage),
#[serde(rename = "v4_markets")]
Markets(MarketsMessage),
#[serde(rename = "v4_candles")]
Candles(CandlesMessage),
#[serde(rename = "v4_parent_subaccounts")]
ParentSubaccounts(ParentSubaccountsMessage),
#[serde(rename = "v4_block_height")]
BlockHeight(BlockHeightMessage),
}
macro_rules! impl_feed_message_try_from {
($target_type:ty, $variant:ident) => {
impl TryFrom<FeedMessage> for $target_type {
type Error = ();
fn try_from(value: FeedMessage) -> Result<Self, Self::Error> {
match value {
FeedMessage::$variant(a) => Ok(a),
_ => Err(()),
}
}
}
};
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum SubaccountsMessage {
#[serde(rename = "subscribed")]
Initial(SubaccountsInitialMessage),
#[serde(untagged)]
Update(SubaccountsUpdateMessage),
}
impl_feed_message_try_from!(SubaccountsMessage, Subaccounts);
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum ParentSubaccountsMessage {
#[serde(rename = "subscribed")]
Initial(ParentSubaccountsInitialMessage),
#[serde(untagged)]
Update(ParentSubaccountsUpdateMessage),
}
impl_feed_message_try_from!(ParentSubaccountsMessage, ParentSubaccounts);
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum TradesMessage {
#[serde(rename = "subscribed")]
Initial(TradesInitialMessage),
#[serde(untagged)]
Update(TradesUpdateMessage),
}
impl_feed_message_try_from!(TradesMessage, Trades);
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum OrdersMessage {
#[serde(rename = "subscribed")]
Initial(OrdersInitialMessage),
#[serde(untagged)]
Update(OrdersUpdateMessage),
}
impl_feed_message_try_from!(OrdersMessage, Orders);
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum MarketsMessage {
#[serde(rename = "subscribed")]
Initial(MarketsInitialMessage),
#[serde(untagged)]
Update(MarketsUpdateMessage),
}
impl_feed_message_try_from!(MarketsMessage, Markets);
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum CandlesMessage {
#[serde(rename = "subscribed")]
Initial(CandlesInitialMessage),
#[serde(untagged)]
Update(CandlesUpdateMessage),
}
impl_feed_message_try_from!(CandlesMessage, Candles);
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum BlockHeightMessage {
#[serde(rename = "subscribed")]
Initial(BlockHeightInitialMessage),
#[serde(untagged)]
Update(BlockHeightUpdateMessage),
}
impl_feed_message_try_from!(BlockHeightMessage, BlockHeight);
impl FeedMessage {
pub(crate) fn subscription(&self) -> Option<Subscription> {
let parse_subacc_id = |id: &str| -> Option<Subaccount> {
let mut id_split = id.split('/');
let address = id_split.next()?.parse().ok()?;
let number_str = id_split.next()?;
let number = serde_json::from_str::<SubaccountNumber>(number_str).ok()?;
Some(Subaccount::new(address, number))
};
let parse_psubacc_id = |id: &str| -> Option<ParentSubaccount> {
let mut id_split = id.split('/');
let address = id_split.next()?.parse().ok()?;
let number_str = id_split.next()?;
let number = serde_json::from_str::<ParentSubaccountNumber>(number_str).ok()?;
Some(ParentSubaccount::new(address, number))
};
let parse_candles_id = |id: &str| -> Option<(Ticker, CandleResolution)> {
let mut id_split = id.split('/');
let ticker = Ticker(id_split.next()?.into());
let resolution_str = format!("\"{}\"", id_split.next()?);
let resolution = serde_json::from_str(&resolution_str).ok()?;
Some((ticker, resolution))
};
match self {
Self::Subaccounts(SubaccountsMessage::Initial(msg)) => {
let subacc = parse_subacc_id(&msg.id)?;
Some(Subscription::Subaccounts(subacc))
}
Self::Subaccounts(SubaccountsMessage::Update(msg)) => {
let subacc = parse_subacc_id(&msg.id)?;
Some(Subscription::Subaccounts(subacc))
}
Self::ParentSubaccounts(ParentSubaccountsMessage::Initial(msg)) => {
let subacc = parse_psubacc_id(&msg.id)?;
Some(Subscription::ParentSubaccounts(subacc))
}
Self::ParentSubaccounts(ParentSubaccountsMessage::Update(msg)) => {
let subacc = parse_psubacc_id(&msg.id)?;
Some(Subscription::ParentSubaccounts(subacc))
}
Self::Orders(OrdersMessage::Initial(msg)) => {
Some(Subscription::Orders(Ticker(msg.id.clone())))
}
Self::Orders(OrdersMessage::Update(msg)) => {
Some(Subscription::Orders(Ticker(msg.id.clone())))
}
Self::Trades(TradesMessage::Initial(msg)) => {
Some(Subscription::Trades(Ticker(msg.id.clone())))
}
Self::Trades(TradesMessage::Update(msg)) => {
Some(Subscription::Trades(Ticker(msg.id.clone())))
}
Self::Markets(MarketsMessage::Update(_)) => Some(Subscription::Markets),
Self::Markets(MarketsMessage::Initial(_)) => Some(Subscription::Markets),
Self::Candles(CandlesMessage::Initial(msg)) => {
let (ticker, resolution) = parse_candles_id(&msg.id)?;
Some(Subscription::Candles(ticker, resolution))
}
Self::Candles(CandlesMessage::Update(msg)) => {
let (ticker, resolution) = parse_candles_id(&msg.id)?;
Some(Subscription::Candles(ticker, resolution))
}
Self::BlockHeight(BlockHeightMessage::Initial(_)) => Some(Subscription::BlockHeight),
Self::BlockHeight(BlockHeightMessage::Update(_)) => Some(Subscription::BlockHeight),
}
}
}
#[derive(Debug, Deserialize)]
pub struct SubaccountsInitialMessage {
pub connection_id: String,
pub contents: SubaccountsInitialMessageContents,
pub id: String,
pub message_id: u64,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct SubaccountsInitialMessageContents {
pub subaccount: SubaccountMessageObject,
pub orders: Vec<OrderMessageObject>,
pub block_height: Height,
}
#[derive(Debug, Deserialize)]
pub struct ParentSubaccountsInitialMessage {
pub connection_id: String,
pub contents: ParentSubaccountsInitialMessageContents,
pub id: String,
pub message_id: u64,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct ParentSubaccountsInitialMessageContents {
pub subaccount: ParentSubaccountMessageObject,
pub orders: Vec<OrderMessageObject>,
pub block_height: Height,
}
#[derive(Debug, Deserialize)]
pub struct OrdersInitialMessage {
pub connection_id: String,
pub contents: OrdersInitialMessageContents,
pub id: String,
pub message_id: u64,
}
#[derive(Debug, Deserialize)]
pub struct TradesInitialMessage {
pub connection_id: String,
pub contents: TradesInitialMessageContents,
pub id: String,
pub message_id: u64,
}
#[derive(Debug, Deserialize)]
pub struct MarketsInitialMessage {
pub connection_id: String,
pub contents: MarketsInitialMessageContents,
pub message_id: u64,
}
#[derive(Debug, Deserialize)]
pub struct CandlesInitialMessage {
pub connection_id: String,
pub contents: CandlesInitialMessageContents,
pub id: String,
pub message_id: u64,
}
#[derive(Debug, Deserialize)]
pub struct BlockHeightInitialMessage {
pub connection_id: String,
pub contents: BlockHeightInitialMessageContents,
pub message_id: u64,
}
macro_rules! generate_contents_deserialize_function {
($fn_name:ident, $result_type:ty) => {
fn $fn_name<'de, D>(deserializer: D) -> Result<Vec<$result_type>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
match value {
Value::Array(arr) => arr
.into_iter()
.map(|v| serde_json::from_value(v))
.collect::<Result<Vec<$result_type>, _>>()
.map_err(serde::de::Error::custom),
Value::Object(obj) => {
let item = serde_json::from_value::<$result_type>(Value::Object(obj.clone()))
.map_err(serde::de::Error::custom)?;
Ok(vec![item])
}
_ => Err(serde::de::Error::custom("Expected array or object")),
}
}
};
}
#[derive(Debug, Deserialize)]
pub struct SubaccountsUpdateMessage {
pub connection_id: String,
#[serde(deserialize_with = "deserialize_subaccounts_contents")]
pub contents: Vec<SubaccountUpdateMessageContents>,
pub id: String,
pub message_id: u64,
pub version: String,
}
generate_contents_deserialize_function!(
deserialize_subaccounts_contents,
SubaccountUpdateMessageContents
);
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct SubaccountUpdateMessageContents {
pub perpetual_positions: Option<Vec<PerpetualPositionSubaccountMessageContents>>,
pub asset_positions: Option<Vec<AssetPositionSubaccountMessageContents>>,
pub orders: Option<Vec<OrderSubaccountMessageContents>>,
pub fills: Option<Vec<FillSubaccountMessageContents>>,
pub transfers: Option<TransferSubaccountMessageContents>,
pub trading_reward: Option<TradingRewardSubaccountMessageContents>,
pub block_height: Option<Height>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct PerpetualPositionSubaccountMessageContents {
pub address: Address,
pub subaccount_number: SubaccountNumber,
pub position_id: String,
pub market: Ticker,
pub side: PositionSide,
pub status: PerpetualPositionStatus,
pub size: Quantity,
pub max_size: Quantity,
pub net_funding: BigDecimal,
pub entry_price: Price,
pub exit_price: Option<Price>,
pub sum_open: BigDecimal,
pub sum_close: BigDecimal,
pub realized_pnl: Option<BigDecimal>,
pub unrealized_pnl: Option<BigDecimal>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct AssetPositionSubaccountMessageContents {
pub address: Address,
pub subaccount_number: SubaccountNumber,
pub position_id: String,
pub asset_id: AssetId,
pub symbol: Symbol,
pub side: PositionSide,
pub size: Quantity,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct OrderSubaccountMessageContents {
pub id: String,
pub subaccount_id: SubaccountId,
pub client_id: ClientId,
pub clob_pair_id: Option<ClobPairId>,
pub side: Option<OrderSide>,
pub size: Option<Quantity>,
pub ticker: Option<Ticker>,
pub price: Option<Price>,
#[serde(rename = "type")]
pub order_type: Option<OrderType>,
pub time_in_force: Option<ApiTimeInForce>,
pub post_only: Option<bool>,
pub reduce_only: Option<bool>,
pub status: ApiOrderStatus,
pub order_flags: OrderFlags,
pub total_filled: Option<BigDecimal>,
pub total_optimistic_filled: Option<BigDecimal>,
pub good_til_block: Option<Height>,
pub good_til_block_time: Option<DateTime<Utc>>,
pub trigger_price: Option<Price>,
pub updated_at: Option<DateTime<Utc>>,
pub updated_at_height: Option<Height>,
pub removal_reason: Option<String>,
pub created_at_height: Option<Height>,
pub client_metadata: Option<ClientMetadata>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct FillSubaccountMessageContents {
pub id: FillId,
pub subaccount_id: SubaccountId,
pub side: OrderSide,
pub liquidity: Liquidity,
#[serde(rename = "type")]
pub fill_type: FillType,
pub clob_pair_id: ClobPairId,
pub size: Quantity,
pub price: Price,
pub quote_amount: String,
pub event_id: String,
pub transaction_hash: String,
pub created_at: DateTime<Utc>,
pub created_at_height: Height,
pub ticker: Ticker,
pub order_id: Option<OrderId>,
pub client_metadata: Option<ClientMetadata>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct TransferSubaccountMessageContents {
pub sender: Account,
pub recipient: Account,
pub symbol: Symbol,
pub size: Quantity,
#[serde(rename = "type")]
pub transfer_type: TransferType,
pub transaction_hash: String,
pub created_at: DateTime<Utc>,
pub created_at_height: Height,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct TradingRewardSubaccountMessageContents {
pub trading_reward: BigDecimal,
pub created_at: DateTime<Utc>,
pub created_at_height: Height,
}
#[derive(Debug, Deserialize)]
pub struct ParentSubaccountsUpdateMessage {
pub connection_id: String,
#[serde(deserialize_with = "deserialize_subaccounts_contents")]
pub contents: Vec<SubaccountUpdateMessageContents>,
pub id: String,
pub message_id: u64,
pub version: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct ParentSubaccountUpdateMessageContents {
pub perpetual_positions: Option<Vec<PerpetualPositionSubaccountMessageContents>>,
pub asset_positions: Option<Vec<AssetPositionSubaccountMessageContents>>,
pub orders: Option<Vec<OrderSubaccountMessageContents>>,
pub fills: Option<Vec<FillSubaccountMessageContents>>,
pub transfers: Option<TransferSubaccountMessageContents>,
pub trading_reward: Option<TradingRewardSubaccountMessageContents>,
pub block_height: Option<Height>,
}
#[derive(Debug, Deserialize)]
pub struct OrdersUpdateMessage {
pub connection_id: String,
#[serde(deserialize_with = "deserialize_orders_contents")]
pub contents: OrdersUpdateMessageContents,
pub id: String,
pub message_id: u64,
pub version: String,
}
fn deserialize_orders_contents<'de, D>(
deserializer: D,
) -> Result<OrdersUpdateMessageContents, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
match value {
Value::Array(arr) => {
let mut bids = Vec::new();
let mut asks = Vec::new();
for v in arr {
let item: OrdersUpdateMessageContents =
serde_json::from_value(v).map_err(serde::de::Error::custom)?;
if let Some(item_bids) = item.bids {
bids.extend(item_bids);
}
if let Some(item_asks) = item.asks {
asks.extend(item_asks);
}
}
Ok(OrdersUpdateMessageContents {
bids: if bids.is_empty() { None } else { Some(bids) },
asks: if asks.is_empty() { None } else { Some(asks) },
})
}
Value::Object(obj) => {
let item =
serde_json::from_value::<OrdersUpdateMessageContents>(Value::Object(obj.clone()))
.map_err(serde::de::Error::custom)?;
Ok(item)
}
_ => Err(serde::de::Error::custom("Expected array or object")),
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct OrdersUpdateMessageContents {
pub bids: Option<Vec<OrderbookResponsePriceLevel>>,
pub asks: Option<Vec<OrderbookResponsePriceLevel>>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct TradesUpdateMessage {
pub connection_id: String,
#[serde(deserialize_with = "deserialize_trades_contents")]
pub contents: Vec<TradesUpdateMessageContents>,
pub id: String,
pub message_id: u64,
pub version: String,
}
generate_contents_deserialize_function!(deserialize_trades_contents, TradesUpdateMessageContents);
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct TradesUpdateMessageContents {
pub trades: Vec<TradeUpdate>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct TradeUpdate {
pub id: TradeId,
pub created_at: DateTime<Utc>,
pub side: OrderSide,
pub price: Price,
pub size: Quantity,
#[serde(rename = "type")]
pub trade_type: TradeType,
}
#[derive(Debug, Deserialize)]
pub struct MarketsUpdateMessage {
pub connection_id: String,
#[serde(deserialize_with = "deserialize_markets_contents")]
pub contents: Vec<MarketsUpdateMessageContents>,
pub message_id: u64,
pub version: String,
}
generate_contents_deserialize_function!(deserialize_markets_contents, MarketsUpdateMessageContents);
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct MarketsUpdateMessageContents {
pub trading: Option<HashMap<Ticker, TradingPerpetualMarket>>,
pub oracle_prices: Option<HashMap<Ticker, OraclePriceMarket>>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct TradingPerpetualMarket {
pub atomic_resolution: Option<i32>,
pub base_asset: Option<String>,
pub base_open_interest: Option<BigDecimal>,
pub base_position_size: Option<Quantity>,
pub clob_pair_id: Option<ClobPairId>,
pub id: Option<String>,
pub market_id: Option<u64>,
pub incremental_position_size: Option<Quantity>,
pub initial_margin_fraction: Option<BigDecimal>,
pub maintenance_margin_fraction: Option<BigDecimal>,
pub max_position_size: Option<Quantity>,
pub open_interest: Option<BigDecimal>,
pub quantum_conversion_exponent: Option<i32>,
pub quote_asset: Option<String>,
pub status: Option<PerpetualMarketStatus>,
pub step_base_quantums: Option<i32>,
pub subticks_per_tick: Option<i32>,
pub ticker: Option<Ticker>,
#[serde(rename = "priceChange24H")]
pub price_change_24h: Option<BigDecimal>,
#[serde(rename = "trades24H")]
pub trades_24h: Option<u64>,
#[serde(rename = "volume24H")]
pub volume_24h: Option<Quantity>,
pub next_funding_rate: Option<BigDecimal>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct OraclePriceMarket {
pub oracle_price: Price,
pub effective_at: DateTime<Utc>,
pub effective_at_height: Height,
pub market_id: u64,
}
#[derive(Debug, Deserialize)]
pub struct CandlesUpdateMessage {
pub connection_id: String,
#[serde(deserialize_with = "deserialize_candles_contents")]
pub contents: Vec<Candle>,
pub id: String,
pub message_id: u64,
pub version: String,
}
generate_contents_deserialize_function!(deserialize_candles_contents, Candle);
#[derive(Debug, Deserialize)]
pub struct BlockHeightUpdateMessage {
pub connection_id: String,
#[serde(deserialize_with = "deserialize_block_height_contents")]
pub contents: Vec<BlockHeightUpdateMessageContents>,
pub message_id: u64,
pub version: String,
}
generate_contents_deserialize_function!(
deserialize_block_height_contents,
BlockHeightUpdateMessageContents
);
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
pub struct BlockHeightUpdateMessageContents {
pub block_height: Height,
pub time: DateTime<Utc>,
}