use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::types::common::BuySell;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ContractType {
#[default]
Perpetual,
#[serde(alias = "futures_inverse", alias = "futures_vanilla")]
FixedMaturity,
#[serde(alias = "spot index")]
Index,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FuturesOrderType {
#[serde(alias = "lmt")]
#[default]
Limit,
#[serde(alias = "mkt")]
Market,
#[serde(alias = "stp")]
Stop,
TakeProfit,
#[serde(alias = "ioc")]
ImmediateOrCancel,
PostOnly,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FuturesOrderStatus {
#[serde(alias = "untouched")]
#[default]
Open,
#[serde(alias = "partiallyFilled")]
PartiallyFilled,
Filled,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FillType {
Maker,
Taker,
Liquidation,
Assignee,
Assignor,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AccountType {
#[serde(alias = "cashAccount")]
#[default]
Cash,
#[serde(alias = "marginAccount")]
Margin,
#[serde(alias = "multiCollateralMarginAccount")]
MultiCollateral,
FlexFutures,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesPosition {
pub symbol: String,
pub side: BuySell,
pub size: Decimal,
#[serde(alias = "price")]
pub entry_price: Decimal,
#[serde(default)]
pub mark_price: Option<Decimal>,
#[serde(default)]
pub liquidation_threshold: Option<Decimal>,
#[serde(default)]
pub unrealized_pnl: Option<Decimal>,
#[serde(default, alias = "unrealizedFunding")]
pub unrealized_funding: Option<Decimal>,
#[serde(default)]
pub initial_margin: Option<Decimal>,
#[serde(default)]
pub maintenance_margin: Option<Decimal>,
#[serde(default)]
pub effective_leverage: Option<Decimal>,
#[serde(default)]
pub return_on_equity: Option<Decimal>,
#[serde(default)]
pub pnl_currency: Option<String>,
#[serde(default, alias = "maxFixedLeverage")]
pub max_fixed_leverage: Option<Decimal>,
#[serde(default, alias = "fillTime")]
pub fill_time: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesOrder {
#[serde(alias = "order_id")]
pub order_id: String,
#[serde(default, alias = "cliOrdId")]
pub cli_ord_id: Option<String>,
pub symbol: String,
pub side: BuySell,
#[serde(alias = "orderType")]
pub order_type: FuturesOrderType,
pub status: FuturesOrderStatus,
#[serde(alias = "quantity", alias = "qty")]
pub size: Decimal,
#[serde(default, alias = "filledSize")]
pub filled_size: Decimal,
#[serde(default, alias = "unfilledSize")]
pub unfilled_size: Decimal,
#[serde(default, alias = "limitPrice")]
pub limit_price: Option<Decimal>,
#[serde(default, alias = "stopPrice")]
pub stop_price: Option<Decimal>,
#[serde(default, alias = "reduceOnly")]
pub reduce_only: bool,
#[serde(default, alias = "receivedTime")]
pub received_time: Option<String>,
#[serde(default, alias = "lastUpdateTime")]
pub last_update_time: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesFill {
#[serde(alias = "fill_id")]
pub fill_id: String,
#[serde(alias = "order_id")]
pub order_id: String,
#[serde(default, alias = "cliOrdId")]
pub cli_ord_id: Option<String>,
pub symbol: String,
pub side: BuySell,
pub size: Decimal,
pub price: Decimal,
#[serde(alias = "fillType")]
pub fill_type: FillType,
#[serde(alias = "fillTime")]
pub fill_time: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesAccount {
#[serde(alias = "type")]
pub account_type: AccountType,
#[serde(default)]
pub currency: Option<String>,
#[serde(default)]
pub balances: Option<std::collections::HashMap<String, Decimal>>,
#[serde(default)]
pub margin_requirements: Option<MarginRequirements>,
#[serde(default)]
pub trigger_estimates: Option<TriggerEstimates>,
#[serde(default)]
pub auxiliary: Option<AuxiliaryInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarginRequirements {
pub im: Decimal,
pub mm: Decimal,
pub lt: Decimal,
pub tt: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriggerEstimates {
pub im: Decimal,
pub mm: Decimal,
pub lt: Decimal,
pub tt: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuxiliaryInfo {
pub af: Decimal,
#[serde(default)]
pub funding: Option<Decimal>,
pub pnl: Decimal,
pub pv: Decimal,
#[serde(default)]
pub usd: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FlexAccountInfo {
pub currencies: std::collections::HashMap<String, FlexCurrencyBalance>,
pub balance_value: Decimal,
pub portfolio_value: Decimal,
pub collateral_value: Decimal,
pub initial_margin: Decimal,
pub maintenance_margin: Decimal,
pub pnl: Decimal,
#[serde(default)]
pub unrealized_funding: Option<Decimal>,
pub available_margin: Decimal,
pub margin_equity: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FlexCurrencyBalance {
pub quantity: Decimal,
pub value: Decimal,
#[serde(alias = "collateral_value")]
pub collateral_value: Decimal,
pub available: Decimal,
#[serde(default)]
pub haircut: Option<Decimal>,
#[serde(default)]
pub conversion_spread: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesInstrument {
pub symbol: String,
#[serde(default)]
pub pair: Option<String>,
#[serde(default, alias = "type")]
pub contract_type: Option<ContractType>,
#[serde(default)]
pub tradeable: Option<bool>,
#[serde(default, alias = "tickSize")]
pub tick_size: Option<Decimal>,
#[serde(default, alias = "contractSize")]
pub contract_size: Option<Decimal>,
#[serde(default)]
pub leverage: Option<String>,
#[serde(default, alias = "marginLevels")]
pub margin_levels: Option<Vec<MarginLevel>>,
#[serde(default, alias = "lastTradingTime")]
pub maturity_time: Option<String>,
#[serde(default, alias = "openingDate")]
pub opening_date: Option<String>,
#[serde(default)]
pub tag: Option<String>,
#[serde(default, alias = "postOnly")]
pub post_only: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarginLevel {
pub contracts: Decimal,
#[serde(alias = "initialMargin")]
pub initial_margin: Decimal,
#[serde(alias = "maintenanceMargin")]
pub maintenance_margin: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesTicker {
pub symbol: String,
#[serde(default)]
pub pair: Option<String>,
pub last: Decimal,
#[serde(default)]
pub bid: Option<Decimal>,
#[serde(default, alias = "bidSize")]
pub bid_size: Option<Decimal>,
#[serde(default)]
pub ask: Option<Decimal>,
#[serde(default, alias = "askSize")]
pub ask_size: Option<Decimal>,
#[serde(default)]
pub volume: Option<Decimal>,
#[serde(default, alias = "volumeQuote")]
pub volume_quote: Option<Decimal>,
#[serde(default, alias = "openInterest")]
pub open_interest: Option<Decimal>,
#[serde(default)]
pub open: Option<Decimal>,
#[serde(default)]
pub high: Option<Decimal>,
#[serde(default)]
pub low: Option<Decimal>,
#[serde(default)]
pub change: Option<Decimal>,
#[serde(default, alias = "markPrice")]
pub mark_price: Option<Decimal>,
#[serde(default, alias = "index")]
pub index_price: Option<Decimal>,
#[serde(default, alias = "fundingRate")]
pub funding_rate: Option<Decimal>,
#[serde(default, alias = "fundingRatePrediction")]
pub funding_rate_prediction: Option<Decimal>,
#[serde(default, alias = "nextFundingRateTime")]
pub next_funding_rate_time: Option<i64>,
#[serde(default)]
pub dtm: Option<i32>,
#[serde(default, alias = "maturityTime")]
pub maturity_time: Option<i64>,
#[serde(default)]
pub tag: Option<String>,
#[serde(default)]
pub suspended: Option<bool>,
#[serde(default, alias = "postOnly")]
pub post_only: Option<bool>,
#[serde(default)]
pub time: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesOrderBook {
pub symbol: String,
pub bids: Vec<BookLevel>,
pub asks: Vec<BookLevel>,
#[serde(default, alias = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookLevel {
pub price: Decimal,
#[serde(alias = "qty", alias = "quantity")]
pub size: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FuturesTrade {
#[serde(alias = "uid")]
pub trade_id: String,
pub price: Decimal,
#[serde(alias = "qty", alias = "quantity")]
pub size: Decimal,
pub side: BuySell,
#[serde(alias = "time")]
pub timestamp: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_position() {
let json = r#"{
"symbol": "PI_XBTUSD",
"side": "buy",
"size": "1000",
"price": "50000.0",
"unrealizedFunding": "0.001"
}"#;
let pos: FuturesPosition = serde_json::from_str(json).unwrap();
assert_eq!(pos.symbol, "PI_XBTUSD");
assert_eq!(pos.side, BuySell::Buy);
assert_eq!(pos.size, Decimal::from(1000));
}
#[test]
fn test_deserialize_order() {
let json = r#"{
"order_id": "abc123",
"symbol": "PI_XBTUSD",
"side": "sell",
"orderType": "lmt",
"status": "open",
"quantity": "500",
"filledSize": "0",
"unfilledSize": "500",
"limitPrice": "55000.0",
"reduceOnly": true
}"#;
let order: FuturesOrder = serde_json::from_str(json).unwrap();
assert_eq!(order.order_id, "abc123");
assert!(order.reduce_only);
assert_eq!(order.limit_price, Some(Decimal::from(55000)));
}
#[test]
fn test_deserialize_fill() {
let json = r#"{
"fill_id": "fill123",
"order_id": "order456",
"symbol": "PI_ETHUSD",
"side": "buy",
"size": "10",
"price": "3500.5",
"fillType": "taker",
"fillTime": "2024-01-15T10:30:00Z"
}"#;
let fill: FuturesFill = serde_json::from_str(json).unwrap();
assert_eq!(fill.fill_type, FillType::Taker);
}
#[test]
fn test_deserialize_ticker() {
let json = r#"{
"symbol": "PI_XBTUSD",
"last": "50000.0",
"bid": "49999.5",
"ask": "50000.5",
"fundingRate": "0.0001",
"openInterest": "1000000"
}"#;
let ticker: FuturesTicker = serde_json::from_str(json).unwrap();
assert_eq!(ticker.symbol, "PI_XBTUSD");
assert!(ticker.funding_rate.is_some());
}
#[test]
fn test_contract_type_serde() {
assert_eq!(
serde_json::from_str::<ContractType>(r#""perpetual""#).unwrap(),
ContractType::Perpetual
);
}
#[test]
fn test_order_type_serde() {
assert_eq!(
serde_json::from_str::<FuturesOrderType>(r#""lmt""#).unwrap(),
FuturesOrderType::Limit
);
assert_eq!(
serde_json::from_str::<FuturesOrderType>(r#""take_profit""#).unwrap(),
FuturesOrderType::TakeProfit
);
}
}