Skip to main content

deribit_http/model/
portfolio_simulation.rs

1//! Portfolio simulation models for Deribit API
2//!
3//! This module contains types for portfolio margin simulation.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Request for simulate_portfolio endpoint
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct SimulatePortfolioRequest {
11    /// Currency for the simulation (e.g., "BTC", "ETH")
12    pub currency: String,
13    /// Whether to add simulated positions to existing positions
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub add_positions: Option<bool>,
16    /// Map of instrument names to simulated position sizes
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub simulated_positions: Option<HashMap<String, f64>>,
19}
20
21impl SimulatePortfolioRequest {
22    /// Creates a new simulation request for the specified currency
23    #[must_use]
24    pub fn new(currency: impl Into<String>) -> Self {
25        Self {
26            currency: currency.into(),
27            add_positions: None,
28            simulated_positions: None,
29        }
30    }
31
32    /// Sets whether to add positions to existing ones
33    #[must_use]
34    pub fn with_add_positions(mut self, add: bool) -> Self {
35        self.add_positions = Some(add);
36        self
37    }
38
39    /// Sets the simulated positions
40    #[must_use]
41    pub fn with_simulated_positions(mut self, positions: HashMap<String, f64>) -> Self {
42        self.simulated_positions = Some(positions);
43        self
44    }
45}
46
47/// Response for simulate_portfolio endpoint
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49pub struct SimulatePortfolioResponse {
50    /// Projected initial margin
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub projected_initial_margin: Option<f64>,
53    /// Projected maintenance margin
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub projected_maintenance_margin: Option<f64>,
56    /// Projected delta total
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub projected_delta_total: Option<f64>,
59    /// Change in margin
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub margin_change: Option<f64>,
62    /// Available funds after simulation
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub available_funds: Option<f64>,
65    /// Additional margin data
66    #[serde(flatten)]
67    pub additional: HashMap<String, serde_json::Value>,
68}
69
70/// Response for PME (Portfolio Margin Engine) simulation
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct PmeSimulateResponse {
73    /// Total projected margin
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub projected_margin: Option<f64>,
76    /// Liquidation price estimate
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub liquidation_price: Option<f64>,
79    /// Risk metrics
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub risk_value: Option<f64>,
82    /// Additional PME data
83    #[serde(flatten)]
84    pub additional: HashMap<String, serde_json::Value>,
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_simulate_portfolio_request_builder() {
93        let mut positions = HashMap::new();
94        positions.insert("BTC-PERPETUAL".to_string(), 1.0);
95
96        let request = SimulatePortfolioRequest::new("BTC")
97            .with_add_positions(true)
98            .with_simulated_positions(positions);
99
100        assert_eq!(request.currency, "BTC");
101        assert_eq!(request.add_positions, Some(true));
102        assert!(request.simulated_positions.is_some());
103    }
104
105    #[test]
106    fn test_simulate_portfolio_response_deserialization() {
107        let json = r#"{
108            "projected_initial_margin": 0.05,
109            "projected_maintenance_margin": 0.03,
110            "projected_delta_total": 1.5,
111            "margin_change": 0.01,
112            "available_funds": 0.95
113        }"#;
114
115        let response: SimulatePortfolioResponse =
116            serde_json::from_str(json).expect("Failed to parse");
117        assert_eq!(response.projected_initial_margin, Some(0.05));
118        assert_eq!(response.projected_maintenance_margin, Some(0.03));
119    }
120
121    #[test]
122    fn test_pme_simulate_response_deserialization() {
123        let json = r#"{
124            "projected_margin": 0.1,
125            "liquidation_price": 50000.0,
126            "risk_value": 0.05
127        }"#;
128
129        let response: PmeSimulateResponse = serde_json::from_str(json).expect("Failed to parse");
130        assert_eq!(response.projected_margin, Some(0.1));
131        assert_eq!(response.liquidation_price, Some(50000.0));
132    }
133}