use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Direction {
Buy,
Sell,
Zero,
}
impl Direction {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Direction::Buy => "buy",
Direction::Sell => "sell",
Direction::Zero => "zero",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Position {
pub average_price: f64,
#[serde(default)]
pub average_price_usd: Option<f64>,
#[serde(default)]
pub delta: Option<f64>,
pub direction: Direction,
#[serde(default)]
pub estimated_liquidation_price: Option<f64>,
#[serde(default)]
pub floating_profit_loss: Option<f64>,
#[serde(default)]
pub floating_profit_loss_usd: Option<f64>,
#[serde(default)]
pub gamma: Option<f64>,
#[serde(default)]
pub index_price: Option<f64>,
#[serde(default)]
pub initial_margin: Option<f64>,
pub instrument_name: String,
#[serde(default)]
pub interest_value: Option<f64>,
#[serde(default)]
pub kind: Option<String>,
#[serde(default)]
pub leverage: Option<i32>,
#[serde(default)]
pub maintenance_margin: Option<f64>,
#[serde(default)]
pub mark_price: Option<f64>,
#[serde(default)]
pub open_orders_margin: Option<f64>,
#[serde(default)]
pub realized_funding: Option<f64>,
#[serde(default)]
pub realized_profit_loss: Option<f64>,
#[serde(default)]
pub settlement_price: Option<f64>,
pub size: f64,
#[serde(default)]
pub size_currency: Option<f64>,
#[serde(default)]
pub theta: Option<f64>,
#[serde(default)]
pub total_profit_loss: Option<f64>,
#[serde(default)]
pub vega: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurrencySummary {
pub currency: String,
pub balance: f64,
pub equity: f64,
pub available_funds: f64,
pub margin_balance: f64,
#[serde(default)]
pub total_pl: Option<f64>,
#[serde(default)]
pub session_rpl: Option<f64>,
#[serde(default)]
pub session_upl: Option<f64>,
pub maintenance_margin: f64,
pub initial_margin: f64,
#[serde(default)]
pub available_withdrawal_funds: Option<f64>,
#[serde(default)]
pub cross_collateral_enabled: Option<bool>,
#[serde(default)]
pub delta_total: Option<f64>,
#[serde(default)]
pub futures_pl: Option<f64>,
#[serde(default)]
pub futures_session_rpl: Option<f64>,
#[serde(default)]
pub futures_session_upl: Option<f64>,
#[serde(default)]
pub options_delta: Option<f64>,
#[serde(default)]
pub options_gamma: Option<f64>,
#[serde(default)]
pub options_pl: Option<f64>,
#[serde(default)]
pub options_session_rpl: Option<f64>,
#[serde(default)]
pub options_session_upl: Option<f64>,
#[serde(default)]
pub options_theta: Option<f64>,
#[serde(default)]
pub options_vega: Option<f64>,
#[serde(default)]
pub portfolio_margining_enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountSummary {
#[serde(default)]
pub id: Option<u64>,
#[serde(default)]
pub email: Option<String>,
#[serde(default)]
pub system_name: Option<String>,
#[serde(default)]
pub username: Option<String>,
#[serde(default)]
pub creation_timestamp: Option<u64>,
#[serde(rename = "type", default)]
pub account_type: Option<String>,
#[serde(default)]
pub mmp_enabled: Option<bool>,
#[serde(default)]
pub summaries: Option<Vec<CurrencySummary>>,
#[serde(default)]
pub currency: Option<String>,
#[serde(default)]
pub balance: Option<f64>,
#[serde(default)]
pub equity: Option<f64>,
#[serde(default)]
pub available_funds: Option<f64>,
#[serde(default)]
pub margin_balance: Option<f64>,
#[serde(default)]
pub initial_margin: Option<f64>,
#[serde(default)]
pub maintenance_margin: Option<f64>,
#[serde(default)]
pub delta_total: Option<f64>,
#[serde(default)]
pub options_value: Option<f64>,
#[serde(default)]
pub futures_pl: Option<f64>,
#[serde(default)]
pub options_pl: Option<f64>,
#[serde(default)]
pub total_pl: Option<f64>,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_direction_as_str() {
assert_eq!(Direction::Buy.as_str(), "buy");
assert_eq!(Direction::Sell.as_str(), "sell");
assert_eq!(Direction::Zero.as_str(), "zero");
}
#[test]
fn test_direction_serialization() {
let dir = Direction::Buy;
let json = serde_json::to_string(&dir).expect("serialize");
assert_eq!(json, "\"buy\"");
}
#[test]
fn test_direction_deserialization() {
let dir: Direction = serde_json::from_str("\"sell\"").expect("deserialize");
assert_eq!(dir, Direction::Sell);
}
#[test]
fn test_position_deserialization() {
let json = r#"{
"average_price": 50000.0,
"direction": "buy",
"instrument_name": "BTC-PERPETUAL",
"size": 100.0,
"floating_profit_loss": 50.0,
"mark_price": 50050.0
}"#;
let position: Position = serde_json::from_str(json).expect("deserialize");
assert_eq!(position.instrument_name, "BTC-PERPETUAL");
assert_eq!(position.size, 100.0);
assert_eq!(position.direction, Direction::Buy);
assert_eq!(position.average_price, 50000.0);
}
#[test]
fn test_currency_summary_deserialization() {
let json = r#"{
"currency": "BTC",
"balance": 1.5,
"equity": 1.6,
"available_funds": 1.0,
"margin_balance": 1.5,
"maintenance_margin": 0.1,
"initial_margin": 0.2
}"#;
let summary: CurrencySummary = serde_json::from_str(json).expect("deserialize");
assert_eq!(summary.currency, "BTC");
assert_eq!(summary.balance, 1.5);
assert_eq!(summary.equity, 1.6);
}
#[test]
fn test_account_summary_deserialization() {
let json = r#"{
"currency": "BTC",
"balance": 1.5,
"equity": 1.6,
"available_funds": 1.0,
"margin_balance": 1.5,
"initial_margin": 0.2,
"maintenance_margin": 0.1,
"delta_total": 0.5
}"#;
let summary: AccountSummary = serde_json::from_str(json).expect("deserialize");
assert_eq!(summary.currency, Some("BTC".to_string()));
assert_eq!(summary.balance, Some(1.5));
}
}