Skip to main content

hypercall_types/
margin_mode.rs

1//! Margin mode enum shared across the persistence and engine layers.
2//!
3//! Two margin regimes exist:
4//! - **Standard**: Deribit-style isolated margin. Each position's margin is
5//!   computed independently; no cross-collateralization between options and perps.
6//! - **Portfolio**: SPAN-based cross-margin. The margin engine runs scenario
7//!   analysis across the entire portfolio, allowing offsetting positions to
8//!   reduce total margin requirement.
9//!
10//! This enum lives in `hypercall-types` (not `hypercall-margin`) so that the
11//! DB trait layer can reference it without pulling in the full margin engine.
12
13use serde::{Deserialize, Serialize};
14use std::str::FromStr;
15
16/// Canonical string representations stored in the DB `user_tiers.margin_mode` column.
17pub mod mode_str {
18    pub const STANDARD: &str = "standard";
19    pub const PORTFOLIO: &str = "portfolio";
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ParseMarginModeError(pub String);
24
25impl std::fmt::Display for ParseMarginModeError {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        write!(f, "unknown margin mode: {}", self.0)
28    }
29}
30
31impl std::error::Error for ParseMarginModeError {}
32
33/// Per-wallet margin regime. Determines which margin engine path is used.
34///
35/// Stored as a lowercase string in the DB. The `FromStr` impl is case-insensitive
36/// for resilience, but writes always use the canonical lowercase form via `Display`.
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
38#[serde(rename_all = "lowercase")]
39pub enum MarginMode {
40    /// Isolated margin (Deribit-style). Each position margined independently.
41    #[default]
42    Standard,
43    /// Portfolio margin (SPAN cross-margin). Offsetting positions reduce requirements.
44    Portfolio,
45}
46
47impl MarginMode {
48    pub fn as_str(&self) -> &'static str {
49        match self {
50            MarginMode::Standard => mode_str::STANDARD,
51            MarginMode::Portfolio => mode_str::PORTFOLIO,
52        }
53    }
54
55    pub fn is_standard(&self) -> bool {
56        matches!(self, MarginMode::Standard)
57    }
58
59    pub fn is_portfolio(&self) -> bool {
60        matches!(self, MarginMode::Portfolio)
61    }
62}
63
64impl std::fmt::Display for MarginMode {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(f, "{}", self.as_str())
67    }
68}
69
70impl FromStr for MarginMode {
71    type Err = ParseMarginModeError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        match s.to_lowercase().as_str() {
75            mode_str::STANDARD => Ok(MarginMode::Standard),
76            mode_str::PORTFOLIO => Ok(MarginMode::Portfolio),
77            _ => Err(ParseMarginModeError(s.to_string())),
78        }
79    }
80}