Skip to main content

hypercall_sdk_types/
enums.rs

1//! Core enums used throughout Hypercall.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6
7/// Order side (buy or sell).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
10pub enum Side {
11    /// Buy side
12    Buy,
13    /// Sell side
14    Sell,
15}
16
17/// Time in force for orders.
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
20pub enum TimeInForce {
21    /// Good Till Cancelled
22    #[serde(rename = "gtc")]
23    #[default]
24    GTC,
25    /// Immediate or Cancel
26    #[serde(rename = "ioc")]
27    IOC,
28    /// Fill or Kill
29    #[serde(rename = "fok")]
30    FOK,
31}
32
33/// Option type (call or put).
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
36pub enum OptionType {
37    /// Call option
38    #[serde(rename = "CALL", alias = "Call", alias = "call")]
39    Call,
40    /// Put option
41    #[serde(rename = "PUT", alias = "Put", alias = "put")]
42    Put,
43}
44
45impl OptionType {
46    pub const fn as_db_str(self) -> &'static str {
47        match self {
48            Self::Call => "call",
49            Self::Put => "put",
50        }
51    }
52
53    pub const fn is_call(self) -> bool {
54        matches!(self, Self::Call)
55    }
56}
57
58impl fmt::Display for OptionType {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        f.write_str(self.as_db_str())
61    }
62}
63
64impl FromStr for OptionType {
65    type Err = ();
66
67    fn from_str(value: &str) -> Result<Self, Self::Err> {
68        if value.eq_ignore_ascii_case("call") {
69            return Ok(Self::Call);
70        }
71        if value.eq_ignore_ascii_case("put") {
72            return Ok(Self::Put);
73        }
74        Err(())
75    }
76}
77
78/// Order status in the order lifecycle.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
81pub enum OrderStatus {
82    /// Order acknowledged by the system
83    #[serde(rename = "ACKED", alias = "ACK")]
84    Acked,
85    /// Order is open on the book
86    #[serde(rename = "OPEN_ORDER", alias = "OPEN")]
87    OpenOrder,
88    /// Order was rejected
89    #[serde(
90        rename = "REJECT_ORDER",
91        alias = "REJECT",
92        alias = "REJECTED",
93        alias = "Rejected"
94    )]
95    RejectOrder,
96    /// Order fully filled
97    #[serde(rename = "FILLED", alias = "Filled")]
98    Filled,
99    /// Order partially filled
100    #[serde(rename = "PARTIALLY_FILLED", alias = "PartiallyFilled")]
101    PartiallyFilled,
102    /// Order was canceled
103    #[serde(rename = "CANCELED", alias = "CANCELLED")]
104    Canceled,
105}
106
107/// Order update status (used in order update messages).
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
109#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
110pub enum OrderUpdateStatus {
111    /// Order acknowledged by the system
112    #[serde(rename = "ACKED")]
113    Acked,
114    /// Order is open on the book
115    #[serde(rename = "OPEN")]
116    Open,
117    /// Order partially filled
118    #[serde(rename = "PARTIALLY_FILLED")]
119    PartiallyFilled,
120    /// Order fully filled
121    #[serde(rename = "FILLED")]
122    Filled,
123    /// Order was canceled
124    #[serde(rename = "CANCELED")]
125    Canceled,
126    /// Order was rejected
127    #[serde(rename = "REJECTED")]
128    Rejected,
129}
130
131/// Trade side for trade messages.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
133#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
134pub enum TradeSide {
135    #[serde(rename = "BUY", alias = "Buy", alias = "buy")]
136    Buy,
137    #[serde(rename = "SELL", alias = "Sell", alias = "sell")]
138    Sell,
139}
140
141/// Market action (create, delete, or expire).
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
143pub enum MarketAction {
144    #[serde(rename = "CREATE_MARKET")]
145    CreateMarket,
146    #[serde(rename = "DELETE_MARKET")]
147    DeleteMarket,
148    #[serde(rename = "EXPIRE_MARKET")]
149    ExpireMarket,
150}
151
152/// Market update status.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
154#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
155pub enum MarketUpdateStatus {
156    /// Market was created
157    #[serde(rename = "MARKET_CREATED")]
158    MarketCreated,
159    /// Market already exists (idempotent - not an error)
160    #[serde(rename = "MARKET_ALREADY_EXISTS")]
161    MarketAlreadyExists,
162    /// Market was deleted
163    #[serde(rename = "MARKET_DELETED")]
164    MarketDeleted,
165    /// Market expired
166    #[serde(rename = "MARKET_EXPIRED")]
167    MarketExpired,
168    /// Market transitioned to expired pending settlement (price unavailable or settlement deferred)
169    #[serde(rename = "MARKET_PENDING_SETTLEMENT")]
170    MarketPendingSettlement,
171    /// Market creation failed
172    #[serde(rename = "MARKET_CREATION_FAILED")]
173    MarketCreationFailed,
174    /// Market deletion failed
175    #[serde(rename = "MARKET_DELETION_FAILED")]
176    MarketDeletionFailed,
177}
178
179/// Order action (create, cancel, or replace).
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
181pub enum OrderAction {
182    #[serde(rename = "CREATE_ORDER")]
183    CreateOrder,
184    #[serde(rename = "CANCEL_ORDER")]
185    CancelOrder,
186    #[serde(rename = "REPLACE_ORDER")]
187    ReplaceOrder,
188}
189
190/// Transaction status for on-chain transactions.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
192pub enum TransactionStatus {
193    #[serde(rename = "PENDING")]
194    Pending,
195    #[serde(rename = "SUBMITTED")]
196    Submitted,
197    #[serde(rename = "CONFIRMED")]
198    Confirmed,
199    #[serde(rename = "FAILED")]
200    Failed,
201    #[serde(rename = "EXPIRED")]
202    Expired,
203}
204
205/// RFQ lifecycle status.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
207#[serde(rename_all = "snake_case")]
208#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
209pub enum RfqStatus {
210    Created,
211    SentToQps,
212    QuotesReceived,
213    NoQuotes,
214    Expired,
215    Accepted,
216    Executed,
217    Failed,
218}
219
220impl RfqStatus {
221    pub fn is_terminal(&self) -> bool {
222        matches!(
223            self,
224            RfqStatus::NoQuotes | RfqStatus::Expired | RfqStatus::Executed | RfqStatus::Failed
225        )
226    }
227
228    pub fn as_str(&self) -> &'static str {
229        match self {
230            RfqStatus::Created => "created",
231            RfqStatus::SentToQps => "sent_to_qps",
232            RfqStatus::QuotesReceived => "quotes_received",
233            RfqStatus::NoQuotes => "no_quotes",
234            RfqStatus::Expired => "expired",
235            RfqStatus::Accepted => "accepted",
236            RfqStatus::Executed => "executed",
237            RfqStatus::Failed => "failed",
238        }
239    }
240}
241
242impl std::fmt::Display for RfqStatus {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        f.write_str(self.as_str())
245    }
246}
247
248/// Quote provider tier.
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
250#[serde(rename_all = "snake_case")]
251#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
252pub enum QpTier {
253    Qp1,
254    Qp2,
255    Qp3Internal,
256}
257
258impl QpTier {
259    pub fn as_str(&self) -> &'static str {
260        match self {
261            QpTier::Qp1 => "qp1",
262            QpTier::Qp2 => "qp2",
263            QpTier::Qp3Internal => "qp3_internal",
264        }
265    }
266}
267
268impl std::fmt::Display for QpTier {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        f.write_str(self.as_str())
271    }
272}
273
274/// Quote provider status.
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
276#[serde(rename_all = "snake_case")]
277#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
278pub enum QpStatus {
279    Active,
280    Suspended,
281}
282
283/// Source of a fill (orderbook matching or RFQ execution).
284#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
285#[serde(tag = "type", rename_all = "snake_case")]
286#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
287pub enum FillSource {
288    #[default]
289    Orderbook,
290    Rfq {
291        rfq_id: String,
292        quote_id: String,
293    },
294}
295
296bitflags::bitflags! {
297    /// Set of trading modes enabled for an instrument. Empty means the
298    /// instrument is fully disabled. New variants can be added without
299    /// the combinatorial explosion of an enum.
300    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
301    pub struct TradingModes: u8 {
302        const ORDERBOOK = 0b0000_0001;
303        const RFQ       = 0b0000_0010;
304    }
305}
306
307impl Default for TradingModes {
308    fn default() -> Self {
309        TradingModes::ORDERBOOK
310    }
311}
312
313impl TradingModes {
314    pub fn allows_orderbook(&self) -> bool {
315        self.contains(Self::ORDERBOOK)
316    }
317
318    pub fn allows_rfq(&self) -> bool {
319        self.contains(Self::RFQ)
320    }
321
322    /// Canonical wire / DB representation: pipe-joined lowercase tokens
323    /// (e.g. `"orderbook"`, `"rfq"`, `"orderbook|rfq"`, `""` for empty).
324    pub fn as_db_str(&self) -> String {
325        let mut parts = Vec::with_capacity(2);
326        if self.contains(Self::ORDERBOOK) {
327            parts.push("orderbook");
328        }
329        if self.contains(Self::RFQ) {
330            parts.push("rfq");
331        }
332        parts.join("|")
333    }
334
335    /// Parse from DB / wire format. Accepts the canonical pipe-delimited
336    /// form and the legacy single-value form (`orderbook_only`, `rfq_only`,
337    /// `both`) so snapshots written before the bitflags migration still
338    /// rehydrate during the rollout window.
339    pub fn from_db_str(s: &str) -> Self {
340        // Fast path for legacy single-value snapshots written before the
341        // bitflags migration; accept them as-is so the rollout window
342        // doesn't require a full backfill.
343        match s {
344            "" => return Self::empty(),
345            "orderbook_only" => return Self::ORDERBOOK,
346            "rfq_only" => return Self::RFQ,
347            "both" => return Self::ORDERBOOK | Self::RFQ,
348            _ => {}
349        }
350        let mut modes = Self::empty();
351        for part in s.split('|') {
352            match part.trim() {
353                "" => {}
354                "orderbook" => modes |= Self::ORDERBOOK,
355                "rfq" => modes |= Self::RFQ,
356                other => {
357                    // Unknown tokens previously panicked and took the
358                    // whole API process down on schema drift. Log loudly
359                    // instead and ignore the bad token — boundary parsing
360                    // should never crash a production binary. The fall-
361                    // back mode becomes whatever other tokens in `s`
362                    // resolved to (may be empty).
363                    tracing::error!(
364                        "TradingModes::from_db_str: unknown token '{}' in '{}', \
365                         ignoring — may indicate schema drift or corruption",
366                        other,
367                        s
368                    );
369                }
370            }
371        }
372        modes
373    }
374}
375
376impl std::fmt::Display for TradingModes {
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        f.write_str(&self.as_db_str())
379    }
380}
381
382impl Serialize for TradingModes {
383    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
384        serializer.serialize_str(&self.as_db_str())
385    }
386}
387
388impl<'de> Deserialize<'de> for TradingModes {
389    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
390        let s = String::deserialize(deserializer)?;
391        Ok(Self::from_db_str(&s))
392    }
393}