architect_api/marketdata/
mod.rs

1use crate::{
2    symbology::MarketdataVenue, utils::pagination::OffsetAndLimit, Dir,
3    SequenceIdAndNumber,
4};
5use chrono::{DateTime, NaiveDate, Utc};
6use derive::grpc;
7use derive_more::Deref;
8use rust_decimal::Decimal;
9use rust_decimal_macros::dec;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use serde_with::skip_serializing_none;
13
14pub mod candle_width;
15pub use candle_width::CandleWidth;
16use strum::EnumString;
17
18#[grpc(package = "json.architect")]
19#[grpc(service = "Marketdata", name = "l1_book_snapshot", response = "L1BookSnapshot")]
20#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
21pub struct L1BookSnapshotRequest {
22    pub symbol: String,
23}
24
25#[grpc(package = "json.architect")]
26#[grpc(service = "Marketdata", name = "l1_book_snapshots", response = "L1BookSnapshot")]
27#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
28pub struct L1BookSnapshotsRequest {
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub symbols: Option<Vec<String>>,
31}
32
33pub type L1BookSnapshots = Vec<L1BookSnapshot>;
34
35#[grpc(package = "json.architect")]
36#[grpc(
37    service = "Marketdata",
38    name = "subscribe_l1_book_snapshots",
39    response = "L1BookSnapshot",
40    server_streaming
41)]
42#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
43pub struct SubscribeL1BookSnapshotsRequest {
44    /// If None, subscribe from all symbols on the feed
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub symbols: Option<Vec<String>>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
50pub struct L1BookSnapshot {
51    #[serde(rename = "s")]
52    #[schemars(title = "symbol")]
53    pub symbol: String,
54    #[serde(rename = "ts")]
55    #[schemars(title = "timestamp")]
56    pub timestamp: i64,
57    #[serde(rename = "tn")]
58    #[schemars(title = "timestamp_ns")]
59    pub timestamp_ns: u32,
60    #[serde(rename = "b")]
61    #[schemars(title = "best_bid")]
62    pub best_bid: Option<(Decimal, Decimal)>,
63    #[serde(rename = "a")]
64    #[schemars(title = "best_ask")]
65    pub best_ask: Option<(Decimal, Decimal)>,
66}
67
68impl L1BookSnapshot {
69    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
70        chrono::DateTime::from_timestamp(self.timestamp, self.timestamp_ns)
71    }
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
75pub struct L2BookSnapshot {
76    #[serde(rename = "ts")]
77    #[schemars(title = "timestamp")]
78    pub timestamp: i64,
79    #[serde(rename = "tn")]
80    #[schemars(title = "timestamp_ns")]
81    pub timestamp_ns: u32,
82    #[serde(flatten)]
83    pub sequence: SequenceIdAndNumber,
84    #[serde(rename = "b")]
85    #[schemars(title = "bids")]
86    pub bids: Vec<(Decimal, Decimal)>,
87    #[serde(rename = "a")]
88    #[schemars(title = "asks")]
89    pub asks: Vec<(Decimal, Decimal)>,
90}
91
92impl L2BookSnapshot {
93    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
94        chrono::DateTime::from_timestamp(self.timestamp, self.timestamp_ns)
95    }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
99pub struct L2BookDiff {
100    #[serde(rename = "ts")]
101    #[schemars(title = "timestamp")]
102    pub timestamp: i64,
103    #[serde(rename = "tn")]
104    #[schemars(title = "timestamp_ns")]
105    pub timestamp_ns: u32,
106    #[serde(flatten)]
107    pub sequence: SequenceIdAndNumber,
108    /// Set of (price, level) updates. If zero, the price level
109    /// has been removed from the book.
110    #[serde(rename = "b")]
111    #[schemars(title = "bids")]
112    pub bids: Vec<(Decimal, Decimal)>,
113    /// Set of (price, level) updates. If zero, the price level
114    /// has been removed from the book.
115    #[serde(rename = "a")]
116    #[schemars(title = "asks")]
117    pub asks: Vec<(Decimal, Decimal)>,
118}
119
120impl L2BookDiff {
121    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
122        chrono::DateTime::from_timestamp(self.timestamp, self.timestamp_ns)
123    }
124}
125
126/// To build a book from a stream of updates, the client should first subscribe to
127/// this update stream, which then returns a stream starting with a snapshot and
128/// following with diffs.
129///
130/// Diffs should be applied consecutively to the snapshot in order to reconstruct
131/// the state of the book.
132///
133/// ```rust
134/// # use architect_api::marketdata::*;
135/// # use std::collections::BTreeMap;
136/// # use rust_decimal::Decimal;
137/// # use rust_decimal_macros::dec;
138///
139/// /// Suppose we receive this snapshot from the server:
140/// let snapshot: L2BookUpdate = serde_json::from_str(r#"{
141///     "t": "s",
142///     "ts": 1729700837,
143///     "tn": 0,
144///     "sid": 123,
145///     "sn": 8999,
146///     "b": [["99.00", "3"], ["98.78", "2"]],
147///     "a": [["100.00", "1"], ["100.10", "2"]]
148/// }"#)?;
149///
150/// /// It corresponds to the following book:
151/// let mut book = BTreeMap::new();
152/// book.insert(dec!(99.00), 3);
153/// book.insert(dec!(98.78), 2);
154/// book.insert(dec!(100.00), 1);
155/// book.insert(dec!(100.10), 2);
156///
157/// /// Then we receive this update:
158/// let diff: L2BookUpdate = serde_json::from_str(r#"{
159///     "t": "d",
160///     "ts": 1729700839,
161///     "tn": 0,
162///     "sid": 123,
163///     "sn": 9000,
164///     "b": [["99.00", "1"]],
165///     "a": []
166/// }"#)?;
167///
168/// /// Verify that the sequence number is correct
169/// assert!(diff.sequence().is_next_in_sequence(&snapshot.sequence()));
170///
171/// /// Apply the update to our book
172/// book.insert(dec!(99.00), 1);
173///
174/// // Suppose we then receive this update:
175/// let diff: L2BookUpdate = serde_json::from_str(r#"{
176///     "t": "d",
177///     "ts": 1729700841,
178///     "tn": 0,
179///     "sid": 123,
180///     "sn": 9005,
181///     "b": [],
182///     "a": [["103.00", "1"]]
183/// }"#)?;
184///
185/// /// We shouldn't apply this update because it's not next in sequence!
186/// assert_eq!(diff.sequence().is_next_in_sequence(&snapshot.sequence()), false);
187///
188/// /// Or if we had received this update:
189/// let diff: L2BookUpdate = serde_json::from_str(r#"{
190///     "t": "d",
191///     "ts": 1729700841,
192///     "tn": 0,
193///     "sid": 170,
194///     "sn": 9001,
195///     "b": [],
196///     "a": [["103.00", "1"]]
197/// }"#)?;
198///
199/// /// It appears that the sequence id is changed, signalling a new sequence.
200/// /// In this case, we should re-request the snapshot from the server.
201/// assert_eq!(diff.sequence().is_next_in_sequence(&snapshot.sequence()), false);
202///
203/// # Ok::<(), anyhow::Error>(())
204/// ```
205#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
206#[serde(tag = "t")]
207/// <!-- py: tag=t -->
208pub enum L2BookUpdate {
209    #[serde(rename = "s")]
210    #[schemars(title = "Snapshot|L2BookSnapshot")]
211    Snapshot(L2BookSnapshot),
212    #[serde(rename = "d")]
213    #[schemars(title = "Diff|L2BookDiff")]
214    Diff(L2BookDiff),
215}
216
217impl L2BookUpdate {
218    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
219        match self {
220            Self::Snapshot(snapshot) => snapshot.timestamp(),
221            Self::Diff(diff) => diff.timestamp(),
222        }
223    }
224
225    pub fn sequence(&self) -> SequenceIdAndNumber {
226        match self {
227            Self::Snapshot(snapshot) => snapshot.sequence,
228            Self::Diff(diff) => diff.sequence,
229        }
230    }
231
232    pub fn is_snapshot(&self) -> bool {
233        match self {
234            Self::Snapshot(_) => true,
235            Self::Diff(_) => false,
236        }
237    }
238}
239
240#[grpc(package = "json.architect")]
241#[grpc(service = "Marketdata", name = "l2_book_snapshot", response = "L2BookSnapshot")]
242#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
243pub struct L2BookSnapshotRequest {
244    #[serde(default, skip_serializing_if = "Option::is_none")]
245    pub venue: Option<MarketdataVenue>,
246    pub symbol: String,
247}
248
249#[grpc(package = "json.architect")]
250#[grpc(
251    service = "Marketdata",
252    name = "subscribe_l2_book_updates",
253    response = "L2BookUpdate",
254    server_streaming
255)]
256#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
257pub struct SubscribeL2BookUpdatesRequest {
258    #[serde(default, skip_serializing_if = "Option::is_none")]
259    pub venue: Option<MarketdataVenue>,
260    pub symbol: String,
261}
262
263// Subscribe to candles for a single market.
264#[grpc(package = "json.architect")]
265#[grpc(
266    service = "Marketdata",
267    name = "subscribe_candles",
268    response = "Candle",
269    server_streaming
270)]
271#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
272pub struct SubscribeCandlesRequest {
273    #[serde(default, skip_serializing_if = "Option::is_none")]
274    pub venue: Option<MarketdataVenue>,
275    pub symbol: String,
276    /// If None, subscribe from all candle widths on the feed
277    pub candle_widths: Option<Vec<CandleWidth>>,
278}
279
280// Subscribe to a single candle width across many markets.
281#[grpc(package = "json.architect")]
282#[grpc(
283    service = "Marketdata",
284    name = "subscribe_many_candles",
285    response = "Candle",
286    server_streaming
287)]
288#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
289pub struct SubscribeManyCandlesRequest {
290    #[serde(default, skip_serializing_if = "Option::is_none")]
291    pub venue: Option<MarketdataVenue>,
292    /// If None, subscribe from all symbols on the feed
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub symbols: Option<Vec<String>>,
295    pub candle_width: CandleWidth,
296}
297
298/// Subscribe to the current candle.  This allows you to display
299/// the most recent/building candle live in a UI, for example.
300#[grpc(package = "json.architect")]
301#[grpc(
302    service = "Marketdata",
303    name = "subscribe_current_candles",
304    response = "Candle",
305    server_streaming
306)]
307#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
308pub struct SubscribeCurrentCandlesRequest {
309    #[serde(default, skip_serializing_if = "Option::is_none")]
310    pub venue: Option<MarketdataVenue>,
311    pub symbol: String,
312    pub candle_width: CandleWidth,
313    /// If None, send the current candle on every trade or candle tick.
314    /// Otherwise, send a candle every `tick_period_ms`.
315    #[serde(default, skip_serializing_if = "Option::is_none")]
316    pub tick_period_ms: Option<u32>,
317}
318
319#[derive(
320    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
321)]
322pub struct Candle {
323    #[serde(rename = "ts")]
324    #[schemars(title = "timestamp")]
325    pub timestamp: i64,
326    #[serde(rename = "tn")]
327    #[schemars(title = "timestamp_ns")]
328    pub timestamp_ns: u32,
329    #[serde(rename = "w")]
330    #[schemars(title = "width")]
331    pub width: CandleWidth,
332    #[serde(rename = "s")]
333    #[schemars(title = "symbol")]
334    pub symbol: String,
335    #[serde(rename = "o")]
336    #[schemars(title = "open")]
337    pub open: Option<Decimal>,
338    #[serde(rename = "h")]
339    #[schemars(title = "high")]
340    pub high: Option<Decimal>,
341    #[serde(rename = "l")]
342    #[schemars(title = "low")]
343    pub low: Option<Decimal>,
344    #[serde(rename = "c")]
345    #[schemars(title = "close")]
346    pub close: Option<Decimal>,
347    #[serde(rename = "v")]
348    #[schemars(title = "volume")]
349    pub volume: Decimal,
350    #[serde(rename = "bv")]
351    #[schemars(title = "buy_volume")]
352    pub buy_volume: Decimal,
353    #[serde(rename = "av")]
354    #[schemars(title = "sell_volume")]
355    pub sell_volume: Decimal,
356    #[serde(default, rename = "mo", skip_serializing_if = "Option::is_none")]
357    #[schemars(title = "mid_open")]
358    pub mid_open: Option<Decimal>,
359    #[serde(default, rename = "mc", skip_serializing_if = "Option::is_none")]
360    #[schemars(title = "mid_close")]
361    pub mid_close: Option<Decimal>,
362    #[serde(default, rename = "mh", skip_serializing_if = "Option::is_none")]
363    #[schemars(title = "mid_high")]
364    pub mid_high: Option<Decimal>,
365    #[serde(default, rename = "ml", skip_serializing_if = "Option::is_none")]
366    #[schemars(title = "mid_low")]
367    pub mid_low: Option<Decimal>,
368    #[serde(default, rename = "bo", skip_serializing_if = "Option::is_none")]
369    #[schemars(title = "bid_open")]
370    pub bid_open: Option<Decimal>,
371    #[serde(default, rename = "bc", skip_serializing_if = "Option::is_none")]
372    #[schemars(title = "bid_close")]
373    pub bid_close: Option<Decimal>,
374    #[serde(default, rename = "bh", skip_serializing_if = "Option::is_none")]
375    #[schemars(title = "bid_high")]
376    pub bid_high: Option<Decimal>,
377    #[serde(default, rename = "bl", skip_serializing_if = "Option::is_none")]
378    #[schemars(title = "bid_low")]
379    pub bid_low: Option<Decimal>,
380    #[serde(default, rename = "ao", skip_serializing_if = "Option::is_none")]
381    #[schemars(title = "ask_open")]
382    pub ask_open: Option<Decimal>,
383    #[serde(default, rename = "ac", skip_serializing_if = "Option::is_none")]
384    #[schemars(title = "ask_close")]
385    pub ask_close: Option<Decimal>,
386    #[serde(default, rename = "ah", skip_serializing_if = "Option::is_none")]
387    #[schemars(title = "ask_high")]
388    pub ask_high: Option<Decimal>,
389    #[serde(default, rename = "al", skip_serializing_if = "Option::is_none")]
390    #[schemars(title = "ask_low")]
391    pub ask_low: Option<Decimal>,
392}
393
394impl Candle {
395    pub fn default(timestamp: DateTime<Utc>, width: CandleWidth, symbol: String) -> Self {
396        Self {
397            timestamp: timestamp.timestamp(),
398            timestamp_ns: timestamp.timestamp_subsec_nanos(),
399            width,
400            symbol,
401            open: None,
402            high: None,
403            low: None,
404            close: None,
405            volume: dec!(0),
406            buy_volume: dec!(0),
407            sell_volume: dec!(0),
408            mid_open: None,
409            mid_close: None,
410            mid_high: None,
411            mid_low: None,
412            bid_open: None,
413            bid_close: None,
414            bid_high: None,
415            bid_low: None,
416            ask_open: None,
417            ask_close: None,
418            ask_high: None,
419            ask_low: None,
420        }
421    }
422
423    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
424        DateTime::<Utc>::from_timestamp(self.timestamp, self.timestamp_ns)
425    }
426}
427
428// Query historical candles for a single market.
429#[grpc(package = "json.architect")]
430#[grpc(
431    service = "Marketdata",
432    name = "historical_candles",
433    response = "HistoricalCandlesResponse"
434)]
435#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
436pub struct HistoricalCandlesRequest {
437    pub symbol: String,
438    pub candle_width: CandleWidth,
439    pub start_date: DateTime<Utc>,
440    pub end_date: DateTime<Utc>,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
444pub struct HistoricalCandlesResponse {
445    pub candles: Vec<Candle>,
446}
447
448#[grpc(package = "json.architect")]
449#[grpc(
450    service = "Marketdata",
451    name = "subscribe_trades",
452    response = "Trade",
453    server_streaming
454)]
455#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
456pub struct SubscribeTradesRequest {
457    #[serde(default, skip_serializing_if = "Option::is_none")]
458    pub venue: Option<MarketdataVenue>,
459    /// If None, subscribe from all symbols on the feed
460    #[serde(default, skip_serializing_if = "Option::is_none")]
461    pub symbol: Option<String>,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
465pub struct Trade {
466    #[serde(rename = "s")]
467    #[schemars(title = "symbol")]
468    pub symbol: String,
469    #[serde(rename = "ts")]
470    #[schemars(title = "timestamp")]
471    pub timestamp: i64,
472    #[serde(rename = "tn")]
473    #[schemars(title = "timestamp_ns")]
474    pub timestamp_ns: u32,
475    #[serde(rename = "d")]
476    #[schemars(title = "direction")]
477    pub direction: Option<Dir>, // maker dir
478    #[serde(rename = "p")]
479    #[schemars(title = "price")]
480    pub price: Decimal,
481    #[serde(rename = "q")]
482    #[schemars(title = "size")]
483    pub size: Decimal,
484}
485
486impl Trade {
487    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
488        DateTime::<Utc>::from_timestamp(self.timestamp, self.timestamp_ns)
489    }
490}
491
492#[grpc(package = "json.architect")]
493#[grpc(service = "Marketdata", name = "market_status", response = "MarketStatus")]
494#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
495pub struct MarketStatusRequest {
496    #[serde(default, skip_serializing_if = "Option::is_none")]
497    pub venue: Option<MarketdataVenue>,
498    pub symbol: String,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
502#[cfg_attr(feature = "graphql", derive(juniper::GraphQLObject))]
503pub struct MarketStatus {
504    #[serde(rename = "s")]
505    #[schemars(title = "symbol")]
506    pub symbol: String,
507    pub is_trading: Option<bool>,
508    pub is_quoting: Option<bool>,
509}
510
511#[grpc(package = "json.architect")]
512#[grpc(service = "Marketdata", name = "ticker", response = "Ticker")]
513#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
514pub struct TickerRequest {
515    #[serde(default, skip_serializing_if = "Option::is_none")]
516    pub venue: Option<MarketdataVenue>,
517    pub symbol: String,
518}
519
520#[skip_serializing_none]
521#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
522pub struct TickerValues {
523    #[serde(rename = "bp")]
524    #[schemars(title = "bid_price")]
525    pub bid_price: Option<Decimal>,
526    #[serde(rename = "bs")]
527    #[schemars(title = "bid_size")]
528    pub bid_size: Option<Decimal>,
529    #[serde(rename = "ap")]
530    #[schemars(title = "ask_price")]
531    pub ask_price: Option<Decimal>,
532    #[serde(rename = "as")]
533    #[schemars(title = "ask_size")]
534    pub ask_size: Option<Decimal>,
535    #[serde(rename = "p")]
536    #[schemars(title = "last_price")]
537    pub last_price: Option<Decimal>,
538    #[serde(rename = "q")]
539    #[schemars(title = "last_size")]
540    pub last_size: Option<Decimal>,
541    #[serde(rename = "xo")]
542    #[schemars(title = "session_open")]
543    pub session_open: Option<Decimal>,
544    #[serde(rename = "xl")]
545    #[schemars(title = "session_low")]
546    pub session_low: Option<Decimal>,
547    #[serde(rename = "xh")]
548    #[schemars(title = "session_high")]
549    pub session_high: Option<Decimal>,
550    #[serde(rename = "xv")]
551    #[schemars(title = "session_volume")]
552    pub session_volume: Option<Decimal>,
553    #[serde(rename = "o")]
554    #[schemars(title = "open_24h")]
555    pub open_24h: Option<Decimal>,
556    #[serde(rename = "l")]
557    #[schemars(title = "low_24h")]
558    pub low_24h: Option<Decimal>,
559    #[serde(rename = "h")]
560    #[schemars(title = "high_24h")]
561    pub high_24h: Option<Decimal>,
562    #[serde(rename = "v")]
563    #[schemars(title = "volume_24h")]
564    pub volume_24h: Option<Decimal>,
565    #[serde(rename = "vm")]
566    #[schemars(title = "volume_30d")]
567    pub volume_30d: Option<Decimal>,
568    #[serde(rename = "oi")]
569    #[schemars(title = "open_interest")]
570    pub open_interest: Option<Decimal>,
571    #[serde(rename = "sp")]
572    #[schemars(title = "last_settlement_price")]
573    pub last_settlement_price: Option<Decimal>,
574    #[serde(rename = "sd")]
575    #[schemars(title = "last_settlement_date")]
576    pub last_settlement_date: Option<NaiveDate>,
577    #[serde(rename = "mp")]
578    #[schemars(title = "mark_price")]
579    pub mark_price: Option<Decimal>,
580    #[serde(rename = "ip")]
581    #[schemars(title = "index_price")]
582    pub index_price: Option<Decimal>,
583    #[serde(rename = "fr")]
584    #[schemars(title = "funding_rate")]
585    pub funding_rate: Option<Decimal>,
586    #[serde(rename = "ft")]
587    #[schemars(title = "next_funding_time")]
588    pub next_funding_time: Option<DateTime<Utc>>,
589    pub market_cap: Option<Decimal>,
590    pub price_to_earnings: Option<Decimal>,
591    pub eps_adj: Option<Decimal>,
592    pub shares_outstanding_weighted_adj: Option<Decimal>,
593    pub dividend: Option<Decimal>,
594    pub dividend_yield: Option<Decimal>,
595    /*
596    #[serde(rename = "dvex")]
597    #[schemars(title = "dividend_ex_date")]
598    pub dividend_ex_date: Option<String>,
599    */
600}
601
602impl TickerValues {
603    pub fn is_none(&self) -> bool {
604        self.session_open.is_none()
605            && self.session_low.is_none()
606            && self.session_high.is_none()
607            && self.open_24h.is_none()
608            && self.low_24h.is_none()
609            && self.high_24h.is_none()
610            && self.volume_24h.is_none()
611            && self.volume_30d.is_none()
612            && self.open_interest.is_none()
613            && self.last_settlement_price.is_none()
614            && self.mark_price.is_none()
615            && self.index_price.is_none()
616            && self.funding_rate.is_none()
617            && self.next_funding_time.is_none()
618    }
619
620    pub fn last_or_mid_price(&self) -> Option<Decimal> {
621        self.last_price.or_else(|| {
622            let bid_price = self.bid_price?;
623            let ask_price = self.ask_price?;
624            Some((bid_price + ask_price) / dec!(2))
625        })
626    }
627}
628
629#[derive(Debug, Deref, Clone, Serialize, Deserialize, JsonSchema)]
630pub struct Ticker {
631    #[serde(rename = "s")]
632    #[schemars(title = "symbol")]
633    pub symbol: String,
634    #[serde(rename = "ve")]
635    #[schemars(title = "venue")]
636    pub venue: MarketdataVenue,
637    #[serde(rename = "ts")]
638    #[schemars(title = "timestamp")]
639    pub timestamp: i64,
640    #[serde(rename = "tn")]
641    #[schemars(title = "timestamp_ns")]
642    pub timestamp_ns: u32,
643    #[serde(flatten)]
644    #[deref]
645    pub values: TickerValues,
646}
647
648impl Ticker {
649    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
650        DateTime::<Utc>::from_timestamp(self.timestamp, self.timestamp_ns)
651    }
652}
653
654#[derive(Debug, Copy, Clone, EnumString, Serialize, Deserialize, JsonSchema)]
655#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
656#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
657#[cfg_attr(feature = "graphql", derive(juniper::GraphQLEnum))]
658pub enum SortTickersBy {
659    VolumeDesc,
660    ChangeAsc,
661    ChangeDesc,
662    AbsChangeDesc,
663}
664
665#[grpc(package = "json.architect")]
666#[grpc(service = "Marketdata", name = "tickers", response = "TickersResponse")]
667#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
668pub struct TickersRequest {
669    #[serde(default, skip_serializing_if = "Option::is_none")]
670    pub venue: Option<MarketdataVenue>,
671    #[serde(default, skip_serializing_if = "Option::is_none")]
672    pub symbols: Option<Vec<String>>,
673    #[serde(default, flatten)]
674    pub pagination: OffsetAndLimit<SortTickersBy>,
675}
676
677#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
678pub struct TickersResponse {
679    pub tickers: Vec<Ticker>,
680}
681
682/// Ticker updates are not strongly ordered because the data is considered
683/// more casual.  You may receive diffs or snapshots slightly out of order.
684#[grpc(package = "json.architect")]
685#[grpc(
686    service = "Marketdata",
687    name = "ticker",
688    response = "TickerUpdate",
689    server_streaming
690)]
691#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
692pub struct SubscribeTickersRequest {
693    /// If None, subscribe from all symbols on the feed
694    #[serde(default, skip_serializing_if = "Option::is_none")]
695    pub symbols: Option<Vec<String>>,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
699#[serde(tag = "t")]
700/// <!-- py: tag=t -->
701
702pub enum TickerUpdate {
703    #[serde(rename = "s")]
704    #[schemars(title = "Snapshot|Ticker")]
705    Snapshot(Ticker),
706    #[serde(rename = "d")]
707    #[schemars(title = "Diff|Ticker")]
708    Diff(Ticker),
709}
710
711#[grpc(package = "json.architect")]
712#[grpc(service = "Marketdata", name = "subscribe_liquidations", response = "Liquidation")]
713#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]
714pub struct SubscribeLiquidationsRequest {
715    #[serde(default, skip_serializing_if = "Option::is_none")]
716    pub symbols: Option<Vec<String>>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
720pub struct Liquidation {
721    #[serde(rename = "s")]
722    #[schemars(title = "symbol")]
723    pub symbol: String,
724    #[serde(rename = "ts")]
725    #[schemars(title = "timestamp")]
726    pub timestamp: i64,
727    #[serde(rename = "tn")]
728    #[schemars(title = "timestamp_ns")]
729    pub timestamp_ns: u32,
730    #[serde(rename = "d")]
731    #[schemars(title = "direction")]
732    pub direction: Dir,
733    #[serde(rename = "p")]
734    #[schemars(title = "price")]
735    pub price: Decimal,
736    #[serde(rename = "q")]
737    #[schemars(title = "size")]
738    pub size: Decimal,
739}