maxt 0.3.2

One Rust API for Upbit, Bithumb, Binance, and Hyperliquid market data, accounts, and orders.
Documentation
//! Exchange adapter implementations.
//!
//! Adapters support public data without credentials and expose
//! provider-specific methods through [`Client::adapter`](crate::Client::adapter).
//! See `docs/providers.md` for capabilities and credential types.

use crate::{
    CancelOrdersRequest, Error, OrderHistoryRequest, OrderLookupRequest, OrderStatus, Result,
    Timestamp,
};

mod binance;
mod bithumb;
mod hyperliquid;
mod order_rules;
mod upbit;

pub(crate) mod candles;
pub(crate) mod decimal;

/// Last whole millisecond strictly before an exclusive timestamp.
pub(crate) fn inclusive_millis_before(end: Timestamp) -> i64 {
    let nanos = end.as_nanos();
    nanos.div_euclid(1_000_000) - i64::from(nanos.rem_euclid(1_000_000) == 0)
}

/// Smallest millisecond satisfying `millis * 1_000_000 >= timestamp`.
pub(crate) fn inclusive_millis_at_or_after(start: Timestamp) -> i64 {
    let nanos = start.as_nanos();
    nanos.div_euclid(1_000_000) + i64::from(nanos.rem_euclid(1_000_000) != 0)
}

/// Provider state code for a final-order filter, or `None` for both states.
pub(crate) fn final_order_state(statuses: &[OrderStatus]) -> Result<Option<&'static str>> {
    let mut filled = false;
    let mut cancelled = false;

    for status in statuses {
        match status {
            OrderStatus::Filled => filled = true,
            OrderStatus::Cancelled => cancelled = true,
            other => {
                return Err(Error::invalid_request(
                    "statuses",
                    format!("order history accepts only Filled or Cancelled, not {other:?}"),
                ));
            }
        }
    }

    Ok(match (filled, cancelled) {
        (true, false) => Some("done"),
        (false, true) => Some("cancel"),
        _ => None,
    })
}

/// Validates the shared seven-day final-order history window.
pub(crate) fn order_history_window(
    request: &OrderHistoryRequest,
) -> Result<(u32, Option<i64>, Option<i64>)> {
    const DEFAULT_LIMIT: u32 = 100;
    const MAX_LIMIT: u32 = 1_000;
    const SEVEN_DAYS_NANOS: i64 = 7 * 24 * 60 * 60 * 1_000_000_000;

    let limit = request.limit.unwrap_or(DEFAULT_LIMIT);
    if !(1..=MAX_LIMIT).contains(&limit) {
        return Err(Error::invalid_request(
            "limit",
            format!("order history serves 1 to {MAX_LIMIT} entries per page, not {limit}"),
        ));
    }

    if let (Some(from), Some(to)) = (request.from, request.to) {
        let width = to
            .as_nanos()
            .checked_sub(from.as_nanos())
            .ok_or_else(|| Error::invalid_request("to", "must be later than `from`"))?;
        if width <= 0 {
            return Err(Error::invalid_request("to", "must be later than `from`"));
        }
        if width > SEVEN_DAYS_NANOS {
            return Err(Error::invalid_request(
                "to",
                "order history windows cannot exceed seven days",
            ));
        }
    }

    Ok((
        limit,
        request.from.map(inclusive_millis_at_or_after),
        request.to.map(inclusive_millis_before),
    ))
}

/// Validates the common multi-order lookup limits before a provider call.
pub(crate) fn validate_order_lookup(request: &OrderLookupRequest) -> Result<()> {
    if !(1..=100).contains(&request.ids.len()) {
        return Err(Error::invalid_request(
            "ids",
            format!(
                "an order lookup requires 1 to 100 identifiers, not {}",
                request.ids.len()
            ),
        ));
    }
    if request.ids.iter().any(|id| id.trim().is_empty()) {
        return Err(Error::invalid_request(
            "ids",
            "order identifiers must not be empty",
        ));
    }
    Ok(())
}

/// Validates the common batch-cancellation contract before a provider call.
pub(crate) fn validate_cancel_orders(request: &CancelOrdersRequest) -> Result<()> {
    if request.ids.is_empty() {
        return Err(Error::invalid_request(
            "ids",
            "a batch cancellation requires at least one identifier",
        ));
    }
    if request.ids.iter().any(|id| id.trim().is_empty()) {
        return Err(Error::invalid_request(
            "ids",
            "order identifiers must not be empty",
        ));
    }
    Ok(())
}

/// Applies a provider's documented batch-cancellation limit.
pub(crate) fn validate_cancel_order_limit(
    request: &CancelOrdersRequest,
    maximum: usize,
) -> Result<()> {
    validate_cancel_orders(request)?;
    if request.ids.len() > maximum {
        return Err(Error::invalid_request(
            "ids",
            format!(
                "this exchange cancels at most {maximum} orders per request, not {}",
                request.ids.len()
            ),
        ));
    }
    Ok(())
}

pub use binance::{
    BinanceAccountStream, BinanceAccountStreamEvent, BinanceAccountTrade, BinanceAdapter,
    BinanceAggregateTrade, BinanceAggregateTradesRequest, BinanceApiKeyPermissions,
    BinanceBalanceStreamEvent, BinanceC2cTrade, BinanceC2cTradeHistoryPage,
    BinanceC2cTradeHistoryRequest, BinanceC2cTradeType, BinanceCandleEvent, BinanceCoinInformation,
    BinanceCoinNetworkInformation, BinanceDepositHistory, BinanceDepositHistoryEntry,
    BinanceDepositHistoryRequest, BinanceExchangeInfo, BinanceExchangeSymbol, BinanceListenKey,
    BinanceMarkPrice, BinanceMarket, BinanceMarketEvent, BinanceMarketStream, BinanceOpenInterest,
    BinanceOrderBookEvent, BinanceOrderResponse, BinanceOrderStreamEvent,
    BinanceQuestionnaireRequirements, BinanceRawAccountEvent, BinanceSpotAccountBalance,
    BinanceSpotAccountInformation, BinanceSpotAveragePrice, BinanceSpotCancelAllOpenOrders,
    BinanceSpotCancelledOrder, BinanceSpotCommissionRates, BinanceSpotOrderDetail,
    BinanceSymbolFilters, BinanceTestOrder, BinanceTestOrderRequest, BinanceTickerEvent,
    BinanceTradeEvent, BinanceUsdMAccountAsset, BinanceUsdMAccountInformation,
    BinanceUsdMAccountPosition, BinanceUsdMPositionInformation, BinanceWithdrawHistory,
    BinanceWithdrawHistoryEntry, BinanceWithdrawHistoryRequest, BinanceWithdrawalAddress,
};
pub use bithumb::{
    BithumbAccountEvent, BithumbAccountStream, BithumbAdapter, BithumbAlertStep, BithumbApiKey,
    BithumbAssetEvent, BithumbAssetFee, BithumbBatchOrder, BithumbBatchOrderFailure,
    BithumbBatchOrderOutcome, BithumbBatchOrdersRequest, BithumbBatchOrdersResult,
    BithumbCancelOrderResponse, BithumbCancelOrdersResponse, BithumbCancelWithdrawalResponse,
    BithumbClosedOrder, BithumbClosedOrderState, BithumbClosedOrdersRequest,
    BithumbDepositResponse, BithumbKrwDeposit, BithumbKrwDepositsRequest,
    BithumbKrwTransferRequest, BithumbKrwWithdrawal, BithumbKrwWithdrawalsRequest,
    BithumbMarketAlert, BithumbMarketEvent, BithumbMarketStream, BithumbNetworkFee, BithumbNotice,
    BithumbOrderBookEvent, BithumbOrderBookSnapshot, BithumbOrderDetail, BithumbOrderDetailRequest,
    BithumbOrderDetailTrade, BithumbOrderDirection, BithumbOrderEvent, BithumbOrderListItem,
    BithumbOrderListRequest, BithumbOrderListState, BithumbOrderResponse, BithumbOrdersResponse,
    BithumbPendingOrderState, BithumbPendingOrdersRequest, BithumbTickerEvent, BithumbTradeEvent,
    BithumbTwapOrder, BithumbTwapOrderDirection, BithumbTwapOrderRequest, BithumbTwapOrdersRequest,
    BithumbTwapState, BithumbWithdrawalAddress, BithumbWithdrawalResponse,
};
pub use hyperliquid::{
    HyperliquidAccountEvent, HyperliquidAccountStream, HyperliquidAdapter, HyperliquidAllMids,
    HyperliquidAssetContext, HyperliquidAssetContextEvent, HyperliquidBookLevel,
    HyperliquidCandleEvent, HyperliquidCandleSnapshot, HyperliquidDailyVolume,
    HyperliquidEvmContract, HyperliquidFundingHistoryEntry, HyperliquidL2Book,
    HyperliquidLedgerEntry, HyperliquidLedgerKind, HyperliquidMarketEvent, HyperliquidMarketStream,
    HyperliquidMidPrice, HyperliquidOpenOrder, HyperliquidOrderActionResponse,
    HyperliquidOrderBookEvent, HyperliquidOrderDetail, HyperliquidOrderInfo,
    HyperliquidOrderReference, HyperliquidOrderStatusResponse, HyperliquidOrderUpdate,
    HyperliquidPortfolioPeriod, HyperliquidPortfolioPoint, HyperliquidProviderResponse,
    HyperliquidRecentTrade, HyperliquidReferral, HyperliquidReferrer, HyperliquidSpotAssetContext,
    HyperliquidSpotBalance, HyperliquidSpotClearinghouseState, HyperliquidSpotMeta,
    HyperliquidSpotMetaAndAssetContexts, HyperliquidSpotPair, HyperliquidSpotStateBalance,
    HyperliquidSpotStateEvent, HyperliquidSpotToken, HyperliquidSubAccount, HyperliquidTradeEvent,
    HyperliquidUserFees, HyperliquidUserFill, HyperliquidUserFunding, HyperliquidUserRateLimit,
    HyperliquidUserRole, HyperliquidVaultEquity,
};
pub use upbit::{
    UpbitAccountStream, UpbitAccountStreamEvent, UpbitAdapter, UpbitApiKey, UpbitAssetStreamEvent,
    UpbitBatchCancelRequest, UpbitBatchCancelScope, UpbitCancelAndNewOrder,
    UpbitCancelAndNewOrderDetailResult, UpbitCancelAndNewOrderRequest,
    UpbitCancelAndNewOrderResult, UpbitCancelOrdersResponse, UpbitCancelWithdrawalResponse,
    UpbitCandleStreamEvent, UpbitClosedOrder, UpbitClosedOrderState, UpbitClosedOrdersRequest,
    UpbitDepositInfo, UpbitDepositResponse, UpbitKrwDeposit, UpbitKrwTransferRequest,
    UpbitKrwTwoFactorType, UpbitKrwWithdrawal, UpbitListedSubscription, UpbitMarketEvent,
    UpbitMarketStream, UpbitMarketStreamEvent, UpbitOrderBookInstrument, UpbitOrderBookStreamEvent,
    UpbitOrderDetail, UpbitOrderDetailRequest, UpbitOrderDetailTrade, UpbitOrderDirection,
    UpbitOrderReference, UpbitOrderResponse, UpbitOrderStreamEvent, UpbitOrderVolume, UpbitPocket,
    UpbitPocketApiKey, UpbitPocketApiKeyGroup, UpbitPocketApiKeysRequest, UpbitPocketBalance,
    UpbitPocketTransfer, UpbitPocketTransferDirection, UpbitPocketTransferOrder,
    UpbitPocketTransferQuery, UpbitPocketTransferRequest, UpbitPocketTransferState,
    UpbitPocketUniversalTransferRequest, UpbitRegion, UpbitSmpType, UpbitSubscriptionList,
    UpbitTickerStreamEvent, UpbitTradeStreamEvent, UpbitTravelRuleVasp,
    UpbitTravelRuleVerification, UpbitWithdrawalAddress, UpbitWithdrawalResponse, UpbitYearCandle,
};