Skip to main content

nautilus_dydx/websocket/
messages.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! WebSocket message types for dYdX public and private channels.
17
18use std::collections::HashMap;
19
20use chrono::{DateTime, Utc};
21use nautilus_model::enums::OrderSide;
22use serde::{Deserialize, Serialize};
23use serde_json::Value;
24use ustr::Ustr;
25
26use super::enums::{DydxWsChannel, DydxWsMessageType, DydxWsOperation};
27use crate::common::enums::{
28    DydxCandleResolution, DydxFillType, DydxLiquidity, DydxOrderStatus, DydxOrderType,
29    DydxPositionSide, DydxPositionStatus, DydxTickerType, DydxTimeInForce, DydxTradeType,
30};
31
32/// dYdX WebSocket subscription message.
33///
34/// # References
35///
36/// <https://docs.dydx.trade/developers/indexer/websockets>
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct DydxSubscription {
39    /// The operation type (subscribe/unsubscribe).
40    #[serde(rename = "type")]
41    pub op: DydxWsOperation,
42    /// The channel to subscribe to.
43    pub channel: DydxWsChannel,
44    /// Optional channel-specific identifier (e.g., market symbol).
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub id: Option<String>,
47}
48
49/// Generic subscription/unsubscription confirmation message.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct DydxWsSubscriptionMsg {
52    /// The message type ("subscribed" or "unsubscribed").
53    /// Note: This field may be consumed by serde's tag attribute when nested in tagged enums.
54    #[serde(rename = "type", default)]
55    pub msg_type: DydxWsMessageType,
56    /// The connection ID.
57    pub connection_id: String,
58    /// The message sequence number.
59    pub message_id: u64,
60    /// The channel name (may be consumed by outer serde tag).
61    #[serde(default)]
62    pub channel: DydxWsChannel,
63    /// Optional channel-specific identifier.
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub id: Option<String>,
66}
67
68/// Connection established message.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct DydxWsConnectedMsg {
71    /// The message type ("connected").
72    #[serde(rename = "type")]
73    pub msg_type: DydxWsMessageType,
74    /// The connection ID assigned by the server.
75    pub connection_id: String,
76    /// The message sequence number.
77    pub message_id: u64,
78}
79
80/// Single channel data update message.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct DydxWsChannelDataMsg {
83    /// The message type (may be absent for channel updates).
84    #[serde(rename = "type", default)]
85    pub msg_type: DydxWsMessageType,
86    /// The connection ID.
87    pub connection_id: String,
88    /// The message sequence number.
89    pub message_id: u64,
90    /// The channel name (optional since serde tag parsing may consume it).
91    #[serde(default)]
92    pub channel: DydxWsChannel,
93    /// Optional channel-specific identifier.
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub id: Option<String>,
96    /// The payload data (format depends on channel).
97    pub contents: Value,
98    /// API version.
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub version: Option<String>,
101}
102
103/// Batch channel data update message (multiple updates in one message).
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct DydxWsChannelBatchDataMsg {
106    /// The message type (may be absent for batch channel updates).
107    #[serde(rename = "type", default)]
108    pub msg_type: DydxWsMessageType,
109    /// The connection ID.
110    pub connection_id: String,
111    /// The message sequence number.
112    pub message_id: u64,
113    /// The channel name (optional since serde tag parsing may consume it).
114    #[serde(default)]
115    pub channel: DydxWsChannel,
116    /// Optional channel-specific identifier.
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub id: Option<String>,
119    /// Array of payload data.
120    pub contents: Value,
121    /// API version.
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub version: Option<String>,
124}
125
126/// Two-level WebSocket message envelope matching dYdX protocol.
127///
128/// First level: Routes by channel field (v4_subaccounts, v4_orderbook, etc.)
129/// Second level: Each channel variant contains type-tagged messages
130///
131/// # References
132///
133/// <https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/src/indexer/sock/messages.rs#L253>
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(tag = "channel")]
136pub enum DydxWsFeedMessage {
137    /// Subaccount updates (orders, fills, positions).
138    #[serde(rename = "v4_subaccounts")]
139    Subaccounts(DydxWsSubaccountsMessage),
140    /// Order book snapshots and updates.
141    #[serde(rename = "v4_orderbook")]
142    Orderbook(DydxWsOrderbookMessage),
143    /// Trade stream for specific market.
144    #[serde(rename = "v4_trades")]
145    Trades(DydxWsTradesMessage),
146    /// Market data for all markets.
147    #[serde(rename = "v4_markets")]
148    Markets(DydxWsMarketsMessage),
149    /// Candlestick/kline data.
150    #[serde(rename = "v4_candles")]
151    Candles(DydxWsCandlesMessage),
152    /// Parent subaccount updates (for isolated positions).
153    #[serde(rename = "v4_parent_subaccounts")]
154    ParentSubaccounts(DydxWsParentSubaccountsMessage),
155    /// Block height updates from chain.
156    #[serde(rename = "v4_block_height")]
157    BlockHeight(DydxWsBlockHeightMessage),
158}
159
160/// Subaccounts channel messages (second level, type-tagged).
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(tag = "type")]
163pub enum DydxWsSubaccountsMessage {
164    /// Initial subscription confirmation.
165    #[serde(rename = "subscribed")]
166    Subscribed(DydxWsSubaccountsSubscribed),
167    /// Channel data update.
168    #[serde(rename = "channel_data")]
169    ChannelData(DydxWsSubaccountsChannelData),
170    /// Unsubscription confirmation.
171    #[serde(rename = "unsubscribed")]
172    Unsubscribed(DydxWsSubscriptionMsg),
173}
174
175/// Orderbook channel messages (second level, type-tagged).
176#[derive(Debug, Clone, Serialize, Deserialize)]
177#[serde(tag = "type")]
178pub enum DydxWsOrderbookMessage {
179    /// Initial subscription confirmation.
180    #[serde(rename = "subscribed")]
181    Subscribed(DydxWsChannelDataMsg),
182    /// Channel data update.
183    #[serde(rename = "channel_data")]
184    ChannelData(DydxWsChannelDataMsg),
185    /// Batch channel data.
186    #[serde(rename = "channel_batch_data")]
187    ChannelBatchData(DydxWsChannelBatchDataMsg),
188    /// Unsubscription confirmation.
189    #[serde(rename = "unsubscribed")]
190    Unsubscribed(DydxWsSubscriptionMsg),
191}
192
193/// Trades channel messages (second level, type-tagged).
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(tag = "type")]
196pub enum DydxWsTradesMessage {
197    /// Initial subscription confirmation.
198    #[serde(rename = "subscribed")]
199    Subscribed(DydxWsChannelDataMsg),
200    /// Channel data update.
201    #[serde(rename = "channel_data")]
202    ChannelData(DydxWsChannelDataMsg),
203    /// Unsubscription confirmation.
204    #[serde(rename = "unsubscribed")]
205    Unsubscribed(DydxWsSubscriptionMsg),
206}
207
208/// Markets channel messages (second level, type-tagged).
209#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(tag = "type")]
211pub enum DydxWsMarketsMessage {
212    /// Initial subscription confirmation.
213    #[serde(rename = "subscribed")]
214    Subscribed(DydxWsChannelDataMsg),
215    /// Channel data update.
216    #[serde(rename = "channel_data")]
217    ChannelData(DydxWsChannelDataMsg),
218    /// Unsubscription confirmation.
219    #[serde(rename = "unsubscribed")]
220    Unsubscribed(DydxWsSubscriptionMsg),
221}
222
223/// Candles channel messages (second level, type-tagged).
224#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(tag = "type")]
226pub enum DydxWsCandlesMessage {
227    /// Initial subscription confirmation.
228    #[serde(rename = "subscribed")]
229    Subscribed(DydxWsChannelDataMsg),
230    /// Channel data update.
231    #[serde(rename = "channel_data")]
232    ChannelData(DydxWsChannelDataMsg),
233    /// Unsubscription confirmation.
234    #[serde(rename = "unsubscribed")]
235    Unsubscribed(DydxWsSubscriptionMsg),
236}
237
238/// Parent subaccounts channel messages (second level, type-tagged).
239#[derive(Debug, Clone, Serialize, Deserialize)]
240#[serde(tag = "type")]
241pub enum DydxWsParentSubaccountsMessage {
242    /// Initial subscription confirmation.
243    #[serde(rename = "subscribed")]
244    Subscribed(DydxWsChannelDataMsg),
245    /// Channel data update.
246    #[serde(rename = "channel_data")]
247    ChannelData(DydxWsChannelDataMsg),
248    /// Unsubscription confirmation.
249    #[serde(rename = "unsubscribed")]
250    Unsubscribed(DydxWsSubscriptionMsg),
251}
252
253/// Block height channel messages (second level, type-tagged).
254#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(tag = "type")]
256pub enum DydxWsBlockHeightMessage {
257    /// Initial subscription confirmation.
258    #[serde(rename = "subscribed")]
259    Subscribed(DydxWsBlockHeightSubscribedData),
260    /// Channel data update.
261    #[serde(rename = "channel_data")]
262    ChannelData(DydxWsBlockHeightChannelData),
263    /// Unsubscription confirmation.
264    #[serde(rename = "unsubscribed")]
265    Unsubscribed(DydxWsSubscriptionMsg),
266}
267
268/// Generic message structure for initial classification (fallback for non-channel messages).
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct DydxWsGenericMsg {
271    /// The message type.
272    #[serde(rename = "type")]
273    pub msg_type: DydxWsMessageType,
274    /// Optional connection ID.
275    #[serde(default, skip_serializing_if = "Option::is_none")]
276    pub connection_id: Option<String>,
277    /// Optional message sequence number.
278    #[serde(default, skip_serializing_if = "Option::is_none")]
279    pub message_id: Option<u64>,
280    /// Optional channel name.
281    #[serde(default, skip_serializing_if = "Option::is_none")]
282    pub channel: Option<DydxWsChannel>,
283    /// Optional channel-specific identifier.
284    #[serde(default, skip_serializing_if = "Option::is_none")]
285    pub id: Option<String>,
286    /// Optional error message.
287    #[serde(default, skip_serializing_if = "Option::is_none")]
288    pub message: Option<String>,
289}
290
291impl DydxWsGenericMsg {
292    /// Returns `true` if this message is an error.
293    #[must_use]
294    pub fn is_error(&self) -> bool {
295        self.msg_type == DydxWsMessageType::Error
296    }
297
298    /// Returns `true` if this message is a subscription confirmation.
299    #[must_use]
300    pub fn is_subscribed(&self) -> bool {
301        self.msg_type == DydxWsMessageType::Subscribed
302    }
303
304    /// Returns `true` if this message is an unsubscription confirmation.
305    #[must_use]
306    pub fn is_unsubscribed(&self) -> bool {
307        self.msg_type == DydxWsMessageType::Unsubscribed
308    }
309
310    /// Returns `true` if this message is a connection notification.
311    #[must_use]
312    pub fn is_connected(&self) -> bool {
313        self.msg_type == DydxWsMessageType::Connected
314    }
315
316    /// Returns `true` if this message is channel data.
317    #[must_use]
318    pub fn is_channel_data(&self) -> bool {
319        self.msg_type == DydxWsMessageType::ChannelData
320    }
321
322    /// Returns `true` if this message is batch channel data.
323    #[must_use]
324    pub fn is_channel_batch_data(&self) -> bool {
325        self.msg_type == DydxWsMessageType::ChannelBatchData
326    }
327
328    /// Returns `true` if this message is an unknown/unrecognized type.
329    #[must_use]
330    pub fn is_unknown(&self) -> bool {
331        self.msg_type == DydxWsMessageType::Unknown
332    }
333}
334
335/// Block height subscription confirmed contents.
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct DydxBlockHeightSubscribedContents {
338    pub height: String,
339    pub time: DateTime<Utc>,
340}
341
342/// Block height subscription confirmed message.
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct DydxWsBlockHeightSubscribedData {
345    pub connection_id: String,
346    pub message_id: u64,
347    pub id: String,
348    pub contents: DydxBlockHeightSubscribedContents,
349}
350
351/// Block height channel data contents.
352#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct DydxBlockHeightChannelContents {
354    #[serde(rename = "blockHeight")]
355    pub block_height: String,
356    pub time: DateTime<Utc>,
357}
358
359/// Block height channel data message.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct DydxWsBlockHeightChannelData {
362    pub connection_id: String,
363    pub message_id: u64,
364    pub id: String,
365    pub version: String,
366    pub contents: DydxBlockHeightChannelContents,
367}
368
369/// Oracle price data for a market (simple format from channel_data).
370#[derive(Debug, Clone, Serialize, Deserialize)]
371#[serde(rename_all = "camelCase")]
372pub struct DydxOraclePriceMarket {
373    /// Oracle price.
374    pub oracle_price: String,
375}
376
377/// Trading data for a market from v4_markets channel.
378///
379/// This matches the `TradingPerpetualMarket` type from dYdX WebSocket docs.
380/// All fields are optional since WebSocket may send partial updates.
381#[derive(Debug, Clone, Serialize, Deserialize)]
382#[serde(rename_all = "camelCase")]
383pub struct DydxMarketTradingUpdate {
384    /// Market ticker (e.g., "BTC-USD").
385    #[serde(default, skip_serializing_if = "Option::is_none")]
386    pub ticker: Option<String>,
387    /// Market status.
388    #[serde(default, skip_serializing_if = "Option::is_none")]
389    pub status: Option<crate::common::enums::DydxMarketStatus>,
390    /// CLOB pair ID.
391    #[serde(default, skip_serializing_if = "Option::is_none")]
392    pub clob_pair_id: Option<String>,
393    /// Atomic resolution for quantization.
394    #[serde(default, skip_serializing_if = "Option::is_none")]
395    pub atomic_resolution: Option<i32>,
396    /// Quantum conversion exponent.
397    #[serde(default, skip_serializing_if = "Option::is_none")]
398    pub quantum_conversion_exponent: Option<i32>,
399    /// Step base quantums.
400    #[serde(default, skip_serializing_if = "Option::is_none")]
401    pub step_base_quantums: Option<i32>,
402    /// Subticks per tick.
403    #[serde(default, skip_serializing_if = "Option::is_none")]
404    pub subticks_per_tick: Option<i32>,
405    /// Initial margin fraction.
406    #[serde(default, skip_serializing_if = "Option::is_none")]
407    pub initial_margin_fraction: Option<String>,
408    /// Maintenance margin fraction.
409    #[serde(default, skip_serializing_if = "Option::is_none")]
410    pub maintenance_margin_fraction: Option<String>,
411    /// Base asset symbol.
412    #[serde(default, skip_serializing_if = "Option::is_none")]
413    pub base_asset: Option<String>,
414    /// Quote asset symbol.
415    #[serde(default, skip_serializing_if = "Option::is_none")]
416    pub quote_asset: Option<String>,
417    /// Open interest.
418    #[serde(default, skip_serializing_if = "Option::is_none")]
419    pub open_interest: Option<String>,
420    /// 24-hour price change.
421    #[serde(default, skip_serializing_if = "Option::is_none")]
422    pub price_change_24h: Option<String>,
423    /// 24-hour volume.
424    #[serde(default, skip_serializing_if = "Option::is_none")]
425    pub volume_24h: Option<String>,
426    /// 24-hour trade count.
427    #[serde(default, skip_serializing_if = "Option::is_none")]
428    pub trades_24h: Option<u64>,
429    /// Maximum position size.
430    #[serde(default, skip_serializing_if = "Option::is_none")]
431    pub max_position_size: Option<String>,
432    /// Incremental position size.
433    #[serde(default, skip_serializing_if = "Option::is_none")]
434    pub incremental_position_size: Option<String>,
435    /// Base position size.
436    #[serde(default, skip_serializing_if = "Option::is_none")]
437    pub base_position_size: Option<String>,
438    /// Next funding rate for the market.
439    #[serde(default, skip_serializing_if = "Option::is_none")]
440    pub next_funding_rate: Option<String>,
441    /// Oracle price (present in initial subscription snapshot).
442    #[serde(default, skip_serializing_if = "Option::is_none")]
443    pub oracle_price: Option<String>,
444}
445
446/// Contents of v4_markets messages (both subscription snapshots and channel_data updates).
447///
448/// Initial subscription responses use `markets` with full market objects.
449/// Subsequent `channel_data` updates use `oraclePrices` and `trading` with partial deltas.
450#[derive(Debug, Clone, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct DydxMarketsContents {
453    /// Oracle prices by market symbol (channel_data updates).
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub oracle_prices: Option<HashMap<String, DydxOraclePriceMarket>>,
456    /// Trading data by market symbol (channel_data updates).
457    #[serde(default, skip_serializing_if = "Option::is_none")]
458    pub trading: Option<HashMap<String, DydxMarketTradingUpdate>>,
459    /// Full market data by market symbol (initial subscription snapshot).
460    #[serde(default, skip_serializing_if = "Option::is_none")]
461    pub markets: Option<HashMap<String, DydxMarketTradingUpdate>>,
462}
463
464/// Trade message from v4_trades channel.
465#[derive(Debug, Clone, Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct DydxTrade {
468    /// Trade ID.
469    pub id: String,
470    /// Order side (BUY/SELL).
471    pub side: OrderSide,
472    /// Trade size.
473    pub size: String,
474    /// Trade price.
475    pub price: String,
476    /// Trade timestamp.
477    pub created_at: DateTime<Utc>,
478    /// Trade type.
479    #[serde(rename = "type")]
480    pub trade_type: DydxTradeType,
481    /// Block height (optional).
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub created_at_height: Option<String>,
484}
485
486/// Contents of v4_trades channel_data message.
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct DydxTradeContents {
489    /// Array of trades.
490    pub trades: Vec<DydxTrade>,
491}
492
493/// Candle/bar data from v4_candles channel.
494#[derive(Debug, Clone, Serialize, Deserialize)]
495#[serde(rename_all = "camelCase")]
496pub struct DydxCandle {
497    /// Base token volume (may be absent in partial updates).
498    #[serde(default)]
499    pub base_token_volume: Option<String>,
500    /// Close price.
501    pub close: String,
502    /// High price.
503    pub high: String,
504    /// Low price.
505    pub low: String,
506    /// Open price.
507    pub open: String,
508    /// Resolution/timeframe.
509    pub resolution: DydxCandleResolution,
510    /// Start time.
511    pub started_at: DateTime<Utc>,
512    /// Starting open interest.
513    pub starting_open_interest: String,
514    /// Market ticker.
515    pub ticker: Ustr,
516    /// Number of trades.
517    pub trades: i64,
518    /// USD volume.
519    pub usd_volume: String,
520    /// Orderbook mid price at close (optional).
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub orderbook_mid_price_close: Option<String>,
523    /// Orderbook mid price at open (optional).
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub orderbook_mid_price_open: Option<String>,
526}
527
528/// Order book price level (price, size tuple).
529pub type PriceLevel = (String, String);
530
531/// Contents of v4_orderbook channel_data/channel_batch_data messages.
532#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct DydxOrderbookContents {
534    /// Bid price levels.
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub bids: Option<Vec<PriceLevel>>,
537    /// Ask price levels.
538    #[serde(skip_serializing_if = "Option::is_none")]
539    pub asks: Option<Vec<PriceLevel>>,
540}
541
542/// Price level for orderbook snapshot (structured format).
543#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct DydxPriceLevel {
545    /// Price.
546    pub price: String,
547    /// Size.
548    pub size: String,
549}
550
551/// Contents of v4_orderbook subscribed (snapshot) message.
552#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct DydxOrderbookSnapshotContents {
554    /// Bid price levels.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub bids: Option<Vec<DydxPriceLevel>>,
557    /// Ask price levels.
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub asks: Option<Vec<DydxPriceLevel>>,
560}
561
562/// Subaccount balance update.
563#[derive(Debug, Clone, Serialize, Deserialize)]
564pub struct DydxAssetBalance {
565    pub symbol: Ustr,
566    pub side: DydxPositionSide,
567    pub size: String,
568    #[serde(rename = "assetId")]
569    pub asset_id: String,
570}
571
572/// Subaccount perpetual position.
573#[derive(Debug, Clone, Serialize, Deserialize)]
574pub struct DydxPerpetualPosition {
575    pub market: Ustr,
576    pub status: DydxPositionStatus,
577    pub side: DydxPositionSide,
578    pub size: String,
579    #[serde(rename = "maxSize")]
580    pub max_size: String,
581    #[serde(rename = "entryPrice")]
582    pub entry_price: String,
583    #[serde(rename = "exitPrice")]
584    pub exit_price: Option<String>,
585    #[serde(rename = "realizedPnl")]
586    pub realized_pnl: String,
587    #[serde(rename = "unrealizedPnl")]
588    pub unrealized_pnl: String,
589    #[serde(rename = "createdAt")]
590    pub created_at: String,
591    #[serde(rename = "closedAt")]
592    pub closed_at: Option<String>,
593    #[serde(rename = "sumOpen")]
594    pub sum_open: String,
595    #[serde(rename = "sumClose")]
596    pub sum_close: String,
597    #[serde(rename = "netFunding")]
598    pub net_funding: String,
599}
600
601/// Subaccount information.
602#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct DydxSubaccountInfo {
604    pub address: String,
605    #[serde(rename = "subaccountNumber")]
606    pub subaccount_number: u32,
607    pub equity: String,
608    #[serde(rename = "freeCollateral")]
609    pub free_collateral: String,
610    #[serde(rename = "openPerpetualPositions")]
611    pub open_perpetual_positions: Option<HashMap<String, DydxPerpetualPosition>>,
612    #[serde(rename = "assetPositions")]
613    pub asset_positions: Option<HashMap<String, DydxAssetBalance>>,
614    #[serde(rename = "marginEnabled")]
615    pub margin_enabled: bool,
616    #[serde(rename = "updatedAtHeight")]
617    pub updated_at_height: String,
618    #[serde(rename = "latestProcessedBlockHeight")]
619    pub latest_processed_block_height: String,
620}
621
622/// Order message from WebSocket.
623#[derive(Debug, Clone, Serialize, Deserialize)]
624pub struct DydxWsOrderSubaccountMessageContents {
625    pub id: String,
626    #[serde(rename = "subaccountId")]
627    pub subaccount_id: String,
628    #[serde(rename = "clientId")]
629    pub client_id: String,
630    #[serde(rename = "clobPairId")]
631    pub clob_pair_id: String,
632    pub side: OrderSide,
633    pub size: String,
634    pub price: String,
635    pub status: DydxOrderStatus,
636    #[serde(rename = "type")]
637    pub order_type: DydxOrderType,
638    #[serde(rename = "timeInForce")]
639    pub time_in_force: DydxTimeInForce,
640    #[serde(rename = "postOnly")]
641    pub post_only: bool,
642    #[serde(rename = "reduceOnly")]
643    pub reduce_only: bool,
644    #[serde(rename = "orderFlags")]
645    pub order_flags: String,
646    #[serde(rename = "goodTilBlock")]
647    pub good_til_block: Option<String>,
648    #[serde(rename = "goodTilBlockTime")]
649    pub good_til_block_time: Option<String>,
650    #[serde(rename = "createdAtHeight")]
651    pub created_at_height: Option<String>,
652    #[serde(rename = "clientMetadata")]
653    pub client_metadata: Option<String>,
654    #[serde(rename = "triggerPrice")]
655    pub trigger_price: Option<String>,
656    #[serde(rename = "totalFilled")]
657    pub total_filled: Option<String>,
658    #[serde(rename = "updatedAt")]
659    pub updated_at: Option<String>,
660    #[serde(rename = "updatedAtHeight")]
661    pub updated_at_height: Option<String>,
662}
663
664/// Fill message from WebSocket.
665#[derive(Debug, Clone, Serialize, Deserialize)]
666pub struct DydxWsFillSubaccountMessageContents {
667    pub id: String,
668    #[serde(rename = "subaccountId")]
669    pub subaccount_id: String,
670    pub side: OrderSide,
671    pub liquidity: DydxLiquidity,
672    #[serde(rename = "type")]
673    pub fill_type: DydxFillType,
674    #[serde(alias = "ticker")]
675    pub market: Ustr,
676    #[serde(rename = "marketType", default)]
677    pub market_type: Option<DydxTickerType>,
678    pub price: String,
679    pub size: String,
680    pub fee: String,
681    #[serde(rename = "createdAt")]
682    pub created_at: String,
683    #[serde(rename = "createdAtHeight")]
684    pub created_at_height: Option<String>,
685    #[serde(rename = "orderId")]
686    pub order_id: Option<String>,
687    #[serde(rename = "clientMetadata")]
688    pub client_metadata: Option<String>,
689}
690
691/// Subaccount subscription contents.
692#[derive(Debug, Clone, Serialize, Deserialize)]
693pub struct DydxWsSubaccountsSubscribedContents {
694    pub subaccount: Option<DydxSubaccountInfo>,
695}
696
697/// Subaccounts subscription confirmed message.
698#[derive(Debug, Clone, Serialize, Deserialize)]
699pub struct DydxWsSubaccountsSubscribed {
700    pub connection_id: String,
701    pub message_id: u64,
702    pub id: String,
703    pub contents: DydxWsSubaccountsSubscribedContents,
704}
705
706/// Subaccounts channel data contents.
707#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct DydxWsSubaccountsChannelContents {
709    pub orders: Option<Vec<DydxWsOrderSubaccountMessageContents>>,
710    pub fills: Option<Vec<DydxWsFillSubaccountMessageContents>>,
711}
712
713/// Subaccounts channel data message.
714#[derive(Debug, Clone, Serialize, Deserialize)]
715pub struct DydxWsSubaccountsChannelData {
716    pub connection_id: String,
717    pub message_id: u64,
718    pub id: String,
719    pub version: String,
720    pub contents: DydxWsSubaccountsChannelContents,
721}