use serde::{Deserialize, Serialize};
use super::asset::Asset;
use super::de::{opt_f64_from_string_or_number, opt_i64_from_string_or_number};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Accounts {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub accounts: Vec<Account>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Account {
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub account_index: Option<i64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub account_type: Option<i64>,
#[serde(default)]
pub l1_address: Option<String>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub total_order_count: Option<i64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub total_isolated_order_count: Option<i64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub pending_order_count: Option<i64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub available_balance: Option<f64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub status: Option<i64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub collateral: Option<f64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub cancel_all_time: Option<i64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub transaction_time: Option<i64>,
#[serde(default)]
pub assets: Option<Vec<Asset>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetailedAccounts {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub accounts: Vec<DetailedAccount>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetailedAccount {
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub account_index: Option<i64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub account_type: Option<i64>,
#[serde(default)]
pub l1_address: Option<String>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub available_balance: Option<f64>,
#[serde(default)]
pub positions: Option<Vec<AccountPosition>>,
#[serde(default)]
pub assets: Option<Vec<Asset>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountPosition {
pub market_id: i64,
pub symbol: String,
pub initial_margin_fraction: String,
pub open_order_count: i64,
pub pending_order_count: i64,
pub position_tied_order_count: i64,
pub sign: i64,
pub position: String,
pub avg_entry_price: String,
pub position_value: String,
pub unrealized_pnl: String,
pub realized_pnl: String,
pub liquidation_price: String,
#[serde(default)]
pub total_funding_paid_out: Option<String>,
pub margin_mode: i64,
pub allocated_margin: String,
}
impl AccountPosition {
pub fn position_f64(&self) -> Option<f64> {
self.position.parse::<f64>().ok()
}
pub fn signed_position(&self) -> f64 {
let qty = self.position_f64().unwrap_or(0.0);
if qty == 0.0 {
return 0.0;
}
if self.sign < 0 {
-qty.abs()
} else if self.sign > 0 {
qty.abs()
} else {
qty
}
}
pub fn side_label(&self) -> &'static str {
if self.signed_position() < 0.0 {
"short"
} else if self.signed_position() > 0.0 {
"long"
} else {
"flat"
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountApiKeys {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub api_keys: Vec<AccountApiKey>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountApiKey {
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub api_key_index: Option<i64>,
#[serde(default)]
pub public_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubAccounts {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub l1_address: Option<String>,
#[serde(default)]
pub sub_accounts: Vec<Account>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountLimits {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub max_llp_percentage: Option<i64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub max_llp_amount: Option<f64>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub user_tier: Option<i64>,
#[serde(default)]
pub can_create_public_pool: Option<bool>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountPnl {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub resolution: Option<String>,
#[serde(default)]
pub pnl: Vec<PnlEntry>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PnlEntry {
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub timestamp: Option<i64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub trade_pnl: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub trade_spot_pnl: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub inflow: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub outflow: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub spot_outflow: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub spot_inflow: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub pool_pnl: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub pool_inflow: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub pool_outflow: Option<f64>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub pool_total_shares: Option<f64>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountMetadatas {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub account_metadatas: Vec<AccountMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountMetadata {
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub account_index: Option<i64>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub can_invite: Option<bool>,
#[serde(default, deserialize_with = "opt_f64_from_string_or_number")]
pub referral_points_percentage: Option<f64>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangeAccountTierResponse {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NextNonce {
pub code: i64,
#[serde(default)]
pub message: Option<String>,
#[serde(default, deserialize_with = "opt_i64_from_string_or_number")]
pub nonce: Option<i64>,
}
#[cfg(test)]
mod tests {
use super::AccountPosition;
#[test]
fn parses_position_fields() {
let raw = serde_json::json!({
"market_id": 93,
"symbol": "XAG",
"initial_margin_fraction": "100",
"open_order_count": 0,
"pending_order_count": 0,
"position_tied_order_count": 0,
"sign": 1,
"position": "0.53",
"avg_entry_price": "76.8259",
"position_value": "40.650311",
"unrealized_pnl": "-0.067422",
"realized_pnl": "0.000000",
"liquidation_price": "70.0",
"total_funding_paid_out": "0.0",
"margin_mode": 1,
"allocated_margin": "40.717733"
});
let pos: AccountPosition = serde_json::from_value(raw).unwrap();
assert_eq!(pos.market_id, 93);
assert_eq!(pos.symbol, "XAG");
assert_eq!(pos.position, "0.53");
assert_eq!(pos.sign, 1);
assert_eq!(pos.avg_entry_price, "76.8259");
assert_eq!(pos.position_value, "40.650311");
assert_eq!(pos.unrealized_pnl, "-0.067422");
assert_eq!(pos.realized_pnl, "0.000000");
assert_eq!(pos.allocated_margin, "40.717733");
assert_eq!(pos.signed_position(), 0.53);
assert_eq!(pos.side_label(), "long");
}
#[test]
fn parses_short_position_sign() {
let raw = serde_json::json!({
"market_id": 93,
"symbol": "XAG",
"initial_margin_fraction": "100",
"open_order_count": 0,
"pending_order_count": 0,
"position_tied_order_count": 0,
"position": "0.40",
"avg_entry_price": "75.0",
"position_value": "30.0",
"unrealized_pnl": "0.2",
"realized_pnl": "0.0",
"liquidation_price": "80.0",
"total_funding_paid_out": "0.0",
"margin_mode": 1,
"allocated_margin": "40.0",
"sign": -1
});
let pos: AccountPosition = serde_json::from_value(raw).unwrap();
assert_eq!(pos.market_id, 93);
assert_eq!(pos.position, "0.40");
assert_eq!(pos.avg_entry_price, "75.0");
assert_eq!(pos.initial_margin_fraction, "100");
assert_eq!(pos.margin_mode, 1);
assert_eq!(pos.sign, -1);
assert_eq!(pos.signed_position(), -0.40);
assert_eq!(pos.side_label(), "short");
}
#[test]
fn parses_avg_entry_price_field() {
let raw = serde_json::json!({
"market_id": 93,
"symbol": "XAG",
"initial_margin_fraction": "100",
"open_order_count": 0,
"pending_order_count": 0,
"position_tied_order_count": 0,
"sign": 1,
"position": "0.40",
"avg_entry_price": "75.0",
"position_value": "30.0",
"unrealized_pnl": "0.2",
"realized_pnl": "0.0",
"liquidation_price": "80.0",
"total_funding_paid_out": "0.0",
"margin_mode": 1,
"allocated_margin": "40.0"
});
let pos: AccountPosition = serde_json::from_value(raw).unwrap();
assert_eq!(pos.avg_entry_price, "75.0");
}
}