//! # Binance Connector
//!
//! Реализация всех core трейтов для Binance.
//!
//! ## Core трейты
//! - `ExchangeIdentity` - идентификация биржи
//! - `MarketData` - рыночные данные
//! - `Trading` - торговые операции
//! - `Account` - информация об аккаунте
//! - `Positions` - futures позиции

use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use serde_json::{json, Value};

use crate::core::{
    HttpClient, Credentials, assemble_rest_url,
    ExchangeId, ExchangeType, AccountType,
    ExchangeError, ExchangeResult,
    Price, Kline, Ticker, OrderBook,
    Order, OrderSide, OrderType, Balance, AccountInfo,
    Position, FundingRate,
    OrderRequest, CancelRequest, CancelScope,
    BalanceQuery, PositionQuery, PositionModification,
    OrderHistoryFilter, PlaceOrderResponse, FeeInfo,
    AmendRequest, CancelAllResponse, OrderResult,
    MarginType,
    UserTrade, UserTradeFilter,
    PublicTrade,
    SymbolInput,
};
use crate::core::types::{MarkPrice, TradeSide, AggTrade};
use crate::core::types::{
    ConnectorStats, SymbolInfo,
    TransferRequest, TransferHistoryFilter, TransferResponse,
    DepositAddress, WithdrawRequest, WithdrawResponse, FundsRecord,
    FundsHistoryFilter, FundsRecordType,
    SubAccountOperation, SubAccountResult,
    LongShortRatio, OpenInterest, Liquidation,
};
use super::parser::PremiumIndexData;
use crate::core::traits::{
    ExchangeIdentity, MarketData, Trading, Account, Positions,
    CancelAll, AmendOrder, BatchOrders,
    AccountTransfers, CustodialFunds, SubAccounts,
    FundingHistory, AccountLedger,
    MarketDataPublic,
};
use crate::core::types::{
    FundingPayment, FundingFilter,
    LedgerEntry, LedgerFilter,
    MarketDataCapabilities, TradingCapabilities, AccountCapabilities,
};
use crate::core::utils::{RuntimeLimiter, RateLimitMonitor, RateLimitPressure};
use crate::core::types::{RateLimitCapabilities, LimitModel, RestLimitPool, WsLimits, EndpointWeight, OrderbookCapabilities};

use super::endpoints::{BinanceUrls, BinanceEndpoint, format_symbol, map_kline_interval};
use super::auth::BinanceAuth;
use super::parser::BinanceParser;

// Binance endpoint weights (from API docs)
mod weights {
    pub const PING: u32 = 1;
    pub const KLINES: u32 = 2;
    pub const TICKER_24H: u32 = 1;
    pub const ACCOUNT: u32 = 10;
    pub const ORDER: u32 = 1;
    pub const DEFAULT: u32 = 1;

    /// Weight for /depth endpoint, scaled by limit parameter.
    /// Reference: https://binance-docs.github.io/apidocs/spot/en/#order-book
    pub const fn depth_weight(limit: u16) -> u32 {
        match limit {
            0..=100 => 5,
            101..=500 => 25,
            501..=1000 => 50,
            _ => 250,
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// RATE LIMIT CAPABILITIES (static — embedded in binary, no allocation)
// ═══════════════════════════════════════════════════════════════════════════════

static BINANCE_SPOT_POOLS: &[RestLimitPool] = &[RestLimitPool {
    name: "default",
    max_budget: 6000,
    window_seconds: 60,
    is_weight: true,
    has_server_headers: true,
    server_header: Some("X-MBX-USED-WEIGHT-1M"),
    header_reports_used: true,
}];

static BINANCE_DEPTH_WEIGHTS: &[EndpointWeight] = &[EndpointWeight {
    endpoint: "depth",
    default_weight: Some(5),
    tiers: &[(5, 5), (10, 5), (20, 5), (50, 5), (100, 5), (500, 25), (1000, 50), (5000, 250)],
}];

static BINANCE_SPOT_RATE_CAPS: RateLimitCapabilities = RateLimitCapabilities {
    model: LimitModel::Weight,
    rest_pools: BINANCE_SPOT_POOLS,
    decaying: None,
    endpoint_weights: BINANCE_DEPTH_WEIGHTS,
    ws: WsLimits {
        max_connections: None,
        max_subs_per_conn: None,
        max_msg_per_sec: Some(5),
        max_streams_per_conn: Some(1024),
    },
};

// ═══════════════════════════════════════════════════════════════════════════════
// CONNECTOR
// ═══════════════════════════════════════════════════════════════════════════════

/// Binance коннектор
pub struct BinanceConnector {
    /// HTTP клиент
    http: HttpClient,
    /// Аутентификация (None для публичных методов)
    auth: Option<BinanceAuth>,
    /// URL'ы (mainnet/testnet)
    urls: BinanceUrls,
    /// Testnet mode
    testnet: bool,
    /// REST base URL override for proxy / Path-B routing.
    /// When set, replaces `self.urls.rest_url(account_type)` in every request.
    rest_override: Option<String>,
    /// Runtime rate limiter (Weight model: 6000 weight per minute)
    limiter: Arc<Mutex<RuntimeLimiter>>,
    /// Pressure monitor — logs transitions, gates non-essential requests at >= 90%
    monitor: Arc<Mutex<RateLimitMonitor>>,
    /// Per-symbol precision cache (populated from get_exchange_info)
    precision: crate::core::utils::precision::PrecisionCache,
}

impl BinanceConnector {
    /// Создать новый коннектор
    pub async fn new(credentials: Option<Credentials>, testnet: bool) -> ExchangeResult<Self> {
        Self::new_with_override(credentials, testnet, None).await
    }

    /// Создать коннектор с необязательным REST base URL override.
    ///
    /// When `rest_override` is `Some(url)`, all REST requests use that URL as
    /// the base instead of the exchange's native endpoint.  Intended for proxy
    /// and Path-B routing (e.g. `ExchangeHub::set_rest_base_override`).
    pub async fn new_with_override(credentials: Option<Credentials>, testnet: bool, rest_override: Option<String>) -> ExchangeResult<Self> {
        let urls = if testnet {
            BinanceUrls::TESTNET
        } else {
            BinanceUrls::MAINNET
        };

        let http = HttpClient::new(30_000)?; // 30 sec timeout

        let mut auth = credentials
            .as_ref()
            .map(BinanceAuth::new)
            .transpose()?;

        // Sync time with server if we have auth
        if auth.is_some() {
            let base_url = urls.rest_url(AccountType::Spot);
            let url = format!("{}/api/v3/time", base_url);
            if let Ok(response) = http.get(&url, &HashMap::new()).await {
                if let Some(server_time) = response.get("serverTime").and_then(|t| t.as_i64()) {
                    if let Some(ref mut a) = auth {
                        a.sync_time(server_time);
                    }
                }
            }
        }

        let limiter = Arc::new(Mutex::new(RuntimeLimiter::from_caps(&BINANCE_SPOT_RATE_CAPS)));
        let monitor = Arc::new(Mutex::new(RateLimitMonitor::new("Binance")));

        Ok(Self {
            http,
            auth,
            urls,
            testnet,
            rest_override,
            limiter,
            monitor,
            precision: crate::core::utils::precision::PrecisionCache::new(),
        })
    }

    /// Создать коннектор только для публичных методов
    pub async fn public(testnet: bool, rest_override: Option<String>) -> ExchangeResult<Self> {
        Self::new_with_override(None, testnet, rest_override).await
    }


    // ═══════════════════════════════════════════════════════════════════════════
    // BINANCE-SPECIFIC PUBLIC METHODS
    // ═══════════════════════════════════════════════════════════════════════════

    /// Fetch up to `total_bars` klines with backward pagination.
    ///
    /// Binance limits to 1000 klines per request. This method chains
    /// multiple requests, walking backward in time from `end_time` (or now
    /// if `None`), until `total_bars` klines are collected or no more data
    /// is available.
    ///
    /// The returned slice is in chronological order (oldest first).
    pub async fn get_klines_paginated(
        &self,
        symbol: &str,
        interval: &str,
        total_bars: usize,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<Kline>> {
        const LIMIT_PER_REQUEST: usize = 1000;

        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotKlines,
            _ => BinanceEndpoint::FuturesKlines,
        };

        let symbol_str = symbol.to_uppercase();
        let interval_str = map_kline_interval(interval).to_string();

        let mut all_klines: Vec<Kline> = Vec::with_capacity(total_bars);
        let mut end_time: Option<i64> = None; // None = latest (now)

        loop {
            let mut params = HashMap::new();
            params.insert("symbol".to_string(), symbol_str.clone());
            params.insert("interval".to_string(), interval_str.clone());
            params.insert("limit".to_string(), LIMIT_PER_REQUEST.to_string());

            if let Some(et) = end_time {
                params.insert("endTime".to_string(), et.to_string());
            }

            let response = self.get(endpoint, params, account_type).await?;
            let batch = BinanceParser::parse_klines(&response)?;

            if batch.is_empty() {
                break;
            }

            let batch_len = batch.len();

            // Use the first bar's open_time - 1ms as the next endTime cursor,
            // so the next request fetches bars strictly before this batch.
            end_time = Some(batch[0].open_time - 1);

            // Prepend the batch to keep chronological order: older data goes first.
            let mut combined = batch;
            combined.append(&mut all_klines);
            all_klines = combined;

            if all_klines.len() >= total_bars {
                break;
            }

            // If the exchange returned fewer bars than the limit, there is no
            // more historical data available.
            if batch_len < LIMIT_PER_REQUEST {
                break;
            }
        }

        // Trim to the requested count, keeping the most recent bars.
        if all_klines.len() > total_bars {
            all_klines = all_klines.split_off(all_klines.len() - total_bars);
        }

        Ok(all_klines)
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // RATE LIMITING
    // ═══════════════════════════════════════════════════════════════════════════

    /// Wait for rate limit budget. Non-essential requests are dropped at >= 90% utilization.
    ///
    /// Returns `true` if acquired, `false` if dropped due to cutoff pressure.
    /// Trading endpoints should pass `essential: true` to always wait through.
    async fn rate_limit_wait(&self, weight: u32, essential: bool) -> bool {
        loop {
            let wait_time = {
                let mut limiter = self.limiter.lock()
                    .expect("rate limiter mutex poisoned");

                // Check pressure on every loop iteration — after server header sync
                // the utilization may change, so we re-check before each acquire attempt.
                let pressure = self.monitor.lock()
                    .expect("rate monitor mutex poisoned")
                    .check(&mut limiter);
                if pressure >= RateLimitPressure::Cutoff && !essential {
                    return false;
                }

                if limiter.try_acquire("default", weight) {
                    return true;
                }
                limiter.time_until_ready("default", weight)
            };
            if wait_time > Duration::ZERO {
                tokio::time::sleep(wait_time).await;
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // HTTP HELPERS
    // ═══════════════════════════════════════════════════════════════════════════

    /// Sync limiter from X-MBX-USED-WEIGHT-1M response header.
    fn update_weight_from_headers(&self, headers: &reqwest::header::HeaderMap) {
        if let Some(weight) = headers
            .get("X-MBX-USED-WEIGHT-1M")
            .and_then(|v| v.to_str().ok())
            .and_then(|s| s.parse::<u32>().ok())
        {
            if let Ok(mut limiter) = self.limiter.lock() {
                limiter.update_from_server("default", weight);
            }
        }
    }

    /// GET запрос
    async fn get(
        &self,
        endpoint: BinanceEndpoint,
        mut params: HashMap<String, String>,
        account_type: AccountType,
    ) -> ExchangeResult<Value> {
        // Rate limit check with per-endpoint weights
        let weight = match endpoint {
            BinanceEndpoint::Ping => weights::PING,
            BinanceEndpoint::SpotKlines
            | BinanceEndpoint::FuturesKlines
            | BinanceEndpoint::FuturesMarkPriceKlines
            | BinanceEndpoint::FuturesIndexPriceKlines
            | BinanceEndpoint::FuturesPremiumIndexKlines => weights::KLINES,
            BinanceEndpoint::SpotOrderbook | BinanceEndpoint::FuturesOrderbook => {
                let limit: u16 = params.get("limit")
                    .and_then(|v| v.parse().ok())
                    .unwrap_or(100);
                weights::depth_weight(limit)
            }
            BinanceEndpoint::SpotTicker | BinanceEndpoint::FuturesTicker => weights::TICKER_24H,
            BinanceEndpoint::SpotAccount | BinanceEndpoint::FuturesAccount => weights::ACCOUNT,
            BinanceEndpoint::SpotGetOrder | BinanceEndpoint::FuturesGetOrder => weights::ORDER,
            BinanceEndpoint::SpotOpenOrders | BinanceEndpoint::FuturesOpenOrders => weights::ORDER,
            BinanceEndpoint::FuturesPositions => weights::ACCOUNT,
            BinanceEndpoint::FundingRate => weights::DEFAULT,
            _ => weights::DEFAULT,
        };
        // Market data = non-essential: drop at >= 90% utilization to preserve budget for trading
        if !self.rate_limit_wait(weight, false).await {
            return Err(ExchangeError::RateLimitExceeded {
                retry_after: None,
                message: "Rate limit budget >= 90% used; non-essential market data request dropped".to_string(),
            });
        }

        let real_base = self.urls.rest_url(account_type);
        let path = endpoint.path();

        // Add auth if needed
        let headers = if endpoint.requires_auth() {
            let auth = self.auth.as_ref()
                .ok_or_else(|| ExchangeError::Auth("Authentication required".to_string()))?;
            auth.sign_request(&mut params)
        } else {
            HashMap::new()
        };

        // Build query string
        let query = if params.is_empty() {
            String::new()
        } else {
            let qs: Vec<String> = params.iter()
                .map(|(k, v)| format!("{}={}", k, v))
                .collect();
            format!("?{}", qs.join("&"))
        };

        let url = assemble_rest_url(self.rest_override.as_deref(), real_base, path, &query);

        let (response, resp_headers) = self.http.get_with_response_headers(&url, &HashMap::new(), &headers).await?;
        self.update_weight_from_headers(&resp_headers);
        BinanceParser::check_error(&response)?;
        Ok(response)
    }

    /// POST запрос
    async fn post(
        &self,
        endpoint: BinanceEndpoint,
        mut params: HashMap<String, String>,
        account_type: AccountType,
    ) -> ExchangeResult<Value> {
        // POST endpoints: order placement/amend = weight 1
        let weight = match endpoint {
            BinanceEndpoint::SpotCreateOrder | BinanceEndpoint::FuturesCreateOrder => weights::ORDER,
            BinanceEndpoint::FuturesSetLeverage => weights::DEFAULT,
            _ => weights::DEFAULT,
        };
        // Order placement = essential: always wait, never drop
        self.rate_limit_wait(weight, true).await;

        let real_base = self.urls.rest_url(account_type);
        let path = endpoint.path();

        // Auth required for POST
        let auth = self.auth.as_ref()
            .ok_or_else(|| ExchangeError::Auth("Authentication required".to_string()))?;

        let headers = auth.sign_request(&mut params);

        // Build query string (Binance uses query params for POST too)
        let query = if params.is_empty() {
            String::new()
        } else {
            let qs: Vec<String> = params.iter()
                .map(|(k, v)| format!("{}={}", k, v))
                .collect();
            format!("?{}", qs.join("&"))
        };

        let url = assemble_rest_url(self.rest_override.as_deref(), real_base, path, &query);

        // POST with empty body, params in query string
        let (response, resp_headers) = self.http.post_with_response_headers(&url, &json!({}), &headers).await?;
        self.update_weight_from_headers(&resp_headers);
        BinanceParser::check_error(&response)?;
        Ok(response)
    }

    /// PUT запрос (for order amend)
    async fn put(
        &self,
        endpoint: BinanceEndpoint,
        mut params: HashMap<String, String>,
        account_type: AccountType,
    ) -> ExchangeResult<Value> {
        // Order amend = essential: always wait, never drop
        self.rate_limit_wait(weights::ORDER, true).await;

        let real_base = self.urls.rest_url(account_type);
        let path = endpoint.path();

        let auth = self.auth.as_ref()
            .ok_or_else(|| ExchangeError::Auth("Authentication required".to_string()))?;

        let headers = auth.sign_request(&mut params);

        let query = if params.is_empty() {
            String::new()
        } else {
            let qs: Vec<String> = params.iter()
                .map(|(k, v)| format!("{}={}", k, v))
                .collect();
            format!("?{}", qs.join("&"))
        };

        let url = assemble_rest_url(self.rest_override.as_deref(), real_base, path, &query);

        // HttpClient::put does not return headers; use it directly
        let response = self.http.put(&url, &json!({}), &headers).await?;
        BinanceParser::check_error(&response)?;
        Ok(response)
    }

    /// PATCH запрос (for batch amend)
    async fn patch(
        &self,
        endpoint: BinanceEndpoint,
        mut params: HashMap<String, String>,
        account_type: AccountType,
    ) -> ExchangeResult<Value> {
        // Batch amend = essential: always wait, never drop
        self.rate_limit_wait(weights::DEFAULT, true).await;

        let real_base = self.urls.rest_url(account_type);
        let path = endpoint.path();

        let auth = self.auth.as_ref()
            .ok_or_else(|| ExchangeError::Auth("Authentication required".to_string()))?;

        let headers = auth.sign_request(&mut params);

        let query = if params.is_empty() {
            String::new()
        } else {
            let qs: Vec<String> = params.iter()
                .map(|(k, v)| format!("{}={}", k, v))
                .collect();
            format!("?{}", qs.join("&"))
        };

        let url = assemble_rest_url(self.rest_override.as_deref(), real_base, path, &query);

        let response = self.http.patch(&url, &json!({}), &headers).await?;
        BinanceParser::check_error(&response)?;
        Ok(response)
    }

    /// DELETE запрос
    async fn delete(
        &self,
        endpoint: BinanceEndpoint,
        mut params: HashMap<String, String>,
        account_type: AccountType,
    ) -> ExchangeResult<Value> {
        // DELETE endpoints: cancel order = weight 1; essential (trading)
        let weight = match endpoint {
            BinanceEndpoint::SpotCancelOrder | BinanceEndpoint::FuturesCancelOrder => weights::ORDER,
            _ => weights::DEFAULT,
        };
        self.rate_limit_wait(weight, true).await;

        let real_base = self.urls.rest_url(account_type);
        let path = endpoint.path();

        // Auth required for DELETE
        let auth = self.auth.as_ref()
            .ok_or_else(|| ExchangeError::Auth("Authentication required".to_string()))?;

        let headers = auth.sign_request(&mut params);

        // Build query string
        let query = if params.is_empty() {
            String::new()
        } else {
            let qs: Vec<String> = params.iter()
                .map(|(k, v)| format!("{}={}", k, v))
                .collect();
            format!("?{}", qs.join("&"))
        };

        let url = assemble_rest_url(self.rest_override.as_deref(), real_base, path, &query);

        let (response, resp_headers) = self.http.delete_with_response_headers(&url, &HashMap::new(), &headers).await?;
        self.update_weight_from_headers(&resp_headers);
        BinanceParser::check_error(&response)?;
        Ok(response)
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // MARKET DATA EXTENSIONS
    // ═══════════════════════════════════════════════════════════════════════════

    /// Get recent trades for a symbol.
    ///
    /// Returns up to `limit` recent trades (max 1000).
    pub async fn get_recent_trades(
        &self,
        symbol: &str,
        limit: Option<u32>,
        account_type: AccountType,
    ) -> ExchangeResult<Value> {
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotRecentTrades,
            _ => BinanceEndpoint::FuturesRecentTrades,
        };
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        self.get(endpoint, params, account_type).await
    }

    /// Get current average price for a symbol (spot only).
    pub async fn get_avg_price(&self, symbol: &str) -> ExchangeResult<Value> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        self.get(BinanceEndpoint::SpotAvgPrice, params, AccountType::Spot).await
    }

    /// Get best bid/ask price for a symbol (or all symbols if `symbol` is None).
    pub async fn get_book_ticker(&self, symbol: Option<&str>) -> ExchangeResult<Value> {
        let mut params = HashMap::new();
        if let Some(s) = symbol {
            params.insert("symbol".to_string(), s.to_string());
        }
        self.get(BinanceEndpoint::SpotBookTicker, params, AccountType::Spot).await
    }

    /// Get open interest for a futures symbol.
    ///
    /// Endpoint: `GET /fapi/v1/openInterest` — no auth.
    pub async fn get_open_interest(&self, symbol: &str) -> ExchangeResult<OpenInterest> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        let v = self.get(BinanceEndpoint::FuturesOpenInterest, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_open_interest(&v)
    }

    /// Get mark price and funding rate for a futures symbol.
    ///
    /// Endpoint: `GET /fapi/v1/premiumIndex` — no auth.
    /// When `symbol` is `None`, Binance returns an array for all symbols; this method
    /// returns the first element in that case.
    pub async fn get_premium_index(&self, symbol: Option<&str>) -> ExchangeResult<PremiumIndexData> {
        let mut params = HashMap::new();
        if let Some(s) = symbol {
            params.insert("symbol".to_string(), s.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesPremiumIndex, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_premium_index(&v)
    }

    /// Get historical funding rates for USDM perpetual futures.
    ///
    /// Endpoint: `GET /fapi/v1/fundingRate` — no auth.
    /// `symbol`: e.g. `"BTCUSDT"`. `limit`: default 100, max 1000.
    /// `start_time` / `end_time`: Unix ms; when both omitted Binance returns the most
    /// recent `limit` entries.
    pub async fn get_funding_rate_history(
        &self,
        symbol: &str,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
    ) -> ExchangeResult<Vec<FundingRate>> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        if let Some(t) = start_time {
            params.insert("startTime".to_string(), t.to_string());
        }
        if let Some(t) = end_time {
            params.insert("endTime".to_string(), t.to_string());
        }
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        let v = self.get(BinanceEndpoint::FundingRate, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_funding_rates(&v)
    }

    /// Shared fetch for the three derived-kline endpoints (mark / index / premium
    /// index price klines). All three share the standard 12-element kline array
    /// shape, so `parse_klines` is reused. `symbol` must already be exchange-native
    /// (e.g. `"BTCUSDT"`). Unlimited historical depth; `limit` default 500, max 1500.
    async fn get_derived_klines(
        &self,
        endpoint: BinanceEndpoint,
        symbol: &str,
        interval: &str,
        limit: Option<u32>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<Kline>> {
        let mut params = HashMap::new();
        // Binance quirk: indexPriceKlines keys the instrument as `pair` (the
        // underlying index pair); markPriceKlines + premiumIndexKlines use `symbol`.
        let sym_key = match endpoint {
            BinanceEndpoint::FuturesIndexPriceKlines => "pair",
            _ => "symbol",
        };
        params.insert(sym_key.to_string(), symbol.to_string());
        params.insert("interval".to_string(), map_kline_interval(interval).to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.min(1500).to_string());
        }
        if let Some(et) = end_time {
            params.insert("endTime".to_string(), et.to_string());
        }
        let v = self.get(endpoint, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_klines(&v)
    }

    /// Get public liquidation orders (force-close events) for futures.
    ///
    /// Endpoint: `GET /fapi/v1/forceOrders` — no authentication required.
    ///
    /// # Parameters
    /// - `symbol`          — filter by symbol (e.g. `"BTCUSDT"`); returns all symbols when `None`.
    /// - `auto_close_type` — `"LIQUIDATION"` (forced liq) or `"ADL"` (auto-deleveraging).
    /// - `start_time`      — Unix ms inclusive lower bound.
    /// - `end_time`        — Unix ms inclusive upper bound.
    /// - `limit`           — max records (default 100, max 1000).
    pub async fn get_force_orders(
        &self,
        symbol: Option<&str>,
        auto_close_type: Option<&str>,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
    ) -> ExchangeResult<Vec<Liquidation>> {
        let mut params = HashMap::new();
        if let Some(s) = symbol {
            params.insert("symbol".to_string(), s.to_string());
        }
        if let Some(t) = auto_close_type {
            params.insert("autoCloseType".to_string(), t.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesForceOrders, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_liquidations(&v)
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // LONG/SHORT RATIOS + OI HISTORY
    // ═══════════════════════════════════════════════════════════════════════════

    /// Get top trader long/short account ratio history.
    ///
    /// Endpoint: `GET /futures/data/topLongShortAccountRatio` — no auth.
    ///
    /// `period`: `"5m"` | `"15m"` | `"30m"` | `"1h"` | `"2h"` | `"4h"` | `"6h"` | `"12h"` | `"1d"`.
    pub async fn get_top_long_short_account_ratio(
        &self,
        symbol: &str,
        period: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<LongShortRatio>> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesLongShortRatio, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_long_short_ratios(&v, "top_account")
    }

    /// Get top trader long/short position ratio history.
    ///
    /// Endpoint: `GET /futures/data/topLongShortPositionRatio` — no auth.
    pub async fn get_top_long_short_position_ratio(
        &self,
        symbol: &str,
        period: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<LongShortRatio>> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesTopLongShortPositionRatio, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_long_short_ratios(&v, "top_position")
    }

    /// Get global long/short account ratio history.
    ///
    /// Endpoint: `GET /futures/data/globalLongShortAccountRatio` — no auth.
    pub async fn get_global_long_short_account_ratio(
        &self,
        symbol: &str,
        period: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<LongShortRatio>> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesGlobalLongShortAccountRatio, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_long_short_ratios(&v, "global_account")
    }

    /// Get taker long/short volume ratio history.
    ///
    /// Endpoint: `GET /futures/data/takerlongshortRatio` — no auth.
    pub async fn get_taker_long_short_ratio(
        &self,
        symbol: &str,
        period: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<LongShortRatio>> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesTakerLongShortRatio, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_long_short_ratios(&v, "taker")
    }

    /// Get open interest history.
    ///
    /// Endpoint: `GET /futures/data/openInterestHist` — no auth.
    pub async fn get_open_interest_history(
        &self,
        symbol: &str,
        period: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<OpenInterest>> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesOpenInterestHist, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_open_interest_history(&v)
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // FILL / TRADE HISTORY
    // ═══════════════════════════════════════════════════════════════════════════

    /// Get personal trade fills for a symbol.
    ///
    /// `account_type` selects spot vs futures endpoint.
    /// `start_time` and `end_time` are Unix milliseconds.
    pub async fn get_my_trades(
        &self,
        symbol: &str,
        account_type: AccountType,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Value> {
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotMyTrades,
            _ => BinanceEndpoint::FuturesMyTrades,
        };
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(st) = start_time {
            params.insert("startTime".to_string(), st.to_string());
        }
        if let Some(et) = end_time {
            params.insert("endTime".to_string(), et.to_string());
        }
        self.get(endpoint, params, account_type).await
    }

    /// Get futures income history (PnL, funding fees, etc.).
    ///
    /// `income_type`: e.g. `"REALIZED_PNL"`, `"FUNDING_FEE"`, `"COMMISSION"`.
    pub async fn get_income_history(
        &self,
        symbol: Option<&str>,
        income_type: Option<&str>,
        limit: Option<u32>,
    ) -> ExchangeResult<Value> {
        let mut params = HashMap::new();
        if let Some(s) = symbol {
            params.insert("symbol".to_string(), s.to_string());
        }
        if let Some(t) = income_type {
            params.insert("incomeType".to_string(), t.to_string());
        }
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        self.get(BinanceEndpoint::FuturesIncomeHistory, params, AccountType::FuturesCross).await
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // BASIS HISTORY
    // ═══════════════════════════════════════════════════════════════════════════

    /// Get basis history (futures premium / discount to index).
    ///
    /// Endpoint: `GET /futures/data/basis` on `fapi.binance.com` — no auth.
    ///
    /// # Parameters
    /// - `pair`           — e.g. `"BTCUSDT"` (USDT-M) or `"BTCUSD"` (coin-M).
    /// - `contract_type`  — `"PERPETUAL"` | `"CURRENT_QUARTER"` | `"NEXT_QUARTER"`.
    /// - `period`         — `"5m"` | `"15m"` | `"30m"` | `"1h"` | `"2h"` | `"4h"` | `"6h"` | `"12h"` | `"1d"`.
    /// - `limit`          — max records (default 30, max 500).
    /// - `start_time`     — Unix ms.
    /// - `end_time`       — Unix ms.
    pub async fn get_basis_history(
        &self,
        pair: &str,
        contract_type: &str,
        period: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Value> {
        let mut params = HashMap::new();
        params.insert("pair".to_string(), pair.to_string());
        params.insert("contractType".to_string(), contract_type.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        self.get(BinanceEndpoint::BasisHistory, params, AccountType::FuturesCross).await
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // INSURANCE FUND INCOME
    // ═══════════════════════════════════════════════════════════════════════════

    /// Get insurance fund income records (signed endpoint).
    ///
    /// Endpoint: `GET /fapi/v1/income` with `incomeType=INSURANCE_CLEAR`.
    /// Requires API key + secret. The caller's account must have made at least
    /// one transaction for the signed endpoint to return data.
    ///
    /// # Parameters
    /// - `start_time` — Unix ms lower bound.
    /// - `end_time`   — Unix ms upper bound.
    /// - `limit`      — max records (default 100, max 1000).
    pub async fn get_insurance_fund_income(
        &self,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
    ) -> ExchangeResult<Value> {
        let mut params = HashMap::new();
        params.insert("incomeType".to_string(), "INSURANCE_CLEAR".to_string());
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        self.get(BinanceEndpoint::InsuranceFundIncome, params, AccountType::FuturesCross).await
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // COIN-MARGINED (DAPI) ENDPOINTS
    // ═══════════════════════════════════════════════════════════════════════════

    /// Make a raw GET request to the coin-margined (DAPI) base URL.
    ///
    /// Used internally for CM endpoints that are not reachable via the USDT-M
    /// `fapi.binance.com` host.
    async fn get_cm(
        &self,
        endpoint: BinanceEndpoint,
        mut params: HashMap<String, String>,
    ) -> ExchangeResult<Value> {
        self.rate_limit_wait(weights::DEFAULT, false).await;

        let base_url = self.urls.coin_futures_rest;
        let path = endpoint.path();

        let headers = if endpoint.requires_auth() {
            let auth = self.auth.as_ref()
                .ok_or_else(|| ExchangeError::Auth("Authentication required".to_string()))?;
            auth.sign_request(&mut params)
        } else {
            HashMap::new()
        };

        let query = if params.is_empty() {
            String::new()
        } else {
            let qs: Vec<String> = params.iter()
                .map(|(k, v)| format!("{}={}", k, v))
                .collect();
            format!("?{}", qs.join("&"))
        };

        let url = format!("{}{}{}", base_url, path, query);
        let (response, resp_headers) = self.http.get_with_response_headers(&url, &HashMap::new(), &headers).await?;
        self.update_weight_from_headers(&resp_headers);
        BinanceParser::check_error(&response)?;
        Ok(response)
    }

    /// Get open interest for a coin-margined symbol.
    ///
    /// Endpoint: `GET /dapi/v1/openInterest` — no auth.
    /// `symbol`: e.g. `"BTCUSD_PERP"`.
    pub async fn get_open_interest_cm(&self, symbol: &str) -> ExchangeResult<OpenInterest> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        let v = self.get_cm(BinanceEndpoint::CmOpenInterest, params).await?;
        BinanceParser::parse_open_interest(&v)
    }

    /// Get public liquidation orders for coin-margined futures.
    ///
    /// Endpoint: `GET /dapi/v1/forceOrders` — no auth.
    pub async fn get_force_orders_cm(
        &self,
        symbol: Option<&str>,
        auto_close_type: Option<&str>,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
    ) -> ExchangeResult<Vec<Liquidation>> {
        let mut params = HashMap::new();
        if let Some(s) = symbol {
            params.insert("symbol".to_string(), s.to_string());
        }
        if let Some(t) = auto_close_type {
            params.insert("autoCloseType".to_string(), t.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        let v = self.get_cm(BinanceEndpoint::CmForceOrders, params).await?;
        BinanceParser::parse_liquidations(&v)
    }

    /// Get funding rate history for coin-margined futures.
    ///
    /// Endpoint: `GET /dapi/v1/fundingRate` — no auth.
    /// `symbol`: e.g. `"BTCUSD_PERP"`.
    pub async fn get_funding_rate_cm(
        &self,
        symbol: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<FundingRate>> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get_cm(BinanceEndpoint::CmFundingRate, params).await?;
        BinanceParser::parse_funding_rates(&v)
    }

    /// Get open interest history for coin-margined futures.
    ///
    /// Endpoint: `GET /futures/data/openInterestHist` on `dapi.binance.com` — no auth.
    /// `pair`: e.g. `"BTCUSD"`. `contract_type`: `"PERPETUAL"` | `"CURRENT_QUARTER"`.
    /// `period`: `"5m"` | `"15m"` | `"30m"` | `"1h"` | `"2h"` | `"4h"` | `"6h"` | `"12h"` | `"1d"`.
    pub async fn get_open_interest_history_cm(
        &self,
        pair: &str,
        contract_type: &str,
        period: &str,
        limit: Option<u32>,
        start_time: Option<i64>,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<OpenInterest>> {
        let mut params = HashMap::new();
        params.insert("pair".to_string(), pair.to_string());
        params.insert("contractType".to_string(), contract_type.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get_cm(BinanceEndpoint::CmOpenInterestHist, params).await?;
        BinanceParser::parse_open_interest_history(&v)
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // LISTEN KEY MANAGEMENT
    // ═══════════════════════════════════════════════════════════════════════════

    /// Keepalive a spot user data stream listen key (extend 60-min TTL).
    pub async fn keepalive_listen_key(&self, listen_key: &str) -> ExchangeResult<Value> {
        // Listen key keepalive is essential — losing it kills the user data stream
        self.rate_limit_wait(weights::DEFAULT, true).await;
        let real_base = self.urls.rest_url(AccountType::Spot);
        let auth = self.auth.as_ref()
            .ok_or_else(|| ExchangeError::Auth("Authentication required".to_string()))?;
        let mut params = HashMap::new();
        params.insert("listenKey".to_string(), listen_key.to_string());
        let headers = auth.sign_request(&mut params);
        let query = format!("?listenKey={}", listen_key);
        let url = assemble_rest_url(self.rest_override.as_deref(), real_base, BinanceEndpoint::ListenKeyKeepAlive.path(), &query);
        let response = self.http.put(&url, &json!({}), &headers).await?;
        BinanceParser::check_error(&response)?;
        Ok(response)
    }

    /// Close a spot user data stream listen key.
    pub async fn close_listen_key(&self, listen_key: &str) -> ExchangeResult<Value> {
        let mut params = HashMap::new();
        params.insert("listenKey".to_string(), listen_key.to_string());
        self.delete(BinanceEndpoint::ListenKeyClose, params, AccountType::Spot).await
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// EXCHANGE IDENTITY
// ═══════════════════════════════════════════════════════════════════════════════

impl ExchangeIdentity for BinanceConnector {
    fn exchange_id(&self) -> ExchangeId {
        ExchangeId::Binance
    }

    fn is_testnet(&self) -> bool {
        self.testnet
    }

    fn supported_account_types(&self) -> Vec<AccountType> {
        vec![
            AccountType::Spot,
            AccountType::Margin,
            AccountType::FuturesCross,
            AccountType::FuturesIsolated,
        ]
    }

    fn exchange_type(&self) -> ExchangeType {
        ExchangeType::Cex
    }

    fn metrics(&self) -> ConnectorStats {
        let (http_requests, http_errors, last_latency_ms) = self.http.stats();
        let (rate_used, rate_max) = if let Ok(mut limiter) = self.limiter.lock() {
            limiter.primary_stats()
        } else {
            (0, 0)
        };
        ConnectorStats {
            http_requests,
            http_errors,
            last_latency_ms,
            rate_used,
            rate_max,
            rate_groups: Vec::new(),
            ws_ping_rtt_ms: 0,
        }
    }

    fn rate_limit_capabilities(&self) -> RateLimitCapabilities {
        BINANCE_SPOT_RATE_CAPS
    }

    fn orderbook_capabilities(&self, account_type: AccountType) -> OrderbookCapabilities {
        match account_type {
            AccountType::Spot => OrderbookCapabilities {
                ws_depths: &[5, 10, 20],
                ws_default_depth: Some(20),
                rest_max_depth: Some(5000),
                rest_depth_values: &[],
                supports_snapshot: true,
                supports_delta: true,
                update_speeds_ms: &[100, 1000],
                default_speed_ms: Some(1000),
                ws_channels: &[],
                checksum: None,
                has_sequence: true,
                has_prev_sequence: false,
                supports_aggregation: false,
                aggregation_levels: &[],
            },
            _ => OrderbookCapabilities {
                ws_depths: &[5, 10, 20],
                ws_default_depth: Some(20),
                rest_max_depth: Some(1000),
                rest_depth_values: &[5, 10, 20, 50, 100, 500, 1000],
                supports_snapshot: true,
                supports_delta: true,
                update_speeds_ms: &[100, 250, 500],
                default_speed_ms: Some(250),
                ws_channels: &[],
                checksum: None,
                has_sequence: true,
                has_prev_sequence: true,
                supports_aggregation: false,
                aggregation_levels: &[],
            },
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// MARKET DATA
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl MarketData for BinanceConnector {
    async fn get_price(
        &self,
        symbol: SymbolInput<'_>,
        account_type: AccountType,
    ) -> ExchangeResult<Price> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotPrice,
            _ => BinanceEndpoint::FuturesPrice,
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_price(&response)
    }

    async fn get_orderbook(
        &self,
        symbol: SymbolInput<'_>,
        depth: Option<u16>,
        account_type: AccountType,
    ) -> ExchangeResult<OrderBook> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotOrderbook,
            _ => BinanceEndpoint::FuturesOrderbook,
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());

        if let Some(d) = depth {
            params.insert("limit".to_string(), d.to_string());
        }

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_orderbook(&response)
    }

    async fn get_klines(
        &self,
        symbol: SymbolInput<'_>,
        interval: &str,
        limit: Option<u16>,
        account_type: AccountType,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<Kline>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotKlines,
            _ => BinanceEndpoint::FuturesKlines,
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        params.insert("interval".to_string(), map_kline_interval(interval).to_string());

        if let Some(l) = limit {
            params.insert("limit".to_string(), l.min(1000).to_string());
        }

        if let Some(et) = end_time {
            params.insert("endTime".to_string(), et.to_string());
        }

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_klines(&response)
    }

    async fn get_ticker(
        &self,
        symbol: SymbolInput<'_>,
        account_type: AccountType,
    ) -> ExchangeResult<Ticker> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotTicker,
            _ => BinanceEndpoint::FuturesTicker,
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_ticker(&response)
    }

    async fn ping(&self) -> ExchangeResult<()> {
        let response = self.get(BinanceEndpoint::Ping, HashMap::new(), AccountType::Spot).await?;
        BinanceParser::check_error(&response)
    }

    /// Получить информацию о всех торговых символах биржи
    ///
    /// Returns only symbols with `status == "TRADING"`.
    /// Use `AccountType::Spot` for spot markets, any other for futures.
    async fn get_exchange_info(&self, account_type: AccountType) -> ExchangeResult<Vec<SymbolInfo>> {
        let endpoint = match account_type {
            AccountType::Spot => BinanceEndpoint::SpotExchangeInfo,
            _ => BinanceEndpoint::FuturesExchangeInfo,
        };
        let response = self.get(endpoint, HashMap::new(), account_type).await?;
        let symbols = BinanceParser::parse_exchange_info(&response, account_type)?;
        self.precision.load_from_symbols(&symbols);
        Ok(symbols)
    }

    fn market_data_capabilities(&self, _account_type: AccountType) -> MarketDataCapabilities {
        // Spot and Futures share the same intervals and kline limit (1000 per request).
        // Both endpoints (SpotKlines / FuturesKlines) accept identical interval strings
        // via map_kline_interval — no branching needed here.
        MarketDataCapabilities {
            has_ping: true,
            has_price: true,
            has_ticker: true,
            has_orderbook: true,
            has_klines: true,
            has_exchange_info: true,
            has_recent_trades: true,
            has_ws_klines: true,
            has_ws_trades: true,
            has_ws_orderbook: true,
            has_ws_ticker: true,
            supported_intervals: &[
                "1m", "3m", "5m", "15m", "30m",
                "1h", "2h", "4h", "6h", "8h", "12h",
                "1d", "3d", "1w", "1M",
            ],
            max_kline_limit: Some(1000),
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// TRADING
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl Trading for BinanceConnector {
    async fn place_order(&self, req: OrderRequest) -> ExchangeResult<PlaceOrderResponse> {
        let symbol = req.symbol;
        let side = req.side;
        let quantity = req.quantity;
        let account_type = req.account_type;
        let symbol_str = format_symbol(&symbol.base, &symbol.quote, account_type);

        match req.order_type {
            OrderType::Market => {
                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotCreateOrder,
                    _ => BinanceEndpoint::FuturesCreateOrder,
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), match side {
                    OrderSide::Buy => "BUY".to_string(),
                    OrderSide::Sell => "SELL".to_string(),
                });
                params.insert("type".to_string(), "MARKET".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));

                let response = self.post(endpoint, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }
            OrderType::Limit { price } => {
                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotCreateOrder,
                    _ => BinanceEndpoint::FuturesCreateOrder,
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "LIMIT".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("price".to_string(), self.precision.price(&symbol_str, price));
                params.insert("timeInForce".to_string(), "GTC".to_string());

                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(endpoint, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::StopMarket { stop_price } => {
                // Spot: no native STOP_MARKET. Futures only.
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "StopMarket not supported on Spot/Margin (Binance Futures only)".to_string()
                        ));
                    }
                    _ => {}
                }

                // Post-2025-12-09: STOP_MARKET moved to /fapi/v1/order/algo on Futures.
                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "STOP_MARKET".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("stopPrice".to_string(), self.precision.price(&symbol_str, stop_price));

                if req.reduce_only {
                    params.insert("reduceOnly".to_string(), "true".to_string());
                }
                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(BinanceEndpoint::FuturesAlgoOrder, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::StopLimit { stop_price, limit_price } => {
                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("stopPrice".to_string(), self.precision.price(&symbol_str, stop_price));
                params.insert("price".to_string(), self.precision.price(&symbol_str, limit_price));

                // Spot uses STOP_LOSS_LIMIT on /api/v3/order (unchanged).
                // Futures: post-2025-12-09 STOP type moved to /fapi/v1/order/algo.
                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        params.insert("type".to_string(), "STOP_LOSS_LIMIT".to_string());
                        params.insert("timeInForce".to_string(), "GTC".to_string());
                        BinanceEndpoint::SpotCreateOrder
                    }
                    _ => {
                        params.insert("type".to_string(), "STOP".to_string());
                        params.insert("timeInForce".to_string(), "GTC".to_string());
                        BinanceEndpoint::FuturesAlgoOrder
                    }
                };

                if req.reduce_only {
                    params.insert("reduceOnly".to_string(), "true".to_string());
                }
                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(endpoint, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::TrailingStop { callback_rate, activation_price } => {
                // Futures only
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "TrailingStop not supported on Spot/Margin (Binance Futures only)".to_string()
                        ));
                    }
                    _ => {}
                }

                // Post-2025-12-09: TRAILING_STOP_MARKET moved to /fapi/v1/order/algo on Futures.
                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "TRAILING_STOP_MARKET".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("callbackRate".to_string(), callback_rate.to_string());

                if let Some(ap) = activation_price {
                    params.insert("activationPrice".to_string(), ap.to_string());
                }
                if req.reduce_only {
                    params.insert("reduceOnly".to_string(), "true".to_string());
                }
                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(BinanceEndpoint::FuturesAlgoOrder, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::Oco { price, stop_price, stop_limit_price } => {
                // Spot only — Binance OCO is not available on Futures
                match account_type {
                    AccountType::Spot | AccountType::Margin => {}
                    _ => {
                        return Err(ExchangeError::NotImplemented(
                            "OCO orders not supported on Futures (Binance Spot only)".to_string()
                        ));
                    }
                }

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("price".to_string(), self.precision.price(&symbol_str, price));
                params.insert("stopPrice".to_string(), self.precision.price(&symbol_str, stop_price));

                if let Some(slp) = stop_limit_price {
                    params.insert("stopLimitPrice".to_string(), self.precision.price(&symbol_str, slp));
                    params.insert("stopLimitTimeInForce".to_string(), "GTC".to_string());
                }
                if let Some(cid) = &req.client_order_id {
                    params.insert("listClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(BinanceEndpoint::SpotOcoOrder, params, account_type).await?;
                let oco = BinanceParser::parse_oco_response(&response)?;
                Ok(PlaceOrderResponse::Oco(Box::new(oco)))
            }

            OrderType::Bracket { price, take_profit, stop_loss } => {
                // Spot: map to OTOCO (One-Triggers-a-One-Cancels-the-Other)
                // Futures: no native bracket — conditional orders are via algo endpoint separately
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        // OTOCO: working order must be LIMIT or LIMIT_MAKER.
                        // entry price is required for OTOCO.
                        let entry_price = price.ok_or_else(|| ExchangeError::InvalidRequest(
                            "Bracket order on Binance Spot requires an entry price (market entry not supported for OTOCO)".to_string()
                        ))?;

                        let mut params = HashMap::new();
                        params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                        params.insert("side".to_string(), side.as_str().to_string());
                        params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                        // Working leg: LIMIT entry
                        params.insert("workingType".to_string(), "LIMIT".to_string());
                        params.insert("workingPrice".to_string(), self.precision.price(&symbol_str, entry_price));
                        params.insert("workingTimeInForce".to_string(), "GTC".to_string());
                        // Pending above leg: take-profit limit order (above entry for buy)
                        params.insert("pendingAboveType".to_string(), "LIMIT_MAKER".to_string());
                        params.insert("pendingAbovePrice".to_string(), self.precision.price(&symbol_str, take_profit));
                        // Pending below leg: stop-loss stop order (below entry for buy)
                        params.insert("pendingBelowType".to_string(), "STOP_LOSS".to_string());
                        params.insert("pendingBelowStopPrice".to_string(), self.precision.price(&symbol_str, stop_loss));

                        if let Some(cid) = &req.client_order_id {
                            params.insert("listClientOrderId".to_string(), cid.clone());
                        }

                        let response = self.post(BinanceEndpoint::SpotOtocoOrder, params, account_type).await?;
                        let bracket = BinanceParser::parse_otoco_response(&response)?;
                        Ok(PlaceOrderResponse::Bracket(Box::new(bracket)))
                    }
                    _ => {
                        Err(ExchangeError::NotImplemented(
                            "Bracket orders not supported on Binance Futures. Use separate conditional/algo orders for TP/SL.".to_string()
                        ))
                    }
                }
            }

            OrderType::Iceberg { price, display_quantity } => {
                // Spot only — Binance Futures does not support iceberg
                match account_type {
                    AccountType::Spot | AccountType::Margin => {}
                    _ => {
                        return Err(ExchangeError::NotImplemented(
                            "Iceberg orders not supported on Futures (Binance Spot only)".to_string()
                        ));
                    }
                }

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "LIMIT".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("price".to_string(), self.precision.price(&symbol_str, price));
                params.insert("icebergQty".to_string(), self.precision.qty(&symbol_str, display_quantity));
                params.insert("timeInForce".to_string(), "GTC".to_string());

                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(BinanceEndpoint::SpotCreateOrder, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::Twap { duration_seconds, .. } => {
                // Binance TWAP lives in a separate Algo API namespace:
                // Spot:    POST /sapi/v1/algo/spot/newOrderTwap
                // Futures: POST /sapi/v1/algo/futures/newOrderTwap
                // Both use api.binance.com (not fapi) as the base URL.
                // Constraints: duration 300–86400 seconds; notional 1,000–100,000 USDT (Spot),
                //              1,000–1,000,000 USDT (Futures).

                // Validate duration range
                if !(300..=86_400).contains(&duration_seconds) {
                    return Err(ExchangeError::InvalidRequest(format!(
                        "Binance TWAP duration must be between 300 and 86400 seconds, got {}",
                        duration_seconds
                    )));
                }

                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotAlgoTwap,
                    _ => BinanceEndpoint::FuturesAlgoTwap,
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("duration".to_string(), duration_seconds.to_string());

                if let Some(cid) = &req.client_order_id {
                    params.insert("clientAlgoId".to_string(), cid.clone());
                }

                // Algo API base URL is always api.binance.com (Spot REST), even for Futures TWAP.
                // We force Spot account type for URL routing since both /sapi endpoints
                // live on api.binance.com.
                let response = self.post(endpoint, params, AccountType::Spot).await?;
                let algo = BinanceParser::parse_algo_order_response(&response)?;
                Ok(PlaceOrderResponse::Algo(algo))
            }

            OrderType::PostOnly { price } => {
                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotCreateOrder,
                    _ => BinanceEndpoint::FuturesCreateOrder,
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "LIMIT".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("price".to_string(), self.precision.price(&symbol_str, price));
                // GTX = Post-Only on Binance (Good Till Crossing)
                params.insert("timeInForce".to_string(), "GTX".to_string());

                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(endpoint, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::Ioc { price } => {
                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotCreateOrder,
                    _ => BinanceEndpoint::FuturesCreateOrder,
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "LIMIT".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("timeInForce".to_string(), "IOC".to_string());

                // Use the provided price, or fall back to a limit order at market
                if let Some(p) = price {
                    params.insert("price".to_string(), self.precision.price(&symbol_str, p));
                } else {
                    // IOC with no price — use MARKET type instead
                    params.insert("type".to_string(), "MARKET".to_string());
                    params.remove("timeInForce");
                }

                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(endpoint, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::Fok { price } => {
                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotCreateOrder,
                    _ => BinanceEndpoint::FuturesCreateOrder,
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "LIMIT".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("price".to_string(), self.precision.price(&symbol_str, price));
                params.insert("timeInForce".to_string(), "FOK".to_string());

                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(endpoint, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::Gtd { price, expire_time } => {
                // GTD is only supported on Binance USDS-M Futures.
                // Spot only supports GTC, IOC, FOK — GTD returns NotImplemented.
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "GTD (Good-Till-Date) is not supported on Binance Spot/Margin. \
                             Binance Spot only supports GTC, IOC, FOK timeInForce.".to_string()
                        ));
                    }
                    _ => {}
                }

                // Binance requires goodTillDate > current_time + 600s.
                // We validate the value is a valid ms timestamp in range:
                // max = 253402300799000 (year 9999).
                if expire_time <= 0 || expire_time > 253_402_300_799_000 {
                    return Err(ExchangeError::InvalidRequest(
                        "GTD expire_time must be a valid Unix ms timestamp (>0 and < 253402300799000)".to_string()
                    ));
                }

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("type".to_string(), "LIMIT".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));
                params.insert("price".to_string(), self.precision.price(&symbol_str, price));
                params.insert("timeInForce".to_string(), "GTD".to_string());
                params.insert("goodTillDate".to_string(), expire_time.to_string());

                if req.reduce_only {
                    params.insert("reduceOnly".to_string(), "true".to_string());
                }
                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(BinanceEndpoint::FuturesCreateOrder, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }

            OrderType::ReduceOnly { price } => {
                // Futures only
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "ReduceOnly not supported on Spot/Margin".to_string()
                        ));
                    }
                    _ => {}
                }

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("side".to_string(), side.as_str().to_string());
                params.insert("reduceOnly".to_string(), "true".to_string());
                params.insert("quantity".to_string(), self.precision.qty(&symbol_str, quantity));

                if let Some(p) = price {
                    params.insert("type".to_string(), "LIMIT".to_string());
                    params.insert("price".to_string(), self.precision.price(&symbol_str, p));
                    params.insert("timeInForce".to_string(), "GTC".to_string());
                } else {
                    params.insert("type".to_string(), "MARKET".to_string());
                }

                if let Some(cid) = &req.client_order_id {
                    params.insert("newClientOrderId".to_string(), cid.clone());
                }

                let response = self.post(BinanceEndpoint::FuturesCreateOrder, params, account_type).await?;
                let order = BinanceParser::parse_order(&response, &symbol.to_string())?;
                Ok(PlaceOrderResponse::Simple(order))
            }
            _ => Err(ExchangeError::NotImplemented(
                "This order type is not supported by Binance".to_string()
            )),
        }
    }

    async fn cancel_order(&self, req: CancelRequest) -> ExchangeResult<Order> {
        match req.scope {
            CancelScope::Single { ref order_id } => {
                let symbol = req.symbol.as_ref()
                    .ok_or_else(|| ExchangeError::InvalidRequest("Symbol required for cancel".into()))?;
                let account_type = req.account_type;

                let endpoint = match account_type {
                    AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotCancelOrder,
                    _ => BinanceEndpoint::FuturesCancelOrder,
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), format_symbol(&symbol.base, &symbol.quote, account_type));
                params.insert("orderId".to_string(), order_id.to_string());

                let response = self.delete(endpoint, params, account_type).await?;
                BinanceParser::parse_order(&response, &symbol.to_string())
            }
            CancelScope::Batch { .. } => {
                // Batch cancel is handled by BatchOrders trait; not available via Trading::cancel_order
                Err(ExchangeError::NotImplemented(
                    "Use BatchOrders::cancel_orders_batch for batch cancellation on Binance".to_string()
                ))
            }
            CancelScope::All { .. } | CancelScope::BySymbol { .. } => {
                // Delegate to CancelAll logic but return a placeholder order since Trading::cancel_order
                // returns a single Order. Users should call CancelAll::cancel_all_orders instead.
                Err(ExchangeError::NotImplemented(
                    "Use CancelAll::cancel_all_orders for cancel-all on Binance".to_string()
                ))
            }
            _ => Err(ExchangeError::NotImplemented(
                "This cancel scope is not supported by Binance".to_string()
            )),
        }
    }

    async fn get_order(
        &self,
        symbol: &str,
        order_id: &str,
        account_type: AccountType,
    ) -> ExchangeResult<Order> {
        // Parse symbol string into base/quote for format_symbol
        let parts: Vec<&str> = symbol.split('/').collect();
        let (base, quote) = if parts.len() == 2 {
            (parts[0], parts[1])
        } else {
            (symbol, "")
        };

        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotGetOrder,
            _ => BinanceEndpoint::FuturesGetOrder,
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), if quote.is_empty() {
            symbol.to_string()
        } else {
            format_symbol(base, quote, account_type)
        });
        params.insert("orderId".to_string(), order_id.to_string());

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_order(&response, symbol)
    }

    async fn get_open_orders(
        &self,
        symbol: Option<&str>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<Order>> {
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotOpenOrders,
            _ => BinanceEndpoint::FuturesOpenOrders,
        };

        let mut params = HashMap::new();
        if let Some(s) = symbol {
            let parts: Vec<&str> = s.split('/').collect();
            let formatted = if parts.len() == 2 {
                format_symbol(parts[0], parts[1], account_type)
            } else {
                s.to_string()
            };
            params.insert("symbol".to_string(), formatted);
        }

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_orders(&response)
    }

    async fn get_order_history(
        &self,
        filter: OrderHistoryFilter,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<Order>> {
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotAllOrders,
            _ => BinanceEndpoint::FuturesAllOrders,
        };

        let mut params = HashMap::new();

        // Symbol is required for Binance allOrders endpoint
        if let Some(ref sym) = filter.symbol {
            params.insert("symbol".to_string(), format_symbol(&sym.base, &sym.quote, account_type));
        } else {
            return Err(ExchangeError::InvalidRequest(
                "Symbol is required for get_order_history on Binance".to_string()
            ));
        }

        if let Some(start) = filter.start_time {
            params.insert("startTime".to_string(), start.to_string());
        }
        if let Some(end) = filter.end_time {
            params.insert("endTime".to_string(), end.to_string());
        }
        if let Some(lim) = filter.limit {
            params.insert("limit".to_string(), lim.min(1000).to_string());
        }

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_orders(&response)
    }

    async fn get_user_trades(
        &self,
        filter: UserTradeFilter,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<UserTrade>> {
        // Binance requires a symbol for both myTrades endpoints.
        let symbol_raw = filter.symbol.as_deref()
            .ok_or_else(|| ExchangeError::InvalidRequest(
                "Symbol is required for get_user_trades on Binance".to_string()
            ))?;

        let is_futures = !matches!(account_type, AccountType::Spot | AccountType::Margin);

        let endpoint = if is_futures {
            BinanceEndpoint::FuturesMyTrades
        } else {
            BinanceEndpoint::SpotMyTrades
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol_raw.to_string());

        if let Some(oid) = filter.order_id {
            params.insert("orderId".to_string(), oid);
        }
        if let Some(st) = filter.start_time {
            params.insert("startTime".to_string(), st.to_string());
        }
        if let Some(et) = filter.end_time {
            params.insert("endTime".to_string(), et.to_string());
        }
        if let Some(lim) = filter.limit {
            params.insert("limit".to_string(), lim.min(1000).to_string());
        }

        let response = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_user_trades(&response, is_futures)
    }

    fn trading_capabilities(&self, account_type: AccountType) -> TradingCapabilities {
        // Futures = FuturesCross / FuturesIsolated; everything else (Spot, Margin) is spot-like.
        let is_futures = !matches!(account_type, AccountType::Spot | AccountType::Margin);

        if is_futures {
            TradingCapabilities {
                has_market_order: true,
                has_limit_order: true,
                // STOP_MARKET available via /fapi/v1/order (type=STOP_MARKET)
                has_stop_market: true,
                // STOP available via /fapi/v1/order (type=STOP)
                has_stop_limit: true,
                // TRAILING_STOP_MARKET available via /fapi/v1/order/algo
                has_trailing_stop: true,
                // Bracket returns NotImplemented for Futures (place_order arm for Bracket)
                has_bracket: false,
                // OCO returns NotImplemented for Futures (place_order arm for Oco)
                has_oco: false,
                // AmendOrder impl uses PUT /fapi/v1/order — Futures only
                has_amend: true,
                // BatchOrders impl uses /fapi/v1/batchOrders — Futures only, max 5 place
                has_batch: true,
                max_batch_size: Some(5),
                // CancelAll implemented for both (SpotCancelAllOrders / FuturesCancelAllOrders)
                has_cancel_all: true,
                has_user_trades: true,
                has_order_history: true,
            }
        } else {
            TradingCapabilities {
                has_market_order: true,
                has_limit_order: true,
                // Spot: place_order returns NotImplemented for StopMarket
                has_stop_market: false,
                // STOP_LOSS_LIMIT available on /api/v3/order for Spot
                has_stop_limit: true,
                // Spot: place_order returns NotImplemented for TrailingStop
                has_trailing_stop: false,
                // Bracket mapped to OTOCO via /api/v3/orderList/otoco on Spot
                has_bracket: true,
                // OCO available via /api/v3/orderList/oco on Spot
                has_oco: true,
                // AmendOrder returns NotImplemented for Spot/Margin
                has_amend: false,
                // BatchOrders returns NotImplemented for Spot/Margin
                has_batch: false,
                max_batch_size: None,
                // CancelAll implemented for both
                has_cancel_all: true,
                has_user_trades: true,
                has_order_history: true,
            }
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// ACCOUNT
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl Account for BinanceConnector {
    async fn get_balance(&self, query: BalanceQuery) -> ExchangeResult<Vec<Balance>> {
        let _asset = query.asset.as_deref();
        let account_type = query.account_type;

        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotAccount,
            _ => BinanceEndpoint::FuturesAccount,
        };

        let mut params = HashMap::new();
        // Optionally exclude zero balances
        if matches!(account_type, AccountType::Spot | AccountType::Margin) {
            params.insert("omitZeroBalances".to_string(), "true".to_string());
        }

        let response = self.get(endpoint, params, account_type).await?;

        match account_type {
            AccountType::Spot | AccountType::Margin => BinanceParser::parse_balances(&response),
            _ => BinanceParser::parse_futures_balances(&response),
        }
    }

    async fn get_account_info(&self, account_type: AccountType) -> ExchangeResult<AccountInfo> {
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotAccount,
            _ => BinanceEndpoint::FuturesAccount,
        };

        let mut params = HashMap::new();
        if matches!(account_type, AccountType::Spot | AccountType::Margin) {
            params.insert("omitZeroBalances".to_string(), "false".to_string());
        }

        let response = self.get(endpoint, params, account_type).await?;

        let balances = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceParser::parse_balances(&response)?,
            _ => BinanceParser::parse_futures_balances(&response)?,
        };

        // Parse commission rates
        let (maker_commission, taker_commission) = if let Some(rates) = response.get("commissionRates") {
            let maker = rates.get("maker")
                .and_then(|m| m.as_str())
                .and_then(|s| s.parse::<f64>().ok())
                .map(|r| r * 100.0) // Convert to percentage
                .unwrap_or(0.1);
            let taker = rates.get("taker")
                .and_then(|t| t.as_str())
                .and_then(|s| s.parse::<f64>().ok())
                .map(|r| r * 100.0)
                .unwrap_or(0.1);
            (maker, taker)
        } else {
            (0.1, 0.1) // Default
        };

        Ok(AccountInfo {
            account_type,
            can_trade: response.get("canTrade").and_then(|c| c.as_bool()).unwrap_or(true),
            can_withdraw: response.get("canWithdraw").and_then(|c| c.as_bool()).unwrap_or(true),
            can_deposit: response.get("canDeposit").and_then(|c| c.as_bool()).unwrap_or(true),
            maker_commission,
            taker_commission,
            balances,
        })
    }

    async fn get_fees(&self, symbol: Option<&str>) -> ExchangeResult<FeeInfo> {
        // Priority order:
        // 1. GET /sapi/v1/asset/tradeFee  — spot per-symbol rates (best accuracy)
        // 2. GET /fapi/v1/commissionRate  — futures per-symbol rates (when symbol given)
        // 3. GET /api/v3/account          — spot account-wide commissionRates fallback
        // 4. GET /fapi/v2/account         — futures feeTier fallback (tier → estimated rates)

        let formatted_symbol = symbol.map(|s| s.replace('/', "").to_uppercase());

        // Attempt 1: Spot /sapi trade fee (per-symbol or account-wide)
        let mut spot_params = HashMap::new();
        if let Some(ref sym) = formatted_symbol {
            spot_params.insert("symbol".to_string(), sym.clone());
        }
        if let Ok(response) = self.get(BinanceEndpoint::SpotTradeFee, spot_params, AccountType::Spot).await {
            return BinanceParser::parse_fee_info(&response, symbol);
        }

        // Attempt 2: Futures /fapi/v1/commissionRate (requires symbol)
        if let Some(ref sym) = formatted_symbol {
            let mut futures_params = HashMap::new();
            futures_params.insert("symbol".to_string(), sym.clone());
            if let Ok(response) = self.get(
                BinanceEndpoint::FuturesCommissionRate,
                futures_params,
                AccountType::FuturesCross,
            ).await {
                return BinanceParser::parse_fee_info(&response, symbol);
            }
        }

        // Attempt 3: Spot account commissionRates
        let mut account_params = HashMap::new();
        account_params.insert("omitZeroBalances".to_string(), "true".to_string());
        if let Ok(response) = self.get(BinanceEndpoint::SpotAccount, account_params, AccountType::Spot).await {
            return BinanceParser::parse_fee_info(&response, symbol);
        }

        // Attempt 4: Futures account feeTier
        let response = self.get(BinanceEndpoint::FuturesAccount, HashMap::new(), AccountType::FuturesCross).await?;
        BinanceParser::parse_fee_info(&response, symbol)
    }

    fn account_capabilities(&self, account_type: AccountType) -> AccountCapabilities {
        // Futures = FuturesCross / FuturesIsolated; everything else is spot-like.
        let is_futures = !matches!(account_type, AccountType::Spot | AccountType::Margin);

        AccountCapabilities {
            has_balances: true,
            has_account_info: true,
            has_fees: true,
            // AccountTransfers always routes through /sapi/v1/asset/transfer (Spot endpoint).
            // Transfers move funds between Spot ↔ Futures wallet — available regardless of account_type.
            has_transfers: true,
            // SubAccounts always use Spot-level SAPI endpoints — available for both account types.
            has_sub_accounts: true,
            // CustodialFunds (deposit/withdraw) uses Spot SAPI endpoints — available for both.
            has_deposit_withdraw: true,
            // No margin borrowing/repayment endpoints implemented
            has_margin: false,
            // No earn/staking endpoints implemented
            has_earn_staking: false,
            // FundingHistory (GET /fapi/v1/income?incomeType=FUNDING_FEE) is Futures-only.
            // Spot returns NotImplemented from get_funding_payments.
            has_funding_history: is_futures,
            // AccountLedger (GET /fapi/v1/income) is Futures-only.
            // Spot returns NotImplemented from get_ledger.
            has_ledger: is_futures,
            // No coin-to-coin conversion endpoint implemented
            has_convert: false,
            // Positions (GET /fapi/v2/positionRisk) are Futures-only.
            has_positions: is_futures,
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// POSITIONS
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl Positions for BinanceConnector {
    async fn get_positions(&self, query: PositionQuery) -> ExchangeResult<Vec<Position>> {
        let symbol = query.symbol;
        let account_type = query.account_type;
        match account_type {
            AccountType::Spot | AccountType::Margin => {
                return Err(ExchangeError::NotImplemented(
                    "Positions not supported for Spot/Margin".to_string()
                ));
            }
            _ => {}
        }

        let mut params = HashMap::new();
        if let Some(s) = symbol {
            params.insert("symbol".to_string(), format_symbol(&s.base, &s.quote, account_type));
        }

        let response = self.get(BinanceEndpoint::FuturesPositions, params, account_type).await?;
        BinanceParser::parse_positions(&response)
    }

    async fn get_funding_rate(
        &self,
        symbol: &str,
        account_type: AccountType,
    ) -> ExchangeResult<FundingRate> {
        match account_type {
            AccountType::Spot | AccountType::Margin => {
                return Err(ExchangeError::NotImplemented(
                    "Funding rate not supported for Spot/Margin".to_string()
                ));
            }
            _ => {}
        }

        // Parse symbol string into parts for format_symbol
        let parts: Vec<&str> = symbol.split('/').collect();
        let formatted = if parts.len() == 2 {
            format_symbol(parts[0], parts[1], account_type)
        } else {
            symbol.to_string()
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), formatted);
        params.insert("limit".to_string(), "1".to_string());

        let response = self.get(BinanceEndpoint::FundingRate, params, account_type).await?;
        BinanceParser::parse_funding_rate(&response)
    }

    async fn get_mark_price(
        &self,
        symbol: &str,
    ) -> ExchangeResult<MarkPrice> {
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());

        let response = self
            .get(
                BinanceEndpoint::FuturesPremiumIndex,
                params,
                AccountType::FuturesCross,
            )
            .await?;

        // Response: {symbol, markPrice, indexPrice, lastFundingRate, nextFundingTime, time, ...}
        let data = if let Some(arr) = response.as_array() {
            arr.first()
                .ok_or_else(|| ExchangeError::Parse("Empty premiumIndex array".to_string()))?
                .clone()
        } else {
            response
        };

        let mark_price = data
            .get("markPrice")
            .and_then(|v| v.as_str())
            .and_then(|s| s.parse::<f64>().ok())
            .or_else(|| data.get("markPrice").and_then(|v| v.as_f64()))
            .ok_or_else(|| ExchangeError::Parse("Missing markPrice".to_string()))?;

        Ok(MarkPrice {
            mark_price,
            index_price: data
                .get("indexPrice")
                .and_then(|v| v.as_str())
                .and_then(|s| s.parse::<f64>().ok()),
            funding_rate: data
                .get("lastFundingRate")
                .and_then(|v| v.as_str())
                .and_then(|s| s.parse::<f64>().ok()),
            timestamp: data.get("time").and_then(|t| t.as_i64()).unwrap_or(0),
            symbol: data.get("symbol").and_then(|v| v.as_str()).map(String::from),
            estimated_settle_price: data
                .get("estimatedSettlePrice")
                .and_then(|v| v.as_str())
                .and_then(|s| s.parse::<f64>().ok()),
            interest_rate: data
                .get("interestRate")
                .and_then(|v| v.as_str())
                .and_then(|s| s.parse::<f64>().ok()),
            next_funding_time: data.get("nextFundingTime").and_then(|t| t.as_i64()),
            ..Default::default()
        })
    }

    async fn get_open_interest(
        &self,
        symbol: &str,
        account_type: AccountType,
    ) -> ExchangeResult<OpenInterest> {
        match account_type {
            AccountType::Spot | AccountType::Margin => {
                return Err(ExchangeError::NotImplemented(
                    "Open interest not supported for Spot/Margin".to_string(),
                ));
            }
            _ => {}
        }
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        let v = self.get(BinanceEndpoint::FuturesOpenInterest, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_open_interest(&v)
    }

    async fn modify_position(&self, req: PositionModification) -> ExchangeResult<()> {
        match req {
            PositionModification::SetLeverage { ref symbol, leverage, account_type } => {
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "Leverage not supported for Spot/Margin".to_string()
                        ));
                    }
                    _ => {}
                }

                let formatted = format_symbol(&symbol.base, &symbol.quote, account_type);

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), formatted);
                params.insert("leverage".to_string(), leverage.to_string());

                let response = self.post(BinanceEndpoint::FuturesSetLeverage, params, account_type).await?;
                BinanceParser::check_error(&response)?;

                Ok(())
            }

            PositionModification::SetMarginMode { ref symbol, margin_type, account_type } => {
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "SetMarginMode not supported for Spot/Margin".to_string()
                        ));
                    }
                    _ => {}
                }

                let formatted = format_symbol(&symbol.base, &symbol.quote, account_type);
                let margin_type_str = match margin_type {
                    MarginType::Isolated => "ISOLATED",
                    MarginType::Cross => "CROSSED",
                };

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), formatted);
                params.insert("marginType".to_string(), margin_type_str.to_string());

                let response = self.post(BinanceEndpoint::FuturesSetMarginType, params, account_type).await?;
                BinanceParser::check_error(&response)?;
                Ok(())
            }

            PositionModification::AddMargin { ref symbol, amount, account_type } => {
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "AddMargin not supported for Spot/Margin".to_string()
                        ));
                    }
                    _ => {}
                }

                let formatted = format_symbol(&symbol.base, &symbol.quote, account_type);

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), formatted);
                params.insert("amount".to_string(), amount.to_string());
                params.insert("type".to_string(), "1".to_string()); // 1 = add margin

                let response = self.post(BinanceEndpoint::FuturesPositionMargin, params, account_type).await?;
                BinanceParser::check_error(&response)?;
                Ok(())
            }

            PositionModification::RemoveMargin { ref symbol, amount, account_type } => {
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "RemoveMargin not supported for Spot/Margin".to_string()
                        ));
                    }
                    _ => {}
                }

                let formatted = format_symbol(&symbol.base, &symbol.quote, account_type);

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), formatted);
                params.insert("amount".to_string(), amount.to_string());
                params.insert("type".to_string(), "2".to_string()); // 2 = remove margin

                let response = self.post(BinanceEndpoint::FuturesPositionMargin, params, account_type).await?;
                BinanceParser::check_error(&response)?;
                Ok(())
            }

            PositionModification::ClosePosition { ref symbol, account_type } => {
                match account_type {
                    AccountType::Spot | AccountType::Margin => {
                        return Err(ExchangeError::NotImplemented(
                            "ClosePosition not supported for Spot/Margin".to_string()
                        ));
                    }
                    _ => {}
                }

                // Get the open position to find its quantity
                let positions = self.get_positions(PositionQuery {
                    symbol: Some(symbol.clone()),
                    account_type,
                }).await?;

                let position = positions.into_iter().next()
                    .ok_or_else(|| ExchangeError::InvalidRequest(
                        format!("No open position found for {}", symbol)
                    ))?;

                // Place a reduce-only market order in the opposite direction
                let close_side = if position.side == crate::core::PositionSide::Long {
                    OrderSide::Sell
                } else {
                    OrderSide::Buy
                };

                let formatted = format_symbol(&symbol.base, &symbol.quote, account_type);

                let mut params = HashMap::new();
                params.insert("symbol".to_string(), formatted);
                params.insert("side".to_string(), close_side.as_str().to_string());
                params.insert("type".to_string(), "MARKET".to_string());
                params.insert("quantity".to_string(), position.quantity.to_string());
                params.insert("reduceOnly".to_string(), "true".to_string());

                let response = self.post(BinanceEndpoint::FuturesCreateOrder, params, account_type).await?;
                BinanceParser::check_error(&response)?;
                Ok(())
            }

            PositionModification::SetTpSl { .. } => {
                Err(ExchangeError::NotImplemented(
                    "SetTpSl is not a single native endpoint on Binance. Place separate TP/SL orders.".to_string()
                ))
            }
            _ => Err(ExchangeError::NotImplemented(
                "This position modification is not supported by Binance".to_string()
            )),
        }
    }

    async fn get_long_short_ratio(
        &self,
        symbol: &str,
        _account_type: AccountType,
    ) -> ExchangeResult<crate::core::types::LongShortRatio> {
        let vec = self
            .get_global_long_short_account_ratio(symbol, "5m", Some(1), None, None)
            .await?;
        vec.into_iter().next().ok_or_else(|| {
            crate::core::types::ExchangeError::NotFound(
                format!("No long/short ratio data for {symbol}"),
            )
        })
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// CANCEL ALL
// ═══════════════════════════════════════════════════════════════════════════════

/// Cancel all open orders (optionally filtered to a single symbol).
///
/// - Spot: `DELETE /api/v3/openOrders` — requires `symbol` param
/// - Futures: `DELETE /fapi/v1/allOpenOrders` — requires `symbol` param
///
/// Note: Binance requires `symbol` on both endpoints; passing `All` with
/// `symbol = None` is not supported and returns `NotImplemented`.
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl CancelAll for BinanceConnector {
    async fn cancel_all_orders(
        &self,
        scope: CancelScope,
        account_type: AccountType,
    ) -> ExchangeResult<CancelAllResponse> {
        let symbol = match &scope {
            CancelScope::All { symbol } => symbol.clone(),
            CancelScope::BySymbol { symbol } => Some(symbol.clone()),
            _ => {
                return Err(ExchangeError::InvalidRequest(
                    "cancel_all_orders only accepts All or BySymbol scope".to_string()
                ));
            }
        };

        let sym = symbol.ok_or_else(|| ExchangeError::InvalidRequest(
            "Binance cancel-all requires a symbol. Pass CancelScope::BySymbol or CancelScope::All with Some(symbol).".to_string()
        ))?;

        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotCancelAllOrders,
            _ => BinanceEndpoint::FuturesCancelAllOrders,
        };

        let mut params = HashMap::new();
        params.insert("symbol".to_string(), format_symbol(&sym.base, &sym.quote, account_type));

        let response = self.delete(endpoint, params, account_type).await?;
        BinanceParser::parse_cancel_all_response(&response)
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// AMEND ORDER
// ═══════════════════════════════════════════════════════════════════════════════

/// Modify a live futures order in-place.
///
/// Binance Futures: `PUT /fapi/v1/order`
/// Spot does NOT support amend — this returns `NotImplemented` for Spot/Margin.
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl AmendOrder for BinanceConnector {
    async fn amend_order(&self, req: AmendRequest) -> ExchangeResult<Order> {
        match req.account_type {
            AccountType::Spot | AccountType::Margin => {
                return Err(ExchangeError::NotImplemented(
                    "Amend order not supported on Spot/Margin (Binance Futures only)".to_string()
                ));
            }
            _ => {}
        }

        // At least one field must be changed
        if req.fields.price.is_none() && req.fields.quantity.is_none() {
            return Err(ExchangeError::InvalidRequest(
                "At least one of price or quantity must be provided for amend".to_string()
            ));
        }

        let account_type = req.account_type;
        let amend_symbol_str = format_symbol(&req.symbol.base, &req.symbol.quote, account_type);
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), amend_symbol_str.clone());
        params.insert("orderId".to_string(), req.order_id.clone());

        if let Some(price) = req.fields.price {
            params.insert("price".to_string(), self.precision.price(&amend_symbol_str, price));
        }
        if let Some(quantity) = req.fields.quantity {
            params.insert("quantity".to_string(), self.precision.qty(&amend_symbol_str, quantity));
        }
        if let Some(stop_price) = req.fields.trigger_price {
            params.insert("stopPrice".to_string(), self.precision.price(&amend_symbol_str, stop_price));
        }

        let response = self.put(BinanceEndpoint::FuturesAmendOrder, params, account_type).await?;
        BinanceParser::parse_order(&response, &req.symbol.to_string())
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// BATCH ORDERS
// ═══════════════════════════════════════════════════════════════════════════════

/// Native batch order placement and cancellation.
///
/// - Futures: `POST /fapi/v1/batchOrders` — max 5 orders per batch
/// - Spot: no native batch endpoint → returns `NotImplemented`
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl BatchOrders for BinanceConnector {
    async fn place_orders_batch(
        &self,
        orders: Vec<OrderRequest>,
    ) -> ExchangeResult<Vec<OrderResult>> {
        if orders.is_empty() {
            return Ok(vec![]);
        }

        // Detect account type from first order — all orders in batch must be same type
        let account_type = orders[0].account_type;

        match account_type {
            AccountType::Spot | AccountType::Margin => {
                return Err(ExchangeError::NotImplemented(
                    "Batch orders not supported on Spot/Margin (Binance Futures only)".to_string()
                ));
            }
            _ => {}
        }

        if orders.len() > self.max_batch_place_size() {
            return Err(ExchangeError::InvalidRequest(
                format!("Batch size {} exceeds Binance limit of {}", orders.len(), self.max_batch_place_size())
            ));
        }

        // Build each order as a JSON object for the batchOrders array
        let batch_orders_json: Vec<serde_json::Value> = orders.iter().map(|req| {
            let mut obj = serde_json::Map::new();
            obj.insert("symbol".to_string(), json!(format_symbol(&req.symbol.base, &req.symbol.quote, account_type)));
            obj.insert("side".to_string(), json!(req.side.as_str()));

            let batch_sym_str = format_symbol(&req.symbol.base, &req.symbol.quote, account_type);
            match &req.order_type {
                OrderType::Market => {
                    obj.insert("type".to_string(), json!("MARKET"));
                    obj.insert("quantity".to_string(), json!(self.precision.qty(&batch_sym_str, req.quantity)));
                }
                OrderType::Limit { price } => {
                    obj.insert("type".to_string(), json!("LIMIT"));
                    obj.insert("quantity".to_string(), json!(self.precision.qty(&batch_sym_str, req.quantity)));
                    obj.insert("price".to_string(), json!(self.precision.price(&batch_sym_str, *price)));
                    obj.insert("timeInForce".to_string(), json!("GTC"));
                }
                _ => {
                    // For other types, encode as MARKET (best-effort fallback)
                    obj.insert("type".to_string(), json!("MARKET"));
                    obj.insert("quantity".to_string(), json!(self.precision.qty(&batch_sym_str, req.quantity)));
                }
            }

            if req.reduce_only {
                obj.insert("reduceOnly".to_string(), json!("true"));
            }
            if let Some(ref cid) = req.client_order_id {
                obj.insert("newClientOrderId".to_string(), json!(cid));
            }

            serde_json::Value::Object(obj)
        }).collect();

        let batch_json_str = serde_json::to_string(&batch_orders_json)
            .map_err(|e| ExchangeError::Parse(format!("Failed to serialize batch orders: {}", e)))?;

        let mut params = HashMap::new();
        params.insert("batchOrders".to_string(), batch_json_str);

        let response = self.post(BinanceEndpoint::FuturesBatchOrders, params, account_type).await?;
        BinanceParser::parse_batch_orders_response(&response)
    }

    async fn cancel_orders_batch(
        &self,
        order_ids: Vec<String>,
        symbol: Option<&str>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<OrderResult>> {
        match account_type {
            AccountType::Spot | AccountType::Margin => {
                return Err(ExchangeError::NotImplemented(
                    "Batch cancel not supported on Spot/Margin (Binance Futures only)".to_string()
                ));
            }
            _ => {}
        }

        let sym = symbol.ok_or_else(|| ExchangeError::InvalidRequest(
            "Symbol is required for batch cancel on Binance".to_string()
        ))?;

        // Futures batch cancel: DELETE /fapi/v1/batchOrders with orderIdList param
        let order_ids_json = serde_json::to_string(&order_ids)
            .map_err(|e| ExchangeError::Parse(format!("Failed to serialize order IDs: {}", e)))?;

        let mut params = HashMap::new();
        // Symbol for batch cancel needs to be formatted — we have it as a raw string
        params.insert("symbol".to_string(), sym.replace('/', "").to_uppercase());
        params.insert("orderIdList".to_string(), order_ids_json);

        let response = self.delete(BinanceEndpoint::FuturesBatchOrders, params, account_type).await?;
        BinanceParser::parse_batch_orders_response(&response)
    }

    fn max_batch_place_size(&self) -> usize {
        5 // Binance Futures limit
    }

    fn max_batch_cancel_size(&self) -> usize {
        10 // Binance Futures limit
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// BATCH AMEND
// ═══════════════════════════════════════════════════════════════════════════════

impl BinanceConnector {
    /// Batch amend multiple futures orders via `PATCH /fapi/v1/batchOrders`.
    ///
    /// Each entry in `amends` is a JSON object with required fields:
    /// `symbol`, `orderId` (or `origClientOrderId`), plus at least one of:
    /// `price`, `quantity`, `stopPrice`.
    ///
    /// Max 5 orders per batch (Binance Futures limit).
    ///
    /// Returns the raw JSON response from Binance.
    pub async fn batch_amend_orders(
        &self,
        amends: Vec<serde_json::Value>,
    ) -> ExchangeResult<Value> {
        if amends.is_empty() {
            return Ok(serde_json::Value::Array(vec![]));
        }
        if amends.len() > 5 {
            return Err(ExchangeError::InvalidRequest(
                format!("Batch amend size {} exceeds Binance Futures limit of 5", amends.len())
            ));
        }

        let batch_json_str = serde_json::to_string(&amends)
            .map_err(|e| ExchangeError::Parse(format!("Failed to serialize batch amend orders: {}", e)))?;

        let mut params = HashMap::new();
        params.insert("batchOrders".to_string(), batch_json_str);

        self.patch(BinanceEndpoint::FuturesBatchAmend, params, AccountType::FuturesCross).await
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// ACCOUNT TRANSFERS
// ═══════════════════════════════════════════════════════════════════════════════

/// Map our AccountType pair to Binance universal transfer type string.
///
/// Binance transfer types (SAPI v1): MAIN_UMFUTURE, UMFUTURE_MAIN, MAIN_MARGIN,
/// MARGIN_MAIN, UMFUTURE_MARGIN, MARGIN_UMFUTURE, etc.
fn map_transfer_type(from: AccountType, to: AccountType) -> ExchangeResult<&'static str> {
    match (from, to) {
        (AccountType::Spot, AccountType::FuturesCross) => Ok("MAIN_UMFUTURE"),
        (AccountType::FuturesCross, AccountType::Spot) => Ok("UMFUTURE_MAIN"),
        (AccountType::Spot, AccountType::FuturesIsolated) => Ok("MAIN_CMFUTURE"),
        (AccountType::FuturesIsolated, AccountType::Spot) => Ok("CMFUTURE_MAIN"),
        (AccountType::Spot, AccountType::Margin) => Ok("MAIN_MARGIN"),
        (AccountType::Margin, AccountType::Spot) => Ok("MARGIN_MAIN"),
        (AccountType::FuturesCross, AccountType::Margin) => Ok("UMFUTURE_MARGIN"),
        (AccountType::Margin, AccountType::FuturesCross) => Ok("MARGIN_UMFUTURE"),
        (AccountType::FuturesIsolated, AccountType::Margin) => Ok("CMFUTURE_MARGIN"),
        (AccountType::Margin, AccountType::FuturesIsolated) => Ok("MARGIN_CMFUTURE"),
        _ => Err(ExchangeError::InvalidRequest(format!(
            "Unsupported transfer direction: {:?} → {:?}",
            from, to
        ))),
    }
}

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl AccountTransfers for BinanceConnector {
    async fn transfer(&self, req: TransferRequest) -> ExchangeResult<TransferResponse> {
        let transfer_type = map_transfer_type(req.from_account, req.to_account)?;

        let mut params = HashMap::new();
        params.insert("type".to_string(), transfer_type.to_string());
        params.insert("asset".to_string(), req.asset.clone());
        params.insert("amount".to_string(), req.amount.to_string());

        let response = self.post(BinanceEndpoint::AssetTransfer, params, AccountType::Spot).await?;
        BinanceParser::parse_transfer_response(&response, &req.asset, req.amount)
    }

    async fn get_transfer_history(
        &self,
        filter: TransferHistoryFilter,
    ) -> ExchangeResult<Vec<TransferResponse>> {
        // Binance requires a `type` param for history; we default to MAIN_UMFUTURE
        // as the most common query. Callers who need a specific type should filter
        // the result or extend the filter type in the future.
        let mut params: HashMap<String, String> = HashMap::new();
        params.insert("type".to_string(), "MAIN_UMFUTURE".to_string());

        if let Some(start) = filter.start_time {
            params.insert("startTime".to_string(), start.to_string());
        }
        if let Some(end) = filter.end_time {
            params.insert("endTime".to_string(), end.to_string());
        }
        if let Some(limit) = filter.limit {
            params.insert("size".to_string(), limit.to_string());
        }

        let response = self.get(BinanceEndpoint::AssetTransferHistory, params, AccountType::Spot).await?;
        BinanceParser::parse_transfer_history(&response)
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// CUSTODIAL FUNDS
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl CustodialFunds for BinanceConnector {
    async fn get_deposit_address(
        &self,
        asset: &str,
        network: Option<&str>,
    ) -> ExchangeResult<DepositAddress> {
        let mut params: HashMap<String, String> = HashMap::new();
        params.insert("coin".to_string(), asset.to_uppercase());

        if let Some(net) = network {
            params.insert("network".to_string(), net.to_string());
        }

        let response = self.get(BinanceEndpoint::DepositAddress, params, AccountType::Spot).await?;
        BinanceParser::parse_deposit_address(&response)
    }

    async fn withdraw(&self, req: WithdrawRequest) -> ExchangeResult<WithdrawResponse> {
        let mut params: HashMap<String, String> = HashMap::new();
        params.insert("coin".to_string(), req.asset.to_uppercase());
        params.insert("address".to_string(), req.address.clone());
        params.insert("amount".to_string(), req.amount.to_string());

        if let Some(net) = &req.network {
            params.insert("network".to_string(), net.clone());
        }
        if let Some(tag) = &req.tag {
            params.insert("addressTag".to_string(), tag.clone());
        }

        let response = self.post(BinanceEndpoint::Withdraw, params, AccountType::Spot).await?;
        BinanceParser::parse_withdraw_response(&response)
    }

    async fn get_funds_history(
        &self,
        filter: FundsHistoryFilter,
    ) -> ExchangeResult<Vec<FundsRecord>> {
        match filter.record_type {
            FundsRecordType::Deposit => {
                let mut params: HashMap<String, String> = HashMap::new();
                if let Some(asset) = &filter.asset {
                    params.insert("coin".to_string(), asset.to_uppercase());
                }
                if let Some(start) = filter.start_time {
                    params.insert("startTime".to_string(), start.to_string());
                }
                if let Some(end) = filter.end_time {
                    params.insert("endTime".to_string(), end.to_string());
                }
                if let Some(limit) = filter.limit {
                    params.insert("limit".to_string(), limit.to_string());
                }
                let response = self.get(BinanceEndpoint::DepositHistory, params, AccountType::Spot).await?;
                BinanceParser::parse_deposit_history(&response)
            }
            FundsRecordType::Withdrawal => {
                let mut params: HashMap<String, String> = HashMap::new();
                if let Some(asset) = &filter.asset {
                    params.insert("coin".to_string(), asset.to_uppercase());
                }
                if let Some(start) = filter.start_time {
                    params.insert("startTime".to_string(), start.to_string());
                }
                if let Some(end) = filter.end_time {
                    params.insert("endTime".to_string(), end.to_string());
                }
                if let Some(limit) = filter.limit {
                    params.insert("limit".to_string(), limit.to_string());
                }
                let response = self.get(BinanceEndpoint::WithdrawHistory, params, AccountType::Spot).await?;
                BinanceParser::parse_withdrawal_history(&response)
            }
            FundsRecordType::Both => {
                // Binance has separate endpoints — fetch both and merge
                let deposit_filter = FundsHistoryFilter {
                    record_type: FundsRecordType::Deposit,
                    asset: filter.asset.clone(),
                    start_time: filter.start_time,
                    end_time: filter.end_time,
                    limit: filter.limit,
                };
                let withdrawal_filter = FundsHistoryFilter {
                    record_type: FundsRecordType::Withdrawal,
                    asset: filter.asset.clone(),
                    start_time: filter.start_time,
                    end_time: filter.end_time,
                    limit: filter.limit,
                };
                let mut deposits = self.get_funds_history(deposit_filter).await?;
                let withdrawals = self.get_funds_history(withdrawal_filter).await?;
                deposits.extend(withdrawals);
                Ok(deposits)
            }
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// SUB-ACCOUNTS
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl SubAccounts for BinanceConnector {
    async fn sub_account_operation(
        &self,
        op: SubAccountOperation,
    ) -> ExchangeResult<SubAccountResult> {
        match op {
            SubAccountOperation::Create { label } => {
                let mut params = HashMap::new();
                params.insert("subAccountString".to_string(), label);

                let response = self.post(BinanceEndpoint::SubAccountCreate, params, AccountType::Spot).await?;
                BinanceParser::parse_sub_account_create(&response)
            }

            SubAccountOperation::List => {
                let params = HashMap::new();
                let response = self.get(BinanceEndpoint::SubAccountList, params, AccountType::Spot).await?;
                BinanceParser::parse_sub_account_list(&response)
            }

            SubAccountOperation::Transfer { sub_account_id, asset, amount, to_sub } => {
                // universalTransfer: fromEmail/toEmail + fromAccountType/toAccountType
                // We treat sub_account_id as the sub-account email.
                // Master account email is not known here, so we use a placeholder approach:
                // if to_sub = true: master(SPOT) → sub(SPOT)
                // if to_sub = false: sub(SPOT) → master(SPOT)
                let mut params = HashMap::new();

                if to_sub {
                    // fromEmail = master (we pass empty, Binance treats missing as master)
                    params.insert("toEmail".to_string(), sub_account_id.clone());
                    params.insert("fromAccountType".to_string(), "SPOT".to_string());
                    params.insert("toAccountType".to_string(), "SPOT".to_string());
                } else {
                    params.insert("fromEmail".to_string(), sub_account_id.clone());
                    params.insert("fromAccountType".to_string(), "SPOT".to_string());
                    params.insert("toAccountType".to_string(), "SPOT".to_string());
                }

                params.insert("asset".to_string(), asset);
                params.insert("amount".to_string(), amount.to_string());

                let response = self.post(BinanceEndpoint::SubAccountTransfer, params, AccountType::Spot).await?;
                BinanceParser::parse_sub_account_transfer(&response)
            }

            SubAccountOperation::GetBalance { sub_account_id } => {
                let mut params = HashMap::new();
                params.insert("email".to_string(), sub_account_id);

                let response = self.get(BinanceEndpoint::SubAccountAssets, params, AccountType::Spot).await?;
                BinanceParser::parse_sub_account_assets(&response)
            }
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FUNDING HISTORY
// ═══════════════════════════════════════════════════════════════════════════════

/// Funding payment history via `GET /fapi/v1/income?incomeType=FUNDING_FEE`
///
/// Only available for futures account types. Spot returns `NotImplemented`.
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl FundingHistory for BinanceConnector {
    async fn get_funding_payments(
        &self,
        filter: FundingFilter,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<FundingPayment>> {
        match account_type {
            AccountType::FuturesCross | AccountType::FuturesIsolated => {}
            _ => {
                return Err(ExchangeError::NotImplemented(
                    "Binance funding payments are futures-only".to_string(),
                ))
            }
        }

        let mut params: HashMap<String, String> = HashMap::new();
        params.insert("incomeType".to_string(), "FUNDING_FEE".to_string());

        if let Some(symbol) = &filter.symbol {
            params.insert("symbol".to_string(), symbol.to_uppercase());
        }
        if let Some(start) = filter.start_time {
            params.insert("startTime".to_string(), start.to_string());
        }
        if let Some(end) = filter.end_time {
            params.insert("endTime".to_string(), end.to_string());
        }
        let limit = filter.limit.unwrap_or(500).min(1000);
        params.insert("limit".to_string(), limit.to_string());

        let response = self.get(BinanceEndpoint::FuturesIncomeHistory, params, account_type).await?;
        BinanceParser::parse_funding_payments(&response)
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// ACCOUNT LEDGER
// ═══════════════════════════════════════════════════════════════════════════════

/// Full account ledger via `GET /fapi/v1/income` (all income types).
///
/// Only available for futures account types.
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl AccountLedger for BinanceConnector {
    async fn get_ledger(
        &self,
        filter: LedgerFilter,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<LedgerEntry>> {
        match account_type {
            AccountType::FuturesCross | AccountType::FuturesIsolated => {}
            _ => {
                return Err(ExchangeError::NotImplemented(
                    "Binance income ledger is futures-only".to_string(),
                ))
            }
        }

        let mut params: HashMap<String, String> = HashMap::new();

        if let Some(start) = filter.start_time {
            params.insert("startTime".to_string(), start.to_string());
        }
        if let Some(end) = filter.end_time {
            params.insert("endTime".to_string(), end.to_string());
        }
        let limit = filter.limit.unwrap_or(500).min(1000);
        params.insert("limit".to_string(), limit.to_string());

        let response = self.get(BinanceEndpoint::FuturesIncomeHistory, params, account_type).await?;
        let mut entries = BinanceParser::parse_ledger(&response)?;

        // Apply client-side filters that Binance does not natively support
        if let Some(ref type_filter) = filter.entry_type {
            entries.retain(|e| &e.entry_type == type_filter);
        }
        if let Some(ref asset_filter) = filter.asset {
            let asset_upper = asset_filter.to_uppercase();
            entries.retain(|e| e.asset.to_uppercase() == asset_upper);
        }

        Ok(entries)
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// MarketDataPublic trait impl
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl MarketDataPublic for BinanceConnector {
    async fn get_recent_trades(
        &self,
        symbol: SymbolInput<'_>,
        limit: Option<u32>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<PublicTrade>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        let raw = self.get_recent_trades(&symbol, limit, account_type).await?;

        let arr = raw.as_array().ok_or_else(|| {
            ExchangeError::Parse("get_recent_trades: expected array".into())
        })?;

        let mut result = Vec::with_capacity(arr.len());
        for item in arr {
            let parse_f64 = |key: &str| -> f64 {
                item.get(key)
                    .and_then(|v| v.as_str().and_then(|s| s.parse().ok()).or_else(|| v.as_f64()))
                    .unwrap_or(0.0)
            };

            let is_buyer_maker = item.get("isBuyerMaker")
                .and_then(|v| v.as_bool())
                .unwrap_or(false);

            result.push(PublicTrade {
                id: item.get("id")
                    .and_then(|v| v.as_i64())
                    .map(|id| id.to_string())
                    .unwrap_or_default(),
                price: parse_f64("price"),
                quantity: parse_f64("qty"),
                side: if is_buyer_maker { TradeSide::Sell } else { TradeSide::Buy },
                timestamp: item.get("time").and_then(|v| v.as_i64()).unwrap_or(0),
                // Binance REST recent-trades fields
                quote_qty: item.get("quoteQty")
                    .and_then(|v| v.as_str().and_then(|s| s.parse().ok()).or_else(|| v.as_f64())),
                is_buyer_maker: Some(is_buyer_maker),
                // spot: isBestMatch present; futures: absent
                is_best_match: item.get("isBestMatch").and_then(|v| v.as_bool()),
                // futures: isRPITrade present; spot: absent
                is_rpi_trade: item.get("isRPITrade").and_then(|v| v.as_bool()),
                ..Default::default()
            });
        }
        Ok(result)
    }

    async fn get_agg_trades(
        &self,
        symbol: SymbolInput<'_>,
        limit: Option<u32>,
        from_id: Option<u64>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<AggTrade>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?.into_owned();
        let endpoint = match account_type {
            AccountType::Spot | AccountType::Margin => BinanceEndpoint::SpotAggTrades,
            _ => BinanceEndpoint::FuturesAggTrades,
        };
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol);
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(id) = from_id {
            params.insert("fromId".to_string(), id.to_string());
        }
        let raw = self.get(endpoint, params, account_type).await?;
        BinanceParser::parse_agg_trades(&raw)
    }

    async fn get_liquidation_history(
        &self,
        _symbol: Option<SymbolInput<'_>>,
        _start_time: Option<i64>,
        _end_time: Option<i64>,
        _limit: Option<u32>,
        _account_type: AccountType,
    ) -> ExchangeResult<Vec<Liquidation>> {
        // GET /fapi/v1/forceOrders has security type USER_DATA (requires API key + HMAC
        // signature). It returns the *authenticated user's own* forced-liquidation orders,
        // not market-wide public events. It must never appear in the unauthenticated market
        // matrix. For market-wide real-time liquidation feed use WS stream `@forceOrder`.
        // Ref: https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Users-Force-Orders
        Err(ExchangeError::WireAbsent(
            "Binance /fapi/v1/forceOrders is USER_DATA (signed, auth-required) — \
             returns own liquidation orders only, not market-wide events. \
             Use WS stream @forceOrder for public market liquidation feed. \
             Ref: https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Users-Force-Orders"
                .into(),
        ))
    }

    async fn get_open_interest_history(
        &self,
        symbol: SymbolInput<'_>,
        period: &str,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<OpenInterest>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        self.get_open_interest_history(&symbol, period, limit, start_time, end_time).await
    }

    async fn get_premium_index(
        &self,
        symbol: Option<SymbolInput<'_>>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<MarkPrice>> {
        let symbol = symbol.map(|s| s.resolve(ExchangeId::Binance, account_type)).transpose()?;
        let data = self.get_premium_index(symbol.as_deref()).await?;
        Ok(vec![MarkPrice {
            mark_price: data.mark_price,
            index_price: Some(data.index_price),
            funding_rate: Some(data.last_funding_rate),
            timestamp: data.timestamp,
            symbol: Some(data.symbol.clone()),
            estimated_settle_price: data.estimated_settle_price,
            interest_rate: Some(data.interest_rate),
            next_funding_time: Some(data.next_funding_time),
            ..Default::default()
        }])
    }

    async fn get_long_short_ratio_history(
        &self,
        symbol: SymbolInput<'_>,
        period: &str,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<crate::core::types::LongShortRatio>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        // Route to global (all-accounts) LSR, not top-20% traders.
        // get_top_long_short_account_ratio still exists as an inherent method for
        // callers that specifically want the top-trader subset.
        self.get_global_long_short_account_ratio(&symbol, period, limit, start_time, end_time).await
    }

    async fn get_funding_rate_history(
        &self,
        symbol: SymbolInput<'_>,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<crate::core::types::FundingRate>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        self.get_funding_rate_history(&symbol, start_time, end_time, limit).await
    }

    async fn get_mark_price_klines(
        &self,
        symbol: SymbolInput<'_>,
        interval: &str,
        limit: Option<u32>,
        account_type: AccountType,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<Kline>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        self.get_derived_klines(
            BinanceEndpoint::FuturesMarkPriceKlines, &symbol, interval, limit, end_time,
        ).await
    }

    async fn get_index_price_klines(
        &self,
        symbol: SymbolInput<'_>,
        interval: &str,
        limit: Option<u32>,
        account_type: AccountType,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<Kline>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        self.get_derived_klines(
            BinanceEndpoint::FuturesIndexPriceKlines, &symbol, interval, limit, end_time,
        ).await
    }

    async fn get_premium_index_klines(
        &self,
        symbol: SymbolInput<'_>,
        interval: &str,
        limit: Option<u32>,
        account_type: AccountType,
        end_time: Option<i64>,
    ) -> ExchangeResult<Vec<Kline>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        self.get_derived_klines(
            BinanceEndpoint::FuturesPremiumIndexKlines, &symbol, interval, limit, end_time,
        ).await
    }

    async fn get_basis_history(
        &self,
        symbol: SymbolInput<'_>,
        period: &str,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<crate::core::types::Basis>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        // /futures/data/basis keys the instrument as `pair` + a `contractType`
        // (PERPETUAL for the perpetual swap). 30-day rolling window, ≥5m.
        let v = self.get_basis_history(&symbol, "PERPETUAL", period, limit, start_time, end_time).await?;
        BinanceParser::parse_basis_history(&v)
    }

    async fn get_taker_volume_history(
        &self,
        symbol: SymbolInput<'_>,
        period: &str,
        start_time: Option<i64>,
        end_time: Option<i64>,
        limit: Option<u32>,
        account_type: AccountType,
    ) -> ExchangeResult<Vec<crate::core::types::TakerVolume>> {
        let symbol = symbol.resolve(ExchangeId::Binance, account_type)?;
        // /futures/data/takerlongshortRatio carries buyVol/sellVol (taker flow)
        // alongside the ratio. 30-day rolling window, ≥5m.
        let mut params = HashMap::new();
        params.insert("symbol".to_string(), symbol.to_string());
        params.insert("period".to_string(), period.to_string());
        if let Some(l) = limit {
            params.insert("limit".to_string(), l.to_string());
        }
        if let Some(s) = start_time {
            params.insert("startTime".to_string(), s.to_string());
        }
        if let Some(e) = end_time {
            params.insert("endTime".to_string(), e.to_string());
        }
        let v = self.get(BinanceEndpoint::FuturesTakerLongShortRatio, params, AccountType::FuturesCross).await?;
        BinanceParser::parse_taker_volume(&v)
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// HAS CAPABILITIES
// ═══════════════════════════════════════════════════════════════════════════════

impl crate::core::traits::HasCapabilities for BinanceConnector {
    fn capabilities(&self) -> crate::core::types::ConnectorCapabilities {
        crate::core::types::ConnectorCapabilities {
            // MarketData
            has_ticker: true,
            has_orderbook: true,
            has_klines: true,
            has_recent_trades: true,
            has_exchange_info: true,
            // MarketDataPublic (verified overrides: get_open_interest_history,
            //   get_premium_index, get_long_short_ratio_history, get_funding_rate_history)
            // get_liquidation_history: NOT supported — /fapi/v1/forceOrders is USER_DATA (signed).
            //   Use WS @forceOrder for market-wide public liquidation events.
            has_liquidation_history: false,
            has_open_interest_history: true,
            has_premium_index: true,
            has_long_short_ratio_history: true,
            has_funding_rate_history: true,
            has_basis_history: true,
            has_taker_volume_history: true,
            has_liquidation_bucket_history: false,
            has_insurance_fund: false,
            has_mark_price_klines: true,
            has_index_price_klines: true,
            has_premium_index_klines: true,
            has_agg_trades: true,
            has_market_order: true,
            has_limit_order: true,
            has_open_orders: true,
            has_order_history: true,
            has_user_trades: true,
            // Positions
            has_positions: true,
            has_mark_price: true,
            has_modify_position: true,
            has_closed_pnl: false,
            has_long_short_ratio: true,
            // Operations
            has_cancel_all: true,
            has_amend_order: true,
            has_batch_place: true,
            has_batch_cancel: true,
            max_batch_place_size: 5,
            max_batch_cancel_size: 10,
            // Account
            has_balance: true,
            has_account_info: true,
            has_fees: true,
            has_transfers: true,
            has_deposit_withdraw: true,
            has_sub_accounts: true,
            has_funding_payments: true,
            has_ledger: true,
            // WebSocket
            has_websocket: true,
            has_ws_klines: true,
            has_ws_trades: true,
            has_ws_orderbook: true,
            has_ws_ticker: true,
            has_ws_mark_price: true,
            has_ws_funding_rate: true,
            validation: self.validation_status(),
        }
    }

    fn validation_status(&self) -> Option<&'static crate::core::types::ValidationStamp> {
        crate::core::utils::validation_snapshot::validation_for(crate::core::types::ExchangeId::Binance)
    }

    fn trade_history_capabilities(&self) -> crate::core::types::TradeHistoryCapabilities {
        use crate::core::types::{HistoryCursor, TradeHistoryTier};
        crate::core::types::TradeHistoryCapabilities {
            // /api/v3/aggTrades fromId cursor — unbounded lookback.
            spot: TradeHistoryTier::RestDeep { cursor: HistoryCursor::FromId },
            // /fapi/v1/aggTrades: venue-side 24h cap (official docs).
            // historicalTrades reaches ~1 month but needs an API key —
            // out of scope for the public aggTrade pagination path.
            futures: TradeHistoryTier::RestWindow {
                cursor: HistoryCursor::FromId,
                max_back_ms: 24 * 60 * 60 * 1000,
            },
            kline_backpage: true,
        }
    }

    fn kline_interval_capabilities(&self) -> crate::core::types::KlineIntervalCapabilities {
        // Probe 2026-07-08: GET /api/v3/klines?symbol=BTCUSDT&interval=1s
        // returns real 1s bars (spot-only). GET /fapi/v1/klines?interval=1s
        // errors `{"code":-1120,"msg":"Invalid interval."}` — futures has
        // no seconds granularity. Connector's own `map_kline_interval`
        // (endpoints.rs) accepts 1m/3m/5m/15m/30m/1h/2h/4h/6h/8h/12h/1d/
        // 3d/1w/1M identically for both account classes (spot vs futures
        // REST paths both call the same mapper) — only the seconds tier
        // diverges.
        crate::core::types::KlineIntervalCapabilities {
            spot: &[
                "1s", "1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d",
                "3d", "1w", "1M",
            ],
            futures: &[
                "1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d",
                "1w", "1M",
            ],
        }
    }
}
