kalshi_rust/websocket/
messages.rs

1use crate::portfolio::{Action, Side};
2use serde::{Deserialize, Serialize};
3
4/// Envelope for all WebSocket messages.
5#[derive(Debug, Deserialize)]
6pub struct MessageEnvelope {
7    #[serde(rename = "type")]
8    pub msg_type: String,
9    pub sid: Option<i32>,
10    pub seq: Option<i64>,
11    pub id: Option<i32>,
12    pub msg: Option<serde_json::Value>,
13}
14
15/// Unified WebSocket message type.
16#[derive(Debug)]
17pub enum WebSocketMessage {
18    // Control messages
19    Subscribed(SubscribedMsg),
20    Ok(OkMsg),
21    Error(ErrorMsg),
22
23    // Orderbook
24    OrderbookSnapshot(OrderbookSnapshotMsg),
25    OrderbookDelta(OrderbookDeltaMsg),
26
27    // Market data
28    Ticker(TickerMsg),
29    Trade(TradeMsg),
30
31    // User data (auth required)
32    Fill(FillMsg),
33    MarketPosition(MarketPositionMsg),
34
35    // Lifecycle
36    MarketLifecycle(MarketLifecycleMsg),
37    EventLifecycle(EventLifecycleMsg),
38
39    // Multivariate
40    MultivariateLookup(MultivariateLookupMsg),
41
42    // Communications
43    RfqCreated(RfqCreatedMsg),
44    QuoteCreated(QuoteCreatedMsg),
45    QuoteAccepted(QuoteAcceptedMsg),
46
47    // Unknown
48    Unknown(serde_json::Value),
49}
50
51// --- Control Messages ---
52
53#[derive(Debug, Deserialize, Serialize)]
54pub struct SubscribedMsg {
55    pub channel: String,
56    pub sid: i32,
57}
58
59#[derive(Debug, Deserialize, Serialize)]
60pub struct OkMsg {
61    pub sid: i32,
62    pub seq: i64,
63}
64
65#[derive(Debug, Deserialize, Serialize)]
66pub struct ErrorMsg {
67    pub code: i32,
68    pub msg: String,
69}
70
71// --- Orderbook Messages ---
72
73#[derive(Debug, Deserialize, Serialize)]
74pub struct OrderbookSnapshotMsg {
75    pub market_ticker: String,
76    pub yes: Vec<Vec<i32>>,
77    pub yes_dollars: Vec<Vec<String>>,
78    pub no: Vec<Vec<i32>>,
79    pub no_dollars: Vec<Vec<String>>,
80}
81
82#[derive(Debug, Deserialize, Serialize)]
83pub struct OrderbookDeltaMsg {
84    pub market_ticker: String,
85    pub price: i32,
86    pub price_dollars: String,
87    pub delta: i32,
88    pub side: String, // "yes" or "no"
89}
90
91// --- Market Data Messages ---
92
93#[derive(Debug, Deserialize, Serialize)]
94pub struct TickerMsg {
95    pub market_ticker: String,
96    pub price: Option<i32>,
97    pub yes_bid: Option<i32>,
98    pub yes_ask: Option<i32>,
99    pub price_dollars: Option<String>,
100    pub volume: Option<i64>,
101    pub open_interest: Option<i64>,
102    pub ts: Option<i64>,
103}
104
105#[derive(Debug, Deserialize, Serialize)]
106pub struct TradeMsg {
107    pub market_ticker: String,
108    pub yes_price: i32,
109    pub no_price: i32,
110    pub count: i32,
111    pub taker_side: String,
112    pub ts: i64,
113}
114
115// --- User Data Messages (auth required) ---
116
117#[derive(Debug, Deserialize, Serialize)]
118pub struct FillMsg {
119    pub trade_id: String,
120    pub order_id: String,
121    pub market_ticker: String,
122    pub side: Side,
123    pub action: Action,
124    pub count: i32,
125    pub post_position: i32,
126}
127
128#[derive(Debug, Deserialize, Serialize)]
129pub struct MarketPositionMsg {
130    pub market_ticker: String,
131    pub position: i32,
132    /// Position cost in centi-cents (divide by 10,000 for USD)
133    pub position_cost: i64,
134    /// Realized P&L in centi-cents
135    pub realized_pnl: i64,
136    /// Fees paid in centi-cents
137    pub fees_paid: i64,
138}
139
140// --- Lifecycle Messages ---
141
142#[derive(Debug, Deserialize, Serialize)]
143pub struct MarketLifecycleMsg {
144    pub market_ticker: String,
145    pub event_type: MarketLifecycleEvent,
146    pub open_ts: Option<i64>,
147    pub close_ts: Option<i64>,
148}
149
150#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
151#[serde(rename_all = "lowercase")]
152pub enum MarketLifecycleEvent {
153    Created,
154    Activated,
155    Deactivated,
156    CloseDateUpdated,
157    Determined,
158    Settled,
159}
160
161#[derive(Debug, Deserialize, Serialize)]
162pub struct EventLifecycleMsg {
163    pub event_ticker: String,
164    pub title: String,
165}
166
167// --- Multivariate Messages ---
168
169#[derive(Debug, Deserialize, Serialize)]
170pub struct MultivariateLookupMsg {
171    pub collection_ticker: String,
172    pub market_ticker: String,
173    pub selected_markets: Vec<SelectedMarket>,
174}
175
176#[derive(Debug, Deserialize, Serialize)]
177pub struct SelectedMarket {
178    pub market_ticker: String,
179    pub side: String,
180}
181
182// --- Communications Messages ---
183
184#[derive(Debug, Deserialize, Serialize)]
185pub struct RfqCreatedMsg {
186    pub id: String,
187    pub market_ticker: String,
188    pub contracts: i32,
189}
190
191#[derive(Debug, Deserialize, Serialize)]
192pub struct QuoteCreatedMsg {
193    pub quote_id: String,
194    pub rfq_id: String,
195    pub yes_bid: i32,
196    pub no_bid: i32,
197}
198
199#[derive(Debug, Deserialize, Serialize)]
200pub struct QuoteAcceptedMsg {
201    pub quote_id: String,
202    pub accepted_side: String,
203}
204
205// --- Message Parsing ---
206
207impl WebSocketMessage {
208    /// Parse a raw WebSocket message into a typed message.
209    pub fn parse(text: &str) -> Result<Self, serde_json::Error> {
210        let envelope: MessageEnvelope = serde_json::from_str(text)?;
211
212        match envelope.msg_type.as_str() {
213            "subscribed" => {
214                let msg: SubscribedMsg = serde_json::from_value(envelope.msg.unwrap_or_default())?;
215                Ok(WebSocketMessage::Subscribed(msg))
216            }
217            "ok" => Ok(WebSocketMessage::Ok(OkMsg {
218                sid: envelope.sid.unwrap_or(0),
219                seq: envelope.seq.unwrap_or(0),
220            })),
221            "error" => {
222                let msg: ErrorMsg = serde_json::from_value(envelope.msg.unwrap_or_default())?;
223                Ok(WebSocketMessage::Error(msg))
224            }
225            "orderbook_snapshot" => {
226                let msg: OrderbookSnapshotMsg =
227                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
228                Ok(WebSocketMessage::OrderbookSnapshot(msg))
229            }
230            "orderbook_delta" => {
231                let msg: OrderbookDeltaMsg =
232                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
233                Ok(WebSocketMessage::OrderbookDelta(msg))
234            }
235            "ticker" => {
236                let msg: TickerMsg = serde_json::from_value(envelope.msg.unwrap_or_default())?;
237                Ok(WebSocketMessage::Ticker(msg))
238            }
239            "trade" => {
240                let msg: TradeMsg = serde_json::from_value(envelope.msg.unwrap_or_default())?;
241                Ok(WebSocketMessage::Trade(msg))
242            }
243            "fill" => {
244                let msg: FillMsg = serde_json::from_value(envelope.msg.unwrap_or_default())?;
245                Ok(WebSocketMessage::Fill(msg))
246            }
247            "market_position" => {
248                let msg: MarketPositionMsg =
249                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
250                Ok(WebSocketMessage::MarketPosition(msg))
251            }
252            "market_lifecycle_v2" => {
253                let msg: MarketLifecycleMsg =
254                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
255                Ok(WebSocketMessage::MarketLifecycle(msg))
256            }
257            "event_lifecycle" => {
258                let msg: EventLifecycleMsg =
259                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
260                Ok(WebSocketMessage::EventLifecycle(msg))
261            }
262            "multivariate_lookup" => {
263                let msg: MultivariateLookupMsg =
264                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
265                Ok(WebSocketMessage::MultivariateLookup(msg))
266            }
267            "rfq_created" => {
268                let msg: RfqCreatedMsg = serde_json::from_value(envelope.msg.unwrap_or_default())?;
269                Ok(WebSocketMessage::RfqCreated(msg))
270            }
271            "quote_created" => {
272                let msg: QuoteCreatedMsg =
273                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
274                Ok(WebSocketMessage::QuoteCreated(msg))
275            }
276            "quote_accepted" => {
277                let msg: QuoteAcceptedMsg =
278                    serde_json::from_value(envelope.msg.unwrap_or_default())?;
279                Ok(WebSocketMessage::QuoteAccepted(msg))
280            }
281            _ => Ok(WebSocketMessage::Unknown(serde_json::json!({
282                "type": envelope.msg_type,
283                "msg": envelope.msg
284            }))),
285        }
286    }
287}