/*
 * Binance Derivatives Trading USDS Futures WebSocket API
 *
 * OpenAPI Specification for the Binance Derivatives Trading USDS Futures WebSocket API
 *
 * The version of the OpenAPI document: 1.0.0
 *
 *
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */

#![allow(unused_imports)]
use anyhow::Context;
use async_trait::async_trait;
use derive_builder::Builder;
use rust_decimal::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{collections::BTreeMap, sync::Arc};

use crate::common::{
    errors::WebsocketError,
    models::{ParamBuildError, WebsocketApiResponse},
    utils::remove_empty_value,
    websocket::{WebsocketApi, WebsocketMessageSendOptions},
};
use crate::derivatives_trading_usds_futures::websocket_api::models;

#[async_trait]
pub trait TradeApi: Send + Sync {
    async fn cancel_algo_order(
        &self,
        params: CancelAlgoOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::CancelAlgoOrderResponseResult>>>;
    async fn cancel_order(
        &self,
        params: CancelOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::CancelOrderResponseResult>>>;
    async fn modify_order(
        &self,
        params: ModifyOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::ModifyOrderResponseResult>>>;
    async fn new_algo_order(
        &self,
        params: NewAlgoOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::NewAlgoOrderResponseResult>>>;
    async fn new_order(
        &self,
        params: NewOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::NewOrderResponseResult>>>;
    async fn position_information(
        &self,
        params: PositionInformationParams,
    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::PositionInformationResponseResultInner>>>;
    async fn position_information_v2(
        &self,
        params: PositionInformationV2Params,
    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::PositionInformationV2ResponseResultInner>>>;
    async fn query_order(
        &self,
        params: QueryOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::QueryOrderResponseResult>>>;
}

#[derive(Clone)]
pub struct TradeApiClient {
    websocket_api_base: Arc<WebsocketApi>,
}

impl TradeApiClient {
    pub fn new(websocket_api_base: Arc<WebsocketApi>) -> Self {
        Self { websocket_api_base }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ModifyOrderSideEnum {
    #[serde(rename = "BUY")]
    Buy,
    #[serde(rename = "SELL")]
    Sell,
}

impl ModifyOrderSideEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Buy => "BUY",
            Self::Sell => "SELL",
        }
    }
}

impl std::str::FromStr for ModifyOrderSideEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "BUY" => Ok(Self::Buy),
            "SELL" => Ok(Self::Sell),
            other => Err(format!("invalid ModifyOrderSideEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ModifyOrderPriceMatchEnum {
    #[serde(rename = "NONE")]
    None,
    #[serde(rename = "OPPONENT")]
    Opponent,
    #[serde(rename = "OPPONENT_5")]
    Opponent5,
    #[serde(rename = "OPPONENT_10")]
    Opponent10,
    #[serde(rename = "OPPONENT_20")]
    Opponent20,
    #[serde(rename = "QUEUE")]
    Queue,
    #[serde(rename = "QUEUE_5")]
    Queue5,
    #[serde(rename = "QUEUE_10")]
    Queue10,
    #[serde(rename = "QUEUE_20")]
    Queue20,
}

impl ModifyOrderPriceMatchEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::None => "NONE",
            Self::Opponent => "OPPONENT",
            Self::Opponent5 => "OPPONENT_5",
            Self::Opponent10 => "OPPONENT_10",
            Self::Opponent20 => "OPPONENT_20",
            Self::Queue => "QUEUE",
            Self::Queue5 => "QUEUE_5",
            Self::Queue10 => "QUEUE_10",
            Self::Queue20 => "QUEUE_20",
        }
    }
}

impl std::str::FromStr for ModifyOrderPriceMatchEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "NONE" => Ok(Self::None),
            "OPPONENT" => Ok(Self::Opponent),
            "OPPONENT_5" => Ok(Self::Opponent5),
            "OPPONENT_10" => Ok(Self::Opponent10),
            "OPPONENT_20" => Ok(Self::Opponent20),
            "QUEUE" => Ok(Self::Queue),
            "QUEUE_5" => Ok(Self::Queue5),
            "QUEUE_10" => Ok(Self::Queue10),
            "QUEUE_20" => Ok(Self::Queue20),
            other => Err(format!("invalid ModifyOrderPriceMatchEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewAlgoOrderSideEnum {
    #[serde(rename = "BUY")]
    Buy,
    #[serde(rename = "SELL")]
    Sell,
}

impl NewAlgoOrderSideEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Buy => "BUY",
            Self::Sell => "SELL",
        }
    }
}

impl std::str::FromStr for NewAlgoOrderSideEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "BUY" => Ok(Self::Buy),
            "SELL" => Ok(Self::Sell),
            other => Err(format!("invalid NewAlgoOrderSideEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewAlgoOrderPositionSideEnum {
    #[serde(rename = "BOTH")]
    Both,
    #[serde(rename = "LONG")]
    Long,
    #[serde(rename = "SHORT")]
    Short,
}

impl NewAlgoOrderPositionSideEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Both => "BOTH",
            Self::Long => "LONG",
            Self::Short => "SHORT",
        }
    }
}

impl std::str::FromStr for NewAlgoOrderPositionSideEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "BOTH" => Ok(Self::Both),
            "LONG" => Ok(Self::Long),
            "SHORT" => Ok(Self::Short),
            other => Err(format!("invalid NewAlgoOrderPositionSideEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewAlgoOrderTimeInForceEnum {
    #[serde(rename = "GTC")]
    Gtc,
    #[serde(rename = "IOC")]
    Ioc,
    #[serde(rename = "FOK")]
    Fok,
    #[serde(rename = "GTX")]
    Gtx,
    #[serde(rename = "GTD")]
    Gtd,
    #[serde(rename = "RPI")]
    Rpi,
}

impl NewAlgoOrderTimeInForceEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Gtc => "GTC",
            Self::Ioc => "IOC",
            Self::Fok => "FOK",
            Self::Gtx => "GTX",
            Self::Gtd => "GTD",
            Self::Rpi => "RPI",
        }
    }
}

impl std::str::FromStr for NewAlgoOrderTimeInForceEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "GTC" => Ok(Self::Gtc),
            "IOC" => Ok(Self::Ioc),
            "FOK" => Ok(Self::Fok),
            "GTX" => Ok(Self::Gtx),
            "GTD" => Ok(Self::Gtd),
            "RPI" => Ok(Self::Rpi),
            other => Err(format!("invalid NewAlgoOrderTimeInForceEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewAlgoOrderWorkingTypeEnum {
    #[serde(rename = "MARK_PRICE")]
    MarkPrice,
    #[serde(rename = "CONTRACT_PRICE")]
    ContractPrice,
}

impl NewAlgoOrderWorkingTypeEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::MarkPrice => "MARK_PRICE",
            Self::ContractPrice => "CONTRACT_PRICE",
        }
    }
}

impl std::str::FromStr for NewAlgoOrderWorkingTypeEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "MARK_PRICE" => Ok(Self::MarkPrice),
            "CONTRACT_PRICE" => Ok(Self::ContractPrice),
            other => Err(format!("invalid NewAlgoOrderWorkingTypeEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewAlgoOrderPriceMatchEnum {
    #[serde(rename = "NONE")]
    None,
    #[serde(rename = "OPPONENT")]
    Opponent,
    #[serde(rename = "OPPONENT_5")]
    Opponent5,
    #[serde(rename = "OPPONENT_10")]
    Opponent10,
    #[serde(rename = "OPPONENT_20")]
    Opponent20,
    #[serde(rename = "QUEUE")]
    Queue,
    #[serde(rename = "QUEUE_5")]
    Queue5,
    #[serde(rename = "QUEUE_10")]
    Queue10,
    #[serde(rename = "QUEUE_20")]
    Queue20,
}

impl NewAlgoOrderPriceMatchEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::None => "NONE",
            Self::Opponent => "OPPONENT",
            Self::Opponent5 => "OPPONENT_5",
            Self::Opponent10 => "OPPONENT_10",
            Self::Opponent20 => "OPPONENT_20",
            Self::Queue => "QUEUE",
            Self::Queue5 => "QUEUE_5",
            Self::Queue10 => "QUEUE_10",
            Self::Queue20 => "QUEUE_20",
        }
    }
}

impl std::str::FromStr for NewAlgoOrderPriceMatchEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "NONE" => Ok(Self::None),
            "OPPONENT" => Ok(Self::Opponent),
            "OPPONENT_5" => Ok(Self::Opponent5),
            "OPPONENT_10" => Ok(Self::Opponent10),
            "OPPONENT_20" => Ok(Self::Opponent20),
            "QUEUE" => Ok(Self::Queue),
            "QUEUE_5" => Ok(Self::Queue5),
            "QUEUE_10" => Ok(Self::Queue10),
            "QUEUE_20" => Ok(Self::Queue20),
            other => Err(format!("invalid NewAlgoOrderPriceMatchEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewAlgoOrderNewOrderRespTypeEnum {
    #[serde(rename = "ACK")]
    Ack,
    #[serde(rename = "RESULT")]
    Result,
}

impl NewAlgoOrderNewOrderRespTypeEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Ack => "ACK",
            Self::Result => "RESULT",
        }
    }
}

impl std::str::FromStr for NewAlgoOrderNewOrderRespTypeEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "ACK" => Ok(Self::Ack),
            "RESULT" => Ok(Self::Result),
            other => Err(format!("invalid NewAlgoOrderNewOrderRespTypeEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewAlgoOrderSelfTradePreventionModeEnum {
    #[serde(rename = "EXPIRE_TAKER")]
    ExpireTaker,
    #[serde(rename = "EXPIRE_BOTH")]
    ExpireBoth,
    #[serde(rename = "EXPIRE_MAKER")]
    ExpireMaker,
}

impl NewAlgoOrderSelfTradePreventionModeEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::ExpireTaker => "EXPIRE_TAKER",
            Self::ExpireBoth => "EXPIRE_BOTH",
            Self::ExpireMaker => "EXPIRE_MAKER",
        }
    }
}

impl std::str::FromStr for NewAlgoOrderSelfTradePreventionModeEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "EXPIRE_TAKER" => Ok(Self::ExpireTaker),
            "EXPIRE_BOTH" => Ok(Self::ExpireBoth),
            "EXPIRE_MAKER" => Ok(Self::ExpireMaker),
            other => {
                Err(format!("invalid NewAlgoOrderSelfTradePreventionModeEnum: {}", other).into())
            }
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewOrderSideEnum {
    #[serde(rename = "BUY")]
    Buy,
    #[serde(rename = "SELL")]
    Sell,
}

impl NewOrderSideEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Buy => "BUY",
            Self::Sell => "SELL",
        }
    }
}

impl std::str::FromStr for NewOrderSideEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "BUY" => Ok(Self::Buy),
            "SELL" => Ok(Self::Sell),
            other => Err(format!("invalid NewOrderSideEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewOrderPositionSideEnum {
    #[serde(rename = "BOTH")]
    Both,
    #[serde(rename = "LONG")]
    Long,
    #[serde(rename = "SHORT")]
    Short,
}

impl NewOrderPositionSideEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Both => "BOTH",
            Self::Long => "LONG",
            Self::Short => "SHORT",
        }
    }
}

impl std::str::FromStr for NewOrderPositionSideEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "BOTH" => Ok(Self::Both),
            "LONG" => Ok(Self::Long),
            "SHORT" => Ok(Self::Short),
            other => Err(format!("invalid NewOrderPositionSideEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewOrderTimeInForceEnum {
    #[serde(rename = "GTC")]
    Gtc,
    #[serde(rename = "IOC")]
    Ioc,
    #[serde(rename = "FOK")]
    Fok,
    #[serde(rename = "GTX")]
    Gtx,
    #[serde(rename = "GTD")]
    Gtd,
    #[serde(rename = "RPI")]
    Rpi,
}

impl NewOrderTimeInForceEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Gtc => "GTC",
            Self::Ioc => "IOC",
            Self::Fok => "FOK",
            Self::Gtx => "GTX",
            Self::Gtd => "GTD",
            Self::Rpi => "RPI",
        }
    }
}

impl std::str::FromStr for NewOrderTimeInForceEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "GTC" => Ok(Self::Gtc),
            "IOC" => Ok(Self::Ioc),
            "FOK" => Ok(Self::Fok),
            "GTX" => Ok(Self::Gtx),
            "GTD" => Ok(Self::Gtd),
            "RPI" => Ok(Self::Rpi),
            other => Err(format!("invalid NewOrderTimeInForceEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewOrderWorkingTypeEnum {
    #[serde(rename = "MARK_PRICE")]
    MarkPrice,
    #[serde(rename = "CONTRACT_PRICE")]
    ContractPrice,
}

impl NewOrderWorkingTypeEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::MarkPrice => "MARK_PRICE",
            Self::ContractPrice => "CONTRACT_PRICE",
        }
    }
}

impl std::str::FromStr for NewOrderWorkingTypeEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "MARK_PRICE" => Ok(Self::MarkPrice),
            "CONTRACT_PRICE" => Ok(Self::ContractPrice),
            other => Err(format!("invalid NewOrderWorkingTypeEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewOrderNewOrderRespTypeEnum {
    #[serde(rename = "ACK")]
    Ack,
    #[serde(rename = "RESULT")]
    Result,
}

impl NewOrderNewOrderRespTypeEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Ack => "ACK",
            Self::Result => "RESULT",
        }
    }
}

impl std::str::FromStr for NewOrderNewOrderRespTypeEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "ACK" => Ok(Self::Ack),
            "RESULT" => Ok(Self::Result),
            other => Err(format!("invalid NewOrderNewOrderRespTypeEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewOrderPriceMatchEnum {
    #[serde(rename = "NONE")]
    None,
    #[serde(rename = "OPPONENT")]
    Opponent,
    #[serde(rename = "OPPONENT_5")]
    Opponent5,
    #[serde(rename = "OPPONENT_10")]
    Opponent10,
    #[serde(rename = "OPPONENT_20")]
    Opponent20,
    #[serde(rename = "QUEUE")]
    Queue,
    #[serde(rename = "QUEUE_5")]
    Queue5,
    #[serde(rename = "QUEUE_10")]
    Queue10,
    #[serde(rename = "QUEUE_20")]
    Queue20,
}

impl NewOrderPriceMatchEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::None => "NONE",
            Self::Opponent => "OPPONENT",
            Self::Opponent5 => "OPPONENT_5",
            Self::Opponent10 => "OPPONENT_10",
            Self::Opponent20 => "OPPONENT_20",
            Self::Queue => "QUEUE",
            Self::Queue5 => "QUEUE_5",
            Self::Queue10 => "QUEUE_10",
            Self::Queue20 => "QUEUE_20",
        }
    }
}

impl std::str::FromStr for NewOrderPriceMatchEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "NONE" => Ok(Self::None),
            "OPPONENT" => Ok(Self::Opponent),
            "OPPONENT_5" => Ok(Self::Opponent5),
            "OPPONENT_10" => Ok(Self::Opponent10),
            "OPPONENT_20" => Ok(Self::Opponent20),
            "QUEUE" => Ok(Self::Queue),
            "QUEUE_5" => Ok(Self::Queue5),
            "QUEUE_10" => Ok(Self::Queue10),
            "QUEUE_20" => Ok(Self::Queue20),
            other => Err(format!("invalid NewOrderPriceMatchEnum: {}", other).into()),
        }
    }
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NewOrderSelfTradePreventionModeEnum {
    #[serde(rename = "EXPIRE_TAKER")]
    ExpireTaker,
    #[serde(rename = "EXPIRE_BOTH")]
    ExpireBoth,
    #[serde(rename = "EXPIRE_MAKER")]
    ExpireMaker,
}

impl NewOrderSelfTradePreventionModeEnum {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::ExpireTaker => "EXPIRE_TAKER",
            Self::ExpireBoth => "EXPIRE_BOTH",
            Self::ExpireMaker => "EXPIRE_MAKER",
        }
    }
}

impl std::str::FromStr for NewOrderSelfTradePreventionModeEnum {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "EXPIRE_TAKER" => Ok(Self::ExpireTaker),
            "EXPIRE_BOTH" => Ok(Self::ExpireBoth),
            "EXPIRE_MAKER" => Ok(Self::ExpireMaker),
            other => Err(format!("invalid NewOrderSelfTradePreventionModeEnum: {}", other).into()),
        }
    }
}

/// Request parameters for the [`cancel_algo_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`cancel_algo_order`](#method.cancel_algo_order).
#[derive(Clone, Debug, Builder, Default)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct CancelAlgoOrderParams {
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    ///
    /// The `algo_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub algo_id: Option<i64>,
    ///
    /// The `client_algo_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub client_algo_id: Option<String>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl CancelAlgoOrderParams {
    /// Create a builder for [`cancel_algo_order`].
    ///
    #[must_use]
    pub fn builder() -> CancelAlgoOrderParamsBuilder {
        CancelAlgoOrderParamsBuilder::default()
    }
}
/// Request parameters for the [`cancel_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`cancel_order`](#method.cancel_order).
#[derive(Clone, Debug, Builder)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct CancelOrderParams {
    ///
    /// The `symbol` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub symbol: String,
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    ///
    /// The `order_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub order_id: Option<i64>,
    ///
    /// The `orig_client_order_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub orig_client_order_id: Option<String>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl CancelOrderParams {
    /// Create a builder for [`cancel_order`].
    ///
    /// Required parameters:
    ///
    /// * `symbol` — String
    ///
    #[must_use]
    pub fn builder(symbol: String) -> CancelOrderParamsBuilder {
        CancelOrderParamsBuilder::default().symbol(symbol)
    }
}
/// Request parameters for the [`modify_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`modify_order`](#method.modify_order).
#[derive(Clone, Debug, Builder)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct ModifyOrderParams {
    ///
    /// The `symbol` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub symbol: String,
    /// `SELL`, `BUY`
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub side: ModifyOrderSideEnum,
    /// Order quantity, cannot be sent with `closePosition=true`
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub quantity: rust_decimal::Decimal,
    ///
    /// The `price` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub price: rust_decimal::Decimal,
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    ///
    /// The `order_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub order_id: Option<i64>,
    ///
    /// The `orig_client_order_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub orig_client_order_id: Option<String>,
    /// only avaliable for `LIMIT`/`STOP`/`TAKE_PROFIT` order; can be set to `OPPONENT`/ `OPPONENT_5`/ `OPPONENT_10`/ `OPPONENT_20`: /`QUEUE`/ `QUEUE_5`/ `QUEUE_10`/ `QUEUE_20`; Can't be passed together with `price`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub price_match: Option<ModifyOrderPriceMatchEnum>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl ModifyOrderParams {
    /// Create a builder for [`modify_order`].
    ///
    /// Required parameters:
    ///
    /// * `symbol` — String
    /// * `side` — `SELL`, `BUY`
    /// * `quantity` — Order quantity, cannot be sent with `closePosition=true`
    /// * `price` — `rust_decimal::Decimal`
    ///
    #[must_use]
    pub fn builder(
        symbol: String,
        side: ModifyOrderSideEnum,
        quantity: rust_decimal::Decimal,
        price: rust_decimal::Decimal,
    ) -> ModifyOrderParamsBuilder {
        ModifyOrderParamsBuilder::default()
            .symbol(symbol)
            .side(side)
            .quantity(quantity)
            .price(price)
    }
}
/// Request parameters for the [`new_algo_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`new_algo_order`](#method.new_algo_order).
#[derive(Clone, Debug, Builder)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct NewAlgoOrderParams {
    /// Only support `CONDITIONAL`
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub algo_type: String,
    ///
    /// The `symbol` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub symbol: String,
    /// `SELL`, `BUY`
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub side: NewAlgoOrderSideEnum,
    ///
    /// The `r#type` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub r#type: String,
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    /// Default `BOTH` for One-way Mode ; `LONG` or `SHORT` for Hedge Mode. It must be sent in Hedge Mode.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub position_side: Option<NewAlgoOrderPositionSideEnum>,
    ///
    /// The `time_in_force` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub time_in_force: Option<NewAlgoOrderTimeInForceEnum>,
    /// Cannot be sent with `closePosition`=`true`(Close-All)
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub quantity: Option<rust_decimal::Decimal>,
    ///
    /// The `price` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub price: Option<rust_decimal::Decimal>,
    ///
    /// The `trigger_price` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub trigger_price: Option<rust_decimal::Decimal>,
    /// stopPrice triggered by: "`MARK_PRICE`", "`CONTRACT_PRICE`". Default "`CONTRACT_PRICE`"
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub working_type: Option<NewAlgoOrderWorkingTypeEnum>,
    /// only avaliable for `LIMIT`/`STOP`/`TAKE_PROFIT` order; can be set to `OPPONENT`/ `OPPONENT_5`/ `OPPONENT_10`/ `OPPONENT_20`: /`QUEUE`/ `QUEUE_5`/ `QUEUE_10`/ `QUEUE_20`; Can't be passed together with `price`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub price_match: Option<NewAlgoOrderPriceMatchEnum>,
    /// `true`, `false`；Close-All，used with `STOP_MARKET` or `TAKE_PROFIT_MARKET`.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub close_position: Option<String>,
    /// "TRUE" or "FALSE", default "FALSE". Used with `STOP/STOP_MARKET` or `TAKE_PROFIT/TAKE_PROFIT_MARKET` orders.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub price_protect: Option<String>,
    /// "true" or "false". default "false". Cannot be sent in Hedge Mode; cannot be sent with `closePosition`=`true`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub reduce_only: Option<String>,
    /// Used with `TRAILING_STOP_MARKET` orders, default as the latest price(supporting different `workingType`)
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub activate_price: Option<rust_decimal::Decimal>,
    /// Used with `TRAILING_STOP_MARKET` orders, min 0.1, max 10 where 1 for 1%
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub callback_rate: Option<rust_decimal::Decimal>,
    ///
    /// The `client_algo_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub client_algo_id: Option<String>,
    /// "ACK", "RESULT", default "ACK"
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub new_order_resp_type: Option<NewAlgoOrderNewOrderRespTypeEnum>,
    /// `EXPIRE_TAKER`:expire taker order when STP triggers/ `EXPIRE_MAKER`:expire taker order when STP triggers/ `EXPIRE_BOTH`:expire both orders when STP triggers; default `NONE`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub self_trade_prevention_mode: Option<NewAlgoOrderSelfTradePreventionModeEnum>,
    /// order cancel time for timeInForce `GTD`, mandatory when `timeInforce` set to `GTD`; order the timestamp only retains second-level precision, ms part will be ignored; The goodTillDate timestamp must be greater than the current time plus 600 seconds and smaller than 253402300799000
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub good_till_date: Option<i64>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl NewAlgoOrderParams {
    /// Create a builder for [`new_algo_order`].
    ///
    /// Required parameters:
    ///
    /// * `algo_type` — Only support `CONDITIONAL`
    /// * `symbol` — String
    /// * `side` — `SELL`, `BUY`
    /// * `r#type` — String
    ///
    #[must_use]
    pub fn builder(
        algo_type: String,
        symbol: String,
        side: NewAlgoOrderSideEnum,
        r#type: String,
    ) -> NewAlgoOrderParamsBuilder {
        NewAlgoOrderParamsBuilder::default()
            .algo_type(algo_type)
            .symbol(symbol)
            .side(side)
            .r#type(r#type)
    }
}
/// Request parameters for the [`new_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`new_order`](#method.new_order).
#[derive(Clone, Debug, Builder)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct NewOrderParams {
    ///
    /// The `symbol` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub symbol: String,
    /// `SELL`, `BUY`
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub side: NewOrderSideEnum,
    ///
    /// The `r#type` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub r#type: String,
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    /// Default `BOTH` for One-way Mode ; `LONG` or `SHORT` for Hedge Mode. It must be sent in Hedge Mode.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub position_side: Option<NewOrderPositionSideEnum>,
    ///
    /// The `time_in_force` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub time_in_force: Option<NewOrderTimeInForceEnum>,
    /// Cannot be sent with `closePosition`=`true`(Close-All)
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub quantity: Option<rust_decimal::Decimal>,
    /// "true" or "false". default "false". Cannot be sent in Hedge Mode; cannot be sent with `closePosition`=`true`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub reduce_only: Option<String>,
    ///
    /// The `price` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub price: Option<rust_decimal::Decimal>,
    /// A unique id among open orders. Automatically generated if not sent. Can only be string following the rule: `^[\.A-Z\:/a-z0-9_-]{1,36}$`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub new_client_order_id: Option<String>,
    /// Used with `STOP/STOP_MARKET` or `TAKE_PROFIT/TAKE_PROFIT_MARKET` orders.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub stop_price: Option<rust_decimal::Decimal>,
    /// `true`, `false`；Close-All，used with `STOP_MARKET` or `TAKE_PROFIT_MARKET`.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub close_position: Option<String>,
    /// Used with `TRAILING_STOP_MARKET` orders, default as the latest price(supporting different `workingType`)
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub activation_price: Option<rust_decimal::Decimal>,
    /// Used with `TRAILING_STOP_MARKET` orders, min 0.1, max 10 where 1 for 1%
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub callback_rate: Option<rust_decimal::Decimal>,
    /// stopPrice triggered by: "`MARK_PRICE`", "`CONTRACT_PRICE`". Default "`CONTRACT_PRICE`"
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub working_type: Option<NewOrderWorkingTypeEnum>,
    /// "TRUE" or "FALSE", default "FALSE". Used with `STOP/STOP_MARKET` or `TAKE_PROFIT/TAKE_PROFIT_MARKET` orders.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub price_protect: Option<String>,
    /// "ACK", "RESULT", default "ACK"
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub new_order_resp_type: Option<NewOrderNewOrderRespTypeEnum>,
    /// only avaliable for `LIMIT`/`STOP`/`TAKE_PROFIT` order; can be set to `OPPONENT`/ `OPPONENT_5`/ `OPPONENT_10`/ `OPPONENT_20`: /`QUEUE`/ `QUEUE_5`/ `QUEUE_10`/ `QUEUE_20`; Can't be passed together with `price`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub price_match: Option<NewOrderPriceMatchEnum>,
    /// `EXPIRE_TAKER`:expire taker order when STP triggers/ `EXPIRE_MAKER`:expire taker order when STP triggers/ `EXPIRE_BOTH`:expire both orders when STP triggers; default `NONE`
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub self_trade_prevention_mode: Option<NewOrderSelfTradePreventionModeEnum>,
    /// order cancel time for timeInForce `GTD`, mandatory when `timeInforce` set to `GTD`; order the timestamp only retains second-level precision, ms part will be ignored; The goodTillDate timestamp must be greater than the current time plus 600 seconds and smaller than 253402300799000
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub good_till_date: Option<i64>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl NewOrderParams {
    /// Create a builder for [`new_order`].
    ///
    /// Required parameters:
    ///
    /// * `symbol` — String
    /// * `side` — `SELL`, `BUY`
    /// * `r#type` — String
    ///
    #[must_use]
    pub fn builder(
        symbol: String,
        side: NewOrderSideEnum,
        r#type: String,
    ) -> NewOrderParamsBuilder {
        NewOrderParamsBuilder::default()
            .symbol(symbol)
            .side(side)
            .r#type(r#type)
    }
}
/// Request parameters for the [`position_information`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`position_information`](#method.position_information).
#[derive(Clone, Debug, Builder, Default)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct PositionInformationParams {
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    ///
    /// The `symbol` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub symbol: Option<String>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl PositionInformationParams {
    /// Create a builder for [`position_information`].
    ///
    #[must_use]
    pub fn builder() -> PositionInformationParamsBuilder {
        PositionInformationParamsBuilder::default()
    }
}
/// Request parameters for the [`position_information_v2`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`position_information_v2`](#method.position_information_v2).
#[derive(Clone, Debug, Builder, Default)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct PositionInformationV2Params {
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    ///
    /// The `symbol` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub symbol: Option<String>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl PositionInformationV2Params {
    /// Create a builder for [`position_information_v2`].
    ///
    #[must_use]
    pub fn builder() -> PositionInformationV2ParamsBuilder {
        PositionInformationV2ParamsBuilder::default()
    }
}
/// Request parameters for the [`query_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`query_order`](#method.query_order).
#[derive(Clone, Debug, Builder)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct QueryOrderParams {
    ///
    /// The `symbol` parameter.
    ///
    /// This field is **required.
    #[builder(setter(into))]
    pub symbol: String,
    /// Unique WebSocket request ID.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub id: Option<String>,
    ///
    /// The `order_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub order_id: Option<i64>,
    ///
    /// The `orig_client_order_id` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub orig_client_order_id: Option<String>,
    ///
    /// The `recv_window` parameter.
    ///
    /// This field is **optional.
    #[builder(setter(into), default)]
    pub recv_window: Option<i64>,
}

impl QueryOrderParams {
    /// Create a builder for [`query_order`].
    ///
    /// Required parameters:
    ///
    /// * `symbol` — String
    ///
    #[must_use]
    pub fn builder(symbol: String) -> QueryOrderParamsBuilder {
        QueryOrderParamsBuilder::default().symbol(symbol)
    }
}

#[async_trait]
impl TradeApi for TradeApiClient {
    async fn cancel_algo_order(
        &self,
        params: CancelAlgoOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::CancelAlgoOrderResponseResult>>> {
        let CancelAlgoOrderParams {
            id,
            algo_id,
            client_algo_id,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = algo_id {
            payload.insert("algoId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = client_algo_id {
            payload.insert("clientAlgoId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Box<models::CancelAlgoOrderResponseResult>>(
                "/algoOrder.cancel".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }

    async fn cancel_order(
        &self,
        params: CancelOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::CancelOrderResponseResult>>> {
        let CancelOrderParams {
            symbol,
            id,
            order_id,
            orig_client_order_id,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        payload.insert("symbol".to_string(), serde_json::json!(symbol));
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = order_id {
            payload.insert("orderId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = orig_client_order_id {
            payload.insert("origClientOrderId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Box<models::CancelOrderResponseResult>>(
                "/order.cancel".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }

    async fn modify_order(
        &self,
        params: ModifyOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::ModifyOrderResponseResult>>> {
        let ModifyOrderParams {
            symbol,
            side,
            quantity,
            price,
            id,
            order_id,
            orig_client_order_id,
            price_match,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        payload.insert("symbol".to_string(), serde_json::json!(symbol));
        payload.insert("side".to_string(), serde_json::json!(side));
        payload.insert("quantity".to_string(), serde_json::json!(quantity));
        payload.insert("price".to_string(), serde_json::json!(price));
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = order_id {
            payload.insert("orderId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = orig_client_order_id {
            payload.insert("origClientOrderId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = price_match {
            payload.insert("priceMatch".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Box<models::ModifyOrderResponseResult>>(
                "/order.modify".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }

    async fn new_algo_order(
        &self,
        params: NewAlgoOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::NewAlgoOrderResponseResult>>> {
        let NewAlgoOrderParams {
            algo_type,
            symbol,
            side,
            r#type,
            id,
            position_side,
            time_in_force,
            quantity,
            price,
            trigger_price,
            working_type,
            price_match,
            close_position,
            price_protect,
            reduce_only,
            activate_price,
            callback_rate,
            client_algo_id,
            new_order_resp_type,
            self_trade_prevention_mode,
            good_till_date,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        payload.insert("algoType".to_string(), serde_json::json!(algo_type));
        payload.insert("symbol".to_string(), serde_json::json!(symbol));
        payload.insert("side".to_string(), serde_json::json!(side));
        payload.insert("type".to_string(), serde_json::json!(r#type));
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = position_side {
            payload.insert("positionSide".to_string(), serde_json::json!(value));
        }
        if let Some(value) = time_in_force {
            payload.insert("timeInForce".to_string(), serde_json::json!(value));
        }
        if let Some(value) = quantity {
            payload.insert("quantity".to_string(), serde_json::json!(value));
        }
        if let Some(value) = price {
            payload.insert("price".to_string(), serde_json::json!(value));
        }
        if let Some(value) = trigger_price {
            payload.insert("triggerPrice".to_string(), serde_json::json!(value));
        }
        if let Some(value) = working_type {
            payload.insert("workingType".to_string(), serde_json::json!(value));
        }
        if let Some(value) = price_match {
            payload.insert("priceMatch".to_string(), serde_json::json!(value));
        }
        if let Some(value) = close_position {
            payload.insert("closePosition".to_string(), serde_json::json!(value));
        }
        if let Some(value) = price_protect {
            payload.insert("priceProtect".to_string(), serde_json::json!(value));
        }
        if let Some(value) = reduce_only {
            payload.insert("reduceOnly".to_string(), serde_json::json!(value));
        }
        if let Some(value) = activate_price {
            payload.insert("activatePrice".to_string(), serde_json::json!(value));
        }
        if let Some(value) = callback_rate {
            payload.insert("callbackRate".to_string(), serde_json::json!(value));
        }
        if let Some(value) = client_algo_id {
            payload.insert("clientAlgoId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = new_order_resp_type {
            payload.insert("newOrderRespType".to_string(), serde_json::json!(value));
        }
        if let Some(value) = self_trade_prevention_mode {
            payload.insert(
                "selfTradePreventionMode".to_string(),
                serde_json::json!(value),
            );
        }
        if let Some(value) = good_till_date {
            payload.insert("goodTillDate".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Box<models::NewAlgoOrderResponseResult>>(
                "/algoOrder.place".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }

    async fn new_order(
        &self,
        params: NewOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::NewOrderResponseResult>>> {
        let NewOrderParams {
            symbol,
            side,
            r#type,
            id,
            position_side,
            time_in_force,
            quantity,
            reduce_only,
            price,
            new_client_order_id,
            stop_price,
            close_position,
            activation_price,
            callback_rate,
            working_type,
            price_protect,
            new_order_resp_type,
            price_match,
            self_trade_prevention_mode,
            good_till_date,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        payload.insert("symbol".to_string(), serde_json::json!(symbol));
        payload.insert("side".to_string(), serde_json::json!(side));
        payload.insert("type".to_string(), serde_json::json!(r#type));
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = position_side {
            payload.insert("positionSide".to_string(), serde_json::json!(value));
        }
        if let Some(value) = time_in_force {
            payload.insert("timeInForce".to_string(), serde_json::json!(value));
        }
        if let Some(value) = quantity {
            payload.insert("quantity".to_string(), serde_json::json!(value));
        }
        if let Some(value) = reduce_only {
            payload.insert("reduceOnly".to_string(), serde_json::json!(value));
        }
        if let Some(value) = price {
            payload.insert("price".to_string(), serde_json::json!(value));
        }
        if let Some(value) = new_client_order_id {
            payload.insert("newClientOrderId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = stop_price {
            payload.insert("stopPrice".to_string(), serde_json::json!(value));
        }
        if let Some(value) = close_position {
            payload.insert("closePosition".to_string(), serde_json::json!(value));
        }
        if let Some(value) = activation_price {
            payload.insert("activationPrice".to_string(), serde_json::json!(value));
        }
        if let Some(value) = callback_rate {
            payload.insert("callbackRate".to_string(), serde_json::json!(value));
        }
        if let Some(value) = working_type {
            payload.insert("workingType".to_string(), serde_json::json!(value));
        }
        if let Some(value) = price_protect {
            payload.insert("priceProtect".to_string(), serde_json::json!(value));
        }
        if let Some(value) = new_order_resp_type {
            payload.insert("newOrderRespType".to_string(), serde_json::json!(value));
        }
        if let Some(value) = price_match {
            payload.insert("priceMatch".to_string(), serde_json::json!(value));
        }
        if let Some(value) = self_trade_prevention_mode {
            payload.insert(
                "selfTradePreventionMode".to_string(),
                serde_json::json!(value),
            );
        }
        if let Some(value) = good_till_date {
            payload.insert("goodTillDate".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Box<models::NewOrderResponseResult>>(
                "/order.place".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }

    async fn position_information(
        &self,
        params: PositionInformationParams,
    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::PositionInformationResponseResultInner>>>
    {
        let PositionInformationParams {
            id,
            symbol,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = symbol {
            payload.insert("symbol".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Vec<models::PositionInformationResponseResultInner>>(
                "/account.position".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }

    async fn position_information_v2(
        &self,
        params: PositionInformationV2Params,
    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::PositionInformationV2ResponseResultInner>>>
    {
        let PositionInformationV2Params {
            id,
            symbol,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = symbol {
            payload.insert("symbol".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Vec<models::PositionInformationV2ResponseResultInner>>(
                "/v2/account.position".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }

    async fn query_order(
        &self,
        params: QueryOrderParams,
    ) -> anyhow::Result<WebsocketApiResponse<Box<models::QueryOrderResponseResult>>> {
        let QueryOrderParams {
            symbol,
            id,
            order_id,
            orig_client_order_id,
            recv_window,
        } = params;

        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
        payload.insert("symbol".to_string(), serde_json::json!(symbol));
        if let Some(value) = id {
            payload.insert("id".to_string(), serde_json::json!(value));
        }
        if let Some(value) = order_id {
            payload.insert("orderId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = orig_client_order_id {
            payload.insert("origClientOrderId".to_string(), serde_json::json!(value));
        }
        if let Some(value) = recv_window {
            payload.insert("recvWindow".to_string(), serde_json::json!(value));
        }
        let payload = remove_empty_value(payload);

        self.websocket_api_base
            .send_message::<Box<models::QueryOrderResponseResult>>(
                "/order.status".trim_start_matches('/'),
                payload,
                WebsocketMessageSendOptions::new().signed(),
            )
            .await
            .map_err(anyhow::Error::from)?
            .into_iter()
            .next()
            .ok_or(WebsocketError::NoResponse)
            .map_err(anyhow::Error::from)
    }
}

#[cfg(all(test, feature = "derivatives_trading_usds_futures"))]
mod tests {
    use super::*;
    use crate::TOKIO_SHARED_RT;
    use crate::common::websocket::{WebsocketApi, WebsocketConnection, WebsocketHandler};
    use crate::config::ConfigurationWebsocketApi;
    use crate::errors::WebsocketError;
    use crate::models::WebsocketApiRateLimit;
    use serde_json::{Value, json};
    use tokio::spawn;
    use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel};
    use tokio::time::{Duration, timeout};
    use tokio_tungstenite::tungstenite::Message;

    async fn setup() -> (
        Arc<WebsocketApi>,
        Arc<WebsocketConnection>,
        UnboundedReceiver<Message>,
    ) {
        let conn = WebsocketConnection::new("test-conn");
        let (tx, rx) = unbounded_channel::<Message>();
        {
            let mut conn_state = conn.state.lock().await;
            conn_state.ws_write_tx = Some(tx);
        }

        let config = ConfigurationWebsocketApi::builder()
            .api_key("key")
            .api_secret("secret")
            .build()
            .expect("Failed to build configuration");
        let ws_api = WebsocketApi::new(config, vec![conn.clone()]);
        conn.set_handler(ws_api.clone() as Arc<dyn WebsocketHandler>)
            .await;
        ws_api.clone().connect().await.unwrap();

        (ws_api, conn, rx)
    }

    #[test]
    fn cancel_algo_order_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = CancelAlgoOrderParams::builder().build().unwrap();
                client.cancel_algo_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/algoOrder.cancel".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"unique-cancel-request-id-5678","status":200,"result":{"algoId":2000000002162519,"clientAlgoId":"rDMG8WSde6LkyMNtk6s825","code":"200","msg":"success"},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":6}]}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Box<models::CancelAlgoOrderResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn cancel_algo_order_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = CancelAlgoOrderParams::builder().build().unwrap();
                client.cancel_algo_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn cancel_algo_order_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = CancelAlgoOrderParams::builder().build().unwrap();
                client.cancel_algo_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }

    #[test]
    fn cancel_order_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = CancelOrderParams::builder("symbol_example".to_string(),).build().unwrap();
                client.cancel_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/order.cancel".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"5633b6a2-90a9-4192-83e7-925c90b6a2fd","status":200,"result":{"clientOrderId":"myOrder1","cumQty":"0","cumQuote":"0","executedQty":"0","orderId":283194212,"origQty":"11","origType":"TRAILING_STOP_MARKET","price":"0","reduceOnly":false,"side":"BUY","positionSide":"SHORT","status":"CANCELED","stopPrice":"9300","closePosition":false,"symbol":"BTCUSDT","timeInForce":"GTC","type":"TRAILING_STOP_MARKET","activatePrice":"9020","priceRate":"0.3","updateTime":1571110484038,"workingType":"CONTRACT_PRICE","priceProtect":false,"priceMatch":"NONE","selfTradePreventionMode":"NONE","goodTillDate":0},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":1}]}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Box<models::CancelOrderResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn cancel_order_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = CancelOrderParams::builder("symbol_example".to_string(),).build().unwrap();
                client.cancel_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn cancel_order_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = CancelOrderParams::builder("symbol_example".to_string())
                    .build()
                    .unwrap();
                client.cancel_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }

    #[test]
    fn modify_order_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = ModifyOrderParams::builder("symbol_example".to_string(),ModifyOrderSideEnum::Buy,dec!(1.0),dec!(1.0),).build().unwrap();
                client.modify_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/order.modify".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"c8c271ba-de70-479e-870c-e64951c753d9","status":200,"result":{"orderId":328971409,"symbol":"BTCUSDT","status":"NEW","clientOrderId":"xGHfltUMExx0TbQstQQfRX","price":"43769.10","avgPrice":"0.00","origQty":"0.110","executedQty":"0.000","cumQty":"0.000","cumQuote":"0.00000","timeInForce":"GTC","type":"LIMIT","reduceOnly":false,"closePosition":false,"side":"SELL","positionSide":"SHORT","stopPrice":"0.00","workingType":"CONTRACT_PRICE","priceProtect":false,"origType":"LIMIT","priceMatch":"NONE","selfTradePreventionMode":"NONE","goodTillDate":0,"updateTime":1703426756190},"rateLimits":[{"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":300,"count":1},{"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200,"count":1},{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":1}]}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Box<models::ModifyOrderResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn modify_order_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = ModifyOrderParams::builder("symbol_example".to_string(),ModifyOrderSideEnum::Buy,dec!(1.0),dec!(1.0),).build().unwrap();
                client.modify_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn modify_order_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = ModifyOrderParams::builder(
                    "symbol_example".to_string(),
                    ModifyOrderSideEnum::Buy,
                    dec!(1.0),
                    dec!(1.0),
                )
                .build()
                .unwrap();
                client.modify_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }

    #[test]
    fn new_algo_order_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = NewAlgoOrderParams::builder("algo_type_example".to_string(),"symbol_example".to_string(),NewAlgoOrderSideEnum::Buy,"r#type_example".to_string(),).build().unwrap();
                client.new_algo_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/algoOrder.place".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"06c9dbd8-ccbf-4ecf-a29c-fe31495ac73f","status":200,"result":{"algoId":3000000000003505,"clientAlgoId":"0Xkl1p621E4EryvufmYre1","algoType":"CONDITIONAL","orderType":"TAKE_PROFIT","symbol":"BTCUSDT","side":"SELL","positionSide":"SHORT","timeInForce":"GTC","quantity":"1.000","algoStatus":"NEW","triggerPrice":"120000.00","price":"160000.00","icebergQuantity":null,"selfTradePreventionMode":"EXPIRE_MAKER","workingType":"CONTRACT_PRICE","priceMatch":"NONE","closePosition":false,"priceProtect":false,"reduceOnly":false,"createTime":1762507264142,"updateTime":1762507264143,"triggerTime":0,"goodTillDate":0},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":1}]}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Box<models::NewAlgoOrderResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn new_algo_order_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = NewAlgoOrderParams::builder("algo_type_example".to_string(),"symbol_example".to_string(),NewAlgoOrderSideEnum::Buy,"r#type_example".to_string(),).build().unwrap();
                client.new_algo_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn new_algo_order_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = NewAlgoOrderParams::builder(
                    "algo_type_example".to_string(),
                    "symbol_example".to_string(),
                    NewAlgoOrderSideEnum::Buy,
                    "r#type_example".to_string(),
                )
                .build()
                .unwrap();
                client.new_algo_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }

    #[test]
    fn new_order_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = NewOrderParams::builder("symbol_example".to_string(),NewOrderSideEnum::Buy,"r#type_example".to_string(),).build().unwrap();
                client.new_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/order.place".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"3f7df6e3-2df4-44b9-9919-d2f38f90a99a","status":200,"result":{"orderId":325078477,"symbol":"BTCUSDT","status":"NEW","clientOrderId":"iCXL1BywlBaf2sesNUrVl3","price":"43187.00","avgPrice":"0.00","origQty":"0.100","executedQty":"0.000","cumQty":"0.000","cumQuote":"0.00000","timeInForce":"GTC","type":"LIMIT","reduceOnly":false,"closePosition":false,"side":"BUY","positionSide":"BOTH","stopPrice":"0.00","workingType":"CONTRACT_PRICE","priceProtect":false,"origType":"LIMIT","priceMatch":"NONE","selfTradePreventionMode":"NONE","goodTillDate":0,"updateTime":1702555534435},"rateLimits":[{"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":300,"count":1},{"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200,"count":1},{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":1}]}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Box<models::NewOrderResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn new_order_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = NewOrderParams::builder("symbol_example".to_string(),NewOrderSideEnum::Buy,"r#type_example".to_string(),).build().unwrap();
                client.new_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn new_order_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = NewOrderParams::builder(
                    "symbol_example".to_string(),
                    NewOrderSideEnum::Buy,
                    "r#type_example".to_string(),
                )
                .build()
                .unwrap();
                client.new_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }

    #[test]
    fn position_information_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = PositionInformationParams::builder().build().unwrap();
                client.position_information(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/account.position".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"605a6d20-6588-4cb9-afa0-b0ab087507ba","status":200,"result":[{"entryPrice":"0.00000","breakEvenPrice":"0.0","marginType":"isolated","isAutoAddMargin":"false","isolatedMargin":"0.00000000","leverage":"10","liquidationPrice":"0","markPrice":"6679.50671178","maxNotionalValue":"20000000","positionAmt":"0.000","notional":"0","isolatedWallet":"0","symbol":"BTCUSDT","unRealizedProfit":"0.00000000","positionSide":"BOTH","updateTime":0},{"symbol":"BTCUSDT","positionAmt":"0.001","entryPrice":"22185.2","breakEvenPrice":"0.0","markPrice":"21123.05052574","unRealizedProfit":"-1.06214947","liquidationPrice":"19731.45529116","leverage":"4","maxNotionalValue":"100000000","marginType":"cross","isolatedMargin":"0.00000000","isAutoAddMargin":"false","positionSide":"LONG","notional":"21.12305052","isolatedWallet":"0","updateTime":1655217461579},{"symbol":"BTCUSDT","positionAmt":"0.000","entryPrice":"0.0","breakEvenPrice":"0.0","markPrice":"21123.05052574","unRealizedProfit":"0.00000000","liquidationPrice":"0","leverage":"4","maxNotionalValue":"100000000","marginType":"cross","isolatedMargin":"0.00000000","isAutoAddMargin":"false","positionSide":"SHORT","notional":"0","isolatedWallet":"0","updateTime":0}],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":20}]}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Vec<models::PositionInformationResponseResultInner> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn position_information_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = PositionInformationParams::builder().build().unwrap();
                client.position_information(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn position_information_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = PositionInformationParams::builder().build().unwrap();
                client.position_information(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }

    #[test]
    fn position_information_v2_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = PositionInformationV2Params::builder().build().unwrap();
                client.position_information_v2(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/v2/account.position".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"605a6d20-6588-4cb9-afa0-b0ab087507ba","status":200,"result":[{"symbol":"BTCUSDT","positionSide":"BOTH","positionAmt":"1.000","entryPrice":"0.00000","breakEvenPrice":"0.0","markPrice":"6679.50671178","unRealizedProfit":"0.00000000","liquidationPrice":"0","isolatedMargin":"0.00000000","notional":"0","marginAsset":"USDT","isolatedWallet":"0","initialMargin":"0","maintMargin":"0","positionInitialMargin":"0","openOrderInitialMargin":"0","adl":0,"bidNotional":"0","askNotional":"0","updateTime":0},{"symbol":"BTCUSDT","positionSide":"LONG","positionAmt":"1.000","entryPrice":"0.00000","breakEvenPrice":"0.0","markPrice":"6679.50671178","unRealizedProfit":"0.00000000","liquidationPrice":"0","isolatedMargin":"0.00000000","notional":"0","marginAsset":"USDT","isolatedWallet":"0","initialMargin":"0","maintMargin":"0","positionInitialMargin":"0","openOrderInitialMargin":"0","adl":0,"bidNotional":"0","askNotional":"0","updateTime":0},{"symbol":"BTCUSDT","positionSide":"SHORT","positionAmt":"1.000","entryPrice":"0.00000","breakEvenPrice":"0.0","markPrice":"6679.50671178","unRealizedProfit":"0.00000000","liquidationPrice":"0","isolatedMargin":"0.00000000","notional":"0","marginAsset":"USDT","isolatedWallet":"0","initialMargin":"0","maintMargin":"0","positionInitialMargin":"0","openOrderInitialMargin":"0","adl":0,"bidNotional":"0","askNotional":"0","updateTime":0}],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":20}]}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Vec<models::PositionInformationV2ResponseResultInner> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn position_information_v2_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = PositionInformationV2Params::builder().build().unwrap();
                client.position_information_v2(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn position_information_v2_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = PositionInformationV2Params::builder().build().unwrap();
                client.position_information_v2(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }

    #[test]
    fn query_order_success() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = QueryOrderParams::builder("symbol_example".to_string(),).build().unwrap();
                client.query_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap();
            assert_eq!(v["method"], "/order.status".trim_start_matches('/'));

            let mut resp_json: Value = serde_json::from_str(r#"{"id":"605a6d20-6588-4cb9-afa0-b0ab087507ba","status":200,"result":{"avgPrice":"0.00000","clientOrderId":"abc","cumQuote":"0","executedQty":"0","orderId":1917641,"origQty":"0.40","origType":"TRAILING_STOP_MARKET","price":"0","reduceOnly":false,"side":"BUY","positionSide":"SHORT","status":"NEW","stopPrice":"9300","closePosition":false,"symbol":"BTCUSDT","time":1579276756075,"timeInForce":"GTC","type":"TRAILING_STOP_MARKET","activatePrice":"9020","priceRate":"0.3","updateTime":1579276756075,"workingType":"CONTRACT_PRICE","priceProtect":false}}"#).unwrap();
            resp_json["id"] = id.into();

            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
            let expected_data: Box<models::QueryOrderResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
            let empty_array = Value::Array(vec![]);
            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
                match raw_rate_limits.as_array() {
                    Some(arr) if arr.is_empty() => None,
                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
                    None => None,
                };

            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");


            let response_rate_limits = response.rate_limits.clone();
            let response_data = response.data().expect("deserialize data");

            assert_eq!(response_rate_limits, expected_rate_limits);
            assert_eq!(response_data, expected_data);
        });
    }

    #[test]
    fn query_order_error_response() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = tokio::spawn(async move {
                let params = QueryOrderParams::builder("symbol_example".to_string(),).build().unwrap();
                client.query_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
            let Message::Text(text) = sent else { panic!() };
            let v: Value = serde_json::from_str(&text).unwrap();
            let id = v["id"].as_str().unwrap().to_string();

            let resp_json = json!({
                "id": id,
                "status": 400,
                    "error": {
                        "code": -2010,
                        "msg": "Account has insufficient balance for requested action.",
                    },
                    "rateLimits": [
                        {
                            "rateLimitType": "ORDERS",
                            "interval": "SECOND",
                            "intervalNum": 10,
                            "limit": 50,
                            "count": 13
                        },
                    ],
            });
            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;

            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
            match join {
                Ok(Err(e)) => {
                    let msg = e.to_string();
                    assert!(
                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
                        "Expected error msg to contain server error, got: {msg}"
                    );
                }
                Ok(Ok(_)) => panic!("Expected error"),
                Err(_) => panic!("Task panicked"),
            }
        });
    }

    #[test]
    fn query_order_request_timeout() {
        TOKIO_SHARED_RT.block_on(async {
            let (ws_api, _conn, mut rx) = setup().await;
            let client = TradeApiClient::new(ws_api.clone());

            let handle = spawn(async move {
                let params = QueryOrderParams::builder("symbol_example".to_string())
                    .build()
                    .unwrap();
                client.query_order(params).await
            });

            let sent = timeout(Duration::from_secs(1), rx.recv())
                .await
                .expect("send should occur")
                .expect("channel closed");
            let Message::Text(text) = sent else {
                panic!("expected Message Text")
            };

            let _: Value = serde_json::from_str(&text).unwrap();

            let result = handle.await.expect("task completed");
            match result {
                Err(e) => {
                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
                        assert!(matches!(inner, WebsocketError::Timeout));
                    } else {
                        panic!("Unexpected error type: {:?}", e);
                    }
                }
                Ok(_) => panic!("Expected timeout error"),
            }
        });
    }
}
