Skip to main content

deribit_fix/model/
position.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 6/3/26
5******************************************************************************/
6
7//! Position model types for Deribit API compatibility
8//!
9//! This module provides position-related types that were previously imported from
10//! deribit-base. These types represent trading positions and their associated data.
11
12use serde::{Deserialize, Serialize};
13
14/// Position direction enumeration
15///
16/// Indicates whether a position is long (buy) or short (sell).
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum Direction {
20    /// Buy direction (long position)
21    Buy,
22    /// Sell direction (short position)
23    Sell,
24}
25
26/// Trading position structure
27///
28/// Represents a trading position with all associated metrics including
29/// size, direction, prices, margins, and Greeks for options.
30#[derive(Clone, Serialize, Deserialize)]
31pub struct Position {
32    /// Name of the instrument (e.g., "BTC-PERPETUAL")
33    pub instrument_name: String,
34    /// Position size (positive for long, negative for short)
35    pub size: f64,
36    /// Direction of the position (buy/sell)
37    pub direction: Direction,
38    /// Average entry price of the position
39    pub average_price: f64,
40    /// Average price in USD
41    pub average_price_usd: Option<f64>,
42    /// Delta (price sensitivity) of the position
43    pub delta: Option<f64>,
44    /// Estimated liquidation price
45    pub estimated_liquidation_price: Option<f64>,
46    /// Floating (unrealized) profit/loss
47    pub floating_profit_loss: Option<f64>,
48    /// Floating profit/loss in USD
49    pub floating_profit_loss_usd: Option<f64>,
50    /// Gamma (delta sensitivity) of the position
51    pub gamma: Option<f64>,
52    /// Current index price
53    pub index_price: Option<f64>,
54    /// Initial margin requirement
55    pub initial_margin: Option<f64>,
56    /// Interest value
57    pub interest_value: Option<f64>,
58    /// Instrument kind (future, option, etc.)
59    pub kind: Option<String>,
60    /// Leverage used for the position
61    pub leverage: Option<i32>,
62    /// Maintenance margin requirement
63    pub maintenance_margin: Option<f64>,
64    /// Current mark price
65    pub mark_price: Option<f64>,
66    /// Margin used by open orders
67    pub open_orders_margin: Option<f64>,
68    /// Realized funding payments
69    pub realized_funding: Option<f64>,
70    /// Realized profit/loss
71    pub realized_profit_loss: Option<f64>,
72    /// Settlement price
73    pub settlement_price: Option<f64>,
74    /// Position size in currency units
75    pub size_currency: Option<f64>,
76    /// Theta (time decay) of the position
77    pub theta: Option<f64>,
78    /// Total profit/loss
79    pub total_profit_loss: Option<f64>,
80    /// Vega (volatility sensitivity) of the position
81    pub vega: Option<f64>,
82    /// Unrealized profit/loss
83    pub unrealized_profit_loss: Option<f64>,
84}
85
86impl_json_display!(Position);
87impl_json_debug_pretty!(Position);
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_direction_serialization() {
95        let buy = Direction::Buy;
96        let sell = Direction::Sell;
97
98        let buy_json = serde_json::to_string(&buy).unwrap();
99        let sell_json = serde_json::to_string(&sell).unwrap();
100
101        assert_eq!(buy_json, "\"buy\"");
102        assert_eq!(sell_json, "\"sell\"");
103
104        let buy_deserialized: Direction = serde_json::from_str(&buy_json).unwrap();
105        let sell_deserialized: Direction = serde_json::from_str(&sell_json).unwrap();
106
107        assert_eq!(buy_deserialized, Direction::Buy);
108        assert_eq!(sell_deserialized, Direction::Sell);
109    }
110
111    #[test]
112    fn test_position_creation() {
113        let position = Position {
114            instrument_name: "BTC-PERPETUAL".to_string(),
115            size: 1.5,
116            direction: Direction::Buy,
117            average_price: 50000.0,
118            average_price_usd: None,
119            delta: Some(0.5),
120            estimated_liquidation_price: None,
121            floating_profit_loss: Some(100.0),
122            floating_profit_loss_usd: None,
123            gamma: Some(0.001),
124            index_price: Some(50100.0),
125            initial_margin: Some(500.0),
126            interest_value: None,
127            kind: Some("future".to_string()),
128            leverage: Some(10),
129            maintenance_margin: Some(250.0),
130            mark_price: Some(50050.0),
131            open_orders_margin: None,
132            realized_funding: None,
133            realized_profit_loss: Some(50.0),
134            settlement_price: Some(50000.0),
135            size_currency: None,
136            theta: None,
137            total_profit_loss: Some(150.0),
138            vega: None,
139            unrealized_profit_loss: Some(100.0),
140        };
141
142        assert_eq!(position.instrument_name, "BTC-PERPETUAL");
143        assert_eq!(position.size, 1.5);
144        assert!(matches!(position.direction, Direction::Buy));
145        assert_eq!(position.average_price, 50000.0);
146    }
147
148    #[test]
149    fn test_position_serialization_roundtrip() {
150        let position = Position {
151            instrument_name: "ETH-PERPETUAL".to_string(),
152            size: -2.0,
153            direction: Direction::Sell,
154            average_price: 3500.0,
155            average_price_usd: Some(3500.0),
156            delta: None,
157            estimated_liquidation_price: Some(4000.0),
158            floating_profit_loss: Some(-50.0),
159            floating_profit_loss_usd: Some(-50.0),
160            gamma: None,
161            index_price: Some(3550.0),
162            initial_margin: Some(350.0),
163            interest_value: None,
164            kind: Some("future".to_string()),
165            leverage: Some(5),
166            maintenance_margin: Some(175.0),
167            mark_price: Some(3525.0),
168            open_orders_margin: None,
169            realized_funding: None,
170            realized_profit_loss: None,
171            settlement_price: None,
172            size_currency: Some(-2.0),
173            theta: None,
174            total_profit_loss: Some(-50.0),
175            vega: None,
176            unrealized_profit_loss: Some(-50.0),
177        };
178
179        let json = serde_json::to_string(&position).unwrap();
180        let deserialized: Position = serde_json::from_str(&json).unwrap();
181
182        assert_eq!(position.instrument_name, deserialized.instrument_name);
183        assert_eq!(position.size, deserialized.size);
184        assert_eq!(position.direction, deserialized.direction);
185        assert_eq!(position.average_price, deserialized.average_price);
186    }
187}