use either::Either;
use rust_decimal::Decimal;
use serde::Deserialize;
use crate::{
types::trading::{OrderSide, PositionSide, Status, TimeInForce},
websocket::error::WsError,
};
use super::{Name, Nameable, StreamFrame, StreamFrameKind};
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "e", rename_all = "camelCase")]
pub enum AccountEvent {
ListenKeyExpired {
#[serde(rename = "E")]
ts: i64,
},
#[serde(rename = "ORDER_TRADE_UPDATE")]
OrderTradeUpdate {
#[serde(rename = "E")]
event_ts: i64,
#[serde(rename = "T")]
trade_ts: i64,
#[serde(rename = "o")]
order: OrderUpdate,
},
#[serde(rename = "executionReport")]
ExecutionReport(ExecutionReport),
}
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OrderType {
Market,
Limit,
Stop,
TakeProfit,
Liquidation,
LimitMaker,
}
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum UpdateKind {
New,
Canceled,
Calculated,
Expired,
Trade,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OrderUpdate {
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "c")]
pub client_id: String,
#[serde(rename = "S")]
pub side: OrderSide,
#[serde(rename = "o")]
pub order_type: OrderType,
#[serde(rename = "f")]
pub time_in_force: TimeInForce,
#[serde(rename = "q")]
pub size: Decimal,
#[serde(rename = "p")]
pub price: Decimal,
#[serde(rename = "ap")]
pub cost: Decimal,
#[serde(rename = "sp")]
pub trigger_price: Decimal,
#[serde(rename = "x")]
pub kind: UpdateKind,
#[serde(rename = "X")]
pub status: Status,
#[serde(rename = "i")]
pub order_id: i64,
#[serde(rename = "l")]
pub last_trade_size: Decimal,
#[serde(rename = "z")]
pub filled_size: Decimal,
#[serde(rename = "L")]
pub last_trade_price: Decimal,
#[serde(rename = "N")]
pub fee_asset: Option<String>,
#[serde(rename = "n", default)]
pub fee: Decimal,
#[serde(rename = "T")]
pub trade_ts: i64,
#[serde(rename = "t")]
pub trade_id: i64,
#[serde(rename = "b")]
pub bid_equity: Decimal,
#[serde(rename = "a")]
pub ask_equity: Decimal,
#[serde(rename = "m")]
pub marker: bool,
#[serde(rename = "R")]
pub reduce_only: bool,
#[serde(rename = "wt")]
pub trigger_type: String,
#[serde(rename = "ot")]
pub original_order_type: OrderType,
#[serde(rename = "ps")]
pub position_side: PositionSide,
#[serde(rename = "cp")]
pub is_triggered_close: Option<bool>,
#[serde(rename = "AP")]
pub active_price: Option<Decimal>,
#[serde(rename = "cr")]
pub cr: Option<Decimal>,
#[serde(rename = "rp")]
pub pnl: Decimal,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ExecutionReport {
#[serde(rename = "E")]
pub event_ts: i64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "c")]
pub client_id: String,
#[serde(rename = "C")]
pub orignal_client_id: String,
#[serde(rename = "S")]
pub side: OrderSide,
#[serde(rename = "o")]
pub order_type: OrderType,
#[serde(rename = "f")]
pub time_in_force: TimeInForce,
#[serde(rename = "Q")]
pub quote_size: Decimal,
#[serde(rename = "q")]
pub size: Decimal,
#[serde(rename = "p")]
pub price: Decimal,
#[serde(rename = "x")]
pub kind: UpdateKind,
#[serde(rename = "X")]
pub status: Status,
#[serde(rename = "i")]
pub order_id: i64,
#[serde(rename = "l")]
pub last_trade_size: Decimal,
#[serde(rename = "Y")]
pub last_trade_quote_size: Decimal,
#[serde(rename = "z")]
pub filled_size: Decimal,
#[serde(rename = "Z")]
pub filled_quote_size: Decimal,
#[serde(rename = "L")]
pub last_trade_price: Decimal,
#[serde(rename = "N")]
pub fee_asset: Option<String>,
#[serde(rename = "n", default)]
pub fee: Decimal,
#[serde(rename = "T")]
pub trade_ts: i64,
#[serde(rename = "t")]
pub trade_id: i64,
#[serde(rename = "m")]
pub marker: bool,
#[serde(rename = "O")]
pub create_ts: i64,
}
impl ExecutionReport {
pub fn client_id(&self) -> &str {
if self.orignal_client_id.is_empty() {
self.client_id.as_str()
} else {
self.orignal_client_id.as_str()
}
}
}
impl Nameable for AccountEvent {
fn to_name(&self) -> Name {
match self {
Self::ListenKeyExpired { .. } => Name::listen_key_expired(),
Self::OrderTradeUpdate { order, .. } => {
Name::order_trade_update(&order.symbol.to_lowercase())
}
Self::ExecutionReport(r) => Name::order_trade_update(&r.symbol.to_lowercase()),
}
}
}
impl TryFrom<StreamFrame> for AccountEvent {
type Error = WsError;
fn try_from(frame: StreamFrame) -> Result<Self, Self::Error> {
if let StreamFrameKind::AccountEvent(e) = frame.data {
Ok(e)
} else {
Err(WsError::UnexpectedFrame(anyhow::anyhow!("{frame:?}")))
}
}
}
impl TryFrom<StreamFrame> for Either<OrderUpdate, ExecutionReport> {
type Error = WsError;
fn try_from(frame: StreamFrame) -> Result<Self, Self::Error> {
if let StreamFrameKind::AccountEvent(e) = frame.data {
match e {
AccountEvent::OrderTradeUpdate { order, .. } => Ok(Either::Left(order)),
AccountEvent::ExecutionReport(r) => Ok(Either::Right(r)),
e => Err(WsError::UnexpectedFrame(anyhow::anyhow!("{e:?}"))),
}
} else {
Err(WsError::UnexpectedFrame(anyhow::anyhow!("{frame:?}")))
}
}
}