Skip to main content

hypercall_sdk_types/
ws_protocol.rs

1use crate::api_models::{
2    OptionsChainStrikeRow, PortfolioGreeksAggregate, PositionGreeksLeg, PositionWithMetrics,
3    SpanMarginSummary,
4};
5use crate::{Side, WalletAddress};
6use rust_decimal::Decimal;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
13#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
14pub enum CandleResolution {
15    #[serde(rename = "1m")]
16    OneMinute,
17    #[serde(rename = "5m")]
18    FiveMinutes,
19    #[serde(rename = "15m")]
20    FifteenMinutes,
21    #[serde(rename = "1h")]
22    OneHour,
23    #[serde(rename = "4h")]
24    FourHours,
25    #[serde(rename = "1d")]
26    OneDay,
27}
28
29impl CandleResolution {
30    pub fn as_str(self) -> &'static str {
31        match self {
32            Self::OneMinute => "1m",
33            Self::FiveMinutes => "5m",
34            Self::FifteenMinutes => "15m",
35            Self::OneHour => "1h",
36            Self::FourHours => "4h",
37            Self::OneDay => "1d",
38        }
39    }
40
41    pub fn interval_ms(self) -> i64 {
42        match self {
43            Self::OneMinute => 60_000,
44            Self::FiveMinutes => 300_000,
45            Self::FifteenMinutes => 900_000,
46            Self::OneHour => 3_600_000,
47            Self::FourHours => 14_400_000,
48            Self::OneDay => 86_400_000,
49        }
50    }
51}
52
53impl fmt::Display for CandleResolution {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        f.write_str(self.as_str())
56    }
57}
58
59impl FromStr for CandleResolution {
60    type Err = String;
61
62    fn from_str(s: &str) -> Result<Self, Self::Err> {
63        match s {
64            "1m" => Ok(Self::OneMinute),
65            "5m" => Ok(Self::FiveMinutes),
66            "15m" => Ok(Self::FifteenMinutes),
67            "1h" => Ok(Self::OneHour),
68            "4h" => Ok(Self::FourHours),
69            "1d" => Ok(Self::OneDay),
70            _ => Err(format!("Unsupported candle resolution: {}", s)),
71        }
72    }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct WsOrderRequest {
77    pub price: Decimal,
78    pub size: Decimal,
79    pub symbol: String,
80    pub side: Side,
81    pub tif: crate::TimeInForce,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct WsOrderMessage {
86    pub order_id: Option<u64>,
87    pub request: WsOrderRequest,
88    pub status: crate::OrderUpdateStatus,
89    pub timestamp: u64,
90    pub reason: Option<String>,
91    pub wallet_address: WalletAddress,
92    #[serde(default = "default_instrument_type")]
93    pub instrument_type: String,
94}
95
96fn default_instrument_type() -> String {
97    "option".to_string()
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
102#[serde(tag = "type")]
103#[cfg_attr(feature = "asyncapi", derive(asyncapi_rust::ToAsyncApiMessage))]
104pub enum WsMessage {
105    /// Subscribe to a data channel
106    Subscribe {
107        channel: String,
108        #[serde(default, skip_serializing_if = "Option::is_none")]
109        symbols: Option<Vec<String>>,
110        #[serde(default, skip_serializing_if = "Option::is_none")]
111        expiry: Option<String>,
112        #[serde(default, skip_serializing_if = "Option::is_none")]
113        option_type: Option<String>,
114    },
115    /// Unsubscribe from a data channel
116    Unsubscribe {
117        channel: String,
118        #[serde(default, skip_serializing_if = "Option::is_none")]
119        symbols: Option<Vec<String>>,
120        #[serde(default, skip_serializing_if = "Option::is_none")]
121        expiry: Option<String>,
122        #[serde(default, skip_serializing_if = "Option::is_none")]
123        option_type: Option<String>,
124    },
125    /// Order status update (authenticated)
126    #[cfg_attr(feature = "schemars", schemars(with = "serde_json::Value"))]
127    OrderUpdate(WsOrderMessage),
128    /// L2 orderbook snapshot/update
129    OrderbookUpdate(WsOrderbookUpdate),
130    /// Fill notification (authenticated)
131    Fill(WsFillUpdate),
132    /// Public trade event
133    Trade(WsTradeUpdate),
134    /// Underlying candle update
135    CandleUpdate(WsCandleUpdate),
136    /// Market listing change
137    MarketUpdate(WsMarketUpdate),
138    /// Incremental options chain update
139    OptionsChainUpdate(WsOptionsChainUpdate),
140    /// Real-time index/spot price update for all underlyings
141    IndexPriceUpdate(WsIndexPriceUpdate),
142    /// Portfolio update (authenticated)
143    #[cfg_attr(feature = "schemars", schemars(with = "serde_json::Value"))]
144    PortfolioUpdate(PortfolioUpdate),
145    /// Position expiry notification (authenticated)
146    PositionExpired(WsPositionExpired),
147    /// Liquidation state change (authenticated)
148    LiquidationStateChange(WsLiquidationStateChange),
149    /// Competition leaderboard update for connected wallet (authenticated)
150    CompetitionUpdate(WsCompetitionUpdate),
151    /// Competition PnL summary for connected wallet (authenticated)
152    CompetitionPnlSummary(WsCompetitionPnlSummary),
153    /// Final competition stats notification for connected wallet (authenticated)
154    CompetitionFinalStats(WsCompetitionFinalStats),
155    /// Competition rank movement notification for connected wallet (authenticated)
156    CompetitionRankChange(WsCompetitionRankChange),
157    /// Competition gap-to-next notification for connected wallet (authenticated)
158    CompetitionGapUpdate(WsCompetitionGapUpdate),
159    /// Competition final standing notification for connected wallet (authenticated)
160    CompetitionFinalStanding(WsCompetitionFinalStanding),
161    /// Identify the connection with a wallet address (replaces query-param ?wallet=).
162    Authenticate { wallet: String },
163    /// Server confirms wallet identification
164    Authenticated { wallet: String },
165    /// Error message from server
166    Error { message: String },
167    /// Subscription confirmed
168    Subscribed { channel: String },
169    /// Unsubscription confirmed
170    Unsubscribed { channel: String },
171    /// Indicative market data from aggregated QP quotes (public)
172    IndicativeMarketData(WsIndicativeMarketData),
173    /// RFQ quotes available for taker (authenticated)
174    RfqQuotes(WsRfqQuotes),
175    /// RFQ status update (authenticated)
176    RfqStatusUpdate(WsRfqStatusUpdate),
177    /// Submit an RFQ request via WebSocket (authenticated)
178    SubmitRfq {
179        rfq_id: String,
180        legs: Vec<WsRfqLegRequest>,
181        wallet_address: String,
182        nonce: u64,
183        signature: String,
184    },
185    /// Submit an RFQ with auto-execute via WebSocket (authenticated).
186    /// The taker pre-authorizes execution with a directional `limit_price`.
187    SubmitAutoExecuteRfq {
188        rfq_id: String,
189        legs: Vec<WsRfqLegRequest>,
190        wallet_address: String,
191        /// Directional premium limit as a decimal string. Buy RFQs use this
192        /// as a max debit. Sell RFQs use it as a min credit.
193        limit_price: String,
194        nonce: u64,
195        signature: String,
196    },
197    /// Accept an RFQ quote via WebSocket (authenticated)
198    AcceptRfqQuote {
199        rfq_id: String,
200        quote_id: String,
201        wallet_address: String,
202        nonce: u64,
203        signature: String,
204    },
205    /// RFQ accept result pushed back to the client
206    RfqAcceptResult {
207        rfq_id: String,
208        quote_id: String,
209        status: String,
210        fill_id: Option<String>,
211        /// Used for wallet-based filtering in `publish_to_channel` so
212        /// the result is only delivered to the taker, not all rfq
213        /// subscribers. Excluded from the wire format.
214        #[serde(skip, default)]
215        #[cfg_attr(feature = "schemars", schemars(skip))]
216        taker_wallet: Option<WalletAddress>,
217    },
218    /// Place an order via WebSocket (authenticated)
219    PlaceOrder {
220        wallet: String,
221        symbol: String,
222        side: String,
223        size: String,
224        price: String,
225        #[serde(default, skip_serializing_if = "Option::is_none")]
226        tif: Option<String>,
227        #[serde(default, skip_serializing_if = "Option::is_none")]
228        client_id: Option<String>,
229        nonce: u64,
230        signature: String,
231        #[serde(default)]
232        mmp_enabled: Option<bool>,
233        #[serde(default, skip_serializing_if = "Option::is_none")]
234        builder_code_address: Option<String>,
235    },
236    /// Order placement result pushed back to the client
237    OrderResult(WsOrderResult),
238    #[serde(other)]
239    Other,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
244pub struct WsOrderResult {
245    pub order_id: Option<u64>,
246    pub status: String,
247    pub symbol: String,
248    pub side: String,
249    pub price: String,
250    pub size: String,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub reason: Option<String>,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
256#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
257pub struct WsIndicativeMarketData {
258    pub instrument: String,
259    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub best_bid: Option<Decimal>,
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub bid_iv: Option<f64>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub ask_iv: Option<f64>,
266    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub best_ask: Option<Decimal>,
269    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub indicative_bid_size: Option<Decimal>,
272    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub indicative_ask_size: Option<Decimal>,
275    pub num_providers: u32,
276    pub timestamp: u64,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
281pub struct WsRfqQuotes {
282    pub rfq_id: String,
283    pub quotes: Vec<WsRfqQuoteEntry>,
284    pub status: String,
285    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
286    pub taker_wallet: WalletAddress,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
290#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
291pub struct WsRfqQuoteEntry {
292    pub quote_id: String,
293    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
294    pub net_premium: Decimal,
295    pub expires_at: u64,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
299#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
300pub struct WsRfqStatusUpdate {
301    pub rfq_id: String,
302    pub status: String,
303    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
304    pub taker_wallet: WalletAddress,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
308#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
309pub struct WsRfqLegRequest {
310    pub instrument: String,
311    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
312    pub side: Side,
313    pub size: String,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
317#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
318pub struct WsOrderbookUpdate {
319    pub symbol: String,
320    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
321    pub option_token_address: Option<WalletAddress>,
322    #[cfg_attr(feature = "schemars", schemars(with = "Vec<(String, String)>"))]
323    pub bids: Vec<(Decimal, Decimal)>,
324    #[cfg_attr(feature = "schemars", schemars(with = "Vec<(String, String)>"))]
325    pub asks: Vec<(Decimal, Decimal)>,
326    pub timestamp: i64,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
331pub struct WsFillUpdate {
332    pub order_id: i64,
333    pub fill_id: i64,
334    pub symbol: String,
335    pub side: String,
336    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
337    pub price: Decimal,
338    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
339    pub size: Decimal,
340    pub timestamp: i64,
341    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
342    pub wallet_address: WalletAddress,
343    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
344    pub fee: Decimal,
345    pub trade_id: i64,
346    pub is_taker: bool,
347    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
348    pub builder_code_address: Option<WalletAddress>,
349    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
350    pub builder_code_fee: Option<Decimal>,
351    #[serde(default = "default_ws_instrument_type")]
352    pub instrument_type: String,
353}
354
355fn default_ws_instrument_type() -> String {
356    "option".to_string()
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
360#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
361pub struct WsTradeUpdate {
362    pub symbol: String,
363    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
364    pub price: Decimal,
365    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
366    pub size: Decimal,
367    pub side: String,
368    pub timestamp: i64,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
372#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
373pub struct WsCandleUpdate {
374    pub underlying: String,
375    pub resolution: CandleResolution,
376    pub start_time_ms: i64,
377    pub end_time_ms: i64,
378    pub open: f64,
379    pub high: f64,
380    pub low: f64,
381    pub close: f64,
382    pub volume: f64,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
386#[serde(tag = "type")]
387pub enum PortfolioUpdate {
388    Initial {
389        positions: Vec<PositionWithMetrics>,
390        timestamp: i64,
391    },
392    PositionUpdate {
393        position: PositionWithMetrics,
394        timestamp: i64,
395    },
396    BalanceUpdate {
397        total_margin_used: Decimal,
398        timestamp: i64,
399    },
400    MarginUpdate {
401        span_margin: SpanMarginSummary,
402        total_margin_used: Decimal,
403        available_balance: Decimal,
404        timestamp: i64,
405    },
406    GreeksUpdate {
407        per_leg: Vec<PositionGreeksLeg>,
408        aggregate: Option<PortfolioGreeksAggregate>,
409        timestamp: i64,
410    },
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
414#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
415pub struct WsPositionExpired {
416    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
417    pub wallet_address: WalletAddress,
418    pub symbol: String,
419    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
420    pub position_size: Decimal,
421    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
422    pub settlement_price: Decimal,
423    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
424    pub settlement_value: Decimal,
425    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
426    pub settlement_entry_price: Option<Decimal>,
427    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
428    pub cost_basis: Option<Decimal>,
429    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
430    pub net_pnl: Option<Decimal>,
431    pub timestamp: i64,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
435#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
436pub struct WsLiquidationStateChange {
437    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
438    pub wallet_address: WalletAddress,
439    pub previous_state: String,
440    pub new_state: String,
441    pub liquidation_mode: Option<String>,
442    pub margin_mode: String,
443    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
444    pub equity: Decimal,
445    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
446    pub mm_required: Decimal,
447    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
448    pub maintenance_margin: Decimal,
449    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
450    pub shortfall: Decimal,
451    pub partial_liquidation: Option<WsPartialLiquidationState>,
452    pub full_liquidation: Option<WsFullLiquidationState>,
453    pub timestamp: i64,
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
457#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
458pub struct WsPartialLiquidationState {
459    pub entered_at: i64,
460    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
461    pub target_equity: Decimal,
462    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
463    pub mm_shortfall: Decimal,
464    pub escalation_deadline: i64,
465    pub last_reprice_at: Option<i64>,
466    pub active_order_request_ids: Vec<String>,
467    pub active_order_client_ids: Vec<String>,
468    pub bonus_bps: i32,
469    pub pending_full_auction_id: Option<String>,
470    pub pending_full_request_id: Option<String>,
471    pub pending_full_tx_hash: Option<String>,
472    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
473    pub pending_full_margin_needed: Option<Decimal>,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
477#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
478pub struct WsFullLiquidationState {
479    pub auction_id: Option<String>,
480    pub request_id: Option<String>,
481    pub tx_hash: Option<String>,
482    pub started_at: Option<i64>,
483    pub chain_start_time: Option<i64>,
484    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
485    pub margin_needed: Option<Decimal>,
486    pub stop_request_id: Option<String>,
487    pub stop_tx_hash: Option<String>,
488    pub liquidated_at: Option<i64>,
489    pub winner: Option<String>,
490    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
491    pub bonus: Option<Decimal>,
492    pub resolution_tx_hash: Option<String>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
496#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
497pub struct WsCompetitionUpdate {
498    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
499    pub wallet_address: WalletAddress,
500    pub competition_id: i64,
501    pub rank: i64,
502    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
503    pub pnl: Decimal,
504    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
505    pub volume: Decimal,
506    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
507    pub efficiency: Decimal,
508    pub timestamp: i64,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
512#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
513pub struct WsCompetitionPnlStanding {
514    pub competition_id: i64,
515    pub competition_name: String,
516    pub competition_state: String,
517    pub rank: Option<usize>,
518    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
519    pub pnl: Decimal,
520    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
521    pub volume: Decimal,
522    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
523    pub efficiency: Decimal,
524    pub medal: Option<u8>,
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize)]
528#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
529pub struct WsCompetitionPnlSummary {
530    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
531    pub wallet_address: WalletAddress,
532    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
533    pub lifetime_realized_pnl: Decimal,
534    pub active_competition: Option<WsCompetitionPnlStanding>,
535    pub timestamp: i64,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize)]
539#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
540pub struct WsCompetitionFinalStats {
541    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
542    pub wallet_address: WalletAddress,
543    pub competition_id: i64,
544    pub rank: i64,
545    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
546    pub pnl: Decimal,
547    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
548    pub volume: Decimal,
549    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
550    pub efficiency: Decimal,
551    pub medal: Option<i64>,
552    pub timestamp: i64,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
556#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
557pub struct IndexPriceEntry {
558    pub underlying: String,
559    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
560    pub price: Decimal,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize)]
564#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
565pub struct WsIndexPriceUpdate {
566    pub prices: Vec<IndexPriceEntry>,
567    pub timestamp: i64,
568}
569
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
572pub struct WsCompetitionRankChange {
573    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
574    pub wallet_address: WalletAddress,
575    pub competition_id: i64,
576    pub from_rank: i64,
577    pub to_rank: i64,
578    pub delta_places: i64,
579    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
580    pub pnl: Decimal,
581    pub timestamp: i64,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize)]
585#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
586pub struct WsCompetitionGapUpdate {
587    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
588    pub wallet_address: WalletAddress,
589    pub competition_id: i64,
590    pub rank: i64,
591    pub next_rank: Option<i64>,
592    #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
593    pub gap_metric_value: Option<Decimal>,
594    pub timestamp: i64,
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize)]
598#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
599pub struct WsCompetitionFinalStanding {
600    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
601    pub wallet_address: WalletAddress,
602    pub competition_id: i64,
603    pub rank: i64,
604    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
605    pub pnl: Decimal,
606    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
607    pub volume: Decimal,
608    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
609    pub efficiency: Decimal,
610    pub medal: Option<i64>,
611    pub timestamp: i64,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize)]
615#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
616#[serde(tag = "action")]
617pub enum WsMarketUpdate {
618    Created {
619        symbol: String,
620        #[cfg_attr(feature = "schemars", schemars(with = "String"))]
621        strike: Decimal,
622        is_call: bool,
623        underlying: String,
624        expiry: u32,
625        timestamp: u64,
626    },
627    Deleted {
628        symbol: String,
629        timestamp: u64,
630    },
631    Expired {
632        symbol: String,
633        #[cfg_attr(feature = "schemars", schemars(with = "String"))]
634        strike: Decimal,
635        is_call: bool,
636        underlying: String,
637        expiry: u32,
638        timestamp: u64,
639    },
640}
641
642#[derive(Debug, Clone, Serialize, Deserialize)]
643#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
644#[serde(tag = "action")]
645#[allow(clippy::large_enum_variant)]
646pub enum WsOptionsChainUpdate {
647    Upsert {
648        currency: String,
649        expiry: u64,
650        row: OptionsChainStrikeRow,
651        timestamp: i64,
652    },
653    Remove {
654        currency: String,
655        expiry: u64,
656        strike: f64,
657        option_type: String,
658        symbol: String,
659        timestamp: i64,
660    },
661}