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/// General WebSocket message structure for routing.
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct DydxWsMessageGeneral {
129    #[serde(rename = "type")]
130    pub msg_type: Option<DydxWsMessageType>,
131    pub connection_id: Option<String>,
132    pub message_id: Option<u64>,
133    pub channel: Option<DydxWsChannel>,
134    pub id: Option<String>,
135    pub message: Option<String>,
136}
137
138/// Two-level WebSocket message envelope matching dYdX protocol.
139///
140/// First level: Routes by channel field (v4_subaccounts, v4_orderbook, etc.)
141/// Second level: Each channel variant contains type-tagged messages
142///
143/// # References
144///
145/// <https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/src/indexer/sock/messages.rs#L253>
146#[derive(Debug, Clone, Serialize, Deserialize)]
147#[serde(tag = "channel")]
148pub enum DydxWsFeedMessage {
149    /// Subaccount updates (orders, fills, positions).
150    #[serde(rename = "v4_subaccounts")]
151    Subaccounts(DydxWsSubaccountsMessage),
152    /// Order book snapshots and updates.
153    #[serde(rename = "v4_orderbook")]
154    Orderbook(DydxWsOrderbookMessage),
155    /// Trade stream for specific market.
156    #[serde(rename = "v4_trades")]
157    Trades(DydxWsTradesMessage),
158    /// Market data for all markets.
159    #[serde(rename = "v4_markets")]
160    Markets(DydxWsMarketsMessage),
161    /// Candlestick/kline data.
162    #[serde(rename = "v4_candles")]
163    Candles(DydxWsCandlesMessage),
164    /// Parent subaccount updates (for isolated positions).
165    #[serde(rename = "v4_parent_subaccounts")]
166    ParentSubaccounts(DydxWsParentSubaccountsMessage),
167    /// Block height updates from chain.
168    #[serde(rename = "v4_block_height")]
169    BlockHeight(DydxWsBlockHeightMessage),
170}
171
172/// Subaccounts channel messages (second level, type-tagged).
173#[derive(Debug, Clone, Serialize, Deserialize)]
174#[serde(tag = "type")]
175pub enum DydxWsSubaccountsMessage {
176    /// Initial subscription confirmation.
177    #[serde(rename = "subscribed")]
178    Subscribed(DydxWsSubaccountsSubscribed),
179    /// Channel data update.
180    #[serde(rename = "channel_data")]
181    ChannelData(DydxWsSubaccountsChannelData),
182    /// Unsubscription confirmation.
183    #[serde(rename = "unsubscribed")]
184    Unsubscribed(DydxWsSubscriptionMsg),
185}
186
187/// Orderbook channel messages (second level, type-tagged).
188#[derive(Debug, Clone, Serialize, Deserialize)]
189#[serde(tag = "type")]
190pub enum DydxWsOrderbookMessage {
191    /// Initial subscription confirmation.
192    #[serde(rename = "subscribed")]
193    Subscribed(DydxWsChannelDataMsg),
194    /// Channel data update.
195    #[serde(rename = "channel_data")]
196    ChannelData(DydxWsChannelDataMsg),
197    /// Batch channel data.
198    #[serde(rename = "channel_batch_data")]
199    ChannelBatchData(DydxWsChannelBatchDataMsg),
200    /// Unsubscription confirmation.
201    #[serde(rename = "unsubscribed")]
202    Unsubscribed(DydxWsSubscriptionMsg),
203}
204
205/// Trades channel messages (second level, type-tagged).
206#[derive(Debug, Clone, Serialize, Deserialize)]
207#[serde(tag = "type")]
208pub enum DydxWsTradesMessage {
209    /// Initial subscription confirmation.
210    #[serde(rename = "subscribed")]
211    Subscribed(DydxWsChannelDataMsg),
212    /// Channel data update.
213    #[serde(rename = "channel_data")]
214    ChannelData(DydxWsChannelDataMsg),
215    /// Unsubscription confirmation.
216    #[serde(rename = "unsubscribed")]
217    Unsubscribed(DydxWsSubscriptionMsg),
218}
219
220/// Markets channel messages (second level, type-tagged).
221#[derive(Debug, Clone, Serialize, Deserialize)]
222#[serde(tag = "type")]
223pub enum DydxWsMarketsMessage {
224    /// Initial subscription confirmation.
225    #[serde(rename = "subscribed")]
226    Subscribed(DydxWsChannelDataMsg),
227    /// Channel data update.
228    #[serde(rename = "channel_data")]
229    ChannelData(DydxWsChannelDataMsg),
230    /// Unsubscription confirmation.
231    #[serde(rename = "unsubscribed")]
232    Unsubscribed(DydxWsSubscriptionMsg),
233}
234
235/// Candles channel messages (second level, type-tagged).
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(tag = "type")]
238pub enum DydxWsCandlesMessage {
239    /// Initial subscription confirmation.
240    #[serde(rename = "subscribed")]
241    Subscribed(DydxWsChannelDataMsg),
242    /// Channel data update.
243    #[serde(rename = "channel_data")]
244    ChannelData(DydxWsChannelDataMsg),
245    /// Unsubscription confirmation.
246    #[serde(rename = "unsubscribed")]
247    Unsubscribed(DydxWsSubscriptionMsg),
248}
249
250/// Parent subaccounts channel messages (second level, type-tagged).
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(tag = "type")]
253pub enum DydxWsParentSubaccountsMessage {
254    /// Initial subscription confirmation.
255    #[serde(rename = "subscribed")]
256    Subscribed(DydxWsChannelDataMsg),
257    /// Channel data update.
258    #[serde(rename = "channel_data")]
259    ChannelData(DydxWsChannelDataMsg),
260    /// Unsubscription confirmation.
261    #[serde(rename = "unsubscribed")]
262    Unsubscribed(DydxWsSubscriptionMsg),
263}
264
265/// Block height channel messages (second level, type-tagged).
266#[derive(Debug, Clone, Serialize, Deserialize)]
267#[serde(tag = "type")]
268pub enum DydxWsBlockHeightMessage {
269    /// Initial subscription confirmation.
270    #[serde(rename = "subscribed")]
271    Subscribed(DydxWsBlockHeightSubscribedData),
272    /// Channel data update.
273    #[serde(rename = "channel_data")]
274    ChannelData(DydxWsBlockHeightChannelData),
275    /// Unsubscription confirmation.
276    #[serde(rename = "unsubscribed")]
277    Unsubscribed(DydxWsSubscriptionMsg),
278}
279
280/// Generic message structure for initial classification (fallback for non-channel messages).
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct DydxWsGenericMsg {
283    /// The message type.
284    #[serde(rename = "type")]
285    pub msg_type: DydxWsMessageType,
286    /// Optional connection ID.
287    #[serde(default, skip_serializing_if = "Option::is_none")]
288    pub connection_id: Option<String>,
289    /// Optional message sequence number.
290    #[serde(default, skip_serializing_if = "Option::is_none")]
291    pub message_id: Option<u64>,
292    /// Optional channel name.
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub channel: Option<DydxWsChannel>,
295    /// Optional channel-specific identifier.
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub id: Option<String>,
298    /// Optional error message.
299    #[serde(default, skip_serializing_if = "Option::is_none")]
300    pub message: Option<String>,
301}
302
303impl DydxWsGenericMsg {
304    /// Returns `true` if this message is an error.
305    #[must_use]
306    pub fn is_error(&self) -> bool {
307        self.msg_type == DydxWsMessageType::Error
308    }
309
310    /// Returns `true` if this message is a subscription confirmation.
311    #[must_use]
312    pub fn is_subscribed(&self) -> bool {
313        self.msg_type == DydxWsMessageType::Subscribed
314    }
315
316    /// Returns `true` if this message is an unsubscription confirmation.
317    #[must_use]
318    pub fn is_unsubscribed(&self) -> bool {
319        self.msg_type == DydxWsMessageType::Unsubscribed
320    }
321
322    /// Returns `true` if this message is a connection notification.
323    #[must_use]
324    pub fn is_connected(&self) -> bool {
325        self.msg_type == DydxWsMessageType::Connected
326    }
327
328    /// Returns `true` if this message is channel data.
329    #[must_use]
330    pub fn is_channel_data(&self) -> bool {
331        self.msg_type == DydxWsMessageType::ChannelData
332    }
333
334    /// Returns `true` if this message is batch channel data.
335    #[must_use]
336    pub fn is_channel_batch_data(&self) -> bool {
337        self.msg_type == DydxWsMessageType::ChannelBatchData
338    }
339
340    /// Returns `true` if this message is an unknown/unrecognized type.
341    #[must_use]
342    pub fn is_unknown(&self) -> bool {
343        self.msg_type == DydxWsMessageType::Unknown
344    }
345}
346
347/// Block height subscription confirmed contents.
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct DydxBlockHeightSubscribedContents {
350    pub height: String,
351    pub time: DateTime<Utc>,
352}
353
354/// Block height subscription confirmed message.
355#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct DydxWsBlockHeightSubscribedData {
357    pub connection_id: String,
358    pub message_id: u64,
359    pub id: String,
360    pub contents: DydxBlockHeightSubscribedContents,
361}
362
363/// Block height channel data contents.
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct DydxBlockHeightChannelContents {
366    #[serde(rename = "blockHeight")]
367    pub block_height: String,
368    pub time: DateTime<Utc>,
369}
370
371/// Block height channel data message.
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct DydxWsBlockHeightChannelData {
374    pub connection_id: String,
375    pub message_id: u64,
376    pub id: String,
377    pub version: String,
378    pub contents: DydxBlockHeightChannelContents,
379}
380
381/// Oracle price data for a market (full format from subscribed message).
382#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct DydxOraclePriceMarketFull {
384    #[serde(rename = "oraclePrice")]
385    pub oracle_price: String,
386    #[serde(rename = "effectiveAt")]
387    pub effective_at: String,
388    #[serde(rename = "effectiveAtHeight")]
389    pub effective_at_height: String,
390    #[serde(rename = "marketId")]
391    pub market_id: u32,
392}
393
394/// Oracle price data for a market (simple format from channel_data).
395#[derive(Debug, Clone, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct DydxOraclePriceMarket {
398    /// Oracle price.
399    pub oracle_price: String,
400}
401
402/// Trading data for a market from v4_markets channel.
403///
404/// This matches the `TradingPerpetualMarket` type from dYdX WebSocket docs.
405/// All fields are optional since WebSocket may send partial updates.
406#[derive(Debug, Clone, Serialize, Deserialize)]
407#[serde(rename_all = "camelCase")]
408pub struct DydxMarketTradingUpdate {
409    /// Market ticker (e.g., "BTC-USD").
410    #[serde(default, skip_serializing_if = "Option::is_none")]
411    pub ticker: Option<String>,
412    /// Market status.
413    #[serde(default, skip_serializing_if = "Option::is_none")]
414    pub status: Option<crate::common::enums::DydxMarketStatus>,
415    /// CLOB pair ID.
416    #[serde(default, skip_serializing_if = "Option::is_none")]
417    pub clob_pair_id: Option<String>,
418    /// Atomic resolution for quantization.
419    #[serde(default, skip_serializing_if = "Option::is_none")]
420    pub atomic_resolution: Option<i32>,
421    /// Quantum conversion exponent.
422    #[serde(default, skip_serializing_if = "Option::is_none")]
423    pub quantum_conversion_exponent: Option<i32>,
424    /// Step base quantums.
425    #[serde(default, skip_serializing_if = "Option::is_none")]
426    pub step_base_quantums: Option<i32>,
427    /// Subticks per tick.
428    #[serde(default, skip_serializing_if = "Option::is_none")]
429    pub subticks_per_tick: Option<i32>,
430    /// Initial margin fraction.
431    #[serde(default, skip_serializing_if = "Option::is_none")]
432    pub initial_margin_fraction: Option<String>,
433    /// Maintenance margin fraction.
434    #[serde(default, skip_serializing_if = "Option::is_none")]
435    pub maintenance_margin_fraction: Option<String>,
436    /// Base asset symbol.
437    #[serde(default, skip_serializing_if = "Option::is_none")]
438    pub base_asset: Option<String>,
439    /// Quote asset symbol.
440    #[serde(default, skip_serializing_if = "Option::is_none")]
441    pub quote_asset: Option<String>,
442    /// Open interest.
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub open_interest: Option<String>,
445    /// 24-hour price change.
446    #[serde(default, skip_serializing_if = "Option::is_none")]
447    pub price_change_24h: Option<String>,
448    /// 24-hour volume.
449    #[serde(default, skip_serializing_if = "Option::is_none")]
450    pub volume_24h: Option<String>,
451    /// 24-hour trade count.
452    #[serde(default, skip_serializing_if = "Option::is_none")]
453    pub trades_24h: Option<u64>,
454    /// Maximum position size.
455    #[serde(default, skip_serializing_if = "Option::is_none")]
456    pub max_position_size: Option<String>,
457    /// Incremental position size.
458    #[serde(default, skip_serializing_if = "Option::is_none")]
459    pub incremental_position_size: Option<String>,
460    /// Base position size.
461    #[serde(default, skip_serializing_if = "Option::is_none")]
462    pub base_position_size: Option<String>,
463    /// Next funding rate for the market.
464    #[serde(default, skip_serializing_if = "Option::is_none")]
465    pub next_funding_rate: Option<String>,
466}
467
468/// Market message contents.
469#[derive(Debug, Clone, Serialize, Deserialize)]
470pub struct DydxMarketMessageContents {
471    #[serde(rename = "oraclePrices")]
472    pub oracle_prices: Option<HashMap<String, DydxOraclePriceMarketFull>>,
473    pub trading: Option<Value>,
474}
475
476/// Markets channel data message.
477#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct DydxWsMarketChannelData {
479    #[serde(rename = "type")]
480    pub msg_type: DydxWsMessageType,
481    pub channel: DydxWsChannel,
482    pub contents: DydxMarketMessageContents,
483    pub version: String,
484    pub message_id: u64,
485    pub connection_id: Option<String>,
486    pub id: Option<String>,
487}
488
489/// Markets subscription confirmed message.
490#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct DydxWsMarketSubscribed {
492    #[serde(rename = "type")]
493    pub msg_type: DydxWsMessageType,
494    pub connection_id: String,
495    pub message_id: u64,
496    pub channel: DydxWsChannel,
497    pub contents: Value,
498}
499
500/// Contents of v4_markets channel_data message (simple format).
501#[derive(Debug, Clone, Serialize, Deserialize)]
502#[serde(rename_all = "camelCase")]
503pub struct DydxMarketsContents {
504    /// Oracle prices by market symbol.
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub oracle_prices: Option<HashMap<String, DydxOraclePriceMarket>>,
507    /// Trading data by market symbol (contains funding rates).
508    #[serde(default, skip_serializing_if = "Option::is_none")]
509    pub trading: Option<HashMap<String, DydxMarketTradingUpdate>>,
510}
511
512/// Trade message from v4_trades channel.
513#[derive(Debug, Clone, Serialize, Deserialize)]
514#[serde(rename_all = "camelCase")]
515pub struct DydxTrade {
516    /// Trade ID.
517    pub id: String,
518    /// Order side (BUY/SELL).
519    pub side: OrderSide,
520    /// Trade size.
521    pub size: String,
522    /// Trade price.
523    pub price: String,
524    /// Trade timestamp.
525    pub created_at: DateTime<Utc>,
526    /// Trade type.
527    #[serde(rename = "type")]
528    pub trade_type: DydxTradeType,
529    /// Block height (optional).
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub created_at_height: Option<String>,
532}
533
534/// Contents of v4_trades channel_data message.
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct DydxTradeContents {
537    /// Array of trades.
538    pub trades: Vec<DydxTrade>,
539}
540
541/// Candle/bar data from v4_candles channel.
542#[derive(Debug, Clone, Serialize, Deserialize)]
543#[serde(rename_all = "camelCase")]
544pub struct DydxCandle {
545    /// Base token volume (may be absent in partial updates).
546    #[serde(default)]
547    pub base_token_volume: Option<String>,
548    /// Close price.
549    pub close: String,
550    /// High price.
551    pub high: String,
552    /// Low price.
553    pub low: String,
554    /// Open price.
555    pub open: String,
556    /// Resolution/timeframe.
557    pub resolution: DydxCandleResolution,
558    /// Start time.
559    pub started_at: DateTime<Utc>,
560    /// Starting open interest.
561    pub starting_open_interest: String,
562    /// Market ticker.
563    pub ticker: Ustr,
564    /// Number of trades.
565    pub trades: i64,
566    /// USD volume.
567    pub usd_volume: String,
568    /// Orderbook mid price at close (optional).
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub orderbook_mid_price_close: Option<String>,
571    /// Orderbook mid price at open (optional).
572    #[serde(skip_serializing_if = "Option::is_none")]
573    pub orderbook_mid_price_open: Option<String>,
574}
575
576/// Order book price level (price, size tuple).
577pub type PriceLevel = (String, String);
578
579/// Contents of v4_orderbook channel_data/channel_batch_data messages.
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct DydxOrderbookContents {
582    /// Bid price levels.
583    #[serde(skip_serializing_if = "Option::is_none")]
584    pub bids: Option<Vec<PriceLevel>>,
585    /// Ask price levels.
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub asks: Option<Vec<PriceLevel>>,
588}
589
590/// Price level for orderbook snapshot (structured format).
591#[derive(Debug, Clone, Serialize, Deserialize)]
592pub struct DydxPriceLevel {
593    /// Price.
594    pub price: String,
595    /// Size.
596    pub size: String,
597}
598
599/// Contents of v4_orderbook subscribed (snapshot) message.
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct DydxOrderbookSnapshotContents {
602    /// Bid price levels.
603    #[serde(skip_serializing_if = "Option::is_none")]
604    pub bids: Option<Vec<DydxPriceLevel>>,
605    /// Ask price levels.
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub asks: Option<Vec<DydxPriceLevel>>,
608}
609
610/// Subaccount balance update.
611#[derive(Debug, Clone, Serialize, Deserialize)]
612pub struct DydxAssetBalance {
613    pub symbol: Ustr,
614    pub side: DydxPositionSide,
615    pub size: String,
616    #[serde(rename = "assetId")]
617    pub asset_id: String,
618}
619
620/// Subaccount perpetual position.
621#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct DydxPerpetualPosition {
623    pub market: Ustr,
624    pub status: DydxPositionStatus,
625    pub side: DydxPositionSide,
626    pub size: String,
627    #[serde(rename = "maxSize")]
628    pub max_size: String,
629    #[serde(rename = "entryPrice")]
630    pub entry_price: String,
631    #[serde(rename = "exitPrice")]
632    pub exit_price: Option<String>,
633    #[serde(rename = "realizedPnl")]
634    pub realized_pnl: String,
635    #[serde(rename = "unrealizedPnl")]
636    pub unrealized_pnl: String,
637    #[serde(rename = "createdAt")]
638    pub created_at: String,
639    #[serde(rename = "closedAt")]
640    pub closed_at: Option<String>,
641    #[serde(rename = "sumOpen")]
642    pub sum_open: String,
643    #[serde(rename = "sumClose")]
644    pub sum_close: String,
645    #[serde(rename = "netFunding")]
646    pub net_funding: String,
647}
648
649/// Subaccount information.
650#[derive(Debug, Clone, Serialize, Deserialize)]
651pub struct DydxSubaccountInfo {
652    pub address: String,
653    #[serde(rename = "subaccountNumber")]
654    pub subaccount_number: u32,
655    pub equity: String,
656    #[serde(rename = "freeCollateral")]
657    pub free_collateral: String,
658    #[serde(rename = "openPerpetualPositions")]
659    pub open_perpetual_positions: Option<HashMap<String, DydxPerpetualPosition>>,
660    #[serde(rename = "assetPositions")]
661    pub asset_positions: Option<HashMap<String, DydxAssetBalance>>,
662    #[serde(rename = "marginEnabled")]
663    pub margin_enabled: bool,
664    #[serde(rename = "updatedAtHeight")]
665    pub updated_at_height: String,
666    #[serde(rename = "latestProcessedBlockHeight")]
667    pub latest_processed_block_height: String,
668}
669
670/// Order message from WebSocket.
671#[derive(Debug, Clone, Serialize, Deserialize)]
672pub struct DydxWsOrderSubaccountMessageContents {
673    pub id: String,
674    #[serde(rename = "subaccountId")]
675    pub subaccount_id: String,
676    #[serde(rename = "clientId")]
677    pub client_id: String,
678    #[serde(rename = "clobPairId")]
679    pub clob_pair_id: String,
680    pub side: OrderSide,
681    pub size: String,
682    pub price: String,
683    pub status: DydxOrderStatus,
684    #[serde(rename = "type")]
685    pub order_type: DydxOrderType,
686    #[serde(rename = "timeInForce")]
687    pub time_in_force: DydxTimeInForce,
688    #[serde(rename = "postOnly")]
689    pub post_only: bool,
690    #[serde(rename = "reduceOnly")]
691    pub reduce_only: bool,
692    #[serde(rename = "orderFlags")]
693    pub order_flags: String,
694    #[serde(rename = "goodTilBlock")]
695    pub good_til_block: Option<String>,
696    #[serde(rename = "goodTilBlockTime")]
697    pub good_til_block_time: Option<String>,
698    #[serde(rename = "createdAtHeight")]
699    pub created_at_height: Option<String>,
700    #[serde(rename = "clientMetadata")]
701    pub client_metadata: Option<String>,
702    #[serde(rename = "triggerPrice")]
703    pub trigger_price: Option<String>,
704    #[serde(rename = "totalFilled")]
705    pub total_filled: Option<String>,
706    #[serde(rename = "updatedAt")]
707    pub updated_at: Option<String>,
708    #[serde(rename = "updatedAtHeight")]
709    pub updated_at_height: Option<String>,
710}
711
712/// Fill message from WebSocket.
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct DydxWsFillSubaccountMessageContents {
715    pub id: String,
716    #[serde(rename = "subaccountId")]
717    pub subaccount_id: String,
718    pub side: OrderSide,
719    pub liquidity: DydxLiquidity,
720    #[serde(rename = "type")]
721    pub fill_type: DydxFillType,
722    #[serde(alias = "ticker")]
723    pub market: Ustr,
724    #[serde(rename = "marketType", default)]
725    pub market_type: Option<DydxTickerType>,
726    pub price: String,
727    pub size: String,
728    pub fee: String,
729    #[serde(rename = "createdAt")]
730    pub created_at: String,
731    #[serde(rename = "createdAtHeight")]
732    pub created_at_height: Option<String>,
733    #[serde(rename = "orderId")]
734    pub order_id: Option<String>,
735    #[serde(rename = "clientMetadata")]
736    pub client_metadata: Option<String>,
737}
738
739/// Subaccount subscription contents.
740#[derive(Debug, Clone, Serialize, Deserialize)]
741pub struct DydxWsSubaccountsSubscribedContents {
742    pub subaccount: Option<DydxSubaccountInfo>,
743}
744
745/// Subaccounts subscription confirmed message.
746#[derive(Debug, Clone, Serialize, Deserialize)]
747pub struct DydxWsSubaccountsSubscribed {
748    pub connection_id: String,
749    pub message_id: u64,
750    pub id: String,
751    pub contents: DydxWsSubaccountsSubscribedContents,
752}
753
754/// Subaccounts channel data contents.
755#[derive(Debug, Clone, Serialize, Deserialize)]
756pub struct DydxWsSubaccountsChannelContents {
757    pub orders: Option<Vec<DydxWsOrderSubaccountMessageContents>>,
758    pub fills: Option<Vec<DydxWsFillSubaccountMessageContents>>,
759}
760
761/// Subaccounts channel data message.
762#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct DydxWsSubaccountsChannelData {
764    pub connection_id: String,
765    pub message_id: u64,
766    pub id: String,
767    pub version: String,
768    pub contents: DydxWsSubaccountsChannelContents,
769}