asterdex-sdk 0.1.1

AsterDex Futures SDK v3 — Rust async client for REST and WebSocket APIs
Documentation
// Tech Lead mini-task (Wave 3): replaced stubs with full type definitions
// per 05_detail_design.md §2.3

use serde::{Deserialize, Serialize};
use crate::futures::models::de;

// --- Type Aliases ---

/// All price values are String to avoid IEEE-754 precision loss (ADR-007).
pub type Price = String;
/// All quantity values are String to avoid IEEE-754 precision loss (ADR-007).
pub type Quantity = String;

// --- Request Params ---

/// Parameters for placing a new order.
#[derive(Debug, Serialize, Default)]
pub struct PlaceOrderParams {
    pub symbol: String,
    pub side: String,
    #[serde(rename = "type")]
    pub order_type: String,
    pub quantity: Quantity,
    pub price: Option<Price>,
    #[serde(rename = "timeInForce")]
    pub time_in_force: Option<String>,
    #[serde(rename = "reduceOnly")]
    pub reduce_only: Option<bool>,
    #[serde(rename = "newClientOrderId")]
    pub new_client_order_id: Option<String>,
    #[serde(rename = "stopPrice")]
    pub stop_price: Option<Price>,
    #[serde(rename = "activationPrice")]
    pub activation_price: Option<Price>,
    #[serde(rename = "callbackRate")]
    pub callback_rate: Option<String>,
    #[serde(rename = "workingType")]
    pub working_type: Option<String>,
    #[serde(rename = "priceProtect")]
    pub price_protect: Option<bool>,
    #[serde(rename = "newOrderRespType")]
    pub new_order_resp_type: Option<String>,
}

/// Parameters for cancelling an existing order.
#[derive(Debug, Serialize, Default)]
pub struct CancelOrderParams {
    pub symbol: String,
    #[serde(rename = "orderId")]
    pub order_id: Option<i64>,
    #[serde(rename = "origClientOrderId")]
    pub orig_client_order_id: Option<String>,
}

/// Parameters for modifying (amending) an existing open LIMIT order.
///
/// Either `order_id` or `orig_client_order_id` must be provided.
/// At least one of `quantity` or `price` must be set.
/// Uses `PUT /fapi/v3/order`.
#[derive(Debug, Default)]
pub struct ModifyOrderParams {
    pub symbol: String,
    pub order_id: Option<i64>,
    pub orig_client_order_id: Option<String>,
    pub quantity: Option<Quantity>,
    pub price: Option<Price>,
}

// --- Response Types ---

/// Response from placing a new order.
#[derive(Debug, Deserialize)]
pub struct OrderResponse {
    #[serde(rename = "orderId")]
    pub order_id: i64,
    pub symbol: String,
    pub status: String,
    #[serde(rename = "clientOrderId")]
    pub client_order_id: String,
    pub price: Price,
    #[serde(rename = "avgPrice")]
    pub avg_price: Price,
    #[serde(rename = "origQty")]
    pub orig_qty: Quantity,
    #[serde(rename = "executedQty")]
    pub executed_qty: Quantity,
    /// Present in POST /order responses; absent in GET /openOrders responses.
    #[serde(rename = "cumQty", default)]
    pub cum_qty: Quantity,
    #[serde(rename = "cumQuote")]
    pub cum_quote: Price,
    #[serde(rename = "timeInForce")]
    pub time_in_force: String,
    #[serde(rename = "type")]
    pub order_type: String,
    pub side: String,
    #[serde(rename = "stopPrice")]
    pub stop_price: Price,
    #[serde(rename = "workingType")]
    pub working_type: String,
    /// Only present for TRAILING_STOP_MARKET orders.
    #[serde(rename = "activatePrice", default)]
    pub activate_price: Price,
    /// Only present for TRAILING_STOP_MARKET orders.
    #[serde(rename = "priceRate", default)]
    pub price_rate: Price,
    #[serde(rename = "updateTime")]
    pub update_time: u64,
    #[serde(rename = "origType")]
    pub orig_type: String,
    #[serde(rename = "positionSide")]
    pub position_side: String,
    #[serde(rename = "priceProtect")]
    pub price_protect: bool,
    #[serde(rename = "reduceOnly")]
    pub reduce_only: bool,
}

/// Cancel order response has the same shape as OrderResponse.
pub type CancelOrderResponse = OrderResponse;

/// Current position risk information for a symbol.
///
/// The testnet (v2 schema) and mainnet (v3 schema) return overlapping but
/// non-identical field sets.  All schema-variant fields use `#[serde(default)]`
/// so both responses deserialize without error.
///
/// Schema differences handled here:
/// - `unrealizedProfit` (v3) ↔ `unRealizedProfit` (v2) — both accepted via alias.
/// - `maxNotional` (v3) ↔ `maxNotionalValue` (v2) — both accepted via alias.
/// - `initialMargin`, `maintMargin`, `positionInitialMargin`,
///   `openOrderInitialMargin`, `isolated` — v3-only fields, default when absent.
/// - `markPrice`, `liquidationPrice`, `marginType`, `isolatedMargin`,
///   `isAutoAddMargin`, `notional`, `isolatedWallet` — v2-only fields, default
///   when absent.
#[derive(Debug, Deserialize)]
pub struct PositionResponse {
    pub symbol: String,
    // ----- v3-only fields (absent in testnet / v2 responses) -----
    #[serde(rename = "initialMargin", default)]
    pub initial_margin: String,
    #[serde(rename = "maintMargin", default)]
    pub maint_margin: String,
    #[serde(rename = "positionInitialMargin", default)]
    pub position_initial_margin: String,
    #[serde(rename = "openOrderInitialMargin", default)]
    pub open_order_initial_margin: String,
    /// `false` when missing (v2 responses use `marginType` instead).
    #[serde(default)]
    pub isolated: bool,
    // ----- shared fields (may differ in key name across schemas) -----
    /// Accepts both `"unrealizedProfit"` (v3) and `"unRealizedProfit"` (v2).
    #[serde(rename = "unrealizedProfit", alias = "unRealizedProfit", default)]
    pub unrealized_profit: String,
    /// Accepts both `"maxNotional"` (v3) and `"maxNotionalValue"` (v2).
    #[serde(rename = "maxNotional", alias = "maxNotionalValue", default)]
    pub max_notional: String,
    pub leverage: String,
    #[serde(rename = "entryPrice")]
    pub entry_price: String,
    #[serde(rename = "positionSide")]
    pub position_side: String,
    #[serde(rename = "positionAmt")]
    pub position_amt: String,
    #[serde(rename = "updateTime")]
    pub update_time: u64,
    // ----- v2-only fields (absent in v3 responses) -----
    #[serde(rename = "markPrice", default)]
    pub mark_price: String,
    #[serde(rename = "liquidationPrice", default)]
    pub liquidation_price: String,
    #[serde(rename = "marginType", default)]
    pub margin_type: String,
    #[serde(rename = "isolatedMargin", default)]
    pub isolated_margin: String,
    #[serde(rename = "isAutoAddMargin", default)]
    pub is_auto_add_margin: String,
    #[serde(default)]
    pub notional: String,
    #[serde(rename = "isolatedWallet", default)]
    pub isolated_wallet: String,
}

/// Stub for listen key response — fully defined in Wave 7.
#[derive(Debug, Deserialize)]
pub struct ListenKeyResponse {
    #[serde(rename = "listenKey")]
    pub listen_key: String,
}

/// Used when the API returns an empty JSON object `{}`.
#[derive(Debug, Deserialize, Default)]
pub struct EmptyResponse {}

// --- Tech Lead mini-task (Wave 9): additional request/response types for US-014 ---

/// Position margin operation type: 1 = add, 2 = reduce.
#[derive(Debug, Clone, Copy)]
pub enum PositionMarginType {
    Add = 1,
    Reduce = 2,
}

impl std::fmt::Display for PositionMarginType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", *self as u8)
    }
}

/// Query a single order by ID or client order ID.
#[derive(Debug, Default)]
pub struct GetOrderParams {
    pub symbol: String,
    pub order_id: Option<i64>,
    pub orig_client_order_id: Option<String>,
}

/// Query all orders (open + historical) for a symbol.
#[derive(Debug, Default)]
pub struct AllOrdersParams {
    pub symbol: String,
    pub order_id: Option<i64>,
    pub start_time: Option<u64>,
    pub end_time: Option<u64>,
    pub limit: Option<u32>,
}

/// Query user trade history for a symbol.
#[derive(Debug, Default)]
pub struct UserTradesParams {
    pub symbol: String,
    pub order_id: Option<i64>,
    pub start_time: Option<u64>,
    pub end_time: Option<u64>,
    pub from_id: Option<i64>,
    pub limit: Option<u32>,
}

/// Query income history (funding fees, realized PnL, etc.).
#[derive(Debug, Default)]
pub struct IncomeParams {
    pub symbol: Option<String>,
    pub income_type: Option<String>,
    pub start_time: Option<u64>,
    pub end_time: Option<u64>,
    pub limit: Option<u32>,
}

/// Add or reduce isolated position margin.
#[derive(Debug)]
pub struct PositionMarginParams {
    pub symbol: String,
    pub position_side: Option<String>,
    pub amount: String,
    pub margin_type: u8,
}

/// Set countdown to auto-cancel all open orders.
#[derive(Debug)]
pub struct CountdownParams {
    pub symbol: String,
    pub countdown_time: i64,
}

/// User trade record.
#[derive(Debug, Deserialize)]
pub struct UserTrade {
    pub symbol: String,
    pub id: i64,
    #[serde(rename = "orderId")] pub order_id: i64,
    pub price: Price,
    pub qty: Quantity,
    #[serde(rename = "realizedPnl")] pub realized_pnl: Price,
    #[serde(rename = "marginAsset")] pub margin_asset: String,
    #[serde(rename = "quoteQty")] pub quote_qty: Price,
    pub commission: Price,
    #[serde(rename = "commissionAsset")] pub commission_asset: String,
    pub time: u64,
    #[serde(rename = "positionSide")] pub position_side: String,
    pub buyer: bool,
    pub maker: bool,
    pub side: String,
}

/// Income history record.
#[derive(Debug, Deserialize)]
pub struct IncomeRecord {
    pub symbol: String,
    #[serde(rename = "incomeType")] pub income_type: String,
    pub income: Price,
    pub asset: String,
    pub info: String,
    pub time: u64,
    /// May be returned as a JSON integer or a JSON string depending on income type.
    #[serde(rename = "tranId", default, deserialize_with = "de::opt_string_or_number")]
    pub tran_id: Option<String>,
    #[serde(rename = "tradeId")] pub trade_id: Option<String>,
}

/// Response from `set_position_margin`.
#[derive(Debug, Deserialize)]
pub struct PositionMarginResponse {
    pub amount: Price,
    pub code: i64,
    pub msg: String,
    #[serde(rename = "type")] pub margin_type: u8,
}

/// Response from `countdown_cancel_all`.
#[derive(Debug, Deserialize)]
pub struct CountdownResponse {
    pub symbol: String,
    /// Returned as a JSON string on some deployments (`"0"`) and as a number on others.
    #[serde(rename = "countdownTime", deserialize_with = "de::string_or_number_i64")]
    pub countdown_time: i64,
}