use ahash::AHashMap;
use derive_builder::Builder;
use nautilus_model::{
data::{
Bar, FundingRateUpdate, IndexPriceUpdate, MarkPriceUpdate, OrderBookDeltas, QuoteTick,
TradeTick,
},
reports::{FillReport, OrderStatusReport},
};
use serde::{Deserialize, Serialize};
use ustr::Ustr;
use crate::common::enums::{
HyperliquidBarInterval, HyperliquidFillDirection, HyperliquidLiquidationMethod,
HyperliquidOrderStatus as HyperliquidOrderStatusEnum, HyperliquidSide, HyperliquidTpSl,
HyperliquidTwapStatus,
};
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "method")]
#[serde(rename_all = "lowercase")]
pub enum HyperliquidWsRequest {
Subscribe {
subscription: SubscriptionRequest,
},
Unsubscribe {
subscription: SubscriptionRequest,
},
Post {
id: u64,
request: PostRequest,
},
Ping,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum SubscriptionRequest {
AllMids {
#[serde(skip_serializing_if = "Option::is_none")]
dex: Option<String>,
},
Notification { user: String },
WebData2 { user: String },
Candle {
coin: Ustr,
interval: HyperliquidBarInterval,
},
L2Book {
coin: Ustr,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "nSigFigs")]
n_sig_figs: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
mantissa: Option<u32>,
},
Trades { coin: Ustr },
OrderUpdates { user: String },
UserEvents { user: String },
UserFills {
user: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "aggregateByTime")]
aggregate_by_time: Option<bool>,
},
UserFundings { user: String },
UserNonFundingLedgerUpdates { user: String },
ActiveAssetCtx { coin: Ustr },
ActiveSpotAssetCtx { coin: Ustr },
ActiveAssetData { user: String, coin: String },
UserTwapSliceFills { user: String },
UserTwapHistory { user: String },
Bbo { coin: Ustr },
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum PostRequest {
Info { payload: serde_json::Value },
Action { payload: ActionPayload },
}
#[derive(Debug, Clone, Serialize)]
pub struct ActionPayload {
pub action: ActionRequest,
pub nonce: u64,
pub signature: SignatureData,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "vaultAddress")]
pub vault_address: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SignatureData {
pub r: String,
pub s: String,
pub v: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum ActionRequest {
Order {
orders: Vec<OrderRequest>,
grouping: String,
},
Cancel { cancels: Vec<CancelRequest> },
CancelByCloid { cancels: Vec<CancelByCloidRequest> },
Modify { modifies: Vec<ModifyRequest> },
}
impl ActionRequest {
pub fn order(orders: Vec<OrderRequest>, grouping: impl Into<String>) -> Self {
Self::Order {
orders,
grouping: grouping.into(),
}
}
pub fn cancel(cancels: Vec<CancelRequest>) -> Self {
Self::Cancel { cancels }
}
pub fn cancel_by_cloid(cancels: Vec<CancelByCloidRequest>) -> Self {
Self::CancelByCloid { cancels }
}
pub fn modify(modifies: Vec<ModifyRequest>) -> Self {
Self::Modify { modifies }
}
}
#[derive(Debug, Clone, Serialize, Builder)]
pub struct OrderRequest {
pub a: u32,
pub b: bool,
pub p: String,
pub s: String,
pub r: bool,
pub t: OrderTypeRequest,
#[serde(skip_serializing_if = "Option::is_none")]
pub c: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum OrderTypeRequest {
Limit {
tif: TimeInForceRequest,
},
Trigger {
#[serde(rename = "isMarket")]
is_market: bool,
#[serde(rename = "triggerPx")]
trigger_px: String,
tpsl: TpSlRequest,
},
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "PascalCase")]
pub enum TimeInForceRequest {
Alo,
Ioc,
Gtc,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TpSlRequest {
Tp,
Sl,
}
#[derive(Debug, Clone, Serialize)]
pub struct CancelRequest {
pub a: u32,
pub o: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct CancelByCloidRequest {
pub asset: u32,
pub cloid: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct ModifyRequest {
pub oid: u64,
pub order: OrderRequest,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SubscriptionResponseData {
pub method: String,
pub subscription: SubscriptionRequest,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "channel")]
#[serde(rename_all = "camelCase")]
pub enum HyperliquidWsMessage {
SubscriptionResponse { data: SubscriptionResponseData },
Post { data: PostResponse },
AllMids { data: AllMidsData },
Notification { data: NotificationData },
WebData2 { data: serde_json::Value },
Candle { data: CandleData },
L2Book { data: WsBookData },
Trades { data: Vec<WsTradeData> },
OrderUpdates { data: Vec<WsOrderData> },
UserEvents { data: WsUserEventData },
#[serde(rename = "user")]
User { data: WsUserEventData },
UserFills { data: WsUserFillsData },
UserFundings { data: WsUserFundingsData },
UserNonFundingLedgerUpdates { data: serde_json::Value },
ActiveAssetCtx { data: WsActiveAssetCtxData },
ActiveSpotAssetCtx { data: WsActiveAssetCtxData },
ActiveAssetData { data: WsActiveAssetData },
UserTwapSliceFills { data: WsUserTwapSliceFillsData },
UserTwapHistory { data: WsUserTwapHistoryData },
Bbo { data: WsBboData },
Error { data: String },
Pong,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PostResponse {
pub id: u64,
pub response: PostResponsePayload,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum PostResponsePayload {
Info { payload: serde_json::Value },
Action { payload: serde_json::Value },
Error { payload: String },
}
#[derive(Debug, Clone, Deserialize)]
pub struct AllMidsData {
pub mids: AHashMap<String, String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NotificationData {
pub notification: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CandleData {
pub t: u64,
#[serde(rename = "T")]
pub close_time: u64,
pub s: Ustr,
pub i: Ustr,
pub o: String,
pub c: String,
pub h: String,
pub l: String,
pub v: String,
pub n: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsBookData {
pub coin: Ustr,
pub levels: [Vec<WsLevelData>; 2], pub time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsLevelData {
pub px: String,
pub sz: String,
pub n: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsTradeData {
pub coin: Ustr,
pub side: HyperliquidSide,
pub px: String,
pub sz: String,
pub hash: String,
pub time: u64,
pub tid: u64,
pub users: [String; 2], }
#[derive(Debug, Clone, Deserialize)]
pub struct WsOrderData {
pub order: WsBasicOrderData,
pub status: HyperliquidOrderStatusEnum,
#[serde(rename = "statusTimestamp")]
pub status_timestamp: u64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsBasicOrderData {
pub coin: Ustr,
pub side: HyperliquidSide,
#[serde(rename = "limitPx")]
pub limit_px: String,
pub sz: String,
pub oid: u64,
pub timestamp: u64,
#[serde(rename = "origSz")]
pub orig_sz: String,
pub cloid: Option<String>,
#[serde(rename = "triggerPx")]
pub trigger_px: Option<String>,
#[serde(rename = "isMarket")]
pub is_market: Option<bool>,
pub tpsl: Option<HyperliquidTpSl>,
#[serde(rename = "triggerActivated")]
pub trigger_activated: Option<bool>,
#[serde(rename = "trailingStop")]
pub trailing_stop: Option<WsTrailingStopData>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum TrailingOffsetType {
Price,
Percentage,
BasisPoints,
}
impl TrailingOffsetType {
pub fn format_offset(&self, offset: &str) -> String {
match self {
Self::Price => offset.to_string(),
Self::Percentage => format!("{offset}%"),
Self::BasisPoints => format!("{offset} bps"),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsTrailingStopData {
pub offset: String,
#[serde(rename = "offsetType")]
pub offset_type: TrailingOffsetType,
#[serde(rename = "callbackPrice")]
pub callback_price: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum WsUserEventData {
Fills {
fills: Vec<WsFillData>,
},
Funding {
funding: WsUserFundingData,
},
Liquidation {
liquidation: WsLiquidationData,
},
NonUserCancel {
#[serde(rename = "nonUserCancel")]
non_user_cancel: Vec<WsNonUserCancelData>,
},
TriggerActivated {
#[serde(rename = "triggerActivated")]
trigger_activated: WsTriggerActivatedData,
},
TriggerTriggered {
#[serde(rename = "triggerTriggered")]
trigger_triggered: WsTriggerTriggeredData,
},
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsFillData {
pub coin: Ustr,
pub px: String,
pub sz: String,
pub side: HyperliquidSide,
pub time: u64,
#[serde(rename = "startPosition")]
pub start_position: String,
pub dir: HyperliquidFillDirection,
#[serde(rename = "closedPnl")]
pub closed_pnl: String,
pub hash: String,
pub oid: u64,
pub crossed: bool,
pub fee: String,
pub tid: u64,
#[serde(default)]
pub liquidation: Option<FillLiquidationData>,
#[serde(rename = "feeToken")]
pub fee_token: Ustr,
#[serde(rename = "builderFee")]
pub builder_fee: Option<String>,
pub cloid: Option<String>,
#[serde(rename = "twapId")]
pub twap_id: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FillLiquidationData {
#[serde(rename = "liquidatedUser")]
pub liquidated_user: Option<String>,
#[serde(rename = "markPx")]
pub mark_px: f64,
pub method: HyperliquidLiquidationMethod,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsUserFundingData {
pub time: u64,
pub coin: Ustr,
pub usdc: String,
pub szi: String,
#[serde(rename = "fundingRate")]
pub funding_rate: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsLiquidationData {
pub lid: u64,
pub liquidator: String,
pub liquidated_user: String,
pub liquidated_ntl_pos: String,
pub liquidated_account_value: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsNonUserCancelData {
pub coin: Ustr,
pub oid: u64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsTriggerActivatedData {
pub coin: Ustr,
pub oid: u64,
pub time: u64,
#[serde(rename = "triggerPx")]
pub trigger_px: String,
pub tpsl: HyperliquidTpSl,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsTriggerTriggeredData {
pub coin: Ustr,
pub oid: u64,
pub time: u64,
#[serde(rename = "triggerPx")]
pub trigger_px: String,
#[serde(rename = "marketPx")]
pub market_px: String,
pub tpsl: HyperliquidTpSl,
#[serde(rename = "resultingOid")]
pub resulting_oid: Option<u64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsUserFillsData {
#[serde(rename = "isSnapshot")]
pub is_snapshot: Option<bool>,
pub user: String,
pub fills: Vec<WsFillData>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsUserFundingsData {
#[serde(rename = "isSnapshot")]
pub is_snapshot: Option<bool>,
pub user: String,
pub fundings: Vec<WsUserFundingData>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum WsActiveAssetCtxData {
Perp { coin: Ustr, ctx: PerpsAssetCtx },
Spot { coin: Ustr, ctx: SpotAssetCtx },
}
#[derive(Debug, Clone, Deserialize)]
pub struct SharedAssetCtx {
#[serde(rename = "dayNtlVlm")]
pub day_ntl_vlm: String,
#[serde(rename = "prevDayPx")]
pub prev_day_px: String,
#[serde(rename = "markPx")]
pub mark_px: String,
#[serde(rename = "midPx")]
pub mid_px: Option<String>,
#[serde(rename = "impactPxs")]
pub impact_pxs: Option<Vec<String>>,
#[serde(rename = "dayBaseVlm")]
pub day_base_vlm: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PerpsAssetCtx {
#[serde(flatten)]
pub shared: SharedAssetCtx,
pub funding: String,
#[serde(rename = "openInterest")]
pub open_interest: String,
#[serde(rename = "oraclePx")]
pub oracle_px: String,
pub premium: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SpotAssetCtx {
#[serde(flatten)]
pub shared: SharedAssetCtx,
#[serde(rename = "circulatingSupply")]
pub circulating_supply: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsActiveAssetData {
pub user: String,
pub coin: Ustr,
pub leverage: LeverageData,
#[serde(rename = "maxTradeSzs")]
pub max_trade_szs: [f64; 2],
#[serde(rename = "availableToTrade")]
pub available_to_trade: [f64; 2],
}
#[derive(Debug, Clone, Deserialize)]
pub struct LeverageData {
pub value: f64,
pub type_: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsUserTwapSliceFillsData {
#[serde(rename = "isSnapshot")]
pub is_snapshot: Option<bool>,
pub user: String,
#[serde(rename = "twapSliceFills")]
pub twap_slice_fills: Vec<WsTwapSliceFillData>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsTwapSliceFillData {
pub fill: WsFillData,
#[serde(rename = "twapId")]
pub twap_id: u64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsUserTwapHistoryData {
#[serde(rename = "isSnapshot")]
pub is_snapshot: Option<bool>,
pub user: String,
pub history: Vec<WsTwapHistoryData>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsTwapHistoryData {
pub state: TwapStateData,
pub status: TwapStatusData,
pub time: u64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TwapStateData {
pub coin: Ustr,
pub user: String,
pub side: HyperliquidSide,
pub sz: f64,
#[serde(rename = "executedSz")]
pub executed_sz: f64,
#[serde(rename = "executedNtl")]
pub executed_ntl: f64,
pub minutes: u32,
#[serde(rename = "reduceOnly")]
pub reduce_only: bool,
pub randomize: bool,
pub timestamp: u64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TwapStatusData {
pub status: HyperliquidTwapStatus,
pub description: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WsBboData {
pub coin: Ustr,
pub time: u64,
pub bbo: [Option<WsLevelData>; 2], }
#[cfg(test)]
mod tests {
use rstest::rstest;
use serde_json;
use super::*;
#[rstest]
fn test_subscription_request_serialization() {
let sub = SubscriptionRequest::L2Book {
coin: Ustr::from("BTC"),
n_sig_figs: Some(5),
mantissa: None,
};
let json = serde_json::to_string(&sub).unwrap();
assert!(json.contains(r#""type":"l2Book""#));
assert!(json.contains(r#""coin":"BTC""#));
}
#[rstest]
fn test_hyperliquid_ws_request_serialization() {
let req = HyperliquidWsRequest::Subscribe {
subscription: SubscriptionRequest::Trades {
coin: Ustr::from("ETH"),
},
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains(r#""method":"subscribe""#));
assert!(json.contains(r#""type":"trades""#));
}
#[rstest]
fn test_order_request_serialization() {
let order = OrderRequest {
a: 0, b: true, p: "50000.0".to_string(),
s: "0.1".to_string(),
r: false,
t: OrderTypeRequest::Limit {
tif: TimeInForceRequest::Gtc,
},
c: Some("client-123".to_string()),
};
let json = serde_json::to_string(&order).unwrap();
assert!(json.contains(r#""a":0"#));
assert!(json.contains(r#""b":true"#));
assert!(json.contains(r#""p":"50000.0""#));
}
#[rstest]
fn test_ws_trade_data_deserialization() {
let json = r#"{
"coin": "BTC",
"side": "B",
"px": "50000.0",
"sz": "0.1",
"hash": "0x123",
"time": 1234567890,
"tid": 12345,
"users": ["0xabc", "0xdef"]
}"#;
let trade: WsTradeData = serde_json::from_str(json).unwrap();
assert_eq!(trade.coin, "BTC");
assert_eq!(trade.side, HyperliquidSide::Buy);
assert_eq!(trade.px, "50000.0");
}
#[rstest]
fn test_ws_book_data_deserialization() {
let json = r#"{
"coin": "ETH",
"levels": [
[{"px": "3000.0", "sz": "1.0", "n": 1}],
[{"px": "3001.0", "sz": "2.0", "n": 2}]
],
"time": 1234567890
}"#;
let book: WsBookData = serde_json::from_str(json).unwrap();
assert_eq!(book.coin, "ETH");
assert_eq!(book.levels[0].len(), 1);
assert_eq!(book.levels[1].len(), 1);
}
#[rstest]
fn test_ws_trailing_stop_data_deserialization() {
let json = r#"{
"offset": "100.0",
"offsetType": "price",
"callbackPrice": "50000.0"
}"#;
let data: WsTrailingStopData = serde_json::from_str(json).unwrap();
assert_eq!(data.offset, "100.0");
assert_eq!(data.offset_type, TrailingOffsetType::Price);
assert_eq!(data.callback_price.unwrap(), "50000.0");
}
#[rstest]
fn test_ws_trigger_activated_data_deserialization() {
let json = r#"{
"coin": "BTC",
"oid": 12345,
"time": 1704470400000,
"triggerPx": "50000.0",
"tpsl": "sl"
}"#;
let data: WsTriggerActivatedData = serde_json::from_str(json).unwrap();
assert_eq!(data.coin, Ustr::from("BTC"));
assert_eq!(data.oid, 12345);
assert_eq!(data.trigger_px, "50000.0");
assert_eq!(data.tpsl, HyperliquidTpSl::Sl);
assert_eq!(data.time, 1704470400000);
}
#[rstest]
fn test_ws_trigger_triggered_data_deserialization() {
let json = r#"{
"coin": "ETH",
"oid": 67890,
"time": 1704470500000,
"triggerPx": "3000.0",
"marketPx": "3001.0",
"tpsl": "tp",
"resultingOid": 99999
}"#;
let data: WsTriggerTriggeredData = serde_json::from_str(json).unwrap();
assert_eq!(data.coin, Ustr::from("ETH"));
assert_eq!(data.oid, 67890);
assert_eq!(data.trigger_px, "3000.0");
assert_eq!(data.market_px, "3001.0");
assert_eq!(data.tpsl, HyperliquidTpSl::Tp);
assert_eq!(data.resulting_oid, Some(99999));
}
#[rstest]
fn test_ws_fill_data_deserialization_with_cloid_and_twap() {
let json = r#"{
"coin": "@107",
"px": "31.737",
"sz": "0.31",
"side": "B",
"time": 1769920606068,
"startPosition": "0.0",
"dir": "Buy",
"closedPnl": "0.0",
"hash": "0xc731e7561e5334a0c8ab043472ce7d01d400ff3bb95653726afa92a8dd570e8b",
"oid": 308086083674,
"crossed": true,
"fee": "0.00021699",
"tid": 812806034449156,
"cloid": "0xd211f1c27288259290850338d22132a0",
"feeToken": "HYPE",
"twapId": null
}"#;
let fill: WsFillData = serde_json::from_str(json).unwrap();
assert_eq!(fill.coin, "@107");
assert_eq!(fill.px, "31.737");
assert_eq!(fill.sz, "0.31");
assert_eq!(fill.side, HyperliquidSide::Buy);
assert_eq!(fill.oid, 308086083674);
assert!(fill.crossed);
assert_eq!(fill.fee, "0.00021699");
assert_eq!(fill.fee_token, "HYPE");
assert_eq!(
fill.cloid,
Some("0xd211f1c27288259290850338d22132a0".to_string())
);
assert!(fill.twap_id.is_none() || fill.twap_id == Some(serde_json::Value::Null));
}
#[rstest]
fn test_ws_user_fills_message_deserialization() {
let json = r#"{"channel":"user","data":{"fills":[{"coin":"@107","px":"31.737","sz":"0.31","side":"B","time":1769920606068,"startPosition":"0.0","dir":"Buy","closedPnl":"0.0","hash":"0xc731e7561e5334a0c8ab043472ce7d01d400ff3bb95653726afa92a8dd570e8b","oid":308086083674,"crossed":true,"fee":"0.00021699","tid":812806034449156,"cloid":"0xd211f1c27288259290850338d22132a0","feeToken":"HYPE","twapId":null}]}}"#;
let msg: HyperliquidWsMessage = serde_json::from_str(json).unwrap();
match msg {
HyperliquidWsMessage::User { data } => match data {
WsUserEventData::Fills { fills } => {
assert_eq!(fills.len(), 1);
let fill = &fills[0];
assert_eq!(fill.coin, "@107");
assert_eq!(fill.px, "31.737");
assert_eq!(
fill.cloid,
Some("0xd211f1c27288259290850338d22132a0".to_string())
);
}
_ => panic!("Expected Fills variant"),
},
_ => panic!("Expected User channel message"),
}
}
#[rstest]
fn test_ws_user_fills_message_with_builder_fee() {
let json = r#"{"channel":"user","data":{"fills":[{"coin":"BTC","px":"79146.0","sz":"0.001","side":"A","time":1769940855551,"startPosition":"0.00093","dir":"Long > Short","closedPnl":"0.046128","hash":"0x5f8b9c337a197c4061050434769793020e020019151c9b1203544786391d562b","oid":308254271324,"crossed":false,"fee":"0.019785","builderFee":"0.007914","tid":404237815023429,"cloid":"0x50663504b0f4fedea00080176229d94f","feeToken":"USDC","twapId":null}]}}"#;
let msg: HyperliquidWsMessage = serde_json::from_str(json).unwrap();
match msg {
HyperliquidWsMessage::User { data } => match data {
WsUserEventData::Fills { fills } => {
assert_eq!(fills.len(), 1);
let fill = &fills[0];
assert_eq!(fill.coin, "BTC");
assert_eq!(fill.px, "79146.0");
assert_eq!(fill.side, HyperliquidSide::Sell);
assert_eq!(fill.builder_fee, Some("0.007914".to_string()));
assert_eq!(fill.fee_token, "USDC");
}
_ => panic!("Expected Fills variant"),
},
_ => panic!("Expected User channel message"),
}
}
}
#[derive(Debug, Clone)]
pub enum NautilusWsMessage {
ExecutionReports(Vec<ExecutionReport>),
Trades(Vec<TradeTick>),
Quote(QuoteTick),
Deltas(OrderBookDeltas),
Candle(Bar),
MarkPrice(MarkPriceUpdate),
IndexPrice(IndexPriceUpdate),
FundingRate(FundingRateUpdate),
Error(String),
Reconnected,
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum ExecutionReport {
Order(OrderStatusReport),
Fill(FillReport),
}