architect_api/marketdata/
mod.rs

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