1use chrono::{DateTime, Utc};
2use rust_decimal::Decimal;
3use serde::{Deserialize, Serialize};
4use serde_repr::{Deserialize_repr, Serialize_repr};
5use std::fmt;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
14pub enum Dir {
15 #[serde(alias = "Buy", alias = "buy")]
16 Buy,
17 #[serde(alias = "Sell", alias = "sell")]
18 Sell,
19}
20
21#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23pub struct SequenceIdAndNumber {
24 #[serde(rename = "sid")]
25 pub sequence_id: u64,
26 #[serde(rename = "sn")]
27 pub sequence_number: u64,
28}
29
30impl SequenceIdAndNumber {
31 pub fn advance(&mut self) {
32 self.sequence_number += 1;
33 }
34}
35
36#[derive(
38 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize_repr, Deserialize_repr,
39)]
40#[repr(i32)]
41pub enum CandleWidth {
42 OneSecond = 1,
43 FiveSecond = 5,
44 OneMinute = 60,
45 TwoMinute = 120,
46 ThreeMinute = 180,
47 FifteenMinute = 900,
48 OneHour = 3600,
49 OneDay = 86400,
50}
51
52impl CandleWidth {
53 pub fn as_seconds(&self) -> i64 {
54 *self as i64
55 }
56
57 pub fn as_str(&self) -> &'static str {
58 match self {
59 Self::OneSecond => "1s",
60 Self::FiveSecond => "5s",
61 Self::OneMinute => "1m",
62 Self::TwoMinute => "2m",
63 Self::ThreeMinute => "3m",
64 Self::FifteenMinute => "15m",
65 Self::OneHour => "1h",
66 Self::OneDay => "1d",
67 }
68 }
69
70 #[allow(clippy::should_implement_trait)]
71 pub fn from_str(s: &str) -> Option<Self> {
72 match s {
73 "1s" => Some(Self::OneSecond),
74 "5s" => Some(Self::FiveSecond),
75 "1m" => Some(Self::OneMinute),
76 "2m" => Some(Self::TwoMinute),
77 "3m" => Some(Self::ThreeMinute),
78 "15m" => Some(Self::FifteenMinute),
79 "1h" => Some(Self::OneHour),
80 "1d" => Some(Self::OneDay),
81 _ => None,
82 }
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct L1BookSnapshot {
93 #[serde(rename = "s")]
94 pub symbol: String,
95 #[serde(rename = "ts")]
96 pub timestamp: i64,
97 #[serde(rename = "tn")]
98 pub timestamp_ns: u32,
99 #[serde(default, rename = "rt", skip_serializing_if = "Option::is_none")]
100 pub recv_time: Option<i64>,
101 #[serde(default, rename = "rtn", skip_serializing_if = "Option::is_none")]
102 pub recv_time_ns: Option<u32>,
103 #[serde(rename = "b")]
105 pub best_bid: Option<(Decimal, Decimal)>,
106 #[serde(rename = "a")]
108 pub best_ask: Option<(Decimal, Decimal)>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct L2BookSnapshot {
114 #[serde(rename = "ts")]
115 pub timestamp: i64,
116 #[serde(rename = "tn")]
117 pub timestamp_ns: u32,
118 #[serde(flatten)]
119 pub sequence: SequenceIdAndNumber,
120 #[serde(rename = "b")]
122 pub bids: Vec<(Decimal, Decimal)>,
123 #[serde(rename = "a")]
124 pub asks: Vec<(Decimal, Decimal)>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct L2BookDiff {
130 #[serde(rename = "ts")]
131 pub timestamp: i64,
132 #[serde(rename = "tn")]
133 pub timestamp_ns: u32,
134 #[serde(flatten)]
135 pub sequence: SequenceIdAndNumber,
136 #[serde(rename = "b")]
138 pub bids: Vec<(Decimal, Decimal)>,
139 #[serde(rename = "a")]
140 pub asks: Vec<(Decimal, Decimal)>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145#[serde(tag = "t")]
146pub enum L2BookUpdate {
147 #[serde(rename = "s")]
148 Snapshot(L2BookSnapshot),
149 #[serde(rename = "d")]
150 Diff(L2BookDiff),
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct Trade {
156 #[serde(rename = "s")]
157 pub symbol: String,
158 #[serde(rename = "ts")]
159 pub timestamp: i64,
160 #[serde(rename = "tn")]
161 pub timestamp_ns: u32,
162 #[serde(rename = "d")]
163 pub direction: Option<Dir>,
164 #[serde(rename = "p")]
165 pub price: Decimal,
166 #[serde(rename = "q")]
167 pub size: Decimal,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172pub struct Candle {
173 #[serde(rename = "ts")]
174 pub timestamp: i64,
175 #[serde(rename = "tn")]
176 pub timestamp_ns: u32,
177 #[serde(rename = "w")]
178 pub width: CandleWidth,
179 #[serde(rename = "s")]
180 pub symbol: String,
181 #[serde(rename = "o")]
182 pub open: Option<Decimal>,
183 #[serde(rename = "h")]
184 pub high: Option<Decimal>,
185 #[serde(rename = "l")]
186 pub low: Option<Decimal>,
187 #[serde(rename = "c")]
188 pub close: Option<Decimal>,
189 #[serde(rename = "v")]
190 pub volume: Decimal,
191 #[serde(rename = "bv")]
192 pub buy_volume: Decimal,
193 #[serde(rename = "av")]
194 pub sell_volume: Decimal,
195 #[serde(default, rename = "mo", skip_serializing_if = "Option::is_none")]
196 pub mid_open: Option<Decimal>,
197 #[serde(default, rename = "mc", skip_serializing_if = "Option::is_none")]
198 pub mid_close: Option<Decimal>,
199 #[serde(default, rename = "mh", skip_serializing_if = "Option::is_none")]
200 pub mid_high: Option<Decimal>,
201 #[serde(default, rename = "ml", skip_serializing_if = "Option::is_none")]
202 pub mid_low: Option<Decimal>,
203 #[serde(default, rename = "bo", skip_serializing_if = "Option::is_none")]
204 pub bid_open: Option<Decimal>,
205 #[serde(default, rename = "bc", skip_serializing_if = "Option::is_none")]
206 pub bid_close: Option<Decimal>,
207 #[serde(default, rename = "bh", skip_serializing_if = "Option::is_none")]
208 pub bid_high: Option<Decimal>,
209 #[serde(default, rename = "bl", skip_serializing_if = "Option::is_none")]
210 pub bid_low: Option<Decimal>,
211 #[serde(default, rename = "ao", skip_serializing_if = "Option::is_none")]
212 pub ask_open: Option<Decimal>,
213 #[serde(default, rename = "ac", skip_serializing_if = "Option::is_none")]
214 pub ask_close: Option<Decimal>,
215 #[serde(default, rename = "ah", skip_serializing_if = "Option::is_none")]
216 pub ask_high: Option<Decimal>,
217 #[serde(default, rename = "al", skip_serializing_if = "Option::is_none")]
218 pub ask_low: Option<Decimal>,
219}
220
221impl Candle {
222 pub fn timestamp(&self) -> Option<DateTime<Utc>> {
223 DateTime::<Utc>::from_timestamp(self.timestamp, self.timestamp_ns)
224 }
225}
226
227pub fn split_timestamp(dt: DateTime<Utc>) -> (i64, u32) {
232 (dt.timestamp(), dt.timestamp_subsec_nanos())
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
241#[serde(rename_all = "snake_case")]
242pub enum Channel {
243 L1,
244 L2,
245 Trades,
246 Candles,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251#[serde(tag = "type", rename_all = "snake_case")]
252pub enum ClientMessage {
253 Subscribe {
254 channel: Channel,
255 #[serde(skip_serializing_if = "Option::is_none")]
256 symbols: Option<Vec<String>>,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 width: Option<CandleWidth>,
260 },
261 Unsubscribe {
262 channel: Channel,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 symbols: Option<Vec<String>>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 width: Option<CandleWidth>,
267 },
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
274#[serde(tag = "type", rename_all = "snake_case")]
275pub enum ServerMessage {
276 L1(L1BookSnapshot),
277 L2Diff {
278 #[serde(rename = "s")]
279 symbol: String,
280 #[serde(flatten)]
281 diff: L2BookDiff,
282 },
283 L2Snapshot {
284 #[serde(rename = "s")]
285 symbol: String,
286 #[serde(flatten)]
287 snapshot: L2BookSnapshot,
288 },
289 Trade(Trade),
290 Candle(Box<Candle>),
291}
292
293impl ServerMessage {
294 pub fn symbol(&self) -> &str {
295 match self {
296 Self::L1(l1) => &l1.symbol,
297 Self::L2Diff { symbol, .. } => symbol,
298 Self::L2Snapshot { symbol, .. } => symbol,
299 Self::Trade(t) => &t.symbol,
300 Self::Candle(c) => &c.symbol,
301 }
302 }
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
310#[serde(transparent)]
311pub struct MarketdataVenue(pub String);
312
313impl fmt::Display for MarketdataVenue {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 f.write_str(&self.0)
316 }
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct L1BookSnapshotRequest {
321 #[serde(default, skip_serializing_if = "Option::is_none")]
322 pub venue: Option<MarketdataVenue>,
323 pub symbol: String,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct L1BookSnapshotsRequest {
328 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub venue: Option<MarketdataVenue>,
330 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub symbols: Option<Vec<String>>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct SubscribeL1BookSnapshotsRequest {
336 #[serde(default, skip_serializing_if = "Option::is_none")]
337 pub venue: Option<MarketdataVenue>,
338 #[serde(default, skip_serializing_if = "Option::is_none")]
339 pub symbols: Option<Vec<String>>,
340 #[serde(default)]
341 pub send_initial_snapshots: bool,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct L2BookSnapshotRequest {
346 #[serde(default, skip_serializing_if = "Option::is_none")]
347 pub venue: Option<MarketdataVenue>,
348 pub symbol: String,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct SubscribeL2BookUpdatesRequest {
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub venue: Option<MarketdataVenue>,
355 pub symbol: String,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct SubscribeTradesRequest {
360 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub venue: Option<MarketdataVenue>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub symbol: Option<String>,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct SubscribeCandlesRequest {
368 #[serde(default, skip_serializing_if = "Option::is_none")]
369 pub venue: Option<MarketdataVenue>,
370 pub symbol: String,
371 #[serde(default, skip_serializing_if = "Option::is_none")]
372 pub candle_widths: Option<Vec<CandleWidth>>,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct SubscribeManyCandlesRequest {
377 #[serde(default, skip_serializing_if = "Option::is_none")]
378 pub venue: Option<MarketdataVenue>,
379 #[serde(default, skip_serializing_if = "Option::is_none")]
380 pub symbols: Option<Vec<String>>,
381 pub candle_width: CandleWidth,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct HistoricalCandlesRequest {
386 #[serde(default, skip_serializing_if = "Option::is_none")]
387 pub venue: Option<MarketdataVenue>,
388 pub symbol: String,
389 pub candle_width: CandleWidth,
390 pub start_date: DateTime<Utc>,
391 pub end_date: DateTime<Utc>,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct HistoricalCandlesResponse {
396 pub candles: Vec<Candle>,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct MarketStatusRequest {
401 #[serde(default, skip_serializing_if = "Option::is_none")]
402 pub venue: Option<MarketdataVenue>,
403 pub symbol: String,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct MarketStatus {
408 #[serde(rename = "s")]
409 pub symbol: String,
410 #[serde(default, skip_serializing_if = "Option::is_none")]
411 pub is_trading: Option<bool>,
412 #[serde(default, skip_serializing_if = "Option::is_none")]
413 pub is_quoting: Option<bool>,
414}
415
416#[derive(Default, Debug, Clone, Serialize, Deserialize)]
417pub struct TickerValues {
418 #[serde(default, rename = "bp", skip_serializing_if = "Option::is_none")]
419 pub bid_price: Option<Decimal>,
420 #[serde(default, rename = "bs", skip_serializing_if = "Option::is_none")]
421 pub bid_size: Option<Decimal>,
422 #[serde(default, rename = "ap", skip_serializing_if = "Option::is_none")]
423 pub ask_price: Option<Decimal>,
424 #[serde(default, rename = "as", skip_serializing_if = "Option::is_none")]
425 pub ask_size: Option<Decimal>,
426 #[serde(default, rename = "p", skip_serializing_if = "Option::is_none")]
427 pub last_price: Option<Decimal>,
428 #[serde(default, rename = "q", skip_serializing_if = "Option::is_none")]
429 pub last_size: Option<Decimal>,
430 #[serde(default, rename = "xo", skip_serializing_if = "Option::is_none")]
431 pub session_open: Option<Decimal>,
432 #[serde(default, rename = "xl", skip_serializing_if = "Option::is_none")]
433 pub session_low: Option<Decimal>,
434 #[serde(default, rename = "xh", skip_serializing_if = "Option::is_none")]
435 pub session_high: Option<Decimal>,
436 #[serde(default, rename = "xv", skip_serializing_if = "Option::is_none")]
437 pub session_volume: Option<Decimal>,
438 #[serde(default, rename = "o", skip_serializing_if = "Option::is_none")]
439 pub open_24h: Option<Decimal>,
440 #[serde(default, rename = "l", skip_serializing_if = "Option::is_none")]
441 pub low_24h: Option<Decimal>,
442 #[serde(default, rename = "h", skip_serializing_if = "Option::is_none")]
443 pub high_24h: Option<Decimal>,
444 #[serde(default, rename = "v", skip_serializing_if = "Option::is_none")]
445 pub volume_24h: Option<Decimal>,
446 #[serde(default, rename = "vm", skip_serializing_if = "Option::is_none")]
447 pub volume_30d: Option<Decimal>,
448 #[serde(default, rename = "oi", skip_serializing_if = "Option::is_none")]
449 pub open_interest: Option<Decimal>,
450 #[serde(default, rename = "sp", skip_serializing_if = "Option::is_none")]
451 pub last_settlement_price: Option<Decimal>,
452 #[serde(default, rename = "sd", skip_serializing_if = "Option::is_none")]
453 pub last_settlement_date: Option<chrono::NaiveDate>,
454 #[serde(default, rename = "isp", skip_serializing_if = "Option::is_none")]
455 pub indicative_settlement_price: Option<Decimal>,
456 #[serde(default, rename = "mp", skip_serializing_if = "Option::is_none")]
457 pub mark_price: Option<Decimal>,
458 #[serde(default, rename = "ip", skip_serializing_if = "Option::is_none")]
459 pub index_price: Option<Decimal>,
460 #[serde(default, rename = "fr", skip_serializing_if = "Option::is_none")]
461 pub funding_rate: Option<Decimal>,
462 #[serde(default, rename = "ft", skip_serializing_if = "Option::is_none")]
463 pub next_funding_time: Option<DateTime<Utc>>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub market_cap: Option<Decimal>,
466 #[serde(default, skip_serializing_if = "Option::is_none")]
467 pub price_to_earnings: Option<Decimal>,
468 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub eps_adj: Option<Decimal>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub shares_outstanding_weighted_adj: Option<Decimal>,
472 #[serde(default, skip_serializing_if = "Option::is_none")]
473 pub dividend: Option<Decimal>,
474 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub dividend_yield: Option<Decimal>,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct Ticker {
480 #[serde(rename = "s")]
481 pub symbol: String,
482 #[serde(rename = "ve")]
483 pub venue: MarketdataVenue,
484 #[serde(rename = "ts")]
485 pub timestamp: i64,
486 #[serde(rename = "tn")]
487 pub timestamp_ns: u32,
488 #[serde(flatten)]
489 pub values: TickerValues,
490}
491
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct TickerRequest {
494 #[serde(default, skip_serializing_if = "Option::is_none")]
495 pub venue: Option<MarketdataVenue>,
496 pub symbol: String,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct TickersRequest {
501 #[serde(default, skip_serializing_if = "Option::is_none")]
502 pub venue: Option<MarketdataVenue>,
503 #[serde(default, skip_serializing_if = "Option::is_none")]
504 pub symbols: Option<Vec<String>>,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct TickersResponse {
509 pub tickers: Vec<Ticker>,
510}