use std::result::Result as StdResult;
use chrono::serde as chrono_serde;
use rust_decimal::Decimal;
use serde::{de, de::DeserializeOwned, Deserialize};
use serde_json::Value as JsonValue;
use crate::common::*;
use crate::error::*;
pub trait Feed
where
Self: Sized + DeserializeOwned,
{
type Records;
fn is_snapshot(&self) -> bool;
fn into_record(self) -> Self::Records;
fn from_json_value(value: JsonValue) -> Result<Self> {
serde_json::from_value::<Self>(value).map_err(Error::WsApiParse)
}
}
fn parse_pub_feed_type<'de, D>(deserializer: D) -> StdResult<bool, D::Error>
where
D: de::Deserializer<'de>,
{
let val: String = Deserialize::deserialize(deserializer)?;
match val.to_lowercase().as_str() {
"snapshot" => Ok(true),
"update" => Ok(false),
_ => Err(de::Error::invalid_value(
de::Unexpected::Str(val.as_str()),
&"snapshot/update",
)),
}
}
fn parse_priv_feed_type<'de, D>(deserializer: D) -> StdResult<bool, D::Error>
where
D: de::Deserializer<'de>,
{
let val: String = Deserialize::deserialize(deserializer)?;
match val.to_lowercase().as_str() {
s if s.ends_with("_snapshot") => Ok(true),
s if s.ends_with("_update") => Ok(false),
_ => Err(de::Error::invalid_value(
de::Unexpected::Str(val.as_str()),
&"*_snapshot/*_update",
)),
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PubOrderBookFeed {
#[serde(rename = "e", deserialize_with = "parse_pub_feed_type")]
pub is_snapshot: bool,
#[serde(rename = "M")]
pub market: Symbol,
#[serde(rename = "a")]
pub ask: Vec<PubOrderBookRec>,
#[serde(rename = "b")]
pub bid: Vec<PubOrderBookRec>,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub time: DateTime,
}
impl Feed for PubOrderBookFeed {
type Records = (Vec<PubOrderBookRec>, Vec<PubOrderBookRec>);
fn is_snapshot(&self) -> bool {
self.is_snapshot
}
fn into_record(self) -> Self::Records {
(self.ask, self.bid)
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PubOrderBookRec {
pub price: Decimal,
pub volume: Decimal,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PubTradeFeed {
#[serde(rename = "e", deserialize_with = "parse_pub_feed_type")]
pub is_snapshot: bool,
#[serde(rename = "M")]
pub market: Symbol,
#[serde(rename = "t")]
pub trades: Vec<PubTradeRec>,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub time: DateTime,
}
impl Feed for PubTradeFeed {
type Records = Vec<PubTradeRec>;
fn is_snapshot(&self) -> bool {
self.is_snapshot
}
fn into_record(self) -> Self::Records {
self.trades
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PubTradeRec {
#[serde(rename = "p")]
pub price: Decimal,
#[serde(rename = "v")]
pub volume: Decimal,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub create_time: DateTime,
#[serde(rename = "tr")]
pub trend: String,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PubTickerFeed {
#[serde(rename = "e", deserialize_with = "parse_pub_feed_type")]
pub is_snapshot: bool,
#[serde(rename = "M")]
pub market: Symbol,
#[serde(rename = "tk")]
pub tick: TickerRec,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub time: DateTime,
}
impl Feed for PubTickerFeed {
type Records = TickerRec;
fn is_snapshot(&self) -> bool {
self.is_snapshot
}
fn into_record(self) -> Self::Records {
self.tick
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct TickerRec {
#[serde(rename = "O")]
pub open: Decimal,
#[serde(rename = "H")]
pub close: Decimal,
#[serde(rename = "L")]
pub high: Decimal,
#[serde(rename = "C")]
pub low: Decimal,
#[serde(rename = "v")]
pub volume: Decimal,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PubMarketStatueFeed {
#[serde(rename = "c")]
pub channel: String,
#[serde(rename = "e", deserialize_with = "parse_pub_feed_type")]
pub is_snapshot: bool,
#[serde(rename = "ms")]
pub markets: Vec<MarketStatusInfo>,
}
impl Feed for PubMarketStatueFeed {
type Records = Vec<MarketStatusInfo>;
fn is_snapshot(&self) -> bool {
self.is_snapshot
}
fn into_record(self) -> Self::Records {
self.markets
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct MarketStatusInfo {
#[serde(rename = "M")]
pub market: String,
#[serde(rename = "st")]
pub status: String,
#[serde(rename = "bu")]
pub base_unit: String,
#[serde(rename = "bup")]
pub base_unit_precision: i8,
#[serde(rename = "mba")]
pub min_base_amount: Decimal,
#[serde(rename = "qu")]
pub quote_unit: String,
#[serde(rename = "qup")]
pub quote_unit_precision: i8,
#[serde(rename = "mqa")]
pub min_quote_amount: Decimal,
#[serde(rename = "mws")]
pub m_wallet_supported: bool,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PrivOrderBookFeed {
#[serde(rename = "e", deserialize_with = "parse_priv_feed_type")]
pub is_snapshot: bool,
#[serde(rename = "o")]
pub orders: Vec<PrivOrderBookRec>,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub time: DateTime,
}
impl Feed for PrivOrderBookFeed {
type Records = Vec<PrivOrderBookRec>;
fn is_snapshot(&self) -> bool {
self.is_snapshot
}
fn into_record(self) -> Self::Records {
self.orders
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PrivOrderBookRec {
#[serde(rename = "i")]
pub oid: u64,
#[serde(rename = "sd")]
pub side: String,
#[serde(rename = "ot")]
pub ord_type: String,
#[serde(rename = "p")]
pub price: Option<Decimal>,
#[serde(rename = "sp")]
pub stop_price: Option<Decimal>,
#[serde(rename = "ap")]
pub avg_price: Option<Decimal>,
#[serde(rename = "S")]
pub state: String,
#[serde(rename = "M")]
pub market: Symbol,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub create_time: DateTime,
#[serde(rename = "v")]
pub volume: Decimal,
#[serde(rename = "rv")]
pub remaining_volume: Option<Decimal>,
#[serde(rename = "ev")]
pub executed_volume: Option<Decimal>,
#[serde(rename = "tc")]
pub trade_count: Option<u64>,
#[serde(rename = "ci")]
pub client_oid: Option<String>,
#[serde(rename = "gi")]
pub group_id: Option<u64>,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PrivTradeFeed {
#[serde(rename = "e", deserialize_with = "parse_priv_feed_type")]
pub is_snapshot: bool,
#[serde(rename = "t")]
pub trades: Vec<PrivTradeRec>,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub time: DateTime,
}
impl Feed for PrivTradeFeed {
type Records = Vec<PrivTradeRec>;
fn is_snapshot(&self) -> bool {
self.is_snapshot
}
fn into_record(self) -> Self::Records {
self.trades
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PrivTradeRec {
#[serde(rename = "i")]
pub tid: u64,
#[serde(rename = "sd")]
pub side: String,
#[serde(rename = "p")]
pub price: Decimal,
#[serde(rename = "v")]
pub volume: Decimal,
#[serde(rename = "M")]
pub market: Symbol,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub create_time: DateTime,
#[serde(rename = "f")]
pub fee: Decimal,
#[serde(rename = "fc")]
pub fee_currency: String,
#[serde(rename = "m")]
pub is_maker: bool,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PrivBalanceFeed {
#[serde(rename = "e", deserialize_with = "parse_priv_feed_type")]
pub is_snapshot: bool,
#[serde(rename = "B")]
pub balance: Vec<PrivBalanceItem>,
#[serde(rename = "T", with = "chrono_serde::ts_milliseconds")]
pub time: DateTime,
}
impl Feed for PrivBalanceFeed {
type Records = Vec<PrivBalanceItem>;
fn is_snapshot(&self) -> bool {
self.is_snapshot
}
fn into_record(self) -> Self::Records {
self.balance
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct PrivBalanceItem {
#[serde(rename = "cu")]
pub currency: String,
#[serde(rename = "av")]
pub available: Decimal,
#[serde(rename = "l")]
pub locked: Decimal,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pub_feed_type_parse() {
fn parse(input: &str) -> StdResult<bool, serde_json::Error> {
let mut deserializer = serde_json::Deserializer::from_str(input);
parse_pub_feed_type(&mut deserializer)
}
assert!(parse(r#""snapshot""#).expect("invalid test case"));
assert!(!parse(r#""update""#).expect("invalid test case"));
const ERROR_MSG: &str = "must not allow value other than snapshot and update";
parse(r#""?""#).expect_err(ERROR_MSG);
parse(r#""_snapshot""#).expect_err(ERROR_MSG);
parse(r#"" update""#).expect_err(ERROR_MSG);
parse(r#""""#).expect_err(ERROR_MSG);
parse(r#""updatesnapshot""#).expect_err(ERROR_MSG);
}
#[test]
fn test_priv_feed_type_parse() {
fn parse(input: &str) -> StdResult<bool, serde_json::Error> {
let mut deserializer = serde_json::Deserializer::from_str(input);
parse_priv_feed_type(&mut deserializer)
}
assert!(parse(r#""order_snapshot""#).expect("invalid test case"));
assert!(!parse(r#""order_update""#).expect("invalid test case"));
assert!(parse(r#""trade_snapshot""#).expect("invalid test case"));
assert!(!parse(r#""trade_update""#).expect("invalid test case"));
assert!(parse(r#""account_snapshot""#).expect("invalid test case"));
assert!(!parse(r#""account_update""#).expect("invalid test case"));
assert!(parse(r#""*_snapshot""#).expect("invalid test case"));
assert!(!parse(r#""??_update""#).expect("invalid test case"));
assert!(parse(r#""_snapshot""#).expect("invalid test case"));
assert!(!parse(r#"" _update""#).expect("invalid test case"));
const ERROR_MSG: &str = "must not allow value other than snapshot and update";
parse(r#""?""#).expect_err(ERROR_MSG);
parse(r#""order_snapshot_""#).expect_err(ERROR_MSG);
parse(r#""order update""#).expect_err(ERROR_MSG);
parse(r#""order""#).expect_err(ERROR_MSG);
parse(r#""""#).expect_err(ERROR_MSG);
parse(r#""updatesnapshot""#).expect_err(ERROR_MSG);
}
}