# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code from ccxt.base.exchange import Exchange from ccxt.abstract.htx import ImplicitAPI import hashlib from ccxt.base.types import Account, Any, Balances, BorrowInterest, Currencies, Currency, DepositAddress, Int, IsolatedBorrowRate, IsolatedBorrowRates, LedgerEntry, LeverageTier, LeverageTiers, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, TransferEntry from typing import List from ccxt.base.errors import ExchangeError from ccxt.base.errors import AuthenticationError from ccxt.base.errors import PermissionDenied from ccxt.base.errors import AccountNotEnabled from ccxt.base.errors import ArgumentsRequired from ccxt.base.errors import BadRequest from ccxt.base.errors import BadSymbol from ccxt.base.errors import InsufficientFunds from ccxt.base.errors import InvalidOrder from ccxt.base.errors import OrderNotFound from ccxt.base.errors import NotSupported from ccxt.base.errors import OperationFailed from ccxt.base.errors import RateLimitExceeded from ccxt.base.errors import ExchangeNotAvailable from ccxt.base.errors import OnMaintenance from ccxt.base.errors import RequestTimeout from ccxt.base.decimal_to_precision import TRUNCATE from ccxt.base.decimal_to_precision import TICK_SIZE from ccxt.base.precise import Precise class htx(Exchange, ImplicitAPI): def describe(self) -> Any: return self.deep_extend(super(htx, self).describe(), { 'id': 'htx', 'name': 'HTX', 'countries': ['CN'], 'rateLimit': 100, 'userAgent': self.userAgents['chrome100'], 'certified': True, 'version': 'v1', 'hostname': 'api.huobi.pro', # api.testnet.huobi.pro 'pro': True, 'has': { 'CORS': None, 'spot': True, 'margin': True, 'swap': True, 'future': True, 'option': None, 'addMargin': None, 'borrowCrossMargin': True, 'borrowIsolatedMargin': True, 'cancelAllOrders': True, 'cancelAllOrdersAfter': True, 'cancelOrder': True, 'cancelOrders': True, 'closeAllPositions': False, 'closePosition': True, 'createDepositAddress': None, 'createMarketBuyOrderWithCost': True, 'createMarketOrderWithCost': False, 'createMarketSellOrderWithCost': False, 'createOrder': True, 'createOrders': True, 'createReduceOnlyOrder': False, 'createStopLimitOrder': True, 'createStopLossOrder': True, 'createStopMarketOrder': True, 'createStopOrder': True, 'createTakeProfitOrder': True, 'createTrailingPercentOrder': True, 'createTriggerOrder': True, 'fetchAccounts': True, 'fetchBalance': True, 'fetchBidsAsks': None, 'fetchBorrowInterest': True, 'fetchBorrowRateHistories': None, 'fetchBorrowRateHistory': None, 'fetchCanceledOrders': None, 'fetchClosedOrder': None, 'fetchClosedOrders': True, 'fetchCrossBorrowRate': False, 'fetchCrossBorrowRates': False, 'fetchCurrencies': True, 'fetchDeposit': None, 'fetchDepositAddress': True, 'fetchDepositAddresses': None, 'fetchDepositAddressesByNetwork': True, 'fetchDeposits': True, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': True, 'fetchFundingHistory': True, 'fetchFundingRate': True, 'fetchFundingRateHistory': True, 'fetchFundingRates': True, 'fetchIndexOHLCV': True, 'fetchIsolatedBorrowRate': False, 'fetchIsolatedBorrowRates': True, 'fetchL3OrderBook': None, 'fetchLastPrices': True, 'fetchLedger': True, 'fetchLedgerEntry': None, 'fetchLeverage': False, 'fetchLeverageTiers': True, 'fetchLiquidations': True, 'fetchMarginAdjustmentHistory': False, 'fetchMarketLeverageTiers': 'emulated', 'fetchMarkets': True, 'fetchMarkOHLCV': True, 'fetchMyLiquidations': False, 'fetchMyTrades': True, 'fetchOHLCV': True, 'fetchOpenInterest': True, 'fetchOpenInterestHistory': True, 'fetchOpenInterests': True, 'fetchOpenOrder': None, 'fetchOpenOrders': True, 'fetchOrder': True, 'fetchOrderBook': True, 'fetchOrderBooks': None, 'fetchOrders': True, 'fetchOrderTrades': True, 'fetchPosition': True, 'fetchPositionHistory': 'emulated', 'fetchPositions': True, 'fetchPositionsHistory': False, 'fetchPositionsRisk': False, 'fetchPremiumIndexOHLCV': True, 'fetchSettlementHistory': True, 'fetchStatus': True, 'fetchTicker': True, 'fetchTickers': True, 'fetchTime': True, 'fetchTrades': True, 'fetchTradingFee': True, 'fetchTradingFees': False, 'fetchTradingLimits': True, 'fetchTransactionFee': None, 'fetchTransactionFees': None, 'fetchTransactions': None, 'fetchTransfers': None, 'fetchWithdrawAddresses': True, 'fetchWithdrawal': None, 'fetchWithdrawals': True, 'fetchWithdrawalWhitelist': None, 'reduceMargin': None, 'repayCrossMargin': True, 'repayIsolatedMargin': True, 'setLeverage': True, 'setMarginMode': False, 'setPositionMode': True, 'signIn': None, 'transfer': True, 'withdraw': True, }, 'timeframes': { '1m': '1min', '5m': '5min', '15m': '15min', '30m': '30min', '1h': '60min', '4h': '4hour', '1d': '1day', '1w': '1week', '1M': '1mon', '1y': '1year', }, 'urls': { # 'test': { # 'market': 'https://api.testnet.huobi.pro', # 'public': 'https://api.testnet.huobi.pro', # 'private': 'https://api.testnet.huobi.pro', # }, 'logo': 'https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg', 'hostnames': { 'contract': 'api.hbdm.com', 'spot': 'api.huobi.pro', 'status': { 'spot': 'status.huobigroup.com', 'future': { 'inverse': 'status-dm.huobigroup.com', 'linear': 'status-linear-swap.huobigroup.com', # USDT-Margined Contracts }, 'swap': { 'inverse': 'status-swap.huobigroup.com', 'linear': 'status-linear-swap.huobigroup.com', # USDT-Margined Contracts }, }, # recommended for AWS # 'contract': 'api.hbdm.vn', # 'spot': 'api-aws.huobi.pro', }, 'api': { 'status': 'https://{hostname}', 'contract': 'https://{hostname}', 'spot': 'https://{hostname}', 'public': 'https://{hostname}', 'private': 'https://{hostname}', 'v2Public': 'https://{hostname}', 'v2Private': 'https://{hostname}', }, 'www': 'https://www.huobi.com', 'referral': { 'url': 'https://www.htx.com.vc/invite/en-us/1h?invite_code=6rmm2223', 'discount': 0.15, }, 'doc': [ 'https://huobiapi.github.io/docs/spot/v1/en/', 'https://huobiapi.github.io/docs/dm/v1/en/', 'https://huobiapi.github.io/docs/coin_margined_swap/v1/en/', 'https://huobiapi.github.io/docs/usdt_swap/v1/en/', 'https://www.huobi.com/en-us/opend/newApiPages/', ], 'fees': 'https://www.huobi.com/about/fee/', }, 'api': { # ------------------------------------------------------------ # old api definitions 'v2Public': { 'get': { 'reference/currencies': 1, # 币链参考信息 'market-status': 1, # 获取当前市场状态 }, }, 'v2Private': { 'get': { 'account/ledger': 1, 'account/withdraw/quota': 1, 'account/withdraw/address': 1, # 提币地址查询(限母用户可用) 'account/deposit/address': 1, 'account/repayment': 5, # 还币交易记录查询 'reference/transact-fee-rate': 1, 'account/asset-valuation': 0.2, # 获取账户资产估值 'point/account': 5, # 点卡余额查询 'sub-user/user-list': 1, # 获取子用户列表 'sub-user/user-state': 1, # 获取特定子用户的用户状态 'sub-user/account-list': 1, # 获取特定子用户的账户列表 'sub-user/deposit-address': 1, # 子用户充币地址查询 'sub-user/query-deposit': 1, # 子用户充币记录查询 'user/api-key': 1, # 母子用户API key信息查询 'user/uid': 1, # 母子用户获取用户UID 'algo-orders/opening': 1, # 查询未触发OPEN策略委托 'algo-orders/history': 1, # 查询策略委托历史 'algo-orders/specific': 1, # 查询特定策略委托 'c2c/offers': 1, # 查询借入借出订单 'c2c/offer': 1, # 查询特定借入借出订单及其交易记录 'c2c/transactions': 1, # 查询借入借出交易记录 'c2c/repayment': 1, # 查询还币交易记录 'c2c/account': 1, # 查询账户余额 'etp/reference': 1, # 基础参考信息 'etp/transactions': 5, # 获取杠杆ETP申赎记录 'etp/transaction': 5, # 获取特定杠杆ETP申赎记录 'etp/rebalance': 1, # 获取杠杆ETP调仓记录 'etp/limit': 1, # 获取ETP持仓限额 }, 'post': { 'account/transfer': 1, 'account/repayment': 5, # 归还借币(全仓逐仓通用) 'point/transfer': 5, # 点卡划转 'sub-user/management': 1, # 冻结/解冻子用户 'sub-user/creation': 1, # 子用户创建 'sub-user/tradable-market': 1, # 设置子用户交易权限 'sub-user/transferability': 1, # 设置子用户资产转出权限 'sub-user/api-key-generation': 1, # 子用户API key创建 'sub-user/api-key-modification': 1, # 修改子用户API key 'sub-user/api-key-deletion': 1, # 删除子用户API key 'sub-user/deduct-mode': 1, # 设置子用户手续费抵扣模式 'algo-orders': 1, # 策略委托下单 'algo-orders/cancel-all-after': 1, # 自动撤销订单 'algo-orders/cancellation': 1, # 策略委托(触发前)撤单 'c2c/offer': 1, # 借入借出下单 'c2c/cancellation': 1, # 借入借出撤单 'c2c/cancel-all': 1, # 撤销所有借入借出订单 'c2c/repayment': 1, # 还币 'c2c/transfer': 1, # 资产划转 'etp/creation': 5, # 杠杆ETP换入 'etp/redemption': 5, # 杠杆ETP换出 'etp/{transactId}/cancel': 10, # 杠杆ETP单个撤单 'etp/batch-cancel': 50, # 杠杆ETP批量撤单 }, }, 'public': { 'get': { 'common/symbols': 1, # 查询系统支持的所有交易对 'common/currencys': 1, # 查询系统支持的所有币种 'common/timestamp': 1, # 查询系统当前时间 'common/exchange': 1, # order limits 'settings/currencys': 1, # ?language=en-US }, }, 'private': { 'get': { 'account/accounts': 0.2, # 查询当前用户的所有账户(即account-id) 'account/accounts/{id}/balance': 0.2, # 查询指定账户的余额 'account/accounts/{sub-uid}': 1, 'account/history': 4, 'cross-margin/loan-info': 1, 'margin/loan-info': 1, # 查询借币币息率及额度 'fee/fee-rate/get': 1, 'order/openOrders': 0.4, 'order/orders': 0.4, 'order/orders/{id}': 0.4, # 查询某个订单详情 'order/orders/{id}/matchresults': 0.4, # 查询某个订单的成交明细 'order/orders/getClientOrder': 0.4, 'order/history': 1, # 查询当前委托、历史委托 'order/matchresults': 1, # 查询当前成交、历史成交 # 'dw/withdraw-virtual/addresses', # 查询虚拟币提现地址(Deprecated) 'query/deposit-withdraw': 1, # 'margin/loan-info', # duplicate 'margin/loan-orders': 0.2, # 借贷订单 'margin/accounts/balance': 0.2, # 借贷账户详情 'cross-margin/loan-orders': 1, # 查询借币订单 'cross-margin/accounts/balance': 1, # 借币账户详情 'points/actions': 1, 'points/orders': 1, 'subuser/aggregate-balance': 10, 'stable-coin/exchange_rate': 1, 'stable-coin/quote': 1, }, 'post': { 'account/transfer': 1, # 资产划转(该节点为母用户和子用户进行资产划转的通用接口。) 'futures/transfer': 1, 'order/batch-orders': 0.4, 'order/orders/place': 0.2, # 创建并执行一个新订单(一步下单, 推荐使用) 'order/orders/submitCancelClientOrder': 0.2, 'order/orders/batchCancelOpenOrders': 0.4, # 'order/orders', # 创建一个新的订单请求 (仅创建订单,不执行下单) # 'order/orders/{id}/place', # 执行一个订单 (仅执行已创建的订单) 'order/orders/{id}/submitcancel': 0.2, # 申请撤销一个订单请求 'order/orders/batchcancel': 0.4, # 批量撤销订单 # 'dw/balance/transfer', # 资产划转 'dw/withdraw/api/create': 1, # 申请提现虚拟币 # 'dw/withdraw-virtual/create', # 申请提现虚拟币 # 'dw/withdraw-virtual/{id}/place', # 确认申请虚拟币提现(Deprecated) 'dw/withdraw-virtual/{id}/cancel': 1, # 申请取消提现虚拟币 'dw/transfer-in/margin': 10, # 现货账户划入至借贷账户 'dw/transfer-out/margin': 10, # 借贷账户划出至现货账户 'margin/orders': 10, # 申请借贷 'margin/orders/{id}/repay': 10, # 归还借贷 'cross-margin/transfer-in': 1, # 资产划转 'cross-margin/transfer-out': 1, # 资产划转 'cross-margin/orders': 1, # 申请借币 'cross-margin/orders/{id}/repay': 1, # 归还借币 'stable-coin/exchange': 1, 'subuser/transfer': 10, }, }, # ------------------------------------------------------------ # new api definitions # 'https://status.huobigroup.com/api/v2/summary.json': 1, # 'https://status-dm.huobigroup.com/api/v2/summary.json': 1, # 'https://status-swap.huobigroup.com/api/v2/summary.json': 1, # 'https://status-linear-swap.huobigroup.com/api/v2/summary.json': 1, 'status': { 'public': { 'spot': { 'get': { 'api/v2/summary.json': 1, }, }, 'future': { 'inverse': { 'get': { 'api/v2/summary.json': 1, }, }, 'linear': { 'get': { 'api/v2/summary.json': 1, }, }, }, 'swap': { 'inverse': { 'get': { 'api/v2/summary.json': 1, }, }, 'linear': { 'get': { 'api/v2/summary.json': 1, }, }, }, }, }, 'spot': { 'public': { 'get': { 'v2/market-status': 1, 'v1/common/symbols': 1, 'v1/common/currencys': 1, 'v2/settings/common/currencies': 1, 'v2/reference/currencies': 1, 'v1/common/timestamp': 1, 'v1/common/exchange': 1, # order limits 'v1/settings/common/chains': 1, 'v1/settings/common/currencys': 1, 'v1/settings/common/symbols': 1, 'v2/settings/common/symbols': 1, 'v1/settings/common/market-symbols': 1, # Market Data 'market/history/candles': 1, 'market/history/kline': 1, 'market/detail/merged': 1, 'market/tickers': 1, 'market/detail': 1, 'market/depth': 1, 'market/trade': 1, 'market/history/trade': 1, 'market/etp': 1, # Get real-time equity of leveraged ETP # ETP 'v2/etp/reference': 1, 'v2/etp/rebalance': 1, }, }, 'private': { 'get': { # Account 'v1/account/accounts': 0.2, 'v1/account/accounts/{account-id}/balance': 0.2, 'v2/account/valuation': 1, 'v2/account/asset-valuation': 0.2, 'v1/account/history': 4, 'v2/account/ledger': 1, 'v2/point/account': 5, # Wallet(Deposit and Withdraw) 'v2/account/deposit/address': 1, 'v2/account/withdraw/quota': 1, 'v2/account/withdraw/address': 1, 'v2/reference/currencies': 1, 'v1/query/deposit-withdraw': 1, 'v1/query/withdraw/client-order-id': 1, # Sub user management 'v2/user/api-key': 1, 'v2/user/uid': 1, 'v2/sub-user/user-list': 1, 'v2/sub-user/user-state': 1, 'v2/sub-user/account-list': 1, 'v2/sub-user/deposit-address': 1, 'v2/sub-user/query-deposit': 1, 'v1/subuser/aggregate-balance': 10, 'v1/account/accounts/{sub-uid}': 1, # Trading 'v1/order/openOrders': 0.4, 'v1/order/orders/{order-id}': 0.4, 'v1/order/orders/getClientOrder': 0.4, 'v1/order/orders/{order-id}/matchresult': 0.4, 'v1/order/orders/{order-id}/matchresults': 0.4, 'v1/order/orders': 0.4, 'v1/order/history': 1, 'v1/order/matchresults': 1, 'v2/reference/transact-fee-rate': 1, # Conditional Order 'v2/algo-orders/opening': 1, 'v2/algo-orders/history': 1, 'v2/algo-orders/specific': 1, # Margin Loan(Cross/Isolated) 'v1/margin/loan-info': 1, 'v1/margin/loan-orders': 0.2, 'v1/margin/accounts/balance': 0.2, 'v1/cross-margin/loan-info': 1, 'v1/cross-margin/loan-orders': 1, 'v1/cross-margin/accounts/balance': 1, 'v2/account/repayment': 5, # Stable Coin Exchange 'v1/stable-coin/quote': 1, 'v1/stable_coin/exchange_rate': 1, # ETP 'v2/etp/transactions': 5, 'v2/etp/transaction': 5, 'v2/etp/limit': 1, }, 'post': { # Account 'v1/account/transfer': 1, 'v1/futures/transfer': 1, # future transfers 'v2/point/transfer': 5, 'v2/account/transfer': 1, # swap transfers # Wallet(Deposit and Withdraw) 'v1/dw/withdraw/api/create': 1, 'v1/dw/withdraw-virtual/{withdraw-id}/cancel': 1, # Sub user management 'v2/sub-user/deduct-mode': 1, 'v2/sub-user/creation': 1, 'v2/sub-user/management': 1, 'v2/sub-user/tradable-market': 1, 'v2/sub-user/transferability': 1, 'v2/sub-user/api-key-generation': 1, 'v2/sub-user/api-key-modification': 1, 'v2/sub-user/api-key-deletion': 1, 'v1/subuser/transfer': 10, 'v1/trust/user/active/credit': 10, # Trading 'v1/order/orders/place': 0.2, 'v1/order/batch-orders': 0.4, 'v1/order/auto/place': 0.2, 'v1/order/orders/{order-id}/submitcancel': 0.2, 'v1/order/orders/submitCancelClientOrder': 0.2, 'v1/order/orders/batchCancelOpenOrders': 0.4, 'v1/order/orders/batchcancel': 0.4, 'v2/algo-orders/cancel-all-after': 1, # Conditional Order 'v2/algo-orders': 1, 'v2/algo-orders/cancellation': 1, # Margin Loan(Cross/Isolated) 'v2/account/repayment': 5, 'v1/dw/transfer-in/margin': 10, 'v1/dw/transfer-out/margin': 10, 'v1/margin/orders': 10, 'v1/margin/orders/{order-id}/repay': 10, 'v1/cross-margin/transfer-in': 1, 'v1/cross-margin/transfer-out': 1, 'v1/cross-margin/orders': 1, 'v1/cross-margin/orders/{order-id}/repay': 1, # Stable Coin Exchange 'v1/stable-coin/exchange': 1, # ETP 'v2/etp/creation': 5, 'v2/etp/redemption': 5, 'v2/etp/{transactId}/cancel': 10, 'v2/etp/batch-cancel': 50, }, }, }, 'contract': { 'public': { 'get': { 'api/v1/timestamp': 1, 'heartbeat/': 1, # backslash is not a typo # Future Market Data interface 'api/v1/contract_contract_info': 1, 'api/v1/contract_index': 1, 'api/v1/contract_query_elements': 1, 'api/v1/contract_price_limit': 1, 'api/v1/contract_open_interest': 1, 'api/v1/contract_delivery_price': 1, 'market/depth': 1, 'market/bbo': 1, 'market/history/kline': 1, 'index/market/history/mark_price_kline': 1, 'market/detail/merged': 1, 'market/detail/batch_merged': 1, 'v2/market/detail/batch_merged': 1, 'market/trade': 1, 'market/history/trade': 1, 'api/v1/contract_risk_info': 1, 'api/v1/contract_insurance_fund': 1, 'api/v1/contract_adjustfactor': 1, 'api/v1/contract_his_open_interest': 1, 'api/v1/contract_ladder_margin': 1, 'api/v1/contract_api_state': 1, 'api/v1/contract_elite_account_ratio': 1, 'api/v1/contract_elite_position_ratio': 1, 'api/v1/contract_liquidation_orders': 1, 'api/v1/contract_settlement_records': 1, 'index/market/history/index': 1, 'index/market/history/basis': 1, 'api/v1/contract_estimated_settlement_price': 1, 'api/v3/contract_liquidation_orders': 1, # Swap Market Data interface 'swap-api/v1/swap_contract_info': 1, 'swap-api/v1/swap_index': 1, 'swap-api/v1/swap_query_elements': 1, 'swap-api/v1/swap_price_limit': 1, 'swap-api/v1/swap_open_interest': 1, 'swap-ex/market/depth': 1, 'swap-ex/market/bbo': 1, 'swap-ex/market/history/kline': 1, 'index/market/history/swap_mark_price_kline': 1, 'swap-ex/market/detail/merged': 1, 'v2/swap-ex/market/detail/batch_merged': 1, 'index/market/history/swap_premium_index_kline': 1, 'swap-ex/market/detail/batch_merged': 1, 'swap-ex/market/trade': 1, 'swap-ex/market/history/trade': 1, 'swap-api/v1/swap_risk_info': 1, 'swap-api/v1/swap_insurance_fund': 1, 'swap-api/v1/swap_adjustfactor': 1, 'swap-api/v1/swap_his_open_interest': 1, 'swap-api/v1/swap_ladder_margin': 1, 'swap-api/v1/swap_api_state': 1, 'swap-api/v1/swap_elite_account_ratio': 1, 'swap-api/v1/swap_elite_position_ratio': 1, 'swap-api/v1/swap_estimated_settlement_price': 1, 'swap-api/v1/swap_liquidation_orders': 1, 'swap-api/v1/swap_settlement_records': 1, 'swap-api/v1/swap_funding_rate': 1, 'swap-api/v1/swap_batch_funding_rate': 1, 'swap-api/v1/swap_historical_funding_rate': 1, 'swap-api/v3/swap_liquidation_orders': 1, 'index/market/history/swap_estimated_rate_kline': 1, 'index/market/history/swap_basis': 1, # Swap Market Data interface 'linear-swap-api/v1/swap_contract_info': 1, 'linear-swap-api/v1/swap_index': 1, 'linear-swap-api/v1/swap_query_elements': 1, 'linear-swap-api/v1/swap_price_limit': 1, 'linear-swap-api/v1/swap_open_interest': 1, 'linear-swap-ex/market/depth': 1, 'linear-swap-ex/market/bbo': 1, 'linear-swap-ex/market/history/kline': 1, 'index/market/history/linear_swap_mark_price_kline': 1, 'linear-swap-ex/market/detail/merged': 1, 'linear-swap-ex/market/detail/batch_merged': 1, 'v2/linear-swap-ex/market/detail/batch_merged': 1, 'linear-swap-ex/market/trade': 1, 'linear-swap-ex/market/history/trade': 1, 'linear-swap-api/v1/swap_risk_info': 1, 'swap-api/v1/linear-swap-api/v1/swap_insurance_fund': 1, 'linear-swap-api/v1/swap_adjustfactor': 1, 'linear-swap-api/v1/swap_cross_adjustfactor': 1, 'linear-swap-api/v1/swap_his_open_interest': 1, 'linear-swap-api/v1/swap_ladder_margin': 1, 'linear-swap-api/v1/swap_cross_ladder_margin': 1, 'linear-swap-api/v1/swap_api_state': 1, 'linear-swap-api/v1/swap_cross_transfer_state': 1, 'linear-swap-api/v1/swap_cross_trade_state': 1, 'linear-swap-api/v1/swap_elite_account_ratio': 1, 'linear-swap-api/v1/swap_elite_position_ratio': 1, 'linear-swap-api/v1/swap_liquidation_orders': 1, 'linear-swap-api/v1/swap_settlement_records': 1, 'linear-swap-api/v1/swap_funding_rate': 1, 'linear-swap-api/v1/swap_batch_funding_rate': 1, 'linear-swap-api/v1/swap_historical_funding_rate': 1, 'linear-swap-api/v3/swap_liquidation_orders': 1, 'index/market/history/linear_swap_premium_index_kline': 1, 'index/market/history/linear_swap_estimated_rate_kline': 1, 'index/market/history/linear_swap_basis': 1, 'linear-swap-api/v1/swap_estimated_settlement_price': 1, }, }, 'private': { 'get': { # Future Account Interface 'api/v1/contract_sub_auth_list': 1, 'api/v1/contract_api_trading_status': 1, # Swap Account Interface 'swap-api/v1/swap_sub_auth_list': 1, 'swap-api/v1/swap_api_trading_status': 1, # Swap Account Interface 'linear-swap-api/v1/swap_sub_auth_list': 1, 'linear-swap-api/v1/swap_api_trading_status': 1, 'linear-swap-api/v1/swap_cross_position_side': 1, 'linear-swap-api/v1/swap_position_side': 1, 'linear-swap-api/v3/unified_account_info': 1, 'linear-swap-api/v3/fix_position_margin_change_record': 1, 'linear-swap-api/v3/swap_unified_account_type': 1, 'linear-swap-api/v3/linear_swap_overview_account_info': 1, }, 'post': { # Future Account Interface 'api/v1/contract_balance_valuation': 1, 'api/v1/contract_account_info': 1, 'api/v1/contract_position_info': 1, 'api/v1/contract_sub_auth': 1, 'api/v1/contract_sub_account_list': 1, 'api/v1/contract_sub_account_info_list': 1, 'api/v1/contract_sub_account_info': 1, 'api/v1/contract_sub_position_info': 1, 'api/v1/contract_financial_record': 1, 'api/v1/contract_financial_record_exact': 1, 'api/v1/contract_user_settlement_records': 1, 'api/v1/contract_order_limit': 1, 'api/v1/contract_fee': 1, 'api/v1/contract_transfer_limit': 1, 'api/v1/contract_position_limit': 1, 'api/v1/contract_account_position_info': 1, 'api/v1/contract_master_sub_transfer': 1, 'api/v1/contract_master_sub_transfer_record': 1, 'api/v1/contract_available_level_rate': 1, 'api/v3/contract_financial_record': 1, 'api/v3/contract_financial_record_exact': 1, # Future Trade Interface 'api/v1/contract-cancel-after': 1, 'api/v1/contract_order': 1, 'api/v1/contract_batchorder': 1, 'api/v1/contract_cancel': 1, 'api/v1/contract_cancelall': 1, 'api/v1/contract_switch_lever_rate': 30, 'api/v1/lightning_close_position': 1, 'api/v1/contract_order_info': 1, 'api/v1/contract_order_detail': 1, 'api/v1/contract_openorders': 1, 'api/v1/contract_hisorders': 1, 'api/v1/contract_hisorders_exact': 1, 'api/v1/contract_matchresults': 1, 'api/v1/contract_matchresults_exact': 1, 'api/v3/contract_hisorders': 1, 'api/v3/contract_hisorders_exact': 1, 'api/v3/contract_matchresults': 1, 'api/v3/contract_matchresults_exact': 1, # Contract Strategy Order Interface 'api/v1/contract_trigger_order': 1, 'api/v1/contract_trigger_cancel': 1, 'api/v1/contract_trigger_cancelall': 1, 'api/v1/contract_trigger_openorders': 1, 'api/v1/contract_trigger_hisorders': 1, 'api/v1/contract_tpsl_order': 1, 'api/v1/contract_tpsl_cancel': 1, 'api/v1/contract_tpsl_cancelall': 1, 'api/v1/contract_tpsl_openorders': 1, 'api/v1/contract_tpsl_hisorders': 1, 'api/v1/contract_relation_tpsl_order': 1, 'api/v1/contract_track_order': 1, 'api/v1/contract_track_cancel': 1, 'api/v1/contract_track_cancelall': 1, 'api/v1/contract_track_openorders': 1, 'api/v1/contract_track_hisorders': 1, # Swap Account Interface 'swap-api/v1/swap_balance_valuation': 1, 'swap-api/v1/swap_account_info': 1, 'swap-api/v1/swap_position_info': 1, 'swap-api/v1/swap_account_position_info': 1, 'swap-api/v1/swap_sub_auth': 1, 'swap-api/v1/swap_sub_account_list': 1, 'swap-api/v1/swap_sub_account_info_list': 1, 'swap-api/v1/swap_sub_account_info': 1, 'swap-api/v1/swap_sub_position_info': 1, 'swap-api/v1/swap_financial_record': 1, 'swap-api/v1/swap_financial_record_exact': 1, 'swap-api/v1/swap_user_settlement_records': 1, 'swap-api/v1/swap_available_level_rate': 1, 'swap-api/v1/swap_order_limit': 1, 'swap-api/v1/swap_fee': 1, 'swap-api/v1/swap_transfer_limit': 1, 'swap-api/v1/swap_position_limit': 1, 'swap-api/v1/swap_master_sub_transfer': 1, 'swap-api/v1/swap_master_sub_transfer_record': 1, 'swap-api/v3/swap_financial_record': 1, 'swap-api/v3/swap_financial_record_exact': 1, # Swap Trade Interface 'swap-api/v1/swap-cancel-after': 1, 'swap-api/v1/swap_order': 1, 'swap-api/v1/swap_batchorder': 1, 'swap-api/v1/swap_cancel': 1, 'swap-api/v1/swap_cancelall': 1, 'swap-api/v1/swap_lightning_close_position': 1, 'swap-api/v1/swap_switch_lever_rate': 30, 'swap-api/v1/swap_order_info': 1, 'swap-api/v1/swap_order_detail': 1, 'swap-api/v1/swap_openorders': 1, 'swap-api/v1/swap_hisorders': 1, 'swap-api/v1/swap_hisorders_exact': 1, 'swap-api/v1/swap_matchresults': 1, 'swap-api/v1/swap_matchresults_exact': 1, 'swap-api/v3/swap_matchresults': 1, 'swap-api/v3/swap_matchresults_exact': 1, 'swap-api/v3/swap_hisorders': 1, 'swap-api/v3/swap_hisorders_exact': 1, # Swap Strategy Order Interface 'swap-api/v1/swap_trigger_order': 1, 'swap-api/v1/swap_trigger_cancel': 1, 'swap-api/v1/swap_trigger_cancelall': 1, 'swap-api/v1/swap_trigger_openorders': 1, 'swap-api/v1/swap_trigger_hisorders': 1, 'swap-api/v1/swap_tpsl_order': 1, 'swap-api/v1/swap_tpsl_cancel': 1, 'swap-api/v1/swap_tpsl_cancelall': 1, 'swap-api/v1/swap_tpsl_openorders': 1, 'swap-api/v1/swap_tpsl_hisorders': 1, 'swap-api/v1/swap_relation_tpsl_order': 1, 'swap-api/v1/swap_track_order': 1, 'swap-api/v1/swap_track_cancel': 1, 'swap-api/v1/swap_track_cancelall': 1, 'swap-api/v1/swap_track_openorders': 1, 'swap-api/v1/swap_track_hisorders': 1, # Swap Account Interface 'linear-swap-api/v1/swap_lever_position_limit': 1, 'linear-swap-api/v1/swap_cross_lever_position_limit': 1, 'linear-swap-api/v1/swap_balance_valuation': 1, 'linear-swap-api/v1/swap_account_info': 1, 'linear-swap-api/v1/swap_cross_account_info': 1, 'linear-swap-api/v1/swap_position_info': 1, 'linear-swap-api/v1/swap_cross_position_info': 1, 'linear-swap-api/v1/swap_account_position_info': 1, 'linear-swap-api/v1/swap_cross_account_position_info': 1, 'linear-swap-api/v1/swap_sub_auth': 1, 'linear-swap-api/v1/swap_sub_account_list': 1, 'linear-swap-api/v1/swap_cross_sub_account_list': 1, 'linear-swap-api/v1/swap_sub_account_info_list': 1, 'linear-swap-api/v1/swap_cross_sub_account_info_list': 1, 'linear-swap-api/v1/swap_sub_account_info': 1, 'linear-swap-api/v1/swap_cross_sub_account_info': 1, 'linear-swap-api/v1/swap_sub_position_info': 1, 'linear-swap-api/v1/swap_cross_sub_position_info': 1, 'linear-swap-api/v1/swap_financial_record': 1, 'linear-swap-api/v1/swap_financial_record_exact': 1, 'linear-swap-api/v1/swap_user_settlement_records': 1, 'linear-swap-api/v1/swap_cross_user_settlement_records': 1, 'linear-swap-api/v1/swap_available_level_rate': 1, 'linear-swap-api/v1/swap_cross_available_level_rate': 1, 'linear-swap-api/v1/swap_order_limit': 1, 'linear-swap-api/v1/swap_fee': 1, 'linear-swap-api/v1/swap_transfer_limit': 1, 'linear-swap-api/v1/swap_cross_transfer_limit': 1, 'linear-swap-api/v1/swap_position_limit': 1, 'linear-swap-api/v1/swap_cross_position_limit': 1, 'linear-swap-api/v1/swap_master_sub_transfer': 1, 'linear-swap-api/v1/swap_master_sub_transfer_record': 1, 'linear-swap-api/v1/swap_transfer_inner': 1, 'linear-swap-api/v3/swap_financial_record': 1, 'linear-swap-api/v3/swap_financial_record_exact': 1, # Swap Trade Interface 'linear-swap-api/v1/swap_order': 1, 'linear-swap-api/v1/swap_cross_order': 1, 'linear-swap-api/v1/swap_batchorder': 1, 'linear-swap-api/v1/swap_cross_batchorder': 1, 'linear-swap-api/v1/swap_cancel': 1, 'linear-swap-api/v1/swap_cross_cancel': 1, 'linear-swap-api/v1/swap_cancelall': 1, 'linear-swap-api/v1/swap_cross_cancelall': 1, 'linear-swap-api/v1/swap_switch_lever_rate': 30, 'linear-swap-api/v1/swap_cross_switch_lever_rate': 30, 'linear-swap-api/v1/swap_lightning_close_position': 1, 'linear-swap-api/v1/swap_cross_lightning_close_position': 1, 'linear-swap-api/v1/swap_order_info': 1, 'linear-swap-api/v1/swap_cross_order_info': 1, 'linear-swap-api/v1/swap_order_detail': 1, 'linear-swap-api/v1/swap_cross_order_detail': 1, 'linear-swap-api/v1/swap_openorders': 1, 'linear-swap-api/v1/swap_cross_openorders': 1, 'linear-swap-api/v1/swap_hisorders': 1, 'linear-swap-api/v1/swap_cross_hisorders': 1, 'linear-swap-api/v1/swap_hisorders_exact': 1, 'linear-swap-api/v1/swap_cross_hisorders_exact': 1, 'linear-swap-api/v1/swap_matchresults': 1, 'linear-swap-api/v1/swap_cross_matchresults': 1, 'linear-swap-api/v1/swap_matchresults_exact': 1, 'linear-swap-api/v1/swap_cross_matchresults_exact': 1, 'linear-swap-api/v1/linear-cancel-after': 1, 'linear-swap-api/v1/swap_switch_position_mode': 1, 'linear-swap-api/v1/swap_cross_switch_position_mode': 1, 'linear-swap-api/v3/swap_matchresults': 1, 'linear-swap-api/v3/swap_cross_matchresults': 1, 'linear-swap-api/v3/swap_matchresults_exact': 1, 'linear-swap-api/v3/swap_cross_matchresults_exact': 1, 'linear-swap-api/v3/swap_hisorders': 1, 'linear-swap-api/v3/swap_cross_hisorders': 1, 'linear-swap-api/v3/swap_hisorders_exact': 1, 'linear-swap-api/v3/swap_cross_hisorders_exact': 1, 'linear-swap-api/v3/fix_position_margin_change': 1, 'linear-swap-api/v3/swap_switch_account_type': 1, 'linear-swap-api/v3/linear_swap_fee_switch': 1, # Swap Strategy Order Interface 'linear-swap-api/v1/swap_trigger_order': 1, 'linear-swap-api/v1/swap_cross_trigger_order': 1, 'linear-swap-api/v1/swap_trigger_cancel': 1, 'linear-swap-api/v1/swap_cross_trigger_cancel': 1, 'linear-swap-api/v1/swap_trigger_cancelall': 1, 'linear-swap-api/v1/swap_cross_trigger_cancelall': 1, 'linear-swap-api/v1/swap_trigger_openorders': 1, 'linear-swap-api/v1/swap_cross_trigger_openorders': 1, 'linear-swap-api/v1/swap_trigger_hisorders': 1, 'linear-swap-api/v1/swap_cross_trigger_hisorders': 1, 'linear-swap-api/v1/swap_tpsl_order': 1, 'linear-swap-api/v1/swap_cross_tpsl_order': 1, 'linear-swap-api/v1/swap_tpsl_cancel': 1, 'linear-swap-api/v1/swap_cross_tpsl_cancel': 1, 'linear-swap-api/v1/swap_tpsl_cancelall': 1, 'linear-swap-api/v1/swap_cross_tpsl_cancelall': 1, 'linear-swap-api/v1/swap_tpsl_openorders': 1, 'linear-swap-api/v1/swap_cross_tpsl_openorders': 1, 'linear-swap-api/v1/swap_tpsl_hisorders': 1, 'linear-swap-api/v1/swap_cross_tpsl_hisorders': 1, 'linear-swap-api/v1/swap_relation_tpsl_order': 1, 'linear-swap-api/v1/swap_cross_relation_tpsl_order': 1, 'linear-swap-api/v1/swap_track_order': 1, 'linear-swap-api/v1/swap_cross_track_order': 1, 'linear-swap-api/v1/swap_track_cancel': 1, 'linear-swap-api/v1/swap_cross_track_cancel': 1, 'linear-swap-api/v1/swap_track_cancelall': 1, 'linear-swap-api/v1/swap_cross_track_cancelall': 1, 'linear-swap-api/v1/swap_track_openorders': 1, 'linear-swap-api/v1/swap_cross_track_openorders': 1, 'linear-swap-api/v1/swap_track_hisorders': 1, 'linear-swap-api/v1/swap_cross_track_hisorders': 1, }, }, }, }, 'fees': { 'trading': { 'feeSide': 'get', 'tierBased': False, 'percentage': True, 'maker': self.parse_number('0.002'), 'taker': self.parse_number('0.002'), }, }, 'exceptions': { 'broad': { 'contract is restricted of closing positions on API. Please contact customer service': OnMaintenance, 'maintain': OnMaintenance, 'API key has no permission': PermissionDenied, # {"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: API key has no permission [API Key没有权限]","data":null} }, 'exact': { # err-code '403': AuthenticationError, # {"status":"error","err_code":403,"err_msg":"Incorrect Access key [Access key错误]","ts":1652774224344} '1010': AccountNotEnabled, # {"status":"error","err_code":1010,"err_msg":"Account doesnt exist.","ts":1648137970490} '1003': AuthenticationError, # {code: '1003', message: 'invalid signature'} '1013': BadSymbol, # {"status":"error","err_code":1013,"err_msg":"This contract symbol doesnt exist.","ts":1640550459583} '1017': OrderNotFound, # {"status":"error","err_code":1017,"err_msg":"Order doesnt exist.","ts":1640550859242} '1034': InvalidOrder, # {"status":"error","err_code":1034,"err_msg":"Incorrect field of order price type.","ts":1643802870182} '1036': InvalidOrder, # {"status":"error","err_code":1036,"err_msg":"Incorrect field of open long form.","ts":1643802518986} '1039': InvalidOrder, # {"status":"error","err_code":1039,"err_msg":"Buy price must be lower than 39270.9USDT. Sell price must exceed 37731USDT.","ts":1643802374403} '1041': InvalidOrder, # {"status":"error","err_code":1041,"err_msg":"The order amount exceeds the limit(170000Cont), please modify and order again.","ts":1643802784940} '1047': InsufficientFunds, # {"status":"error","err_code":1047,"err_msg":"Insufficient margin available.","ts":1643802672652} '1048': InsufficientFunds, # {"status":"error","err_code":1048,"err_msg":"Insufficient close amount available.","ts":1652772408864} '1061': OrderNotFound, # {"status":"ok","data":{"errors":[{"order_id":"1349442392365359104","err_code":1061,"err_msg":"The order does not exist."}],"successes":""},"ts":1741773744526} '1051': InvalidOrder, # {"status":"error","err_code":1051,"err_msg":"No orders to cancel.","ts":1652552125876} '1066': BadSymbol, # {"status":"error","err_code":1066,"err_msg":"The symbol field cannot be empty. Please re-enter.","ts":1640550819147} '1067': InvalidOrder, # {"status":"error","err_code":1067,"err_msg":"The client_order_id field is invalid. Please re-enter.","ts":1643802119413} '1094': InvalidOrder, # {"status":"error","err_code":1094,"err_msg":"The leverage cannot be empty, please switch the leverage or contact customer service","ts":1640496946243} '1220': AccountNotEnabled, # {"status":"error","err_code":1220,"err_msg":"You don’t have access permission have not opened contracts trading.","ts":1645096660718} '1303': BadRequest, # {"code":1303,"data":null,"message":"Each transfer-out cannot be less than 5USDT.","success":false,"print-log":true} '1461': InvalidOrder, # {"status":"error","err_code":1461,"err_msg":"Current positions have triggered position limits(5000USDT). Please modify.","ts":1652554651234} '4007': BadRequest, # {"code":"4007","msg":"Unified account special interface, non - one account is not available","data":null,"ts":"1698413427651"}' 'bad-request': BadRequest, 'validation-format-error': BadRequest, # {"status":"error","err-code":"validation-format-error","err-msg":"Format Error: order-id.","data":null} 'validation-constraints-required': BadRequest, # {"status":"error","err-code":"validation-constraints-required","err-msg":"Field is missing: client-order-id.","data":null} 'base-date-limit-error': BadRequest, # {"status":"error","err-code":"base-date-limit-error","err-msg":"date less than system limit","data":null} 'api-not-support-temp-addr': PermissionDenied, # {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null} 'timeout': RequestTimeout, # {"ts":1571653730865,"status":"error","err-code":"timeout","err-msg":"Request Timeout"} 'gateway-internal-error': ExchangeNotAvailable, # {"status":"error","err-code":"gateway-internal-error","err-msg":"Failed to load data. Try again later.","data":null} 'account-frozen-balance-insufficient-error': InsufficientFunds, # {"status":"error","err-code":"account-frozen-balance-insufficient-error","err-msg":"trade account balance is not enough, left: `0.0027`","data":null} 'invalid-amount': InvalidOrder, # eg "Paramemter `amount` is invalid." 'order-limitorder-amount-min-error': InvalidOrder, # limit order amount error, min: `0.001` 'order-limitorder-amount-max-error': InvalidOrder, # market order amount error, max: `1000000` 'order-marketorder-amount-min-error': InvalidOrder, # market order amount error, min: `0.01` 'order-limitorder-price-min-error': InvalidOrder, # limit order price error 'order-limitorder-price-max-error': InvalidOrder, # limit order price error 'order-stop-order-hit-trigger': InvalidOrder, # {"status":"error","err-code":"order-stop-order-hit-trigger","err-msg":"Orders that are triggered immediately are not supported.","data":null} 'order-value-min-error': InvalidOrder, # {"status":"error","err-code":"order-value-min-error","err-msg":"Order total cannot be lower than: 1 USDT","data":null} 'order-invalid-price': InvalidOrder, # {"status":"error","err-code":"order-invalid-price","err-msg":"invalid price","data":null} 'order-holding-limit-failed': InvalidOrder, # {"status":"error","err-code":"order-holding-limit-failed","err-msg":"Order failed, exceeded the holding limit of self currency","data":null} 'order-orderprice-precision-error': InvalidOrder, # {"status":"error","err-code":"order-orderprice-precision-error","err-msg":"order price precision error, scale: `4`","data":null} 'order-etp-nav-price-max-error': InvalidOrder, # {"status":"error","err-code":"order-etp-nav-price-max-error","err-msg":"Order price cannot be higher than 5% of NAV","data":null} 'order-orderstate-error': OrderNotFound, # canceling an already canceled order 'order-queryorder-invalid': OrderNotFound, # querying a non-existent order 'order-update-error': ExchangeNotAvailable, # undocumented error 'api-signature-check-failed': AuthenticationError, 'api-signature-not-valid': AuthenticationError, # {"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: Incorrect Access key [Access key错误]","data":null} 'base-record-invalid': OrderNotFound, # https://github.com/ccxt/ccxt/issues/5750 'base-symbol-trade-disabled': BadSymbol, # {"status":"error","err-code":"base-symbol-trade-disabled","err-msg":"Trading is disabled for self symbol","data":null} 'base-symbol-error': BadSymbol, # {"status":"error","err-code":"base-symbol-error","err-msg":"The symbol is invalid","data":null} 'system-maintenance': OnMaintenance, # {"status": "error", "err-code": "system-maintenance", "err-msg": "System is in maintenance!", "data": null} 'base-request-exceed-frequency-limit': RateLimitExceeded, # {"status":"error","err-code":"base-request-exceed-frequency-limit","err-msg":"Frequency of requests has exceeded the limit, please try again later","data":null} # err-msg 'invalid symbol': BadSymbol, # {"ts":1568813334794,"status":"error","err-code":"invalid-parameter","err-msg":"invalid symbol"} 'symbol trade not open now': BadSymbol, # {"ts":1576210479343,"status":"error","err-code":"invalid-parameter","err-msg":"symbol trade not open now"} 'require-symbol': BadSymbol, # {"status":"error","err-code":"require-symbol","err-msg":"Parameter `symbol` is required.","data":null}, 'invalid-address': BadRequest, # {"status":"error","err-code":"invalid-address","err-msg":"Invalid address.","data":null}, 'base-currency-chain-error': BadRequest, # {"status":"error","err-code":"base-currency-chain-error","err-msg":"The current currency chain does not exist","data":null}, 'dw-insufficient-balance': InsufficientFunds, # {"status":"error","err-code":"dw-insufficient-balance","err-msg":"Insufficient balance. You can only transfer `12.3456` at most.","data":null} 'base-withdraw-fee-error': BadRequest, # {"status":"error","err-code":"base-withdraw-fee-error","err-msg":"withdrawal fee is not within limits","data":null} 'dw-withdraw-min-limit': BadRequest, # {"status":"error","err-code":"dw-withdraw-min-limit","err-msg":"The withdrawal amount is less than the minimum limit.","data":null} 'request limit': RateLimitExceeded, # {"ts":1687004814731,"status":"error","err-code":"invalid-parameter","err-msg":"request limit"} }, }, 'precisionMode': TICK_SIZE, 'options': { 'include_OS_certificates': False, # temporarily leave self, remove in future 'fetchMarkets': { 'types': { 'spot': True, 'linear': True, 'inverse': True, }, }, 'timeDifference': 0, # the difference between system clock and exchange clock 'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation 'fetchOHLCV': { 'useHistoricalEndpointForSpot': True, }, 'withdraw': { 'includeFee': False, }, 'defaultType': 'spot', # spot, future, swap 'defaultSubType': 'linear', # inverse, linear 'defaultNetwork': 'ERC20', 'defaultNetworks': { 'ETH': 'ERC20', 'BTC': 'BTC', 'USDT': 'TRC20', }, 'networks': { # by displaynames 'TRC20': 'TRX', # TRON for mainnet 'BTC': 'BTC', 'ERC20': 'ETH', # ETH for mainnet 'SOL': 'SOLANA', 'HRC20': 'HECO', 'BEP20': 'BSC', 'XMR': 'XMR', 'LTC': 'LTC', 'XRP': 'XRP', 'XLM': 'XLM', 'CRONOS': 'CRO', 'CRO': 'CRO', 'GLMR': 'GLMR', 'POLYGON': 'MATIC', 'MATIC': 'MATIC', 'BTT': 'BTT', 'CUBE': 'CUBE', 'IOST': 'IOST', 'NEO': 'NEO', 'KLAY': 'KLAY', 'EOS': 'EOS', 'THETA': 'THETA', 'NAS': 'NAS', 'NULS': 'NULS', 'QTUM': 'QTUM', 'FTM': 'FTM', 'CELO': 'CELO', 'DOGE': 'DOGE', 'DOGECHAIN': 'DOGECHAIN', 'NEAR': 'NEAR', 'STEP': 'STEP', 'BITCI': 'BITCI', 'CARDANO': 'ADA', 'ADA': 'ADA', 'ETC': 'ETC', 'LUK': 'LUK', 'MINEPLEX': 'MINEPLEX', 'DASH': 'DASH', 'ZEC': 'ZEC', 'IOTA': 'IOTA', 'NEON3': 'NEON3', 'XEM': 'XEM', 'HC': 'HC', 'LSK': 'LSK', 'DCR': 'DCR', 'BTG': 'BTG', 'STEEM': 'STEEM', 'BTS': 'BTS', 'ICX': 'ICX', 'WAVES': 'WAVES', 'CMT': 'CMT', 'BTM': 'BTM', 'VET': 'VET', 'XZC': 'XZC', 'ACT': 'ACT', 'SMT': 'SMT', 'BCD': 'BCD', 'WAX': 'WAX1', 'WICC': 'WICC', 'ELF': 'ELF', 'ZIL': 'ZIL', 'ELA': 'ELA', 'BCX': 'BCX', 'SBTC': 'SBTC', 'BIFI': 'BIFI', 'CTXC': 'CTXC', 'WAN': 'WAN', 'POLYX': 'POLYX', 'PAI': 'PAI', 'WTC': 'WTC', 'DGB': 'DGB', 'XVG': 'XVG', 'AAC': 'AAC', 'AE': 'AE', 'SEELE': 'SEELE', 'BCV': 'BCV', 'GRS': 'GRS', 'ARDR': 'ARDR', 'NANO': 'NANO', 'ZEN': 'ZEN', 'RBTC': 'RBTC', 'BSV': 'BSV', 'GAS': 'GAS', 'XTZ': 'XTZ', 'LAMB': 'LAMB', 'CVNT1': 'CVNT1', 'DOCK': 'DOCK', 'SC': 'SC', 'KMD': 'KMD', 'ETN': 'ETN', 'TOP': 'TOP', 'IRIS': 'IRIS', 'UGAS': 'UGAS', 'TT': 'TT', 'NEWTON': 'NEWTON', 'VSYS': 'VSYS', 'FSN': 'FSN', 'BHD': 'BHD', 'ONE': 'ONE', 'EM': 'EM', 'CKB': 'CKB', 'EOSS': 'EOSS', 'HIVE': 'HIVE', 'RVN': 'RVN', 'DOT': 'DOT', 'KSM': 'KSM', 'BAND': 'BAND', 'OEP4': 'OEP4', 'NBS': 'NBS', 'FIS': 'FIS', 'AR': 'AR', 'HBAR': 'HBAR', 'FIL': 'FIL', 'MASS': 'MASS', 'KAVA': 'KAVA', 'XYM': 'XYM', 'ENJ': 'ENJ', 'CRUST': 'CRUST', 'ICP': 'ICP', 'CSPR': 'CSPR', 'FLOW': 'FLOW', 'IOTX': 'IOTX', 'LAT': 'LAT', 'APT': 'APT', 'XCH': 'XCH', 'MINA': 'MINA', 'XEC': 'ECASH', 'XPRT': 'XPRT', 'CCA': 'ACA', 'AOTI': 'COTI', 'AKT': 'AKT', 'ARS': 'ARS', 'ASTR': 'ASTR', 'AZERO': 'AZERO', 'BLD': 'BLD', 'BRISE': 'BRISE', 'CORE': 'CORE', 'DESO': 'DESO', 'DFI': 'DFI', 'EGLD': 'EGLD', 'ERG': 'ERG', 'ETHF': 'ETHFAIR', 'ETHW': 'ETHW', 'EVMOS': 'EVMOS', 'FIO': 'FIO', 'FLR': 'FLR', 'FINSCHIA': 'FINSCHIA', 'KMA': 'KMA', 'KYVE': 'KYVE', 'MEV': 'MEV', 'MOVR': 'MOVR', 'NODL': 'NODL', 'OAS': 'OAS', 'OSMO': 'OSMO', 'PAYCOIN': 'PAYCOIN', 'POKT': 'POKT', 'PYG': 'PYG', 'REI': 'REI', 'SCRT': 'SCRT', 'SDN': 'SDN', 'SEI': 'SEI', 'SGB': 'SGB', 'SUI': 'SUI', 'SXP': 'SOLAR', 'SYS': 'SYS', 'TENET': 'TENET', 'TON': 'TON', 'UNQ': 'UNQ', 'UYU': 'UYU', 'WEMIX': 'WEMIX', 'XDC': 'XDC', 'XPLA': 'XPLA', # todo: below # 'LUNC': 'LUNC', # 'TERRA': 'TERRA', # tbd # 'LUNA': 'LUNA', tbd # 'FCT2': 'FCT2', # FIL-0X ? # 'COSMOS': 'ATOM1', # 'ATOM': 'ATOM1', # 'CRO': 'CRO', # 'OP': ['OPTIMISM', 'OPTIMISMETH'] # 'ARB': ['ARB', 'ARBITRUMETH'] # 'CHZ': ['CHZ', 'CZH'], # todo: AVAXCCHAIN CCHAIN AVAX # 'ALGO': ['ALGO', 'ALGOUSDT'] # 'ONT': ['ONT', 'ONTOLOGY'], # 'BCC': 'BCC', BCH's somewhat chain # 'DBC1': 'DBC1', }, # https://github.com/ccxt/ccxt/issues/5376 'fetchOrdersByStatesMethod': 'spot_private_get_v1_order_orders', # 'spot_private_get_v1_order_history' # https://github.com/ccxt/ccxt/pull/5392 'createMarketBuyOrderRequiresPrice': True, 'language': 'en-US', 'broker': { 'id': 'AA03022abc', }, 'accountsByType': { 'spot': 'pro', 'funding': 'pro', 'future': 'futures', }, 'accountsById': { 'spot': 'spot', 'margin': 'margin', 'otc': 'otc', 'point': 'point', 'super-margin': 'super-margin', 'investment': 'investment', 'borrow': 'borrow', 'grid-trading': 'grid-trading', 'deposit-earning': 'deposit-earning', 'otc-options': 'otc-options', }, 'typesByAccount': { 'pro': 'spot', 'futures': 'future', }, 'spot': { 'stopOrderTypes': { 'stop-limit': True, 'buy-stop-limit': True, 'sell-stop-limit': True, 'stop-limit-fok': True, 'buy-stop-limit-fok': True, 'sell-stop-limit-fok': True, }, 'limitOrderTypes': { 'limit': True, 'buy-limit': True, 'sell-limit': True, 'ioc': True, 'buy-ioc': True, 'sell-ioc': True, 'limit-maker': True, 'buy-limit-maker': True, 'sell-limit-maker': True, 'stop-limit': True, 'buy-stop-limit': True, 'sell-stop-limit': True, 'limit-fok': True, 'buy-limit-fok': True, 'sell-limit-fok': True, 'stop-limit-fok': True, 'buy-stop-limit-fok': True, 'sell-stop-limit-fok': True, }, }, }, 'commonCurrencies': { # https://github.com/ccxt/ccxt/issues/6081 # https://github.com/ccxt/ccxt/issues/3365 # https://github.com/ccxt/ccxt/issues/2873 'NGL': 'GFNGL', 'GET': 'THEMIS', # conflict with GET(Guaranteed Entrance Token, GET Protocol) 'GTC': 'GAMECOM', # conflict with Gitcoin and Gastrocoin 'HIT': 'HITCHAIN', # https://github.com/ccxt/ccxt/issues/7399 # https://coinmarketcap.com/currencies/pnetwork/ # https://coinmarketcap.com/currencies/penta/markets/ # https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/ 'PNT': 'PENTA', 'SBTC': 'SUPERBITCOIN', 'SOUL': 'SOULSAVER', 'BIFI': 'BITCOINFILE', # conflict with Beefy.Finance https://github.com/ccxt/ccxt/issues/8706 'FUD': 'FTX Users Debt', }, 'features': { 'spot': { 'sandbox': True, 'createOrder': { 'marginMode': True, 'triggerPrice': True, 'triggerDirection': True, 'triggerPriceType': None, 'stopLossPrice': False, # todo: add support by triggerprice 'takeProfitPrice': False, 'attachedStopLossTakeProfit': None, 'timeInForce': { 'IOC': True, 'FOK': True, 'PO': True, 'GTD': False, }, 'hedged': False, 'trailing': False, 'iceberg': False, 'selfTradePrevention': True, # todo implement 'leverage': True, # todo implement 'marketBuyByCost': True, 'marketBuyRequiresPrice': True, }, 'createOrders': { 'max': 10, }, 'fetchMyTrades': { 'marginMode': False, 'limit': 500, 'daysBack': 120, 'untilDays': 2, 'symbolRequired': False, }, 'fetchOrder': { 'marginMode': False, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOpenOrders': { 'marginMode': False, 'trigger': True, 'trailing': False, 'limit': 500, 'symbolRequired': False, }, 'fetchOrders': { 'marginMode': False, 'trigger': True, 'trailing': False, 'limit': 500, 'untilDays': 2, 'daysBack': 180, 'symbolRequired': False, }, 'fetchClosedOrders': { 'marginMode': False, 'trigger': True, 'trailing': False, 'untilDays': 2, 'limit': 500, 'daysBack': 180, 'daysBackCanceled': 1 / 12, 'symbolRequired': False, }, 'fetchOHLCV': { 'limit': 1000, # 2000 for non-historical }, }, 'forDerivatives': { 'extends': 'spot', 'createOrder': { 'stopLossPrice': True, 'takeProfitPrice': True, 'trailing': True, 'hedged': True, # 'leverage': True, # todo }, 'createOrders': { 'max': 25, }, 'fetchOrder': { 'marginMode': True, }, 'fetchOpenOrders': { 'marginMode': True, 'trigger': False, 'trailing': False, 'limit': 50, }, 'fetchOrders': { 'marginMode': True, 'trigger': False, 'trailing': False, 'limit': 50, 'daysBack': 90, }, 'fetchClosedOrders': { 'marginMode': True, 'trigger': False, 'trailing': False, 'untilDays': 2, 'limit': 50, 'daysBack': 90, 'daysBackCanceled': 1 / 12, }, 'fetchOHLCV': { 'limit': 2000, }, }, 'swap': { 'linear': { 'extends': 'forDerivatives', }, 'inverse': { 'extends': 'forDerivatives', }, }, 'future': { 'linear': { 'extends': 'forDerivatives', }, 'inverse': { 'extends': 'forDerivatives', }, }, }, }) def fetch_status(self, params={}): """ the latest known information on the availability of the exchange API https://huobiapi.github.io/docs/spot/v1/en/#get-system-status https://huobiapi.github.io/docs/dm/v1/en/#get-system-status https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-system-status https://huobiapi.github.io/docs/usdt_swap/v1/en/#get-system-status https://huobiapi.github.io/docs/usdt_swap/v1/en/#query-whether-the-system-is-available # contractPublicGetHeartbeat :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `status structure ` """ self.load_markets() marketType = None marketType, params = self.handle_market_type_and_params('fetchStatus', None, params) enabledForContracts = self.handle_option('fetchStatus', 'enableForContracts', False) # temp fix for: https://status-linear-swap.huobigroup.com/api/v2/summary.json response = None if marketType != 'spot' and enabledForContracts: subType = self.safe_string(params, 'subType', self.options['defaultSubType']) if marketType == 'swap': if subType == 'linear': response = self.statusPublicSwapLinearGetApiV2SummaryJson() elif subType == 'inverse': response = self.statusPublicSwapInverseGetApiV2SummaryJson() elif marketType == 'future': if subType == 'linear': response = self.statusPublicFutureLinearGetApiV2SummaryJson() elif subType == 'inverse': response = self.statusPublicFutureInverseGetApiV2SummaryJson() elif marketType == 'contract': response = self.contractPublicGetHeartbeat() elif marketType == 'spot': response = self.statusPublicSpotGetApiV2SummaryJson() # # statusPublicSpotGetApiV2SummaryJson, statusPublicSwapInverseGetApiV2SummaryJson, statusPublicFutureLinearGetApiV2SummaryJson, statusPublicFutureInverseGetApiV2SummaryJson # # { # "page": { # "id":"mn7l2lw8pz4p", # "name":"Huobi Futures-USDT-margined Swaps", # "url":"https://status-linear-swap.huobigroup.com", # "time_zone":"Asia/Singapore", # "updated_at":"2022-04-29T12:47:21.319+08:00"}, # "components": [ # { # "id":"lrv093qk3yp5", # "name":"market data", # "status":"operational", # "created_at":"2020-10-29T14:08:59.427+08:00", # "updated_at":"2020-10-29T14:08:59.427+08:00", # "position":1,"description":null, # "showcase":false, # "start_date":null, # "group_id":null, # "page_id":"mn7l2lw8pz4p", # "group":true, # "only_show_if_degraded":false, # "components": [ # "82k5jxg7ltxd" # list of related components # ] # }, # ], # "incidents": [ # empty array if there are no issues # { # "id": "rclfxz2g21ly", # incident id # "name": "Market data is delayed", # incident name # "status": "investigating", # incident status # "created_at": "2020-02-11T03:15:01.913Z", # incident create time # "updated_at": "2020-02-11T03:15:02.003Z", # incident update time # "monitoring_at": null, # "resolved_at": null, # "impact": "minor", # incident impact # "shortlink": "http://stspg.io/pkvbwp8jppf9", # "started_at": "2020-02-11T03:15:01.906Z", # "page_id": "p0qjfl24znv5", # "incident_updates": [ # { # "id": "dwfsk5ttyvtb", # "status": "investigating", # "body": "Market data is delayed", # "incident_id": "rclfxz2g21ly", # "created_at": "2020-02-11T03:15:02.000Z", # "updated_at": "2020-02-11T03:15:02.000Z", # "display_at": "2020-02-11T03:15:02.000Z", # "affected_components": [ # { # "code": "nctwm9tghxh6", # "name": "Market data", # "old_status": "operational", # "new_status": "degraded_performance" # } # ], # "deliver_notifications": True, # "custom_tweet": null, # "tweet_id": null # } # ], # "components": [ # { # "id": "nctwm9tghxh6", # "name": "Market data", # "status": "degraded_performance", # "created_at": "2020-01-13T09:34:48.284Z", # "updated_at": "2020-02-11T03:15:01.951Z", # "position": 8, # "description": null, # "showcase": False, # "group_id": null, # "page_id": "p0qjfl24znv5", # "group": False, # "only_show_if_degraded": False # } # ] # }, ... # ], # "scheduled_maintenances":[ # empty array if there are no scheduled maintenances # { # "id": "k7g299zl765l", # incident id # "name": "Schedule maintenance", # incident name # "status": "scheduled", # incident status # "created_at": "2020-02-11T03:16:31.481Z", # incident create time # "updated_at": "2020-02-11T03:16:31.530Z", # incident update time # "monitoring_at": null, # "resolved_at": null, # "impact": "maintenance", # incident impact # "shortlink": "http://stspg.io/md4t4ym7nytd", # "started_at": "2020-02-11T03:16:31.474Z", # "page_id": "p0qjfl24znv5", # "incident_updates": [ # { # "id": "8whgr3rlbld8", # "status": "scheduled", # "body": "We will be undergoing scheduled maintenance during self time.", # "incident_id": "k7g299zl765l", # "created_at": "2020-02-11T03:16:31.527Z", # "updated_at": "2020-02-11T03:16:31.527Z", # "display_at": "2020-02-11T03:16:31.527Z", # "affected_components": [ # { # "code": "h028tnzw1n5l", # "name": "Deposit And Withdraw - Deposit", # "old_status": "operational", # "new_status": "operational" # } # ], # "deliver_notifications": True, # "custom_tweet": null, # "tweet_id": null # } # ], # "components": [ # { # "id": "h028tnzw1n5l", # "name": "Deposit", # "status": "operational", # "created_at": "2019-12-05T02:07:12.372Z", # "updated_at": "2020-02-10T12:34:52.970Z", # "position": 1, # "description": null, # "showcase": False, # "group_id": "gtd0nyr3pf0k", # "page_id": "p0qjfl24znv5", # "group": False, # "only_show_if_degraded": False # } # ], # "scheduled_for": "2020-02-15T00:00:00.000Z", # scheduled maintenance start time # "scheduled_until": "2020-02-15T01:00:00.000Z" # scheduled maintenance end time # } # ], # "status": { # "indicator":"none", # none, minor, major, critical, maintenance # "description":"all systems operational" # All Systems Operational, Minor Service Outage, Partial System Outage, Partially Degraded Service, Service Under Maintenance # } # } # # # contractPublicGetHeartbeat # # { # "status": "ok", # 'ok', 'error' # "data": { # "heartbeat": 1, # future 1: available, 0: maintenance with service suspended # "estimated_recovery_time": null, # estimated recovery time in milliseconds # "swap_heartbeat": 1, # "swap_estimated_recovery_time": null, # "option_heartbeat": 1, # "option_estimated_recovery_time": null, # "linear_swap_heartbeat": 1, # "linear_swap_estimated_recovery_time": null # }, # "ts": 1557714418033 # } # status = None updated = None url = None if marketType == 'contract': statusRaw = self.safe_string(response, 'status') if statusRaw is None: status = None else: status = 'ok' if (statusRaw == 'ok') else 'maintenance' # 'ok', 'error' updated = self.safe_string(response, 'ts') else: statusData = self.safe_value(response, 'status', {}) statusRaw = self.safe_string(statusData, 'indicator') status = 'ok' if (statusRaw == 'none') else 'maintenance' # none, minor, major, critical, maintenance pageData = self.safe_value(response, 'page', {}) datetime = self.safe_string(pageData, 'updated_at') updated = self.parse8601(datetime) url = self.safe_string(pageData, 'url') return { 'status': status, 'updated': updated, 'eta': None, 'url': url, 'info': response, } def fetch_time(self, params={}) -> Int: """ fetches the current integer timestamp in milliseconds from the exchange server https://huobiapi.github.io/docs/spot/v1/en/#get-current-timestamp https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-current-system-timestamp :param dict [params]: extra parameters specific to the exchange API endpoint :returns int: the current integer timestamp in milliseconds from the exchange server """ options = self.safe_value(self.options, 'fetchTime', {}) defaultType = self.safe_string(self.options, 'defaultType', 'spot') type = self.safe_string(options, 'type', defaultType) type = self.safe_string(params, 'type', type) response = None if (type == 'future') or (type == 'swap'): response = self.contractPublicGetApiV1Timestamp(params) else: response = self.spotPublicGetV1CommonTimestamp(params) # # spot # # {"status":"ok","data":1637504261099} # # future, swap # # {"status":"ok","ts":1637504164707} # return self.safe_integer_2(response, 'data', 'ts') def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface: # # { # "symbol":"btcusdt", # "actualMakerRate":"0.002", # "actualTakerRate":"0.002", # "takerFeeRate":"0.002", # "makerFeeRate":"0.002" # } # marketId = self.safe_string(fee, 'symbol') return { 'info': fee, 'symbol': self.safe_symbol(marketId, market), 'maker': self.safe_number(fee, 'actualMakerRate'), 'taker': self.safe_number(fee, 'actualTakerRate'), 'percentage': None, 'tierBased': None, } def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface: """ fetch the trading fees for a market https://huobiapi.github.io/docs/spot/v1/en/#get-current-fee-rate-applied-to-the-user :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `fee structure ` """ self.load_markets() market = self.market(symbol) request: dict = { 'symbols': market['id'], # trading symbols comma-separated } response = self.spotPrivateGetV2ReferenceTransactFeeRate(self.extend(request, params)) # # { # "code":200, # "data":[ # { # "symbol":"btcusdt", # "actualMakerRate":"0.002", # "actualTakerRate":"0.002", # "takerFeeRate":"0.002", # "makerFeeRate":"0.002" # } # ], # "success":true # } # data = self.safe_value(response, 'data', []) first = self.safe_value(data, 0, {}) return self.parse_trading_fee(first, market) def fetch_trading_limits(self, symbols: Strings = None, params={}): # self method should not be called directly, use loadTradingLimits() instead # by default it will try load withdrawal fees of all currencies(with separate requests) # however if you define symbols = ['ETH/BTC', 'LTC/BTC'] in args it will only load those self.load_markets() if symbols is None: symbols = self.symbols result: dict = {} for i in range(0, len(symbols)): symbol = symbols[i] result[symbol] = self.fetch_trading_limits_by_id(self.market_id(symbol), params) return result def fetch_trading_limits_by_id(self, id: str, params={}): """ @ignore https://huobiapi.github.io/docs/spot/v1/en/#get-current-fee-rate-applied-to-the-user :param str id: market id :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: the limits object of a market structure """ request: dict = { 'symbol': id, } response = self.spotPublicGetV1CommonExchange(self.extend(request, params)) # # {status: "ok", # "data": { symbol: "aidocbtc", # "buy-limit-must-less-than": 1.1, # "sell-limit-must-greater-than": 0.9, # "limit-order-must-greater-than": 1, # "limit-order-must-less-than": 5000000, # "market-buy-order-must-greater-than": 0.0001, # "market-buy-order-must-less-than": 100, # "market-sell-order-must-greater-than": 1, # "market-sell-order-must-less-than": 500000, # "circuit-break-when-greater-than": 10000, # "circuit-break-when-less-than": 10, # "market-sell-order-rate-must-less-than": 0.1, # "market-buy-order-rate-must-less-than": 0.1 }} # return self.parse_trading_limits(self.safe_value(response, 'data', {})) def parse_trading_limits(self, limits, symbol: Str = None, params={}): # # { "symbol": "aidocbtc", # "buy-limit-must-less-than": 1.1, # "sell-limit-must-greater-than": 0.9, # "limit-order-must-greater-than": 1, # "limit-order-must-less-than": 5000000, # "market-buy-order-must-greater-than": 0.0001, # "market-buy-order-must-less-than": 100, # "market-sell-order-must-greater-than": 1, # "market-sell-order-must-less-than": 500000, # "circuit-break-when-greater-than": 10000, # "circuit-break-when-less-than": 10, # "market-sell-order-rate-must-less-than": 0.1, # "market-buy-order-rate-must-less-than": 0.1 } # return { 'info': limits, 'limits': { 'amount': { 'min': self.safe_number(limits, 'limit-order-must-greater-than'), 'max': self.safe_number(limits, 'limit-order-must-less-than'), }, }, } def cost_to_precision(self, symbol, cost): return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['cost'], self.precisionMode) def fetch_markets(self, params={}) -> List[Market]: """ retrieves data on all markets for huobi https://huobiapi.github.io/docs/spot/v1/en/#get-all-supported-trading-symbol-v1-deprecated https://huobiapi.github.io/docs/dm/v1/en/#get-contract-info https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-swap-info https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-swap-info :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ if self.options['adjustForTimeDifference']: self.load_time_difference() types = None types, params = self.handle_option_and_params(params, 'fetchMarkets', 'types', {}) allMarkets = [] promises = [] keys = list(types.keys()) for i in range(0, len(keys)): key = keys[i] if self.safe_bool(types, key): if key == 'spot': promises.append(self.fetch_markets_by_type_and_sub_type('spot', None, params)) elif key == 'linear': promises.append(self.fetch_markets_by_type_and_sub_type(None, 'linear', params)) elif key == 'inverse': promises.append(self.fetch_markets_by_type_and_sub_type('swap', 'inverse', params)) promises.append(self.fetch_markets_by_type_and_sub_type('future', 'inverse', params)) promises = promises for i in range(0, len(promises)): allMarkets = self.array_concat(allMarkets, promises[i]) return allMarkets def fetch_markets_by_type_and_sub_type(self, type: Str, subType: Str, params={}): """ @ignore retrieves data on all markets of a certain type and/or subtype https://huobiapi.github.io/docs/spot/v1/en/#get-all-supported-trading-symbol-v1-deprecated https://huobiapi.github.io/docs/dm/v1/en/#get-contract-info https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-swap-info https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-swap-info :param str [type]: 'spot', 'swap' or 'future' :param str [subType]: 'linear' or 'inverse' :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ isSpot = (type == 'spot') request: dict = {} response = None if not isSpot: if subType == 'linear': request['business_type'] = 'all' # override default to fetch all linear markets response = self.contractPublicGetLinearSwapApiV1SwapContractInfo(self.extend(request, params)) elif subType == 'inverse': if type == 'future': response = self.contractPublicGetApiV1ContractContractInfo(self.extend(request, params)) elif type == 'swap': response = self.contractPublicGetSwapApiV1SwapContractInfo(self.extend(request, params)) else: response = self.spotPublicGetV1CommonSymbols(self.extend(request, params)) # # spot # # { # "status":"ok", # "data":[ # { # "base-currency":"xrp3s", # "quote-currency":"usdt", # "price-precision":4, # "amount-precision":4, # "symbol-partition":"innovation", # "symbol":"xrp3susdt", # "state":"online", # "value-precision":8, # "min-order-amt":0.01, # "max-order-amt":1616.4353, # "min-order-value":5, # "limit-order-min-order-amt":0.01, # "limit-order-max-order-amt":1616.4353, # "limit-order-max-buy-amt":1616.4353, # "limit-order-max-sell-amt":1616.4353, # "sell-market-min-order-amt":0.01, # "sell-market-max-order-amt":1616.4353, # "buy-market-max-order-value":2500, # "max-order-value":2500, # "underlying":"xrpusdt", # "mgmt-fee-rate":0.035000000000000000, # "charge-time":"23:55:00", # "rebal-time":"00:00:00", # "rebal-threshold":-5, # "init-nav":10.000000000000000000, # "api-trading":"enabled", # "tags":"etp,nav,holdinglimit" # }, # ] # } # # inverse(swap & future) # # { # "status":"ok", # "data":[ # { # "symbol":"BTC", # "contract_code":"BTC211126", #/ BTC-USD in swap # "contract_type":"self_week", # only in future # "contract_size":100, # "price_tick":0.1, # "delivery_date":"20211126", # only in future # "delivery_time":"1637913600000", # empty in swap # "create_date":"20211112", # "contract_status":1, # "settlement_time":"1637481600000" # only in future # "settlement_date":"16xxxxxxxxxxx" # only in swap # }, # ... # ], # "ts":1637474595140 # } # # linear(swap & future) # # { # "status":"ok", # "data":[ # { # "symbol":"BTC", # "contract_code":"BTC-USDT-211231", # or "BTC-USDT" in swap # "contract_size":0.001, # "price_tick":0.1, # "delivery_date":"20211231", # empty in swap # "delivery_time":"1640937600000", # empty in swap # "create_date":"20211228", # "contract_status":1, # "settlement_date":"1640764800000", # "support_margin_mode":"cross", # "all" or "cross" # "business_type":"futures", # "swap" or "futures" # "pair":"BTC-USDT", # "contract_type":"self_week", # "swap", "self_week", "next_week", "quarter" # "trade_partition":"USDT", # } # ], # "ts":1640736207263 # } # markets = self.safe_list(response, 'data', []) numMarkets = len(markets) if numMarkets < 1: raise OperationFailed(self.id + ' fetchMarkets() returned an empty response: ' + self.json(response)) result = [] for i in range(0, len(markets)): market = markets[i] baseId = None quoteId = None settleId = None id = None lowercaseId = None contract = ('contract_code' in market) spot = not contract swap = False future = False linear = None inverse = None # check if parsed market is contract if contract: id = self.safe_string(market, 'contract_code') lowercaseId = id.lower() delivery_date = self.safe_string(market, 'delivery_date') business_type = self.safe_string(market, 'business_type') future = delivery_date is not None swap = not future linear = business_type is not None inverse = not linear if swap: type = 'swap' parts = id.split('-') baseId = self.safe_string_lower(market, 'symbol') quoteId = self.safe_string_lower(parts, 1) settleId = baseId if inverse else quoteId elif future: type = 'future' baseId = self.safe_string_lower(market, 'symbol') if inverse: quoteId = 'USD' settleId = baseId else: pair = self.safe_string(market, 'pair') parts = pair.split('-') quoteId = self.safe_string_lower(parts, 1) settleId = quoteId else: type = 'spot' baseId = self.safe_string(market, 'base-currency') quoteId = self.safe_string(market, 'quote-currency') id = baseId + quoteId lowercaseId = id.lower() base = self.safe_currency_code(baseId) quote = self.safe_currency_code(quoteId) settle = self.safe_currency_code(settleId) symbol = base + '/' + quote expiry = None if contract: if inverse: symbol += ':' + base elif linear: symbol += ':' + quote if future: expiry = self.safe_integer(market, 'delivery_time') symbol += '-' + self.yymmdd(expiry) contractSize = self.safe_number(market, 'contract_size') minCost = self.safe_number(market, 'min-order-value') maxAmount = self.safe_number(market, 'max-order-amt') minAmount = self.safe_number(market, 'min-order-amt') if contract: if linear: minAmount = contractSize elif inverse: minCost = contractSize pricePrecision = None amountPrecision = None costPrecision = None maker = None taker = None active = None if spot: pricePrecision = self.parse_number(self.parse_precision(self.safe_string(market, 'price-precision'))) amountPrecision = self.parse_number(self.parse_precision(self.safe_string(market, 'amount-precision'))) costPrecision = self.parse_number(self.parse_precision(self.safe_string(market, 'value-precision'))) maker = self.parse_number('0.002') taker = self.parse_number('0.002') state = self.safe_string(market, 'state') active = (state == 'online') else: pricePrecision = self.safe_number(market, 'price_tick') amountPrecision = self.parse_number('1') # other markets have step size of 1 contract maker = self.parse_number('0.0002') taker = self.parse_number('0.0005') contractStatus = self.safe_integer(market, 'contract_status') active = (contractStatus == 1) leverageRatio = self.safe_string(market, 'leverage-ratio', '1') superLeverageRatio = self.safe_string(market, 'super-margin-leverage-ratio', '1') hasLeverage = Precise.string_gt(leverageRatio, '1') or Precise.string_gt(superLeverageRatio, '1') # 0 Delisting # 1 Listing # 2 Pending Listing # 3 Suspension # 4 Suspending of Listing # 5 In Settlement # 6 Delivering # 7 Settlement Completed # 8 Delivered # 9 Suspending of Trade created = None createdDate = self.safe_string(market, 'create_date') # i.e 20230101 if createdDate is not None: createdArray = self.string_to_chars_array(createdDate) createdDate = createdArray[0] + createdArray[1] + createdArray[2] + createdArray[3] + '-' + createdArray[4] + createdArray[5] + '-' + createdArray[6] + createdArray[7] + ' 00:00:00' created = self.parse8601(createdDate) result.append({ 'id': id, 'lowercaseId': lowercaseId, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': type, 'spot': spot, 'margin': (spot and hasLeverage), 'swap': swap, 'future': future, 'option': False, 'active': active, 'contract': contract, 'linear': linear, 'inverse': inverse, 'taker': taker, 'maker': maker, 'contractSize': contractSize, 'expiry': expiry, 'expiryDatetime': self.iso8601(expiry), 'strike': None, 'optionType': None, 'precision': { 'amount': amountPrecision, 'price': pricePrecision, 'cost': costPrecision, }, 'limits': { 'leverage': { 'min': self.parse_number('1'), 'max': self.parse_number(leverageRatio), 'superMax': self.parse_number(superLeverageRatio), }, 'amount': { 'min': minAmount, 'max': maxAmount, }, 'price': { 'min': None, 'max': None, }, 'cost': { 'min': minCost, 'max': None, }, }, 'created': created, 'info': market, }) return result def try_get_symbol_from_future_markets(self, symbolOrMarketId: str): if symbolOrMarketId in self.markets: return symbolOrMarketId # only on "future" market type(inverse & linear), market-id differs between "fetchMarkets" and "fetchTicker" # so we have to create a mapping # - market-id from fetchMarkts: `BTC-USDT-240419`(linear future) or `BTC240412`(inverse future) # - market-id from fetchTciker[s]: `BTC-USDT-CW` (linear future) or `BTC_CW` (inverse future) if not ('futureMarketIdsForSymbols' in self.options): self.options['futureMarketIdsForSymbols'] = {} futureMarketIdsForSymbols = self.safe_dict(self.options, 'futureMarketIdsForSymbols', {}) if symbolOrMarketId in futureMarketIdsForSymbols: return futureMarketIdsForSymbols[symbolOrMarketId] futureMarkets = self.filter_by(self.markets, 'future', True) futuresCharsMaps: dict = { 'this_week': 'CW', 'next_week': 'NW', 'quarter': 'CQ', 'next_quarter': 'NQ', } for i in range(0, len(futureMarkets)): market = futureMarkets[i] info = self.safe_value(market, 'info', {}) contractType = self.safe_string(info, 'contract_type') contractSuffix = futuresCharsMaps[contractType] # see comment on formats a bit above constructedId = market['base'] + '-' + market['quote'] + '-' + contractSuffix if market['linear'] else market['base'] + '_' + contractSuffix if constructedId == symbolOrMarketId: symbol = market['symbol'] self.options['futureMarketIdsForSymbols'][symbolOrMarketId] = symbol return symbol # if not found, just save it to avoid unnecessary future iterations self.options['futureMarketIdsForSymbols'][symbolOrMarketId] = symbolOrMarketId return symbolOrMarketId def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker: # # fetchTicker # # { # "amount": 26228.672978342216, # "open": 9078.95, # "close": 9146.86, # "high": 9155.41, # "id": 209988544334, # "count": 265846, # "low": 8988.0, # "version": 209988544334, # "ask": [9146.87, 0.156134], # "vol": 2.3822168242201668E8, # "bid": [9146.86, 0.080758], # } # # fetchTickers # # { # "symbol": "bhdht", # "open": 2.3938, # "high": 2.4151, # "low": 2.3323, # "close": 2.3909, # "amount": 628.992, # "vol": 1493.71841095, # "count": 2088, # "bid": 2.3643, # "bidSize": 0.7136, # "ask": 2.4061, # "askSize": 0.4156 # } # # watchTikcer - bbo # { # "seqId": 161499562790, # "ask": 16829.51, # "askSize": 0.707776, # "bid": 16829.5, # "bidSize": 1.685945, # "quoteTime": 1671941599612, # "symbol": "btcusdt" # } # marketId = self.safe_string_2(ticker, 'symbol', 'contract_code') symbol = self.safe_symbol(marketId, market) symbol = self.try_get_symbol_from_future_markets(symbol) timestamp = self.safe_integer_2(ticker, 'ts', 'quoteTime') bid = None bidVolume = None ask = None askVolume = None if 'bid' in ticker: if ticker['bid'] is not None and isinstance(ticker['bid'], list): bid = self.safe_string(ticker['bid'], 0) bidVolume = self.safe_string(ticker['bid'], 1) else: bid = self.safe_string(ticker, 'bid') bidVolume = self.safe_string(ticker, 'bidSize') if 'ask' in ticker: if ticker['ask'] is not None and isinstance(ticker['ask'], list): ask = self.safe_string(ticker['ask'], 0) askVolume = self.safe_string(ticker['ask'], 1) else: ask = self.safe_string(ticker, 'ask') askVolume = self.safe_string(ticker, 'askSize') open = self.safe_string(ticker, 'open') close = self.safe_string(ticker, 'close') baseVolume = self.safe_string(ticker, 'amount') quoteVolume = self.safe_string(ticker, 'vol') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.safe_string(ticker, 'high'), 'low': self.safe_string(ticker, 'low'), 'bid': bid, 'bidVolume': bidVolume, 'ask': ask, 'askVolume': askVolume, 'vwap': None, 'open': open, 'close': close, 'last': close, 'previousClose': None, 'change': None, 'percentage': None, 'average': None, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }, market) def fetch_ticker(self, symbol: str, params={}) -> Ticker: """ fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market https://huobiapi.github.io/docs/spot/v1/en/#get-latest-aggregated-ticker https://huobiapi.github.io/docs/dm/v1/en/#get-market-data-overview https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-market-data-overview https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-get-market-data-overview :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ self.load_markets() market = self.market(symbol) request: dict = {} response = None if market['linear']: request['contract_code'] = market['id'] response = self.contractPublicGetLinearSwapExMarketDetailMerged(self.extend(request, params)) elif market['inverse']: if market['future']: request['symbol'] = market['id'] response = self.contractPublicGetMarketDetailMerged(self.extend(request, params)) elif market['swap']: request['contract_code'] = market['id'] response = self.contractPublicGetSwapExMarketDetailMerged(self.extend(request, params)) else: request['symbol'] = market['id'] response = self.spotPublicGetMarketDetailMerged(self.extend(request, params)) # # spot # # { # "status": "ok", # "ch": "market.btcusdt.detail.merged", # "ts": 1583494336669, # "tick": { # "amount": 26228.672978342216, # "open": 9078.95, # "close": 9146.86, # "high": 9155.41, # "id": 209988544334, # "count": 265846, # "low": 8988.0, # "version": 209988544334, # "ask": [9146.87, 0.156134], # "vol": 2.3822168242201668E8, # "bid": [9146.86, 0.080758], # } # } # # future, swap # # { # "ch":"market.BTC211126.detail.merged", # "status":"ok", # "tick":{ # "amount":"669.3385682049668320322569544150680718474", # "ask":[59117.44,48], # "bid":[59082,48], # "close":"59087.97", # "count":5947, # "high":"59892.62", # "id":1637502670, # "low":"57402.87", # "open":"57638", # "ts":1637502670059, # "vol":"394598" # }, # "ts":1637502670059 # } # tick = self.safe_value(response, 'tick', {}) ticker = self.parse_ticker(tick, market) timestamp = self.safe_integer(response, 'ts') ticker['timestamp'] = timestamp ticker['datetime'] = self.iso8601(timestamp) return ticker def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market https://huobiapi.github.io/docs/spot/v1/en/#get-latest-tickers-for-all-pairs https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-get-a-batch-of-market-data-overview https://huobiapi.github.io/docs/dm/v1/en/#get-a-batch-of-market-data-overview https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-a-batch-of-market-data-overview-v2 :param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `ticker structures ` """ self.load_markets() symbols = self.market_symbols(symbols) first = self.safe_string(symbols, 0) market = None if first is not None: market = self.market(first) isSubTypeRequested = ('subType' in params) or ('business_type' in params) type = None subType = None type, params = self.handle_market_type_and_params('fetchTickers', market, params) subType, params = self.handle_sub_type_and_params('fetchTickers', market, params) request: dict = {} isSpot = (type == 'spot') future = (type == 'future') swap = (type == 'swap') linear = (subType == 'linear') inverse = (subType == 'inverse') response = None if not isSpot or isSubTypeRequested: if linear: # independently of type, supports calling all linear symbols i.e. fetchTickers(None, {subType:'linear'}) if future: request['business_type'] = 'futures' elif swap: request['business_type'] = 'swap' else: request['business_type'] = 'all' response = self.contractPublicGetLinearSwapExMarketDetailBatchMerged(self.extend(request, params)) elif inverse: if future: response = self.contractPublicGetMarketDetailBatchMerged(self.extend(request, params)) elif swap: response = self.contractPublicGetSwapExMarketDetailBatchMerged(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchTickers() you have to set params["type"] to either "swap" or "future" for inverse contracts') else: raise NotSupported(self.id + ' fetchTickers() you have to set params["subType"] to either "linear" or "inverse" for contracts') else: response = self.spotPublicGetMarketTickers(self.extend(request, params)) # # spot # # { # "data":[ # { # "symbol":"hbcbtc", # "open":5.313E-5, # "high":5.34E-5, # "low":5.112E-5, # "close":5.175E-5, # "amount":1183.87, # "vol":0.0618599229, # "count":205, # "bid":5.126E-5, # "bidSize":5.25, # "ask":5.214E-5, # "askSize":150.0 # }, # ], # "status":"ok", # "ts":1639547261293 # } # # linear swap, linear future, inverse swap, inverse future # # { # "status":"ok", # "ticks":[ # { # "id":1637504679, # "ts":1637504679372, # "ask":[0.10644,100], # "bid":[0.10624,26], # "symbol":"TRX_CW", # "open":"0.10233", # "close":"0.10644", # "low":"0.1017", # "high":"0.10725", # "amount":"2340267.415144052378486261756692535687481566", # "count":882, # "vol":"24706", # "trade_turnover":"840726.5048", # only in linear futures # "business_type":"futures", # only in linear futures # "contract_code":"BTC-USDT-CW", # only in linear futures, instead of 'symbol' # } # ], # "ts":1637504679376 # } # rawTickers = self.safe_list_2(response, 'data', 'ticks', []) tickers = self.parse_tickers(rawTickers, symbols, params) return self.filter_by_array_tickers(tickers, 'symbol', symbols) def fetch_last_prices(self, symbols: Strings = None, params={}): """ fetches the last price for multiple markets https://www.htx.com/en-us/opend/newApiPages/?id=8cb81024-77b5-11ed-9966-0242ac110003 linear swap & linear future https://www.htx.com/en-us/opend/newApiPages/?id=28c2e8fc-77ae-11ed-9966-0242ac110003 inverse future https://www.htx.com/en-us/opend/newApiPages/?id=5d517ef5-77b6-11ed-9966-0242ac110003 inverse swap :param str[] [symbols]: unified symbols of the markets to fetch the last prices :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of lastprices structures """ self.load_markets() symbols = self.market_symbols(symbols) market = self.get_market_from_symbols(symbols) type = None subType = None subType, params = self.handle_sub_type_and_params('fetchLastPrices', market, params) type, params = self.handle_market_type_and_params('fetchLastPrices', market, params) response = None if ((type == 'swap') or (type == 'future')) and (subType == 'linear'): response = self.contractPublicGetLinearSwapExMarketTrade(params) # # { # "ch": "market.*.trade.detail", # "status": "ok", # "tick": { # "data": [ # { # "amount": "4", # "quantity": "40", # "trade_turnover": "22.176", # "ts": 1703697705028, # "id": 1000003558478170000, # "price": "0.5544", # "direction": "buy", # "contract_code": "MANA-USDT", # "business_type": "swap", # "trade_partition": "USDT" # }, # ], # "id": 1703697740147, # "ts": 1703697740147 # }, # "ts": 1703697740147 # } # elif (type == 'swap') and (subType == 'inverse'): response = self.contractPublicGetSwapExMarketTrade(params) # # { # "ch": "market.*.trade.detail", # "status": "ok", # "tick": { # "data": [ # { # "amount": "6", # "quantity": "94.5000945000945000945000945000945000945", # "ts": 1703698704594, # "id": 1000001187811060000, # "price": "0.63492", # "direction": "buy", # "contract_code": "XRP-USD" # }, # ], # "id": 1703698706589, # "ts": 1703698706589 # }, # "ts": 1703698706589 # } # elif (type == 'future') and (subType == 'inverse'): response = self.contractPublicGetMarketTrade(params) # # { # "ch": "market.*.trade.detail", # "status": "ok", # "tick": { # "data": [ # { # "amount": "20", # "quantity": "44.4444444444444444444444444444444444444", # "ts": 1686134498885, # "id": 2323000000174820000, # "price": "4.5", # "direction": "sell", # "symbol": "DORA_CW" # }, # ], # "id": 1703698855142, # "ts": 1703698855142 # }, # "ts": 1703698855142 # } # else: raise NotSupported(self.id + ' fetchLastPrices() does not support ' + type + ' markets yet') tick = self.safe_value(response, 'tick', {}) data = self.safe_list(tick, 'data', []) return self.parse_last_prices(data, symbols) def parse_last_price(self, entry, market: Market = None): # example responses are documented in fetchLastPrices marketId = self.safe_string_2(entry, 'symbol', 'contract_code') market = self.safe_market(marketId, market) price = self.safe_number(entry, 'price') direction = self.safe_string(entry, 'direction') # "buy" or "sell" # group timestamp should not be assigned to the individual trades' times return { 'symbol': market['symbol'], 'timestamp': None, 'datetime': None, 'price': price, 'side': direction, 'info': entry, } def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://huobiapi.github.io/docs/spot/v1/en/#get-market-depth https://huobiapi.github.io/docs/dm/v1/en/#get-market-depth https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-market-depth https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-get-market-depth :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ self.load_markets() market = self.market(symbol) request: dict = { # # from the API docs # # to get depth data within step 150, use step0, step1, step2, step3, step4, step5, step14, step15(merged depth data 0-5,14-15, when step is 0,depth data will not be merged # to get depth data within step 20, use step6, step7, step8, step9, step10, step11, step12, step13(merged depth data 7-13), when step is 6, depth data will not be merged # 'type': 'step0', # 'symbol': market['id'], # spot, future # 'contract_code': market['id'], # swap } response = None if market['linear']: request['contract_code'] = market['id'] response = self.contractPublicGetLinearSwapExMarketDepth(self.extend(request, params)) elif market['inverse']: if market['future']: request['symbol'] = market['id'] response = self.contractPublicGetMarketDepth(self.extend(request, params)) elif market['swap']: request['contract_code'] = market['id'] response = self.contractPublicGetSwapExMarketDepth(self.extend(request, params)) else: if limit is not None: # Valid depths are 5, 10, 20 or empty https://huobiapi.github.io/docs/spot/v1/en/#get-market-depth if (limit != 5) and (limit != 10) and (limit != 20) and (limit != 150): raise BadRequest(self.id + ' fetchOrderBook() limit argument must be None, 5, 10, 20, or 150, default is 150') # only set the depth if it is not 150 # 150 is the implicit default on the exchange side for step0 and no orderbook aggregation # it is not accepted by the exchange if you set it explicitly if limit != 150: request['depth'] = limit request['symbol'] = market['id'] response = self.spotPublicGetMarketDepth(self.extend(request, params)) # # spot, future, swap # # { # "status": "ok", # "ch": "market.btcusdt.depth.step0", # "ts": 1583474832790, # "tick": { # "bids": [ # [9100.290000000000000000, 0.200000000000000000], # [9099.820000000000000000, 0.200000000000000000], # [9099.610000000000000000, 0.205000000000000000], # ], # "asks": [ # [9100.640000000000000000, 0.005904000000000000], # [9101.010000000000000000, 0.287311000000000000], # [9101.030000000000000000, 0.012121000000000000], # ], # "ch":"market.BTC-USD.depth.step0", # "ts":1583474832008, # "id":1637554816, # "mrid":121654491624, # "version":104999698781 # } # } # if 'tick' in response: if not response['tick']: raise BadSymbol(self.id + ' fetchOrderBook() returned empty response: ' + self.json(response)) tick = self.safe_value(response, 'tick') timestamp = self.safe_integer(tick, 'ts', self.safe_integer(response, 'ts')) result = self.parse_order_book(tick, symbol, timestamp) result['nonce'] = self.safe_integer(tick, 'version') return result raise ExchangeError(self.id + ' fetchOrderBook() returned unrecognized response: ' + self.json(response)) def parse_trade(self, trade: dict, market: Market = None) -> Trade: # # spot fetchTrades(public) # # { # "amount": 0.010411000000000000, # "trade-id": 102090736910, # "ts": 1583497692182, # "id": 10500517034273194594947, # "price": 9096.050000000000000000, # "direction": "sell" # } # # spot fetchMyTrades(private) # # { # "symbol": "swftcbtc", # "fee-currency": "swftc", # "filled-fees": "0", # "source": "spot-api", # "id": 83789509854000, # "type": "buy-limit", # "order-id": 83711103204909, # 'filled-points': "0.005826843283532154", # "fee-deduct-currency": "ht", # 'filled-amount': "45941.53", # "price": "0.0000001401", # "created-at": 1597933260729, # "match-id": 100087455560, # "role": "maker", # "trade-id": 100050305348 # } # # linear swap isolated margin fetchOrder details # # { # "trade_id": 131560927, # "trade_price": 13059.800000000000000000, # "trade_volume": 1.000000000000000000, # "trade_turnover": 13.059800000000000000, # "trade_fee": -0.005223920000000000, # "created_at": 1603703614715, # "role": "taker", # "fee_asset": "USDT", # "profit": 0, # "real_profit": 0, # "id": "131560927-770334322963152896-1" # } # # inverse swap cross margin fetchMyTrades # # { # "contract_type":"swap", # "pair":"O3-USDT", # "business_type":"swap", # "query_id":652123190, # "match_id":28306009409, # "order_id":941137865226903553, # "symbol":"O3", # "contract_code":"O3-USDT", # "direction":"sell", # "offset":"open", # "trade_volume":100.000000000000000000, # "trade_price":0.398500000000000000, # "trade_turnover":39.850000000000000000, # "trade_fee":-0.007970000000000000, # "offset_profitloss":0E-18, # "create_date":1644426352999, # "role":"Maker", # "order_source":"api", # "order_id_str":"941137865226903553", # "id":"28306009409-941137865226903553-1", # "fee_asset":"USDT", # "margin_mode":"cross", # "margin_account":"USDT", # "real_profit":0E-18, # "trade_partition":"USDT" # } # marketId = self.safe_string_2(trade, 'contract_code', 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] timestamp = self.safe_integer_2(trade, 'ts', 'created-at') timestamp = self.safe_integer_2(trade, 'created_at', 'create_date', timestamp) order = self.safe_string_2(trade, 'order-id', 'order_id') side = self.safe_string(trade, 'direction') type = self.safe_string(trade, 'type') if type is not None: typeParts = type.split('-') side = typeParts[0] type = typeParts[1] takerOrMaker = self.safe_string_lower(trade, 'role') priceString = self.safe_string_2(trade, 'price', 'trade_price') amountString = self.safe_string_2(trade, 'filled-amount', 'amount') amountString = self.safe_string(trade, 'trade_volume', amountString) costString = self.safe_string(trade, 'trade_turnover') fee = None feeCost = self.safe_string(trade, 'filled-fees') if feeCost is None: feeCost = Precise.string_neg(self.safe_string(trade, 'trade_fee')) feeCurrencyId = self.safe_string_2(trade, 'fee-currency', 'fee_asset') feeCurrency = self.safe_currency_code(feeCurrencyId) filledPoints = self.safe_string(trade, 'filled-points') if filledPoints is not None: if (feeCost is None) or Precise.string_equals(feeCost, '0'): feeDeductCurrency = self.safe_string(trade, 'fee-deduct-currency') if feeDeductCurrency is not None: feeCost = filledPoints feeCurrency = self.safe_currency_code(feeDeductCurrency) if feeCost is not None: fee = { 'cost': feeCost, 'currency': feeCurrency, } # htx's multi-market trade-id is a bit complex to parse accordingly. # - for `id` which contains hyphen, it would be the unique id, eg. xxxxxx-1, xxxxxx-2(self happens mostly for contract markets) # - otherwise the least priority is given to the `id` key id: Str = None safeId = self.safe_string(trade, 'id') if safeId is not None and safeId.find('-') >= 0: id = safeId else: id = self.safe_string_n(trade, ['trade_id', 'trade-id', 'id']) return self.safe_trade({ 'id': id, 'info': trade, 'order': order, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': symbol, 'type': type, 'side': side, 'takerOrMaker': takerOrMaker, 'price': priceString, 'amount': amountString, 'cost': costString, 'fee': fee, }, market) def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetch all the trades made from a single order https://huobiapi.github.io/docs/spot/v1/en/#get-the-match-result-of-an-order :param str id: order id :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('fetchOrderTrades', market, params) if marketType != 'spot': raise NotSupported(self.id + ' fetchOrderTrades() is only supported for spot markets') return self.fetch_spot_order_trades(id, symbol, since, limit, params) def fetch_spot_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ @ignore fetch all the trades made from a single order https://huobiapi.github.io/docs/spot/v1/en/#get-the-match-result-of-an-order :param str id: order id :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ self.load_markets() request: dict = { 'order-id': id, } response = self.spotPrivateGetV1OrderOrdersOrderIdMatchresults(self.extend(request, params)) return self.parse_trades(response['data'], None, since, limit) def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-get-history-match-results-via-multiple-fields-new https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-get-history-match-results-via-multiple-fields-new https://huobiapi.github.io/docs/spot/v1/en/#search-match-results fetch all trades made by the user :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: the latest time in ms to fetch trades for :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns Trade[]: a list of `trade structures ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate') if paginate: return self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params) market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('fetchMyTrades', market, params) request: dict = { # spot ----------------------------------------------------------- # 'symbol': market['id'], # 'types': 'buy-market,sell-market,buy-limit,sell-limit,buy-ioc,sell-ioc,buy-limit-maker,sell-limit-maker,buy-stop-limit,sell-stop-limit', # 'start-time': since, # max 48 hours within 120 days # 'end-time': self.milliseconds(), # max 48 hours within 120 days # 'from': 'id', # tring False N/A Search internal id to begin with if search next page, then self should be the last id(not trade-id) of last page; if search previous page, then self should be the first id(not trade-id) of last page # 'direct': 'next', # next, prev # 'size': limit, # default 100, max 500 The number of orders to return [1-500] # contracts ------------------------------------------------------ # 'symbol': market['settleId'], # required # 'trade_type': 0, # required, 0 all, 1 open long, 2 open short, 3 close short, 4 close long, 5 liquidate long positions, 6 liquidate short positions # 'contract_code': market['id'], # 'start_time': since, # max 48 hours within 120 days # 'end_time': self.milliseconds(), # max 48 hours within 120 days # 'from_id': 'id', # tring False N/A Search internal id to begin with if search next page, then self should be the last id(not trade-id) of last page; if search previous page, then self should be the first id(not trade-id) of last page # 'direct': 'prev', # next, prev # 'size': limit, # default 20, max 50 } response = None if marketType == 'spot': if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] if limit is not None: request['size'] = limit # default 100, max 500 if since is not None: request['start-time'] = since # a date within 120 days from today # request['end-time'] = self.sum(since, 172800000) # 48 hours window request, params = self.handle_until_option('end-time', request, params) response = self.spotPrivateGetV1OrderMatchresults(self.extend(request, params)) else: if symbol is None: raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument') request['contract'] = market['id'] request['trade_type'] = 0 # 0 all, 1 open long, 2 open short, 3 close short, 4 close long, 5 liquidate long positions, 6 liquidate short positions if since is not None: request['start_time'] = since # a date within 120 days from today # request['end_time'] = self.sum(request['start_time'], 172800000) # 48 hours window request, params = self.handle_until_option('end_time', request, params) if limit is not None: request['page_size'] = limit # default 100, max 500 if market['linear']: marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchMyTrades', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': response = self.contractPrivatePostLinearSwapApiV3SwapMatchresultsExact(self.extend(request, params)) elif marginMode == 'cross': response = self.contractPrivatePostLinearSwapApiV3SwapCrossMatchresultsExact(self.extend(request, params)) elif market['inverse']: if marketType == 'future': request['symbol'] = market['settleId'] response = self.contractPrivatePostApiV3ContractMatchresultsExact(self.extend(request, params)) elif marketType == 'swap': response = self.contractPrivatePostSwapApiV3SwapMatchresultsExact(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchMyTrades() does not support ' + marketType + ' markets') # # spot # # { # "status": "ok", # "data": [ # { # "symbol": "polyusdt", # "fee-currency": "poly", # "source": "spot-web", # "price": "0.338", # "created-at": 1629443051839, # "role": "taker", # "order-id": 345487249132375, # "match-id": 5014, # "trade-id": 1085, # "filled-amount": "147.928994082840236", # "filled-fees": "0", # "filled-points": "0.1", # "fee-deduct-currency": "hbpoint", # "fee-deduct-state": "done", # "id": 313288753120940, # "type": "buy-market" # } # ] # } # # contracts # # { # "status": "ok", # "data": { # "trades": [ # { # "query_id": 2424420723, # "match_id": 113891764710, # "order_id": 773135295142658048, # "symbol": "ADA", # "contract_type": "quarter", # swap # "business_type": "futures", # swap # "contract_code": "ADA201225", # "direction": "buy", # "offset": "open", # "trade_volume": 1, # "trade_price": 0.092, # "trade_turnover": 10, # "trade_fee": -0.021739130434782608, # "offset_profitloss": 0, # "create_date": 1604371703183, # "role": "Maker", # "order_source": "web", # "order_id_str": "773135295142658048", # "fee_asset": "ADA", # "margin_mode": "isolated", # cross # "margin_account": "BTC-USDT", # "real_profit": 0, # "id": "113891764710-773135295142658048-1", # "trade_partition":"USDT", # } # ], # "remain_size": 15, # "next_id": 2424413094 # }, # "ts": 1604372202243 # } # trades = self.safe_value(response, 'data') if not isinstance(trades, list): trades = self.safe_value(trades, 'trades') return self.parse_trades(trades, market, since, limit) def fetch_trades(self, symbol: str, since: Int = None, limit: Int = 1000, params={}) -> List[Trade]: """ https://huobiapi.github.io/docs/spot/v1/en/#get-the-most-recent-trades https://huobiapi.github.io/docs/dm/v1/en/#query-a-batch-of-trade-records-of-a-contract https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-a-batch-of-trade-records-of-a-contract https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-a-batch-of-trade-records-of-a-contract get the list of most recent trades for a particular symbol :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns Trade[]: a list of `trade structures ` """ self.load_markets() market = self.market(symbol) request: dict = { # 'symbol': market['id'], # spot, future # 'contract_code': market['id'], # swap } if limit is not None: request['size'] = min(limit, 2000) # max 2000 response = None if market['future']: if market['inverse']: request['symbol'] = market['id'] response = self.contractPublicGetMarketHistoryTrade(self.extend(request, params)) elif market['linear']: request['contract_code'] = market['id'] response = self.contractPublicGetLinearSwapExMarketHistoryTrade(self.extend(request, params)) elif market['swap']: request['contract_code'] = market['id'] if market['inverse']: response = self.contractPublicGetSwapExMarketHistoryTrade(self.extend(request, params)) elif market['linear']: response = self.contractPublicGetLinearSwapExMarketHistoryTrade(self.extend(request, params)) else: request['symbol'] = market['id'] response = self.spotPublicGetMarketHistoryTrade(self.extend(request, params)) # # { # "status": "ok", # "ch": "market.btcusdt.trade.detail", # "ts": 1583497692365, # "data": [ # { # "id": 105005170342, # "ts": 1583497692182, # "data": [ # { # "amount": 0.010411000000000000, # "trade-id": 102090736910, # "ts": 1583497692182, # "id": 10500517034273194594947, # "price": 9096.050000000000000000, # "direction": "sell" # } # ] # }, # # ... # ] # } # data = self.safe_value(response, 'data', []) result = [] for i in range(0, len(data)): trades = self.safe_value(data[i], 'data', []) for j in range(0, len(trades)): trade = self.parse_trade(trades[j], market) result.append(trade) result = self.sort_by(result, 'timestamp') return self.filter_by_symbol_since_limit(result, market['symbol'], since, limit) def parse_ohlcv(self, ohlcv, market: Market = None) -> list: # # { # "amount":1.2082, # "open":0.025096, # "close":0.025095, # "high":0.025096, # "id":1591515300, # "count":6, # "low":0.025095, # "vol":0.0303205097 # } # return [ self.safe_timestamp(ohlcv, 'id'), self.safe_number(ohlcv, 'open'), self.safe_number(ohlcv, 'high'), self.safe_number(ohlcv, 'low'), self.safe_number(ohlcv, 'close'), self.safe_number(ohlcv, 'amount'), ] def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://huobiapi.github.io/docs/spot/v1/en/#get-klines-candles https://huobiapi.github.io/docs/dm/v1/en/#get-kline-data https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-kline-data https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-get-kline-data :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :param str [params.useHistoricalEndpointForSpot]: True/false - whether use the historical candles endpoint for spot markets or default klines endpoint :returns int[][]: A list of candles ordered, open, high, low, close, volume """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate') if paginate: return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 1000) market = self.market(symbol) request: dict = { 'period': self.safe_string(self.timeframes, timeframe, timeframe), # 'symbol': market['id'], # spot, future # 'contract_code': market['id'], # swap # 'size': 1000, # max 1000 for spot, 2000 for contracts # 'from': int((since / str(1000))), spot only # 'to': self.seconds(), spot only } priceType = self.safe_string_n(params, ['priceType', 'price']) params = self.omit(params, ['priceType', 'price']) until = None until, params = self.handle_param_integer(params, 'until') untilSeconds = self.parse_to_int(until / 1000) if (until is not None) else None if market['contract']: if limit is not None: request['size'] = min(limit, 2000) # when using limit: from & to are ignored # https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-get-kline-data else: limit = 2000 # only used for from/to calculation if priceType is None: duration = self.parse_timeframe(timeframe) calcualtedEnd = None if since is None: now = self.seconds() request['from'] = now - duration * (limit - 1) calcualtedEnd = now else: start = self.parse_to_int(since / 1000) request['from'] = start calcualtedEnd = self.sum(start, duration * (limit - 1)) request['to'] = untilSeconds if (untilSeconds is not None) else calcualtedEnd response = None if market['future']: if market['inverse']: request['symbol'] = market['id'] if priceType == 'mark': response = self.contractPublicGetIndexMarketHistoryMarkPriceKline(self.extend(request, params)) elif priceType == 'index': response = self.contractPublicGetIndexMarketHistoryIndex(self.extend(request, params)) elif priceType == 'premiumIndex': raise BadRequest(self.id + ' ' + market['type'] + ' has no api endpoint for ' + priceType + ' kline data') else: response = self.contractPublicGetMarketHistoryKline(self.extend(request, params)) elif market['linear']: request['contract_code'] = market['id'] if priceType == 'mark': response = self.contractPublicGetIndexMarketHistoryLinearSwapMarkPriceKline(self.extend(request, params)) elif priceType == 'index': raise BadRequest(self.id + ' ' + market['type'] + ' has no api endpoint for ' + priceType + ' kline data') elif priceType == 'premiumIndex': response = self.contractPublicGetIndexMarketHistoryLinearSwapPremiumIndexKline(self.extend(request, params)) else: response = self.contractPublicGetLinearSwapExMarketHistoryKline(self.extend(request, params)) elif market['swap']: request['contract_code'] = market['id'] if market['inverse']: if priceType == 'mark': response = self.contractPublicGetIndexMarketHistorySwapMarkPriceKline(self.extend(request, params)) elif priceType == 'index': raise BadRequest(self.id + ' ' + market['type'] + ' has no api endpoint for ' + priceType + ' kline data') elif priceType == 'premiumIndex': response = self.contractPublicGetIndexMarketHistorySwapPremiumIndexKline(self.extend(request, params)) else: response = self.contractPublicGetSwapExMarketHistoryKline(self.extend(request, params)) elif market['linear']: if priceType == 'mark': response = self.contractPublicGetIndexMarketHistoryLinearSwapMarkPriceKline(self.extend(request, params)) elif priceType == 'index': raise BadRequest(self.id + ' ' + market['type'] + ' has no api endpoint for ' + priceType + ' kline data') elif priceType == 'premiumIndex': response = self.contractPublicGetIndexMarketHistoryLinearSwapPremiumIndexKline(self.extend(request, params)) else: response = self.contractPublicGetLinearSwapExMarketHistoryKline(self.extend(request, params)) else: request['symbol'] = market['id'] useHistorical = None useHistorical, params = self.handle_option_and_params(params, 'fetchOHLCV', 'useHistoricalEndpointForSpot', True) if not useHistorical: if limit is not None: request['size'] = min(limit, 2000) # max 2000 response = self.spotPublicGetMarketHistoryKline(self.extend(request, params)) else: # "from & to" only available for the self endpoint if since is not None: request['from'] = self.parse_to_int(since / 1000) if untilSeconds is not None: request['to'] = untilSeconds if limit is not None: request['size'] = min(1000, limit) # max 1000, otherwise default returns 150 response = self.spotPublicGetMarketHistoryCandles(self.extend(request, params)) # # { # "status":"ok", # "ch":"market.ethbtc.kline.1min", # "ts":1591515374371, # "data":[ # {"amount":0.0,"open":0.025095,"close":0.025095,"high":0.025095,"id":1591515360,"count":0,"low":0.025095,"vol":0.0}, # {"amount":1.2082,"open":0.025096,"close":0.025095,"high":0.025096,"id":1591515300,"count":6,"low":0.025095,"vol":0.0303205097}, # {"amount":0.0648,"open":0.025096,"close":0.025096,"high":0.025096,"id":1591515240,"count":2,"low":0.025096,"vol":0.0016262208}, # ] # } # data = self.safe_list(response, 'data', []) return self.parse_ohlcvs(data, market, timeframe, since, limit) def fetch_accounts(self, params={}) -> List[Account]: """ fetch all the accounts associated with a profile https://huobiapi.github.io/docs/spot/v1/en/#get-all-accounts-of-the-current-user :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `account structures ` indexed by the account type """ self.load_markets() response = self.spotPrivateGetV1AccountAccounts(params) # # { # "status":"ok", # "data":[ # {"id":5202591,"type":"point","subtype":"","state":"working"}, # {"id":1528640,"type":"spot","subtype":"","state":"working"}, # ] # } # data = self.safe_value(response, 'data') return self.parse_accounts(data) def parse_account(self, account): # # { # "id": 5202591, # "type": "point", # spot, margin, otc, point, super-margin, investment, borrow, grid-trading, deposit-earning, otc-options # "subtype": "", # The corresponding trading symbol(currency pair) the isolated margin is based on, e.g. btcusdt # "state": "working" # working, lock # } # typeId = self.safe_string(account, 'type') accountsById = self.safe_value(self.options, 'accountsById', {}) type = self.safe_value(accountsById, typeId, typeId) return { 'info': account, 'id': self.safe_string(account, 'id'), 'type': type, 'code': None, } def fetch_account_id_by_type(self, type: str, marginMode: Str = None, symbol: Str = None, params={}): """ fetch all the accounts by a type and marginModeassociated with a profile https://huobiapi.github.io/docs/spot/v1/en/#get-all-accounts-of-the-current-user :param str type: 'spot', 'swap' or 'future :param str [marginMode]: 'cross' or 'isolated' :param str [symbol]: unified ccxt market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `account structures ` indexed by the account type """ accounts = self.load_accounts() accountId = self.safe_value_2(params, 'accountId', 'account-id') if accountId is not None: return accountId if type == 'spot': if marginMode == 'cross': type = 'super-margin' elif marginMode == 'isolated': type = 'margin' marketId = None if symbol is not None: marketId = self.market_id(symbol) for i in range(0, len(accounts)): account = accounts[i] info = self.safe_value(account, 'info') subtype = self.safe_string(info, 'subtype', None) typeFromAccount = self.safe_string(account, 'type') if type == 'margin': if subtype == marketId: return self.safe_string(account, 'id') elif type == typeFromAccount: return self.safe_string(account, 'id') defaultAccount = self.safe_value(accounts, 0, {}) return self.safe_string(defaultAccount, 'id') def fetch_currencies(self, params={}) -> Currencies: """ fetches all available currencies on an exchange https://huobiapi.github.io/docs/spot/v1/en/#apiv2-currency-amp-chains :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an associative dictionary of currencies """ response = self.spotPublicGetV2ReferenceCurrencies(params) # # { # "code": 200, # "data": [ # { # "currency": "sxp", # "assetType": "1", # "chains": [ # { # "chain": "sxp", # "displayName": "ERC20", # "baseChain": "ETH", # "baseChainProtocol": "ERC20", # "isDynamic": True, # "numOfConfirmations": "12", # "numOfFastConfirmations": "12", # "depositStatus": "allowed", # "minDepositAmt": "0.23", # "withdrawStatus": "allowed", # "minWithdrawAmt": "0.23", # "withdrawPrecision": "8", # "maxWithdrawAmt": "227000.000000000000000000", # "withdrawQuotaPerDay": "227000.000000000000000000", # "withdrawQuotaPerYear": null, # "withdrawQuotaTotal": null, # "withdrawFeeType": "fixed", # "transactFeeWithdraw": "11.1654", # "addrWithTag": False, # "addrDepositTag": False # } # ], # "instStatus": "normal" # } # ] # } # data = self.safe_list(response, 'data', []) result: dict = {} self.options['networkChainIdsByNames'] = {} self.options['networkNamesByChainIds'] = {} for i in range(0, len(data)): entry = data[i] currencyId = self.safe_string(entry, 'currency') code = self.safe_currency_code(currencyId) assetType = self.safe_string(entry, 'assetType') type = assetType == 'crypto' if '1' else 'fiat' self.options['networkChainIdsByNames'][code] = {} chains = self.safe_list(entry, 'chains', []) networks: dict = {} for j in range(0, len(chains)): chainEntry = chains[j] uniqueChainId = self.safe_string(chainEntry, 'chain') # i.e. usdterc20, trc20usdt ... title = self.safe_string_2(chainEntry, 'baseChain', 'displayName') # baseChain and baseChainProtocol are together existent or inexistent in entries, but baseChain is preferred. when they are both inexistent, then we use generic displayName self.options['networkChainIdsByNames'][code][title] = uniqueChainId self.options['networkNamesByChainIds'][uniqueChainId] = title networkCode = self.network_id_to_code(uniqueChainId) networks[networkCode] = { 'info': chainEntry, 'id': uniqueChainId, 'network': networkCode, 'limits': { 'deposit': { 'min': self.safe_number(chainEntry, 'minDepositAmt'), 'max': None, }, 'withdraw': { 'min': self.safe_number(chainEntry, 'minWithdrawAmt'), 'max': self.safe_number(chainEntry, 'maxWithdrawAmt'), }, }, 'active': None, 'deposit': self.safe_string(chainEntry, 'depositStatus') == 'allowed', 'withdraw': self.safe_string(chainEntry, 'withdrawStatus') == 'allowed', 'fee': self.safe_number(chainEntry, 'transactFeeWithdraw'), 'precision': self.parse_number(self.parse_precision(self.safe_string(chainEntry, 'withdrawPrecision'))), } result[code] = self.safe_currency_structure({ 'info': entry, 'code': code, 'id': currencyId, 'active': self.safe_string(entry, 'instStatus') == 'normal', 'deposit': None, 'withdraw': None, 'fee': None, 'name': None, 'type': type, 'limits': { 'amount': { 'min': None, 'max': None, }, 'withdraw': { 'min': None, 'max': None, }, 'deposit': { 'min': None, 'max': None, }, }, 'precision': None, 'networks': networks, }) return result def network_id_to_code(self, networkId: Str = None, currencyCode: Str = None): # here network-id is provided pair of currency & chain(i.e. trc20usdt) keys = list(self.options['networkNamesByChainIds'].keys()) keysLength = len(keys) if keysLength == 0: raise ExchangeError(self.id + ' networkIdToCode() - markets need to be loaded at first') networkTitle = self.safe_value(self.options['networkNamesByChainIds'], networkId, networkId) return super(htx, self).network_id_to_code(networkTitle) def network_code_to_id(self, networkCode: str, currencyCode: Str = None): if currencyCode is None: raise ArgumentsRequired(self.id + ' networkCodeToId() requires a currencyCode argument') keys = list(self.options['networkChainIdsByNames'].keys()) keysLength = len(keys) if keysLength == 0: raise ExchangeError(self.id + ' networkCodeToId() - markets need to be loaded at first') uniqueNetworkIds = self.safe_value(self.options['networkChainIdsByNames'], currencyCode, {}) if networkCode in uniqueNetworkIds: return uniqueNetworkIds[networkCode] else: networkTitle = super(htx, self).network_code_to_id(networkCode) return self.safe_value(uniqueNetworkIds, networkTitle, networkTitle) def fetch_balance(self, params={}) -> Balances: """ https://huobiapi.github.io/docs/spot/v1/en/#get-account-balance-of-a-specific-account https://www.htx.com/en-us/opend/newApiPages/?id=7ec4b429-7773-11ed-9966-0242ac110003 https://www.htx.com/en-us/opend/newApiPages/?id=10000074-77b7-11ed-9966-0242ac110003 https://huobiapi.github.io/docs/dm/v1/en/#query-asset-valuation https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-user-s-account-information https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-query-user-s-account-information https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-query-user-39-s-account-information query for balance and get the amount of funds available for trading or funds locked in orders :param dict [params]: extra parameters specific to the exchange API endpoint :param bool [params.unified]: provide self parameter if you have a recent account with unified cross+isolated margin account :returns dict: a `balance structure ` """ self.load_markets() type = None type, params = self.handle_market_type_and_params('fetchBalance', None, params) options = self.safe_value(self.options, 'fetchBalance', {}) isUnifiedAccount = self.safe_value_2(params, 'isUnifiedAccount', 'unified', False) params = self.omit(params, ['isUnifiedAccount', 'unified']) request: dict = {} spot = (type == 'spot') future = (type == 'future') defaultSubType = self.safe_string_2(self.options, 'defaultSubType', 'subType', 'linear') subType = self.safe_string_2(options, 'defaultSubType', 'subType', defaultSubType) subType = self.safe_string_2(params, 'defaultSubType', 'subType', subType) inverse = (subType == 'inverse') linear = (subType == 'linear') marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchBalance', params) params = self.omit(params, ['defaultSubType', 'subType']) isolated = (marginMode == 'isolated') cross = (marginMode == 'cross') margin = (type == 'margin') or (spot and (cross or isolated)) response = None if spot or margin: if margin: if isolated: response = self.spotPrivateGetV1MarginAccountsBalance(self.extend(request, params)) else: response = self.spotPrivateGetV1CrossMarginAccountsBalance(self.extend(request, params)) else: self.load_accounts() accountId = self.fetch_account_id_by_type(type, None, None, params) request['account-id'] = accountId response = self.spotPrivateGetV1AccountAccountsAccountIdBalance(self.extend(request, params)) elif isUnifiedAccount: response = self.contractPrivateGetLinearSwapApiV3UnifiedAccountInfo(self.extend(request, params)) elif linear: if isolated: response = self.contractPrivatePostLinearSwapApiV1SwapAccountInfo(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCrossAccountInfo(self.extend(request, params)) elif inverse: if future: response = self.contractPrivatePostApiV1ContractAccountInfo(self.extend(request, params)) else: response = self.contractPrivatePostSwapApiV1SwapAccountInfo(self.extend(request, params)) # # spot # # { # "status": "ok", # "data": { # "id": 1528640, # "type": "spot", # "state": "working", # "list": [ # {"currency": "lun", "type": "trade", "balance": "0", "seq-num": "0"}, # {"currency": "lun", "type": "frozen", "balance": "0", "seq-num": "0"}, # {"currency": "ht", "type": "frozen", "balance": "0", "seq-num": "145"}, # ] # }, # "ts":1637644827566 # } # # cross margin # # { # "status": "ok", # "data": { # "id": 51015302, # "type": "cross-margin", # "state": "working", # "risk-rate": "2", # "acct-balance-sum": "100", # "debt-balance-sum": "0", # "list": [ # {"currency": "usdt", "type": "trade", "balance": "100"}, # {"currency": "usdt", "type": "frozen", "balance": "0"}, # {"currency": "usdt", "type": "loan-available", "balance": "200"}, # {"currency": "usdt", "type": "transfer-out-available", "balance": "-1"}, # {"currency": "ht", "type": "loan-available", "balance": "36.60724091"}, # {"currency": "ht", "type": "transfer-out-available", "balance": "-1"}, # {"currency": "btc", "type": "trade", "balance": "1168.533000000000000000"}, # {"currency": "btc", "type": "frozen", "balance": "0.000000000000000000"}, # {"currency": "btc", "type": "loan", "balance": "-2.433000000000000000"}, # {"currency": "btc", "type": "interest", "balance": "-0.000533000000000000"}, # {"currency": "btc", "type": "transfer-out-available", "balance": "1163.872174670000000000"}, # {"currency": "btc", "type": "loan-available", "balance": "8161.876538350676000000"} # ] # }, # "code": 200 # } # # isolated margin # # { # "data": [ # { # "id": 18264, # "type": "margin", # "state": "working", # "symbol": "btcusdt", # "fl-price": "0", # "fl-type": "safe", # "risk-rate": "475.952571086994250554", # "list": [ # {"currency": "btc","type": "trade","balance": "1168.533000000000000000"}, # {"currency": "btc","type": "frozen","balance": "0.000000000000000000"}, # {"currency": "btc","type": "loan","balance": "-2.433000000000000000"}, # {"currency": "btc","type": "interest","balance": "-0.000533000000000000"}, # {"currency": "btc","type": "transfer-out-available", "balance": "1163.872174670000000000"}, # {"currency": "btc","type": "loan-available", "balance": "8161.876538350676000000"} # ] # } # ] # } # # future, swap isolated # # { # "status": "ok", # "data": [ # { # "symbol": "BTC", # "margin_balance": 0, # "margin_position": 0E-18, # "margin_frozen": 0, # "margin_available": 0E-18, # "profit_real": 0, # "profit_unreal": 0, # "risk_rate": null, # "withdraw_available": 0, # "liquidation_price": null, # "lever_rate": 5, # "adjust_factor": 0.025000000000000000, # "margin_static": 0, # "is_debit": 0, # future only # "contract_code": "BTC-USD", # swap only # "margin_asset": "USDT", # linear only # "margin_mode": "isolated", # linear only # "margin_account": "BTC-USDT" # linear only # "transfer_profit_ratio": null # inverse only # }, # ], # "ts": 1637644827566 # } # # linear cross futures and linear cross swap # # { # "status": "ok", # "data": [ # { # "futures_contract_detail": [ # { # "symbol": "ETH", # "contract_code": "ETH-USDT-220325", # "margin_position": 0, # "margin_frozen": 0, # "margin_available": 200.000000000000000000, # "profit_unreal": 0E-18, # "liquidation_price": null, # "lever_rate": 5, # "adjust_factor": 0.060000000000000000, # "contract_type": "quarter", # "pair": "ETH-USDT", # "business_type": "futures" # }, # ], # "margin_mode": "cross", # "margin_account": "USDT", # "margin_asset": "USDT", # "margin_balance": 49.874186030200000000, # "money_in": 50, # "money_out": 0, # "margin_static": 49.872786030200000000, # "margin_position": 6.180000000000000000, # "margin_frozen": 6.000000000000000000, # "profit_unreal": 0.001400000000000000, # "withdraw_available": 37.6927860302, # "risk_rate": 271.984050521072796934, # "new_risk_rate": 0.001858676950514399, # "contract_detail": [ # { # "symbol": "MANA", # "contract_code": "MANA-USDT", # "margin_position": 0, # "margin_frozen": 0, # "margin_available": 200.000000000000000000, # "profit_unreal": 0E-18, # "liquidation_price": null, # "lever_rate": 5, # "adjust_factor": 0.100000000000000000, # "contract_type": "swap", # "pair": "MANA-USDT", # "business_type": "swap" # }, # ] # } # ], # "ts": 1640915104870 # } # # TODO add balance parsing for linear swap # result: dict = {'info': response} data = self.safe_value(response, 'data') if spot or margin: if isolated: for i in range(0, len(data)): entry = data[i] symbol = self.safe_symbol(self.safe_string(entry, 'symbol')) balances = self.safe_value(entry, 'list') subResult: dict = {} for j in range(0, len(balances)): balance = balances[j] currencyId = self.safe_string(balance, 'currency') code = self.safe_currency_code(currencyId) subResult[code] = self.parse_margin_balance_helper(balance, code, subResult) result[symbol] = self.safe_balance(subResult) else: balances = self.safe_value(data, 'list', []) for i in range(0, len(balances)): balance = balances[i] currencyId = self.safe_string(balance, 'currency') code = self.safe_currency_code(currencyId) result[code] = self.parse_margin_balance_helper(balance, code, result) result = self.safe_balance(result) elif isUnifiedAccount: for i in range(0, len(data)): entry = data[i] marginAsset = self.safe_string(entry, 'margin_asset') currencyCode = self.safe_currency_code(marginAsset) if isolated: isolated_swap = self.safe_value(entry, 'isolated_swap', {}) for j in range(0, len(isolated_swap)): balance = isolated_swap[j] marketId = self.safe_string(balance, 'contract_code') subBalance: dict = { 'code': currencyCode, 'free': self.safe_number(balance, 'margin_available'), } symbol = self.safe_symbol(marketId) result[symbol] = subBalance result = self.safe_balance(result) else: account = self.account() account['free'] = self.safe_string(entry, 'margin_static') account['used'] = self.safe_string(entry, 'margin_frozen') result[currencyCode] = account result = self.safe_balance(result) elif linear: first = self.safe_value(data, 0, {}) if isolated: for i in range(0, len(data)): balance = data[i] marketId = self.safe_string_2(balance, 'contract_code', 'margin_account') market = self.safe_market(marketId) currencyId = self.safe_string(balance, 'margin_asset') currency = self.safe_currency(currencyId) code = self.safe_string(market, 'settle', currency['code']) # the exchange outputs positions for delisted markets # https://www.huobi.com/support/en-us/detail/74882968522337 # we skip it if the market was delisted if code is not None: account = self.account() account['free'] = self.safe_string(balance, 'margin_balance') account['used'] = self.safe_string(balance, 'margin_frozen') accountsByCode: dict = {} accountsByCode[code] = account symbol = market['symbol'] result[symbol] = self.safe_balance(accountsByCode) else: account = self.account() account['free'] = self.safe_string(first, 'withdraw_available') account['total'] = self.safe_string(first, 'margin_balance') currencyId = self.safe_string_2(first, 'margin_asset', 'symbol') code = self.safe_currency_code(currencyId) result[code] = account result = self.safe_balance(result) elif inverse: for i in range(0, len(data)): balance = data[i] currencyId = self.safe_string(balance, 'symbol') code = self.safe_currency_code(currencyId) account = self.account() account['free'] = self.safe_string(balance, 'margin_available') account['used'] = self.safe_string(balance, 'margin_frozen') result[code] = account result = self.safe_balance(result) return result def fetch_order(self, id: str, symbol: Str = None, params={}): """ fetches information on an order made by the user https://huobiapi.github.io/docs/spot/v1/en/#get-the-order-detail-of-an-order-based-on-client-order-id https://huobiapi.github.io/docs/spot/v1/en/#get-the-order-detail-of-an-order https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-get-information-of-an-order https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-get-information-of-order https://huobiapi.github.io/docs/dm/v1/en/#get-information-of-an-order https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-information-of-an-order :param str id: order id :param str symbol: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: An `order structure ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('fetchOrder', market, params) request: dict = { # spot ----------------------------------------------------------- # 'order-id': 'id', # 'symbol': market['id'], # 'client-order-id': clientOrderId, # 'clientOrderId': clientOrderId, # contracts ------------------------------------------------------ # 'order_id': id, # 'client_order_id': clientOrderId, # 'contract_code': market['id'], # 'pair': 'BTC-USDT', # 'contract_type': 'this_week', # swap, self_week, next_week, quarter, next_ quarter } response = None if marketType == 'spot': clientOrderId = self.safe_string(params, 'clientOrderId') if clientOrderId is not None: # will be filled below in self.extend() # they expect clientOrderId instead of client-order-id # request['clientOrderId'] = clientOrderId response = self.spotPrivateGetV1OrderOrdersGetClientOrder(self.extend(request, params)) else: request['order-id'] = id response = self.spotPrivateGetV1OrderOrdersOrderId(self.extend(request, params)) else: if symbol is None: raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument') clientOrderId = self.safe_string_2(params, 'client_order_id', 'clientOrderId') if clientOrderId is None: request['order_id'] = id else: request['client_order_id'] = clientOrderId params = self.omit(params, ['client_order_id', 'clientOrderId']) request['contract_code'] = market['id'] if market['linear']: marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchOrder', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': response = self.contractPrivatePostLinearSwapApiV1SwapOrderInfo(self.extend(request, params)) elif marginMode == 'cross': response = self.contractPrivatePostLinearSwapApiV1SwapCrossOrderInfo(self.extend(request, params)) elif market['inverse']: if marketType == 'future': request['symbol'] = market['settleId'] response = self.contractPrivatePostApiV1ContractOrderInfo(self.extend(request, params)) elif marketType == 'swap': response = self.contractPrivatePostSwapApiV1SwapOrderInfo(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchOrder() does not support ' + marketType + ' markets') # # spot # # { # "status":"ok", # "data":{ # "id":438398393065481, # "symbol":"ethusdt", # "account-id":1528640, # "client-order-id":"AA03022abc2163433e-006b-480e-9ad1-d4781478c5e7", # "amount":"0.100000000000000000", # "price":"3000.000000000000000000", # "created-at":1640549994642, # "type":"buy-limit", # "field-amount":"0.0", # "field-cash-amount":"0.0", # "field-fees":"0.0", # "finished-at":0, # "source":"spot-api", # "state":"submitted", # "canceled-at":0 # } # } # # linear swap cross margin # # { # "status":"ok", # "data":[ # { # "business_type":"swap", # "contract_type":"swap", # "pair":"BTC-USDT", # "symbol":"BTC", # "contract_code":"BTC-USDT", # "volume":1, # "price":3000, # "order_price_type":"limit", # "order_type":1, # "direction":"buy", # "offset":"open", # "lever_rate":1, # "order_id":924912513206878210, # "client_order_id":null, # "created_at":1640557927189, # "trade_volume":0, # "trade_turnover":0, # "fee":0, # "trade_avg_price":null, # "margin_frozen":3.000000000000000000, # "profit":0, # "status":3, # "order_source":"api", # "order_id_str":"924912513206878210", # "fee_asset":"USDT", # "liquidation_type":"0", # "canceled_at":0, # "margin_asset":"USDT", # "margin_account":"USDT", # "margin_mode":"cross", # "is_tpsl":0, # "real_profit":0 # } # ], # "ts":1640557982556 # } # # linear swap isolated margin detail # # { # "status": "ok", # "data": { # "symbol": "BTC", # "contract_code": "BTC-USDT", # "instrument_price": 0, # "final_interest": 0, # "adjust_value": 0, # "lever_rate": 10, # "direction": "sell", # "offset": "open", # "volume": 1.000000000000000000, # "price": 13059.800000000000000000, # "created_at": 1603703614712, # "canceled_at": 0, # "order_source": "api", # "order_price_type": "opponent", # "margin_frozen": 0, # "profit": 0, # "trades": [ # { # "trade_id": 131560927, # "trade_price": 13059.800000000000000000, # "trade_volume": 1.000000000000000000, # "trade_turnover": 13.059800000000000000, # "trade_fee": -0.005223920000000000, # "created_at": 1603703614715, # "role": "taker", # "fee_asset": "USDT", # "profit": 0, # "real_profit": 0, # "id": "131560927-770334322963152896-1" # } # ], # "total_page": 1, # "current_page": 1, # "total_size": 1, # "liquidation_type": "0", # "fee_asset": "USDT", # "fee": -0.005223920000000000, # "order_id": 770334322963152896, # "order_id_str": "770334322963152896", # "client_order_id": 57012021045, # "order_type": "1", # "status": 6, # "trade_avg_price": 13059.800000000000000000, # "trade_turnover": 13.059800000000000000, # "trade_volume": 1.000000000000000000, # "margin_asset": "USDT", # "margin_mode": "isolated", # "margin_account": "BTC-USDT", # "real_profit": 0, # "is_tpsl": 0 # }, # "ts": 1603703678477 # } order = self.safe_value(response, 'data') if isinstance(order, list): order = self.safe_value(order, 0) return self.parse_order(order) def parse_margin_balance_helper(self, balance, code, result): account = None if code in result: account = result[code] else: account = self.account() if balance['type'] == 'trade': account['free'] = self.safe_string(balance, 'balance') if balance['type'] == 'frozen': account['used'] = self.safe_string(balance, 'balance') return account def fetch_spot_orders_by_states(self, states, symbol: Str = None, since: Int = None, limit: Int = None, params={}): method = self.safe_string(self.options, 'fetchOrdersByStatesMethod', 'spot_private_get_v1_order_orders') # spot_private_get_v1_order_history if method == 'spot_private_get_v1_order_orders': if symbol is None: raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument') self.load_markets() market = None request: dict = { # spot_private_get_v1_order_orders GET /v1/order/orders ---------- # 'symbol': market['id'], # required # 'types': 'buy-market,sell-market,buy-limit,sell-limit,buy-ioc,sell-ioc,buy-stop-limit,sell-stop-limit,buy-limit-fok,sell-limit-fok,buy-stop-limit-fok,sell-stop-limit-fok', # 'start-time': since, # max window of 48h within a range of 180 days, within past 2 hours for cancelled orders # 'end-time': self.milliseconds(), 'states': states, # filled, partial-canceled, canceled # 'from': order['id'], # 'direct': 'next', # next, prev, used with from # 'size': 100, # max 100 # spot_private_get_v1_order_history GET /v1/order/history -------- # 'symbol': market['id'], # optional # 'start-time': since, # max window of 48h within a range of 180 days, within past 2 hours for cancelled orders # 'end-time': self.milliseconds(), # 'direct': 'next', # next, prev, used with from # 'size': 100, # max 100 } if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] if since is not None: request['start-time'] = since # a window of 48 hours within 180 days request['end-time'] = self.sum(since, 48 * 60 * 60 * 1000) request, params = self.handle_until_option('end-time', request, params) if limit is not None: request['size'] = limit response = None if method == 'spot_private_get_v1_order_orders': response = self.spotPrivateGetV1OrderOrders(self.extend(request, params)) else: response = self.spotPrivateGetV1OrderHistory(self.extend(request, params)) # # spot_private_get_v1_order_orders GET /v1/order/orders # # { # "status": "ok", # "data": [ # { # "id": 13997833014, # "symbol": "ethbtc", # "account-id": 3398321, # "client-order-id": "23456", # "amount": "0.045000000000000000", # "price": "0.034014000000000000", # "created-at": 1545836976871, # "type": "sell-limit", # "field-amount": "0.045000000000000000", # "field-cash-amount": "0.001530630000000000", # "field-fees": "0.000003061260000000", # "finished-at": 1545837948214, # "source": "spot-api", # "state": "filled", # "canceled-at": 0 # } # ] # } # data = self.safe_list(response, 'data', []) return self.parse_orders(data, market, since, limit) def fetch_spot_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): return self.fetch_spot_orders_by_states('pre-submitted,submitted,partial-filled,filled,partial-canceled,canceled', symbol, since, limit, params) def fetch_closed_spot_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): return self.fetch_spot_orders_by_states('filled,partial-canceled,canceled', symbol, since, limit, params) def fetch_contract_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): if symbol is None: raise ArgumentsRequired(self.id + ' fetchContractOrders() requires a symbol argument') self.load_markets() market = self.market(symbol) request: dict = { # POST /api/v1/contract_hisorders inverse futures ---------------- # 'symbol': market['settleId'], # BTC, ETH, ... # 'order_type': '1', # 1 limit,3 opponent,4 lightning, 5 trigger order, 6 pst_only, 7 optimal_5, 8 optimal_10, 9 optimal_20, 10 fok, 11 ioc # POST /swap-api/v3/swap_hisorders inverse swap ------------------ # POST /linear-swap-api/v3/swap_hisorders linear isolated -------- # POST /linear-swap-api/v3/swap_cross_hisorders linear cross ----- 'trade_type': 0, # 0:All; 1: Open long; 2: Open short; 3: Close short; 4: Close long; 5: Liquidate long positions; 6: Liquidate short positions, 17:buy(one-way mode), 18:sell(one-way mode) 'status': '0', # support multiple query seperated by ',',such as '3,4,5', 0: all. 3. Have sumbmitted the orders; 4. Orders partially matched; 5. Orders cancelled with partially matched; 6. Orders fully matched; 7. Orders cancelled } response = None trigger = self.safe_bool_2(params, 'stop', 'trigger') stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit') trailing = self.safe_bool(params, 'trailing', False) params = self.omit(params, ['stop', 'stopLossTakeProfit', 'trailing', 'trigger']) if trigger or stopLossTakeProfit or trailing: if limit is not None: request['page_size'] = limit request['contract_code'] = market['id'] request['create_date'] = 90 else: if since is not None: request['start_time'] = since # max 90 days back # request['end_time'] = since + 172800000 # 48 hours window request['contract'] = market['id'] request['type'] = 1 # 1:All Orders,2:Order in Finished Status request, params = self.handle_until_option('end_time', request, params) if market['linear']: marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchContractOrders', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapTriggerHisorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapTpslHisorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapTrackHisorders(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV3SwapHisorders(self.extend(request, params)) elif marginMode == 'cross': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTriggerHisorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTpslHisorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTrackHisorders(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV3SwapCrossHisorders(self.extend(request, params)) elif market['inverse']: if market['swap']: if trigger: response = self.contractPrivatePostSwapApiV1SwapTriggerHisorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostSwapApiV1SwapTpslHisorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostSwapApiV1SwapTrackHisorders(self.extend(request, params)) else: response = self.contractPrivatePostSwapApiV3SwapHisorders(self.extend(request, params)) elif market['future']: request['symbol'] = market['settleId'] if trigger: response = self.contractPrivatePostApiV1ContractTriggerHisorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostApiV1ContractTpslHisorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostApiV1ContractTrackHisorders(self.extend(request, params)) else: response = self.contractPrivatePostApiV3ContractHisorders(self.extend(request, params)) # # future and swap # # { # "code": 200, # "msg": "ok", # "data": [ # { # "direction": "buy", # "offset": "open", # "volume": 1.000000000000000000, # "price": 25000.000000000000000000, # "profit": 0E-18, # "pair": "BTC-USDT", # "query_id": 47403349100, # "order_id": 1103683465337593856, # "contract_code": "BTC-USDT-230505", # "symbol": "BTC", # "lever_rate": 5, # "create_date": 1683180243577, # "order_source": "web", # "canceled_source": "web", # "order_price_type": 1, # "order_type": 1, # "margin_frozen": 0E-18, # "trade_volume": 0E-18, # "trade_turnover": 0E-18, # "fee": 0E-18, # "trade_avg_price": 0, # "status": 7, # "order_id_str": "1103683465337593856", # "fee_asset": "USDT", # "fee_amount": 0, # "fee_quote_amount": 0, # "liquidation_type": "0", # "margin_asset": "USDT", # "margin_mode": "cross", # "margin_account": "USDT", # "update_time": 1683180352034, # "is_tpsl": 0, # "real_profit": 0, # "trade_partition": "USDT", # "reduce_only": 0, # "contract_type": "self_week", # "business_type": "futures" # } # ], # "ts": 1683239909141 # } # # trigger # # { # "status": "ok", # "data": { # "orders": [ # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "trigger_type": "le", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "buy", # "offset": "open", # "lever_rate": 1, # "order_id": 1103670703588327424, # "order_id_str": "1103670703588327424", # "relation_order_id": "-1", # "order_price_type": "limit", # "status": 6, # "order_source": "web", # "trigger_price": 25000.000000000000000000, # "triggered_price": null, # "order_price": 24000.000000000000000000, # "created_at": 1683177200945, # "triggered_at": null, # "order_insert_at": 0, # "canceled_at": 1683179075234, # "fail_code": null, # "fail_reason": null, # "margin_mode": "cross", # "margin_account": "USDT", # "update_time": 1683179075958, # "trade_partition": "USDT", # "reduce_only": 0 # }, # ], # "total_page": 1, # "current_page": 1, # "total_size": 2 # }, # "ts": 1683239702792 # } # # stop-loss and take-profit # # { # "status": "ok", # "data": { # "orders": [ # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "margin_mode": "cross", # "margin_account": "USDT", # "volume": 1.000000000000000000, # "order_type": 1, # "tpsl_order_type": "sl", # "direction": "sell", # "order_id": 1103680386844839936, # "order_id_str": "1103680386844839936", # "order_source": "web", # "trigger_type": "le", # "trigger_price": 25000.000000000000000000, # "created_at": 1683179509613, # "order_price_type": "market", # "status": 11, # "source_order_id": null, # "relation_tpsl_order_id": "-1", # "canceled_at": 0, # "fail_code": null, # "fail_reason": null, # "triggered_price": null, # "relation_order_id": "-1", # "update_time": 1683179968231, # "order_price": 0E-18, # "trade_partition": "USDT" # }, # ], # "total_page": 1, # "current_page": 1, # "total_size": 2 # }, # "ts": 1683229230233 # } # orders = self.safe_value(response, 'data') if not isinstance(orders, list): orders = self.safe_value(orders, 'orders', []) return self.parse_orders(orders, market, since, limit) def fetch_closed_contract_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): request: dict = { 'status': '5,6,7', # comma separated, 0 all, 3 submitted orders, 4 partially matched, 5 partially cancelled, 6 fully matched and closed, 7 canceled } return self.fetch_contract_orders(symbol, since, limit, self.extend(request, params)) def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://huobiapi.github.io/docs/spot/v1/en/#search-past-orders https://huobiapi.github.io/docs/spot/v1/en/#search-historical-orders-within-48-hours https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-get-history-orders-new https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-get-history-orders-new https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-history-orders-new https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-history-orders-via-multiple-fields-new fetches information on multiple orders made by the user :param str symbol: unified market symbol of the market orders were made in :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param bool [params.trigger]: *contract only* if the orders are trigger trigger orders or not :param bool [params.stopLossTakeProfit]: *contract only* if the orders are stop-loss or take-profit orders :param int [params.until]: the latest time in ms to fetch entries for :param boolean [params.trailing]: *contract only* set to True if you want to fetch trailing stop orders :returns Order[]: a list of `order structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('fetchOrders', market, params) contract = (marketType == 'swap') or (marketType == 'future') if contract and (symbol is None): raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument for ' + marketType + ' orders') if contract: return self.fetch_contract_orders(symbol, since, limit, params) else: return self.fetch_spot_orders(symbol, since, limit, params) def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://huobiapi.github.io/docs/spot/v1/en/#search-past-orders https://huobiapi.github.io/docs/spot/v1/en/#search-historical-orders-within-48-hours https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-get-history-orders-new https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-get-history-orders-new https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-history-orders-new https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-history-orders-via-multiple-fields-new fetches information on multiple closed orders made by the user :param str symbol: unified market symbol of the market orders were made in :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: the latest time in ms to fetch entries for :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns Order[]: a list of `order structures ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'paginate') if paginate: return self.fetch_paginated_call_dynamic('fetchClosedOrders', symbol, since, limit, params, 100) market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('fetchClosedOrders', market, params) if marketType == 'spot': return self.fetch_closed_spot_orders(symbol, since, limit, params) else: return self.fetch_closed_contract_orders(symbol, since, limit, params) def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://huobiapi.github.io/docs/spot/v1/en/#get-all-open-orders https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-current-unfilled-order-acquisition https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-current-unfilled-order-acquisition fetch all unfilled currently open orders :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param bool [params.trigger]: *contract only* if the orders are trigger trigger orders or not :param bool [params.stopLossTakeProfit]: *contract only* if the orders are stop-loss or take-profit orders :param boolean [params.trailing]: *contract only* set to True if you want to fetch trailing stop orders :returns Order[]: a list of `order structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) request: dict = {} marketType = None marketType, params = self.handle_market_type_and_params('fetchOpenOrders', market, params) subType = None subType, params = self.handle_sub_type_and_params('fetchOpenOrders', market, params, 'linear') response = None if marketType == 'spot': if symbol is not None: request['symbol'] = market['id'] # todo replace with fetchAccountIdByType accountId = self.safe_string(params, 'account-id') if accountId is None: # pick the first account self.load_accounts() for i in range(0, len(self.accounts)): account = self.accounts[i] if self.safe_string(account, 'type') == 'spot': accountId = self.safe_string(account, 'id') if accountId is not None: break request['account-id'] = accountId if limit is not None: request['size'] = limit params = self.omit(params, 'account-id') response = self.spotPrivateGetV1OrderOpenOrders(self.extend(request, params)) else: if symbol is not None: # raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument') request['contract_code'] = market['id'] if limit is not None: request['page_size'] = limit trigger = self.safe_bool_2(params, 'stop', 'trigger') stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit') trailing = self.safe_bool(params, 'trailing', False) params = self.omit(params, ['stop', 'stopLossTakeProfit', 'trailing', 'trigger']) if subType == 'linear': marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchOpenOrders', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapTriggerOpenorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapTpslOpenorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapTrackOpenorders(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapOpenorders(self.extend(request, params)) elif marginMode == 'cross': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTriggerOpenorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTpslOpenorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTrackOpenorders(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCrossOpenorders(self.extend(request, params)) elif subType == 'inverse': if marketType == 'swap': if trigger: response = self.contractPrivatePostSwapApiV1SwapTriggerOpenorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostSwapApiV1SwapTpslOpenorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostSwapApiV1SwapTrackOpenorders(self.extend(request, params)) else: response = self.contractPrivatePostSwapApiV1SwapOpenorders(self.extend(request, params)) elif marketType == 'future': request['symbol'] = self.safe_string(market, 'settleId', 'usdt') if trigger: response = self.contractPrivatePostApiV1ContractTriggerOpenorders(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostApiV1ContractTpslOpenorders(self.extend(request, params)) elif trailing: response = self.contractPrivatePostApiV1ContractTrackOpenorders(self.extend(request, params)) else: response = self.contractPrivatePostApiV1ContractOpenorders(self.extend(request, params)) # # spot # # { # "status":"ok", # "data":[ # { # "symbol":"ethusdt", # "source":"api", # "amount":"0.010000000000000000", # "account-id":1528640, # "created-at":1561597491963, # "price":"400.000000000000000000", # "filled-amount":"0.0", # "filled-cash-amount":"0.0", # "filled-fees":"0.0", # "id":38477101630, # "state":"submitted", # "type":"sell-limit" # } # ] # } # # futures # # { # "status": "ok", # "data": { # "orders": [ # { # "symbol": "ADA", # "contract_code": "ADA201225", # "contract_type": "quarter", # "volume": 1, # "price": 0.0925, # "order_price_type": "post_only", # "order_type": 1, # "direction": "buy", # "offset": "close", # "lever_rate": 20, # "order_id": 773131315209248768, # "client_order_id": null, # "created_at": 1604370469629, # "trade_volume": 0, # "trade_turnover": 0, # "fee": 0, # "trade_avg_price": null, # "margin_frozen": 0, # "profit": 0, # "status": 3, # "order_source": "web", # "order_id_str": "773131315209248768", # "fee_asset": "ADA", # "liquidation_type": null, # "canceled_at": null, # "is_tpsl": 0, # "update_time": 1606975980467, # "real_profit": 0 # } # ], # "total_page": 1, # "current_page": 1, # "total_size": 1 # }, # "ts": 1604370488518 # } # # trigger # # { # "status": "ok", # "data": { # "orders": [ # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "trigger_type": "le", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "buy", # "offset": "open", # "lever_rate": 1, # "order_id": 1103670703588327424, # "order_id_str": "1103670703588327424", # "order_source": "web", # "trigger_price": 25000.000000000000000000, # "order_price": 24000.000000000000000000, # "created_at": 1683177200945, # "order_price_type": "limit", # "status": 2, # "margin_mode": "cross", # "margin_account": "USDT", # "trade_partition": "USDT", # "reduce_only": 0 # } # ], # "total_page": 1, # "current_page": 1, # "total_size": 1 # }, # "ts": 1683177805320 # } # # stop-loss and take-profit # # { # "status": "ok", # "data": { # "orders": [ # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "margin_mode": "cross", # "margin_account": "USDT", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "sell", # "order_id": 1103680386844839936, # "order_id_str": "1103680386844839936", # "order_source": "web", # "trigger_type": "le", # "trigger_price": 25000.000000000000000000, # "order_price": 0E-18, # "created_at": 1683179509613, # "order_price_type": "market", # "status": 2, # "tpsl_order_type": "sl", # "source_order_id": null, # "relation_tpsl_order_id": "-1", # "trade_partition": "USDT" # } # ], # "total_page": 1, # "current_page": 1, # "total_size": 1 # }, # "ts": 1683179527011 # } # # trailing # # { # "status": "ok", # "data": { # "orders": [ # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "sell", # "offset": "close", # "lever_rate": 1, # "order_id": 1192021437253877761, # "order_id_str": "1192021437253877761", # "order_source": "api", # "created_at": 1704241657328, # "order_price_type": "formula_price", # "status": 2, # "callback_rate": 0.050000000000000000, # "active_price": 50000.000000000000000000, # "is_active": 0, # "margin_mode": "cross", # "margin_account": "USDT", # "trade_partition": "USDT", # "reduce_only": 1 # }, # ], # "total_page": 1, # "current_page": 1, # "total_size": 2 # }, # "ts": 1704242440106 # } # orders = self.safe_value(response, 'data') if not isinstance(orders, list): orders = self.safe_value(orders, 'orders', []) return self.parse_orders(orders, market, since, limit) def parse_order_status(self, status: Str): statuses: dict = { # spot 'partial-filled': 'open', 'partial-canceled': 'canceled', 'filled': 'closed', 'canceled': 'canceled', 'submitted': 'open', 'created': 'open', # For stop orders # contract '1': 'open', '2': 'open', '3': 'open', '4': 'open', '5': 'canceled', # partially matched '6': 'closed', '7': 'canceled', '11': 'canceling', } return self.safe_string(statuses, status, status) def parse_order(self, order: dict, market: Market = None) -> Order: # # spot # # { # "id": 13997833014, # "symbol": "ethbtc", # "account-id": 3398321, # "amount": "0.045000000000000000", # "price": "0.034014000000000000", # "created-at": 1545836976871, # "type": "sell-limit", # "field-amount": "0.045000000000000000", # they have fixed it for filled-amount # "field-cash-amount": "0.001530630000000000", # they have fixed it for filled-cash-amount # "field-fees": "0.000003061260000000", # they have fixed it for filled-fees # "finished-at": 1545837948214, # "source": "spot-api", # "state": "filled", # "canceled-at": 0 # } # # { # "id": 20395337822, # "symbol": "ethbtc", # "account-id": 5685075, # "amount": "0.001000000000000000", # "price": "0.0", # "created-at": 1545831584023, # "type": "buy-market", # "field-amount": "0.029100000000000000", # they have fixed it for filled-amount # "field-cash-amount": "0.000999788700000000", # they have fixed it for filled-cash-amount # "field-fees": "0.000058200000000000", # they have fixed it for filled-fees # "finished-at": 1545831584181, # "source": "spot-api", # "state": "filled", # "canceled-at": 0 # } # # linear swap cross margin createOrder # # { # "order_id":924660854912552960, # "order_id_str":"924660854912552960" # } # # contracts fetchOrder # # { # "business_type":"swap", # "contract_type":"swap", # "pair":"BTC-USDT", # "symbol":"BTC", # "contract_code":"BTC-USDT", # "volume":1, # "price":3000, # "order_price_type":"limit", # "order_type":1, # "direction":"buy", # "offset":"open", # "lever_rate":1, # "order_id":924912513206878210, # "client_order_id":null, # "created_at":1640557927189, # "trade_volume":0, # "trade_turnover":0, # "fee":0, # "trade_avg_price":null, # "margin_frozen":3.000000000000000000, # "profit":0, # "status":3, # "order_source":"api", # "order_id_str":"924912513206878210", # "fee_asset":"USDT", # "liquidation_type":"0", # "canceled_at":0, # "margin_asset":"USDT", # "margin_account":"USDT", # "margin_mode":"cross", # "is_tpsl":0, # "real_profit":0 # } # # contracts fetchOrder detailed # # { # "symbol": "BTC", # "contract_code": "BTC-USDT", # "instrument_price": 0, # "final_interest": 0, # "adjust_value": 0, # "lever_rate": 10, # "direction": "sell", # "offset": "open", # "volume": 1.000000000000000000, # "price": 13059.800000000000000000, # "created_at": 1603703614712, # "canceled_at": 0, # "order_source": "api", # "order_price_type": "opponent", # "margin_frozen": 0, # "profit": 0, # "trades": [ # { # "trade_id": 131560927, # "trade_price": 13059.800000000000000000, # "trade_volume": 1.000000000000000000, # "trade_turnover": 13.059800000000000000, # "trade_fee": -0.005223920000000000, # "created_at": 1603703614715, # "role": "taker", # "fee_asset": "USDT", # "profit": 0, # "real_profit": 0, # "id": "131560927-770334322963152896-1" # } # ], # "total_page": 1, # "current_page": 1, # "total_size": 1, # "liquidation_type": "0", # "fee_asset": "USDT", # "fee": -0.005223920000000000, # "order_id": 770334322963152896, # "order_id_str": "770334322963152896", # "client_order_id": 57012021045, # "order_type": "1", # "status": 6, # "trade_avg_price": 13059.800000000000000000, # "trade_turnover": 13.059800000000000000, # "trade_volume": 1.000000000000000000, # "margin_asset": "USDT", # "margin_mode": "isolated", # "margin_account": "BTC-USDT", # "real_profit": 0, # "is_tpsl": 0 # } # # future and swap: fetchOrders # # { # "order_id": 773131315209248768, # "contract_code": "ADA201225", # "symbol": "ADA", # "lever_rate": 20, # "direction": "buy", # "offset": "close", # "volume": 1, # "price": 0.0925, # "create_date": 1604370469629, # "update_time": 1603704221118, # "order_source": "web", # "order_price_type": 6, # "order_type": 1, # "margin_frozen": 0, # "profit": 0, # "contract_type": "quarter", # "trade_volume": 0, # "trade_turnover": 0, # "fee": 0, # "trade_avg_price": 0, # "status": 3, # "order_id_str": "773131315209248768", # "fee_asset": "ADA", # "liquidation_type": "0", # "is_tpsl": 0, # "real_profit": 0 # "margin_asset": "USDT", # "margin_mode": "cross", # "margin_account": "USDT", # "trade_partition": "USDT", # only in isolated & cross of linear # "reduce_only": "1", # only in isolated & cross of linear # "contract_type": "quarter", # only in cross-margin(inverse & linear) # "pair": "BTC-USDT", # only in cross-margin(inverse & linear) # "business_type": "futures" # only in cross-margin(inverse & linear) # } # # trigger: fetchOpenOrders # # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "trigger_type": "le", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "buy", # "offset": "open", # "lever_rate": 1, # "order_id": 1103670703588327424, # "order_id_str": "1103670703588327424", # "order_source": "web", # "trigger_price": 25000.000000000000000000, # "order_price": 24000.000000000000000000, # "created_at": 1683177200945, # "order_price_type": "limit", # "status": 2, # "margin_mode": "cross", # "margin_account": "USDT", # "trade_partition": "USDT", # "reduce_only": 0 # } # # stop-loss and take-profit: fetchOpenOrders # # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "margin_mode": "cross", # "margin_account": "USDT", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "sell", # "order_id": 1103680386844839936, # "order_id_str": "1103680386844839936", # "order_source": "web", # "trigger_type": "le", # "trigger_price": 25000.000000000000000000, # "order_price": 0E-18, # "created_at": 1683179509613, # "order_price_type": "market", # "status": 2, # "tpsl_order_type": "sl", # "source_order_id": null, # "relation_tpsl_order_id": "-1", # "trade_partition": "USDT" # } # # trailing: fetchOpenOrders # # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "sell", # "offset": "close", # "lever_rate": 1, # "order_id": 1192021437253877761, # "order_id_str": "1192021437253877761", # "order_source": "api", # "created_at": 1704241657328, # "order_price_type": "formula_price", # "status": 2, # "callback_rate": 0.050000000000000000, # "active_price": 50000.000000000000000000, # "is_active": 0, # "margin_mode": "cross", # "margin_account": "USDT", # "trade_partition": "USDT", # "reduce_only": 1 # } # # trigger: fetchOrders # # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "trigger_type": "le", # "volume": 1.000000000000000000, # "order_type": 1, # "direction": "buy", # "offset": "open", # "lever_rate": 1, # "order_id": 1103670703588327424, # "order_id_str": "1103670703588327424", # "relation_order_id": "-1", # "order_price_type": "limit", # "status": 6, # "order_source": "web", # "trigger_price": 25000.000000000000000000, # "triggered_price": null, # "order_price": 24000.000000000000000000, # "created_at": 1683177200945, # "triggered_at": null, # "order_insert_at": 0, # "canceled_at": 1683179075234, # "fail_code": null, # "fail_reason": null, # "margin_mode": "cross", # "margin_account": "USDT", # "update_time": 1683179075958, # "trade_partition": "USDT", # "reduce_only": 0 # } # # stop-loss and take-profit: fetchOrders # # { # "contract_type": "swap", # "business_type": "swap", # "pair": "BTC-USDT", # "symbol": "BTC", # "contract_code": "BTC-USDT", # "margin_mode": "cross", # "margin_account": "USDT", # "volume": 1.000000000000000000, # "order_type": 1, # "tpsl_order_type": "sl", # "direction": "sell", # "order_id": 1103680386844839936, # "order_id_str": "1103680386844839936", # "order_source": "web", # "trigger_type": "le", # "trigger_price": 25000.000000000000000000, # "created_at": 1683179509613, # "order_price_type": "market", # "status": 11, # "source_order_id": null, # "relation_tpsl_order_id": "-1", # "canceled_at": 0, # "fail_code": null, # "fail_reason": null, # "triggered_price": null, # "relation_order_id": "-1", # "update_time": 1683179968231, # "order_price": 0E-18, # "trade_partition": "USDT" # } # # spot: createOrders # # [ # { # "order-id": 936847569789079, # "client-order-id": "AA03022abc3a55e82c-0087-4fc2-beac-112fdebb1ee9" # }, # { # "client-order-id": "AA03022abcdb3baefb-3cfa-4891-8009-082b3d46ca82", # "err-code": "account-frozen-balance-insufficient-error", # "err-msg": "trade account balance is not enough, left: `89`" # } # ] # # swap and future: createOrders # # [ # { # "index": 2, # "err_code": 1047, # "err_msg": "Insufficient margin available." # }, # { # "order_id": 1172923090632953857, # "index": 1, # "order_id_str": "1172923090632953857" # } # ] # rejectedCreateOrders = self.safe_string_2(order, 'err_code', 'err-code') status = self.parse_order_status(self.safe_string_2(order, 'state', 'status')) if rejectedCreateOrders is not None: status = 'rejected' id = self.safe_string_n(order, ['id', 'order_id_str', 'order-id']) side = self.safe_string(order, 'direction') type = self.safe_string(order, 'order_price_type') if 'type' in order: orderType = order['type'].split('-') side = orderType[0] type = orderType[1] marketId = self.safe_string_2(order, 'contract_code', 'symbol') market = self.safe_market(marketId, market) timestamp = self.safe_integer_n(order, ['created_at', 'created-at', 'create_date']) clientOrderId = self.safe_string_2(order, 'client_order_id', 'client-or' + 'der-id') # transpiler regex trick for php issue cost = None amount = None if (type is not None) and (type.find('market') >= 0): cost = self.safe_string(order, 'field-cash-amount') else: amount = self.safe_string_2(order, 'volume', 'amount') cost = self.safe_string_n(order, ['filled-cash-amount', 'field-cash-amount', 'trade_turnover']) # same typo here filled = self.safe_string_n(order, ['filled-amount', 'field-amount', 'trade_volume']) # typo in their API, filled amount price = self.safe_string_2(order, 'price', 'order_price') feeCost = self.safe_string_2(order, 'filled-fees', 'field-fees') # typo in their API, filled feeSide feeCost = self.safe_string(order, 'fee', feeCost) fee = None if feeCost is not None: feeCurrency = None feeCurrencyId = self.safe_string(order, 'fee_asset') if feeCurrencyId is not None: feeCurrency = self.safe_currency_code(feeCurrencyId) else: feeCurrency = market['quote'] if (side == 'sell') else market['base'] fee = { 'cost': feeCost, 'currency': feeCurrency, } average = self.safe_string(order, 'trade_avg_price') trades = self.safe_value(order, 'trades') reduceOnlyInteger = self.safe_integer(order, 'reduce_only') reduceOnly = None if reduceOnlyInteger is not None: reduceOnly = False if (reduceOnlyInteger == 0) else True return self.safe_order({ 'info': order, 'id': id, 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'symbol': market['symbol'], 'type': type, 'timeInForce': None, 'postOnly': None, 'side': side, 'price': price, 'triggerPrice': self.safe_string_2(order, 'stop-price', 'trigger_price'), 'average': average, 'cost': cost, 'amount': amount, 'filled': filled, 'remaining': None, 'status': status, 'reduceOnly': reduceOnly, 'fee': fee, 'trades': trades, }, market) def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}): """ create a market buy order by providing the symbol and cost https://www.htx.com/en-us/opend/newApiPages/?id=7ec4ee16-7773-11ed-9966-0242ac110003 :param str symbol: unified symbol of the market to create an order in :param float cost: how much you want to trade in units of the quote currency :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) if not market['spot']: raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only') params['createMarketBuyOrderRequiresPrice'] = False return self.create_order(symbol, 'market', 'buy', cost, None, params) def create_trailing_percent_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent: Num = None, trailingTriggerPrice: Num = None, params={}) -> Order: """ create a trailing order by providing the symbol, type, side, amount, price and trailingPercent :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much you want to trade in units of the base currency, or number of contracts :param float [price]: the price for the order to be filled at, in units of the quote currency, ignored in market orders :param float trailingPercent: the percent to trail away from the current market price :param float trailingTriggerPrice: the price to activate a trailing order, default uses the price argument :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `order structure ` """ if trailingPercent is None: raise ArgumentsRequired(self.id + ' createTrailingPercentOrder() requires a trailingPercent argument') if trailingTriggerPrice is None: raise ArgumentsRequired(self.id + ' createTrailingPercentOrder() requires a trailingTriggerPrice argument') params['trailingPercent'] = trailingPercent params['trailingTriggerPrice'] = trailingTriggerPrice return self.create_order(symbol, type, side, amount, price, params) def create_spot_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ @ignore helper function to build request :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much you want to trade in units of the base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.timeInForce]: supports 'IOC' and 'FOK' :param float [params.cost]: the quote quantity that can be used alternative for the amount for market buy orders :returns dict: request to be sent to the exchange """ self.load_markets() self.load_accounts() market = self.market(symbol) marginMode = None marginMode, params = self.handle_margin_mode_and_params('createOrder', params) accountId = self.fetch_account_id_by_type(market['type'], marginMode, symbol) request: dict = { # spot ----------------------------------------------------------- 'account-id': accountId, 'symbol': market['id'], # 'type': side + '-' + type, # buy-market, sell-market, buy-limit, sell-limit, buy-ioc, sell-ioc, buy-limit-maker, sell-limit-maker, buy-stop-limit, sell-stop-limit, buy-limit-fok, sell-limit-fok, buy-stop-limit-fok, sell-stop-limit-fok # 'amount': self.amount_to_precision(symbol, amount), # for buy market orders it's the order cost # 'price': self.price_to_precision(symbol, price), # 'source': 'spot-api', # optional, spot-api, margin-api = isolated margin, super-margin-api = cross margin, c2c-margin-api # 'client-order-id': clientOrderId, # optional, max 64 chars, must be unique within 8 hours # 'stop-price': self.price_to_precision(symbol, stopPrice), # trigger price for stop limit orders # 'operator': 'gte', # gte, lte, trigger price condition } orderType = type.replace('buy-', '') orderType = orderType.replace('sell-', '') options = self.safe_value(self.options, market['type'], {}) triggerPrice = self.safe_string_n(params, ['triggerPrice', 'stopPrice', 'stop-price']) if triggerPrice is None: stopOrderTypes = self.safe_value(options, 'stopOrderTypes', {}) if orderType in stopOrderTypes: raise ArgumentsRequired(self.id + ' createOrder() requires a triggerPrice for a trigger order') else: defaultOperator = 'lte' if (side == 'sell') else 'gte' stopOperator = self.safe_string(params, 'operator', defaultOperator) request['stop-price'] = self.price_to_precision(symbol, triggerPrice) request['operator'] = stopOperator if (orderType == 'limit') or (orderType == 'limit-fok'): orderType = 'stop-' + orderType elif (orderType != 'stop-limit') and (orderType != 'stop-limit-fok'): raise NotSupported(self.id + ' createOrder() does not support ' + type + ' orders') postOnly = None postOnly, params = self.handle_post_only(orderType == 'market', orderType == 'limit-maker', params) if postOnly: orderType = 'limit-maker' timeInForce = self.safe_string(params, 'timeInForce', 'GTC') if timeInForce == 'FOK': orderType = orderType + '-fok' elif timeInForce == 'IOC': orderType = 'ioc' request['type'] = side + '-' + orderType clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client-order-id') # must be 64 chars max and unique within 24 hours if clientOrderId is None: broker = self.safe_value(self.options, 'broker', {}) brokerId = self.safe_string(broker, 'id') request['client-order-id'] = brokerId + self.uuid() else: request['client-order-id'] = clientOrderId if marginMode == 'cross': request['source'] = 'super-margin-api' elif marginMode == 'isolated': request['source'] = 'margin-api' elif marginMode == 'c2c': request['source'] = 'c2c-margin-api' if (orderType == 'market') and (side == 'buy'): quoteAmount = None createMarketBuyOrderRequiresPrice = True createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True) cost = self.safe_number(params, 'cost') params = self.omit(params, 'cost') if cost is not None: quoteAmount = self.amount_to_precision(symbol, cost) elif createMarketBuyOrderRequiresPrice: if price is None: raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend in the amount argument') else: # despite that cost = amount * price is in quote currency and should have quote precision # the exchange API requires the cost supplied in 'amount' to be of base precision # more about it here: # https://github.com/ccxt/ccxt/pull/4395 # https://github.com/ccxt/ccxt/issues/7611 # we use amountToPrecision here because the exchange requires cost in base precision amountString = self.number_to_string(amount) priceString = self.number_to_string(price) quoteAmount = self.amount_to_precision(symbol, Precise.string_mul(amountString, priceString)) else: quoteAmount = self.amount_to_precision(symbol, amount) request['amount'] = quoteAmount else: request['amount'] = self.amount_to_precision(symbol, amount) limitOrderTypes = self.safe_value(options, 'limitOrderTypes', {}) if orderType in limitOrderTypes: request['price'] = self.price_to_precision(symbol, price) params = self.omit(params, ['triggerPrice', 'stopPrice', 'stop-price', 'clientOrderId', 'client-order-id', 'operator', 'timeInForce']) return self.extend(request, params) def create_contract_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ @ignore helper function to build request :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much you want to trade in units of the base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.timeInForce]: supports 'IOC' and 'FOK' :param float [params.trailingPercent]: *contract only* the percent to trail away from the current market price :param float [params.trailingTriggerPrice]: *contract only* the price to trigger a trailing order, default uses the price argument :returns dict: request to be sent to the exchange """ market = self.market(symbol) request: dict = { 'contract_code': market['id'], 'volume': self.amount_to_precision(symbol, amount), 'direction': side, } postOnly = None postOnly, params = self.handle_post_only(type == 'market', type == 'post_only', params) if postOnly: type = 'post_only' timeInForce = self.safe_string(params, 'timeInForce', 'GTC') if timeInForce == 'FOK': type = 'fok' elif timeInForce == 'IOC': type = 'ioc' triggerPrice = self.safe_number_n(params, ['triggerPrice', 'stopPrice', 'trigger_price']) stopLossTriggerPrice = self.safe_number_2(params, 'stopLossPrice', 'sl_trigger_price') takeProfitTriggerPrice = self.safe_number_2(params, 'takeProfitPrice', 'tp_trigger_price') trailingPercent = self.safe_string_2(params, 'trailingPercent', 'callback_rate') trailingTriggerPrice = self.safe_number(params, 'trailingTriggerPrice', price) isTrailingPercentOrder = trailingPercent is not None isTrigger = triggerPrice is not None isStopLossTriggerOrder = stopLossTriggerPrice is not None isTakeProfitTriggerOrder = takeProfitTriggerPrice is not None if isTrigger: triggerType = self.safe_string_2(params, 'triggerType', 'trigger_type', 'le') request['trigger_type'] = triggerType request['trigger_price'] = self.price_to_precision(symbol, triggerPrice) if price is not None: request['order_price'] = self.price_to_precision(symbol, price) elif isStopLossTriggerOrder or isTakeProfitTriggerOrder: if isStopLossTriggerOrder: request['sl_order_price_type'] = type request['sl_trigger_price'] = self.price_to_precision(symbol, stopLossTriggerPrice) if price is not None: request['sl_order_price'] = self.price_to_precision(symbol, price) else: request['tp_order_price_type'] = type request['tp_trigger_price'] = self.price_to_precision(symbol, takeProfitTriggerPrice) if price is not None: request['tp_order_price'] = self.price_to_precision(symbol, price) elif isTrailingPercentOrder: trailingPercentString = Precise.string_div(trailingPercent, '100') request['callback_rate'] = self.parse_to_numeric(trailingPercentString) request['active_price'] = trailingTriggerPrice request['order_price_type'] = self.safe_string(params, 'order_price_type', 'formula_price') else: clientOrderId = self.safe_integer_2(params, 'client_order_id', 'clientOrderId') if clientOrderId is not None: request['client_order_id'] = clientOrderId params = self.omit(params, ['clientOrderId']) if type == 'limit' or type == 'ioc' or type == 'fok' or type == 'post_only': request['price'] = self.price_to_precision(symbol, price) reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only', False) if not isStopLossTriggerOrder and not isTakeProfitTriggerOrder: if reduceOnly: request['reduce_only'] = 1 request['lever_rate'] = self.safe_integer_n(params, ['leverRate', 'lever_rate', 'leverage'], 1) if not isTrailingPercentOrder: request['order_price_type'] = type hedged = self.safe_bool(params, 'hedged', False) if hedged: if reduceOnly: request['offset'] = 'close' else: request['offset'] = 'open' broker = self.safe_value(self.options, 'broker', {}) brokerId = self.safe_string(broker, 'id') request['channel_code'] = brokerId params = self.omit(params, ['reduceOnly', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'triggerType', 'leverRate', 'timeInForce', 'leverage', 'trailingPercent', 'trailingTriggerPrice', 'hedged']) return self.extend(request, params) def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ create a trade order https://huobiapi.github.io/docs/spot/v1/en/#place-a-new-order # spot, margin https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#place-an-order # coin-m swap https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#place-trigger-order # coin-m swap trigger https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-place-an-order # usdt-m swap cross https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-place-trigger-order # usdt-m swap cross trigger https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-place-an-order # usdt-m swap isolated https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-place-trigger-order # usdt-m swap isolated trigger https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-set-a-take-profit-and-stop-loss-order-for-an-existing-position https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-set-a-take-profit-and-stop-loss-order-for-an-existing-position https://huobiapi.github.io/docs/dm/v1/en/#place-an-order # coin-m futures https://huobiapi.github.io/docs/dm/v1/en/#place-trigger-order # coin-m futures contract trigger :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much you want to trade in units of the base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param float [params.triggerPrice]: the price a trigger order is triggered at :param str [params.triggerType]: *contract trigger orders only* ge: greater than or equal to, le: less than or equal to :param float [params.stopLossPrice]: *contract only* the price a stop-loss order is triggered at :param float [params.takeProfitPrice]: *contract only* the price a take-profit order is triggered at :param str [params.operator]: *spot and margin only* gte or lte, trigger price condition :param str [params.offset]: *contract only* 'both'(linear only), 'open', or 'close', required in hedge mode and for inverse markets :param bool [params.postOnly]: *contract only* True or False :param int [params.leverRate]: *contract only* required for all contract orders except tpsl, leverage greater than 20x requires prior approval of high-leverage agreement :param str [params.timeInForce]: supports 'IOC' and 'FOK' :param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount :param float [params.trailingPercent]: *contract only* the percent to trail away from the current market price :param float [params.trailingTriggerPrice]: *contract only* the price to trigger a trailing order, default uses the price argument :param bool [params.hedged]: *contract only* True for hedged mode, False for one way mode, default is False :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) triggerPrice = self.safe_number_n(params, ['triggerPrice', 'stopPrice', 'trigger_price']) stopLossTriggerPrice = self.safe_number_2(params, 'stopLossPrice', 'sl_trigger_price') takeProfitTriggerPrice = self.safe_number_2(params, 'takeProfitPrice', 'tp_trigger_price') trailingPercent = self.safe_number(params, 'trailingPercent') isTrailingPercentOrder = trailingPercent is not None isTrigger = triggerPrice is not None isStopLossTriggerOrder = stopLossTriggerPrice is not None isTakeProfitTriggerOrder = takeProfitTriggerPrice is not None response = None if market['spot']: if isTrailingPercentOrder: raise NotSupported(self.id + ' createOrder() does not support trailing orders for spot markets') spotRequest = self.create_spot_order_request(symbol, type, side, amount, price, params) response = self.spotPrivatePostV1OrderOrdersPlace(spotRequest) else: contractRequest = self.create_contract_order_request(symbol, type, side, amount, price, params) if market['linear']: marginMode = None marginMode, contractRequest = self.handle_margin_mode_and_params('createOrder', contractRequest) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': if isTrigger: response = self.contractPrivatePostLinearSwapApiV1SwapTriggerOrder(contractRequest) elif isStopLossTriggerOrder or isTakeProfitTriggerOrder: response = self.contractPrivatePostLinearSwapApiV1SwapTpslOrder(contractRequest) elif isTrailingPercentOrder: response = self.contractPrivatePostLinearSwapApiV1SwapTrackOrder(contractRequest) else: response = self.contractPrivatePostLinearSwapApiV1SwapOrder(contractRequest) elif marginMode == 'cross': if isTrigger: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTriggerOrder(contractRequest) elif isStopLossTriggerOrder or isTakeProfitTriggerOrder: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTpslOrder(contractRequest) elif isTrailingPercentOrder: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTrackOrder(contractRequest) else: response = self.contractPrivatePostLinearSwapApiV1SwapCrossOrder(contractRequest) elif market['inverse']: offset = self.safe_string(params, 'offset') if offset is None: raise ArgumentsRequired(self.id + ' createOrder() requires an extra parameter params["offset"] to be set to "open" or "close" when placing orders in inverse markets') if market['swap']: if isTrigger: response = self.contractPrivatePostSwapApiV1SwapTriggerOrder(contractRequest) elif isStopLossTriggerOrder or isTakeProfitTriggerOrder: response = self.contractPrivatePostSwapApiV1SwapTpslOrder(contractRequest) elif isTrailingPercentOrder: response = self.contractPrivatePostSwapApiV1SwapTrackOrder(contractRequest) else: response = self.contractPrivatePostSwapApiV1SwapOrder(contractRequest) elif market['future']: if isTrigger: response = self.contractPrivatePostApiV1ContractTriggerOrder(contractRequest) elif isStopLossTriggerOrder or isTakeProfitTriggerOrder: response = self.contractPrivatePostApiV1ContractTpslOrder(contractRequest) elif isTrailingPercentOrder: response = self.contractPrivatePostApiV1ContractTrackOrder(contractRequest) else: response = self.contractPrivatePostApiV1ContractOrder(contractRequest) # # spot # # {"status":"ok","data":"438398393065481"} # # swap and future # # { # "status": "ok", # "data": { # "order_id": 924660854912552960, # "order_id_str": "924660854912552960" # }, # "ts": 1640497927185 # } # # stop-loss and take-profit # # { # "status": "ok", # "data": { # "tp_order": { # "order_id": 1101494204040163328, # "order_id_str": "1101494204040163328" # }, # "sl_order": null # }, # "ts": :1682658283024 # } # data = None result = None if market['spot']: return self.safe_order({ 'info': response, 'id': self.safe_string(response, 'data'), 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'status': None, 'symbol': None, 'type': type, 'side': side, 'price': price, 'amount': amount, 'filled': None, 'remaining': None, 'cost': None, 'trades': None, 'fee': None, 'clientOrderId': None, 'average': None, }, market) elif isStopLossTriggerOrder: data = self.safe_value(response, 'data', {}) result = self.safe_value(data, 'sl_order', {}) elif isTakeProfitTriggerOrder: data = self.safe_value(response, 'data', {}) result = self.safe_value(data, 'tp_order', {}) else: result = self.safe_value(response, 'data', {}) return self.parse_order(result, market) def create_orders(self, orders: List[OrderRequest], params={}): """ create a list of trade orders https://huobiapi.github.io/docs/spot/v1/en/#place-a-batch-of-orders https://huobiapi.github.io/docs/dm/v1/en/#place-a-batch-of-orders https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#place-a-batch-of-orders https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-place-a-batch-of-orders https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-place-a-batch-of-orders :param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `order structure ` """ self.load_markets() ordersRequests = [] symbol = None market = None marginMode = None for i in range(0, len(orders)): rawOrder = orders[i] marketId = self.safe_string(rawOrder, 'symbol') if symbol is None: symbol = marketId else: if symbol != marketId: raise BadRequest(self.id + ' createOrders() requires all orders to have the same symbol') type = self.safe_string(rawOrder, 'type') side = self.safe_string(rawOrder, 'side') amount = self.safe_value(rawOrder, 'amount') price = self.safe_value(rawOrder, 'price') orderParams = self.safe_value(rawOrder, 'params', {}) marginResult = self.handle_margin_mode_and_params('createOrders', orderParams) currentMarginMode = marginResult[0] if currentMarginMode is not None: if marginMode is None: marginMode = currentMarginMode else: if marginMode != currentMarginMode: raise BadRequest(self.id + ' createOrders() requires all orders to have the same margin mode(isolated or cross)') market = self.market(symbol) orderRequest = None if market['spot']: orderRequest = self.create_spot_order_request(marketId, type, side, amount, price, orderParams) else: orderRequest = self.create_contract_order_request(marketId, type, side, amount, price, orderParams) orderRequest = self.omit(orderRequest, 'marginMode') ordersRequests.append(orderRequest) request: dict = {} response = None if market['spot']: response = self.privatePostOrderBatchOrders(ordersRequests) else: request['orders_data'] = ordersRequests if market['linear']: marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': response = self.contractPrivatePostLinearSwapApiV1SwapBatchorder(request) elif marginMode == 'cross': response = self.contractPrivatePostLinearSwapApiV1SwapCrossBatchorder(request) elif market['inverse']: if market['swap']: response = self.contractPrivatePostSwapApiV1SwapBatchorder(request) elif market['future']: response = self.contractPrivatePostApiV1ContractBatchorder(request) # # spot # # { # "status": "ok", # "data": [ # { # "order-id": 936847569789079, # "client-order-id": "AA03022abc3a55e82c-0087-4fc2-beac-112fdebb1ee9" # }, # { # "client-order-id": "AA03022abcdb3baefb-3cfa-4891-8009-082b3d46ca82", # "err-code": "account-frozen-balance-insufficient-error", # "err-msg": "trade account balance is not enough, left: `89`" # } # ] # } # # swap and future # # { # "status": "ok", # "data": { # "errors": [ # { # "index": 2, # "err_code": 1047, # "err_msg": "Insufficient margin available." # } # ], # "success": [ # { # "order_id": 1172923090632953857, # "index": 1, # "order_id_str": "1172923090632953857" # } # ] # }, # "ts": 1699688256671 # } # result = None if market['spot']: result = self.safe_value(response, 'data', []) else: data = self.safe_value(response, 'data', {}) success = self.safe_value(data, 'success', []) errors = self.safe_value(data, 'errors', []) result = self.array_concat(success, errors) return self.parse_orders(result, market) def cancel_order(self, id: str, symbol: Str = None, params={}): """ cancels an open order :param str id: order id :param str symbol: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.trigger]: *contract only* if the order is a trigger trigger order or not :param boolean [params.stopLossTakeProfit]: *contract only* if the order is a stop-loss or take-profit order :param boolean [params.trailing]: *contract only* set to True if you want to cancel a trailing order :returns dict: An `order structure ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('cancelOrder', market, params) request: dict = { # spot ----------------------------------------------------------- # 'order-id': 'id', # 'symbol': market['id'], # 'client-order-id': clientOrderId, # contracts ------------------------------------------------------ # 'order_id': id, # 'client_order_id': clientOrderId, # 'contract_code': market['id'], # 'pair': 'BTC-USDT', # 'contract_type': 'this_week', # swap, self_week, next_week, quarter, next_ quarter } response = None if marketType == 'spot': clientOrderId = self.safe_string_2(params, 'client-order-id', 'clientOrderId') if clientOrderId is None: request['order-id'] = id response = self.spotPrivatePostV1OrderOrdersOrderIdSubmitcancel(self.extend(request, params)) else: request['client-order-id'] = clientOrderId params = self.omit(params, ['client-order-id', 'clientOrderId']) response = self.spotPrivatePostV1OrderOrdersSubmitCancelClientOrder(self.extend(request, params)) else: if symbol is None: raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument') clientOrderId = self.safe_string_2(params, 'client_order_id', 'clientOrderId') if clientOrderId is None: request['order_id'] = id else: request['client_order_id'] = clientOrderId params = self.omit(params, ['client_order_id', 'clientOrderId']) if market['future']: request['symbol'] = market['settleId'] else: request['contract_code'] = market['id'] trigger = self.safe_bool_2(params, 'stop', 'trigger') stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit') trailing = self.safe_bool(params, 'trailing', False) params = self.omit(params, ['stop', 'stopLossTakeProfit', 'trailing', 'trigger']) if market['linear']: marginMode = None marginMode, params = self.handle_margin_mode_and_params('cancelOrder', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapTpslCancel(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapTrackCancel(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCancel(self.extend(request, params)) elif marginMode == 'cross': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTpslCancel(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTrackCancel(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCrossCancel(self.extend(request, params)) elif market['inverse']: if market['swap']: if trigger: response = self.contractPrivatePostSwapApiV1SwapTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostSwapApiV1SwapTpslCancel(self.extend(request, params)) elif trailing: response = self.contractPrivatePostSwapApiV1SwapTrackCancel(self.extend(request, params)) else: response = self.contractPrivatePostSwapApiV1SwapCancel(self.extend(request, params)) elif market['future']: if trigger: response = self.contractPrivatePostApiV1ContractTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostApiV1ContractTpslCancel(self.extend(request, params)) elif trailing: response = self.contractPrivatePostApiV1ContractTrackCancel(self.extend(request, params)) else: response = self.contractPrivatePostApiV1ContractCancel(self.extend(request, params)) else: raise NotSupported(self.id + ' cancelOrder() does not support ' + marketType + ' markets') # # spot # # { # "status": "ok", # "data": "10138899000", # } # # future and swap # # { # "status": "ok", # "data": { # "errors": [], # "successes": "924660854912552960" # }, # "ts": 1640504486089 # } # return self.extend(self.parse_order(response, market), { 'id': id, 'status': 'canceled', }) def cancel_orders(self, ids: List[str], symbol: Str = None, params={}): """ cancel multiple orders :param str[] ids: order ids :param str symbol: unified market symbol, default is None :param dict [params]: extra parameters specific to the exchange API endpoint :param bool [params.trigger]: *contract only* if the orders are trigger trigger orders or not :param bool [params.stopLossTakeProfit]: *contract only* if the orders are stop-loss or take-profit orders :returns dict: an list of `order structures ` """ self.load_markets() market: Market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('cancelOrders', market, params) request: dict = { # spot ----------------------------------------------------------- # 'order-ids': ','.join(ids), # max 50 # 'client-order-ids': ','.join(ids), # max 50 # contracts ------------------------------------------------------ # 'order_id': id, # comma separated, max 10 # 'client_order_id': clientOrderId, # comma separated, max 10 # 'contract_code': market['id'], # 'symbol': market['settleId'], } response = None if marketType == 'spot': clientOrderIds = self.safe_value_2(params, 'client-order-id', 'clientOrderId') clientOrderIds = self.safe_value_2(params, 'client-order-ids', 'clientOrderIds', clientOrderIds) if clientOrderIds is None: if isinstance(clientOrderIds, str): request['order-ids'] = [ids] else: request['order-ids'] = ids else: if isinstance(clientOrderIds, str): request['client-order-ids'] = [clientOrderIds] else: request['client-order-ids'] = clientOrderIds params = self.omit(params, ['client-order-id', 'client-order-ids', 'clientOrderId', 'clientOrderIds']) response = self.spotPrivatePostV1OrderOrdersBatchcancel(self.extend(request, params)) else: if symbol is None: raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument') clientOrderIds = self.safe_string_2(params, 'client_order_id', 'clientOrderId') clientOrderIds = self.safe_string_2(params, 'client_order_ids', 'clientOrderIds', clientOrderIds) if clientOrderIds is None: request['order_id'] = ','.join(ids) else: request['client_order_id'] = clientOrderIds params = self.omit(params, ['client_order_id', 'client_order_ids', 'clientOrderId', 'clientOrderIds']) if market['future']: request['symbol'] = market['settleId'] else: request['contract_code'] = market['id'] trigger = self.safe_bool_2(params, 'stop', 'trigger') stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit') params = self.omit(params, ['stop', 'stopLossTakeProfit', 'trigger']) if market['linear']: marginMode = None marginMode, params = self.handle_margin_mode_and_params('cancelOrders', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapTpslCancel(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCancel(self.extend(request, params)) elif marginMode == 'cross': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTpslCancel(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCrossCancel(self.extend(request, params)) elif market['inverse']: if market['swap']: if trigger: response = self.contractPrivatePostSwapApiV1SwapTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostSwapApiV1SwapTpslCancel(self.extend(request, params)) else: response = self.contractPrivatePostSwapApiV1SwapCancel(self.extend(request, params)) elif market['future']: if trigger: response = self.contractPrivatePostApiV1ContractTriggerCancel(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostApiV1ContractTpslCancel(self.extend(request, params)) else: response = self.contractPrivatePostApiV1ContractCancel(self.extend(request, params)) else: raise NotSupported(self.id + ' cancelOrders() does not support ' + marketType + ' markets') # # spot # # { # "status": "ok", # "data": { # "success": [ # "5983466" # ], # "failed": [ # { # "err-msg": "Incorrect order state", # "order-state": 7, # "order-id": "", # "err-code": "order-orderstate-error", # "client-order-id": "first" # }, # { # "err-msg": "Incorrect order state", # "order-state": 7, # "order-id": "", # "err-code": "order-orderstate-error", # "client-order-id": "second" # }, # { # "err-msg": "The record is not found.", # "order-id": "", # "err-code": "base-not-found", # "client-order-id": "third" # } # ] # } # } # # future and swap # # { # "status": "ok", # "data": { # "errors": [ # { # "order_id": "769206471845261312", # "err_code": 1061, # "err_msg": "This order doesnt exist." # } # ], # "successes": "773120304138219520" # }, # "ts": 1604367997451 # } # data = self.safe_dict(response, 'data') return self.parse_cancel_orders(data) def parse_cancel_orders(self, orders): # # { # "success": [ # "5983466" # ], # "failed": [ # { # "err-msg": "Incorrect order state", # "order-state": 7, # "order-id": "", # "err-code": "order-orderstate-error", # "client-order-id": "first" # }, # ... # ] # } # # { # "errors": [ # { # "order_id": "769206471845261312", # "err_code": 1061, # "err_msg": "This order doesnt exist." # } # ], # "successes": "1258075374411399168,1258075393254871040" # } # successes = self.safe_string(orders, 'successes') success = None if successes is not None: success = successes.split(',') else: success = self.safe_list(orders, 'success', []) failed = self.safe_list_2(orders, 'errors', 'failed', []) result = [] for i in range(0, len(success)): order = success[i] result.append(self.safe_order({ 'info': order, 'id': order, 'status': 'canceled', })) for i in range(0, len(failed)): order = failed[i] result.append(self.safe_order({ 'info': order, 'id': self.safe_string_2(order, 'order-id', 'order_id'), 'status': 'failed', 'clientOrderId': self.safe_string(order, 'client-order-id'), })) return result def cancel_all_orders(self, symbol: Str = None, params={}): """ cancel all open orders :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.trigger]: *contract only* if the orders are trigger trigger orders or not :param boolean [params.stopLossTakeProfit]: *contract only* if the orders are stop-loss or take-profit orders :param boolean [params.trailing]: *contract only* set to True if you want to cancel all trailing orders :returns dict[]: a list of `order structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('cancelAllOrders', market, params) request: dict = { # spot ----------------------------------------------------------- # 'account-id': account['id'], # 'symbol': market['id'], # a list of comma-separated symbols, all symbols by default # 'types' 'string', buy-market, sell-market, buy-limit, sell-limit, buy-ioc, sell-ioc, buy-stop-limit, sell-stop-limit, buy-limit-fok, sell-limit-fok, buy-stop-limit-fok, sell-stop-limit-fok # 'side': 'buy', # or 'sell' # 'size': 100, # the number of orders to cancel 1-100 # contract ------------------------------------------------------- # 'symbol': market['settleId'], # required # 'contract_code': market['id'], # 'contract_type': 'this_week', # swap, self_week, next_week, quarter, next_ quarter # 'direction': 'buy': # buy, sell # 'offset': 'open', # open, close } response = None if marketType == 'spot': if symbol is not None: request['symbol'] = market['id'] response = self.spotPrivatePostV1OrderOrdersBatchCancelOpenOrders(self.extend(request, params)) # # { # "code": 200, # "data": { # "success-count": 2, # "failed-count": 0, # "next-id": 5454600 # } # } # data = self.safe_dict(response, 'data') return [ self.safe_order({ 'info': data, }), ] else: if symbol is None: raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument') if market['future']: request['symbol'] = market['settleId'] request['contract_code'] = market['id'] trigger = self.safe_bool_2(params, 'stop', 'trigger') stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit') trailing = self.safe_bool(params, 'trailing', False) params = self.omit(params, ['stop', 'stopLossTakeProfit', 'trailing', 'trigger']) if market['linear']: marginMode = None marginMode, params = self.handle_margin_mode_and_params('cancelAllOrders', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapTriggerCancelall(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapTpslCancelall(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapTrackCancelall(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCancelall(self.extend(request, params)) elif marginMode == 'cross': if trigger: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTriggerCancelall(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTpslCancelall(self.extend(request, params)) elif trailing: response = self.contractPrivatePostLinearSwapApiV1SwapCrossTrackCancelall(self.extend(request, params)) else: response = self.contractPrivatePostLinearSwapApiV1SwapCrossCancelall(self.extend(request, params)) elif market['inverse']: if market['swap']: if trigger: response = self.contractPrivatePostSwapApiV1SwapTriggerCancelall(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostSwapApiV1SwapTpslCancelall(self.extend(request, params)) elif trailing: response = self.contractPrivatePostSwapApiV1SwapTrackCancelall(self.extend(request, params)) else: response = self.contractPrivatePostSwapApiV1SwapCancelall(self.extend(request, params)) elif market['future']: if trigger: response = self.contractPrivatePostApiV1ContractTriggerCancelall(self.extend(request, params)) elif stopLossTakeProfit: response = self.contractPrivatePostApiV1ContractTpslCancelall(self.extend(request, params)) elif trailing: response = self.contractPrivatePostApiV1ContractTrackCancelall(self.extend(request, params)) else: response = self.contractPrivatePostApiV1ContractCancelall(self.extend(request, params)) else: raise NotSupported(self.id + ' cancelAllOrders() does not support ' + marketType + ' markets') # # { # "status": "ok", # "data": { # "errors": [], # "successes": "1104754904426696704" # }, # "ts": "1683435723755" # } # data = self.safe_dict(response, 'data') return self.parse_cancel_orders(data) def cancel_all_orders_after(self, timeout: Int, params={}): """ dead man's switch, cancel all orders after the given timeout https://huobiapi.github.io/docs/spot/v1/en/#dead-man-s-switch :param number timeout: time in milliseconds, 0 represents cancel the timer :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: the api result """ self.load_markets() request: dict = { 'timeout': self.parse_to_int(timeout / 1000) if (timeout > 0) else 0, } response = self.v2PrivatePostAlgoOrdersCancelAllAfter(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": { # "currentTime": 1630491627230, # "triggerTime": 1630491637230 # } # } # return response def parse_deposit_address(self, depositAddress, currency: Currency = None): # # { # "currency": "usdt", # "address": "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b", # "addressTag": "", # "chain": "usdterc20", # trc20usdt, hrc20usdt, usdt, algousdt # } # address = self.safe_string(depositAddress, 'address') tag = self.safe_string(depositAddress, 'addressTag') currencyId = self.safe_string(depositAddress, 'currency') currency = self.safe_currency(currencyId, currency) code = self.safe_currency_code(currencyId, currency) note = self.safe_string(depositAddress, 'note') networkId = self.safe_string(depositAddress, 'chain') self.check_address(address) return { 'currency': code, 'address': address, 'tag': tag, 'network': self.network_id_to_code(networkId), 'note': note, 'info': depositAddress, } def fetch_deposit_addresses_by_network(self, code: str, params={}) -> List[DepositAddress]: """ https://www.htx.com/en-us/opend/newApiPages/?id=7ec50029-7773-11ed-9966-0242ac110003 fetch a dictionary of addresses for a currency, indexed by network :param str code: unified currency code of the currency for the deposit address :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `address structures ` indexed by the network """ self.load_markets() currency = self.currency(code) request: dict = { 'currency': currency['id'], } response = self.spotPrivateGetV2AccountDepositAddress(self.extend(request, params)) # # { # "code": 200, # "data": [ # { # "currency": "eth", # "address": "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b", # "addressTag": "", # "chain": "eth" # } # ] # } # data = self.safe_value(response, 'data', []) parsed = self.parse_deposit_addresses(data, [currency['code']], False) return self.index_by(parsed, 'network') def fetch_deposit_address(self, code: str, params={}) -> DepositAddress: """ fetch the deposit address for a currency associated with self account https://www.htx.com/en-us/opend/newApiPages/?id=7ec50029-7773-11ed-9966-0242ac110003 :param str code: unified currency code :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `address structure ` """ self.load_markets() currency = self.currency(code) networkCode, paramsOmited = self.handle_network_code_and_params(params) indexedAddresses = self.fetch_deposit_addresses_by_network(code, paramsOmited) selectedNetworkCode = self.select_network_code_from_unified_networks(currency['code'], networkCode, indexedAddresses) return indexedAddresses[selectedNetworkCode] def fetch_withdraw_addresses(self, code: str, note=None, networkCode=None, params={}): self.load_markets() currency = self.currency(code) request: dict = { 'currency': currency['id'], } response = self.spotPrivateGetV2AccountWithdrawAddress(self.extend(request, params)) # # { # "code": 200, # "data": [ # { # "currency": "eth", # "chain": "eth" # "note": "Binance - TRC20", # "addressTag": "", # "address": "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b", # } # ] # } # data = self.safe_value(response, 'data', []) allAddresses = self.parse_deposit_addresses(data, [currency['code']], False) # cjg: to do remove self weird object or array ambiguity addresses = [] for i in range(0, len(allAddresses)): address = allAddresses[i] noteMatch = (note is None) or (address['note'] == note) networkMatch = (networkCode is None) or (address['network'] == networkCode) if noteMatch and networkMatch: addresses.append(address) return addresses def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ https://www.htx.com/en-us/opend/newApiPages/?id=7ec4f050-7773-11ed-9966-0242ac110003 fetch all deposits made to an account :param str code: unified currency code :param int [since]: the earliest time in ms to fetch deposits for :param int [limit]: the maximum number of deposits structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `transaction structures ` """ if limit is None or limit > 100: limit = 100 self.load_markets() currency = None if code is not None: currency = self.currency(code) request: dict = { 'type': 'deposit', 'direct': 'next', 'from': 0, # From 'id' ... if you want to get results after a particular transaction id, pass the id in params.from } if currency is not None: request['currency'] = currency['id'] if limit is not None: request['size'] = limit # max 100 response = self.spotPrivateGetV1QueryDepositWithdraw(self.extend(request, params)) # # { # "status": "ok", # "data": [ # { # "id": "75115912", # "type": "deposit", # "sub-type": "NORMAL", # "request-id": "trc20usdt-a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff-200", # "currency": "usdt", # "chain": "trc20usdt", # "tx-hash": "a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff", # "amount": "12.000000000000000000", # "from-addr-tag": "", # "address-id": "0", # "address": "TRFTd1FxepQE6CnpwzUEMEbFaLm5bJK67s", # "address-tag": "", # "fee": "0", # "state": "safe", # "wallet-confirm": "2", # "created-at": "1621843808662", # "updated-at": "1621843857137" # }, # ] # } # return self.parse_transactions(response['data'], currency, since, limit) def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ fetch all withdrawals made from an account https://huobiapi.github.io/docs/spot/v1/en/#search-for-existed-withdraws-and-deposits :param str code: unified currency code :param int [since]: the earliest time in ms to fetch withdrawals for :param int [limit]: the maximum number of withdrawals structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `transaction structures ` """ if limit is None or limit > 100: limit = 100 self.load_markets() currency = None if code is not None: currency = self.currency(code) request: dict = { 'type': 'withdraw', 'direct': 'next', 'from': 0, # From 'id' ... if you want to get results after a particular transaction id, pass the id in params.from } if currency is not None: request['currency'] = currency['id'] if limit is not None: request['size'] = limit # max 100 response = self.spotPrivateGetV1QueryDepositWithdraw(self.extend(request, params)) # # { # "status": "ok", # "data": [ # { # "id": "61335312", # "type": "withdraw", # "sub-type": "NORMAL", # "currency": "usdt", # "chain": "trc20usdt", # "tx-hash": "30a3111f2fead74fae45c6218ca3150fc33cab2aa59cfe41526b96aae79ce4ec", # "amount": "12.000000000000000000", # "from-addr-tag": "", # "address-id": "27321591", # "address": "TRf5JacJQRsF4Nm2zu11W6maDGeiEWQu9e", # "address-tag": "", # "fee": "1.000000000000000000", # "state": "confirmed", # "created-at": "1621852316553", # "updated-at": "1621852467041" # }, # ] # } # return self.parse_transactions(response['data'], currency, since, limit) def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction: # # fetchDeposits # # { # "id": "75115912", # "type": "deposit", # "sub-type": "NORMAL", # "request-id": "trc20usdt-a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff-200", # "currency": "usdt", # "chain": "trc20usdt", # "tx-hash": "a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff", # "amount": "2849.000000000000000000", # "from-addr-tag": "", # "address-id": "0", # "address": "TRFTd1FxepQE6CnpwzUEMEbFaLm5bJK67s", # "address-tag": "", # "fee": "0", # "state": "safe", # "wallet-confirm": "2", # "created-at": "1621843808662", # "updated-at": "1621843857137" # }, # # fetchWithdrawals # # { # "id": "61335312", # "type": "withdraw", # "sub-type": "NORMAL", # "currency": "usdt", # "chain": "trc20usdt", # "tx-hash": "30a3111f2fead74fae45c6218ca3150fc33cab2aa59cfe41526b96aae79ce4ec", # "amount": "12.000000000000000000", # "from-addr-tag": "", # "address-id": "27321591", # "address": "TRf5JacJQRsF4Nm2zu11W6maDGeiEWQu9e", # "address-tag": "", # "fee": "1.000000000000000000", # "state": "confirmed", # "created-at": "1621852316553", # "updated-at": "1621852467041" # } # # withdraw # # { # "status": "ok", # "data": "99562054" # } # timestamp = self.safe_integer(transaction, 'created-at') code = self.safe_currency_code(self.safe_string(transaction, 'currency')) type = self.safe_string(transaction, 'type') if type == 'withdraw': type = 'withdrawal' feeCost = self.safe_string(transaction, 'fee') if feeCost is not None: feeCost = Precise.string_abs(feeCost) networkId = self.safe_string(transaction, 'chain') txHash = self.safe_string(transaction, 'tx-hash') if networkId == 'ETH' and txHash.find('0x') < 0: txHash = '0x' + txHash subType = self.safe_string(transaction, 'sub-type') internal = subType == 'FAST' return { 'info': transaction, 'id': self.safe_string_2(transaction, 'id', 'data'), 'txid': txHash, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'network': self.network_id_to_code(networkId), 'address': self.safe_string(transaction, 'address'), 'addressTo': None, 'addressFrom': None, 'tag': self.safe_string(transaction, 'address-tag'), 'tagTo': None, 'tagFrom': None, 'type': type, 'amount': self.safe_number(transaction, 'amount'), 'currency': code, 'status': self.parse_transaction_status(self.safe_string(transaction, 'state')), 'updated': self.safe_integer(transaction, 'updated-at'), 'comment': None, 'internal': internal, 'fee': { 'currency': code, 'cost': self.parse_number(feeCost), 'rate': None, }, } def parse_transaction_status(self, status: Str): statuses: dict = { # deposit statuses 'unknown': 'failed', 'confirming': 'pending', 'confirmed': 'ok', 'safe': 'ok', 'orphan': 'failed', # withdrawal statuses 'submitted': 'pending', 'canceled': 'canceled', 'reexamine': 'pending', 'reject': 'failed', 'pass': 'pending', 'wallet-reject': 'failed', # 'confirmed': 'ok', # present in deposit statuses 'confirm-error': 'failed', 'repealed': 'failed', 'wallet-transfer': 'pending', 'pre-transfer': 'pending', 'verifying': 'pending', } return self.safe_string(statuses, status, status) def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction: """ https://www.htx.com/en-us/opend/newApiPages/?id=7ec4cc41-7773-11ed-9966-0242ac110003 make a withdrawal :param str code: unified currency code :param float amount: the amount to withdraw :param str address: the address to withdraw to :param str tag: :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `transaction structure ` """ tag, params = self.handle_withdraw_tag_and_params(tag, params) self.load_markets() self.check_address(address) currency = self.currency(code) request: dict = { 'address': address, # only supports existing addresses in your withdraw address list 'currency': currency['id'].lower(), } if tag is not None: request['addr-tag'] = tag # only for XRP? networkCode = None networkCode, params = self.handle_network_code_and_params(params) if networkCode is not None: request['chain'] = self.network_code_to_id(networkCode, code) amount = float(self.currency_to_precision(code, amount, networkCode)) withdrawOptions = self.safe_value(self.options, 'withdraw', {}) if self.safe_bool(withdrawOptions, 'includeFee', False): fee = self.safe_number(params, 'fee') if fee is None: currencies = self.fetch_currencies() self.currencies = self.map_to_safe_map(self.deep_extend(self.currencies, currencies)) targetNetwork = self.safe_value(currency['networks'], networkCode, {}) fee = self.safe_number(targetNetwork, 'fee') if fee is None: raise ArgumentsRequired(self.id + ' withdraw() function can not find withdraw fee for chosen network. You need to re-load markets with "exchange.loadMarkets(True)", or provide the "fee" parameter') # fee needs to be deducted from whole amount feeString = self.currency_to_precision(code, fee, networkCode) params = self.omit(params, 'fee') amountString = self.number_to_string(amount) amountSubtractedString = Precise.string_sub(amountString, feeString) amountSubtracted = float(amountSubtractedString) request['fee'] = float(feeString) amount = float(self.currency_to_precision(code, amountSubtracted, networkCode)) request['amount'] = amount response = self.spotPrivatePostV1DwWithdrawApiCreate(self.extend(request, params)) # # { # "status": "ok", # "data": "99562054" # } # return self.parse_transaction(response, currency) def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry: # # transfer # # { # "data": 12345, # "status": "ok" # } # id = self.safe_string(transfer, 'data') code = self.safe_currency_code(None, currency) return { 'info': transfer, 'id': id, 'timestamp': None, 'datetime': None, 'currency': code, 'amount': None, 'fromAccount': None, 'toAccount': None, 'status': None, } def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry: """ transfer currency internally between wallets on the same account https://huobiapi.github.io/docs/dm/v1/en/#transfer-margin-between-spot-account-and-future-account https://huobiapi.github.io/docs/spot/v1/en/#transfer-fund-between-spot-account-and-future-contract-account https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-transfer-margin-between-spot-account-and-usdt-margined-contracts-account https://huobiapi.github.io/docs/spot/v1/en/#transfer-asset-from-spot-trading-account-to-cross-margin-account-cross https://huobiapi.github.io/docs/spot/v1/en/#transfer-asset-from-spot-trading-account-to-isolated-margin-account-isolated https://huobiapi.github.io/docs/spot/v1/en/#transfer-asset-from-cross-margin-account-to-spot-trading-account-cross https://huobiapi.github.io/docs/spot/v1/en/#transfer-asset-from-isolated-margin-account-to-spot-trading-account-isolated :param str code: unified currency code :param float amount: amount to transfer :param str fromAccount: account to transfer from 'spot', 'future', 'swap' :param str toAccount: account to transfer to 'spot', 'future', 'swap' :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.symbol]: used for isolated margin transfer :param str [params.subType]: 'linear' or 'inverse', only used when transfering to/from swap accounts :returns dict: a `transfer structure ` """ self.load_markets() currency = self.currency(code) request: dict = { 'currency': currency['id'], 'amount': float(self.currency_to_precision(code, amount)), } subType = None subType, params = self.handle_sub_type_and_params('transfer', None, params) fromAccountId = self.convert_type_to_account(fromAccount) toAccountId = self.convert_type_to_account(toAccount) toCross = toAccountId == 'cross' fromCross = fromAccountId == 'cross' toIsolated = self.in_array(toAccountId, self.ids) fromIsolated = self.in_array(fromAccountId, self.ids) fromSpot = fromAccountId == 'pro' toSpot = toAccountId == 'pro' if fromSpot and toSpot: raise BadRequest(self.id + ' transfer() cannot make a transfer between ' + fromAccount + ' and ' + toAccount) fromOrToFuturesAccount = (fromAccountId == 'futures') or (toAccountId == 'futures') response = None if fromOrToFuturesAccount: type = fromAccountId + '-to-' + toAccountId type = self.safe_string(params, 'type', type) request['type'] = type response = self.spotPrivatePostV1FuturesTransfer(self.extend(request, params)) elif fromSpot and toCross: response = self.privatePostCrossMarginTransferIn(self.extend(request, params)) elif fromCross and toSpot: response = self.privatePostCrossMarginTransferOut(self.extend(request, params)) elif fromSpot and toIsolated: request['symbol'] = toAccountId response = self.privatePostDwTransferInMargin(self.extend(request, params)) elif fromIsolated and toSpot: request['symbol'] = fromAccountId response = self.privatePostDwTransferOutMargin(self.extend(request, params)) else: if subType == 'linear': if (fromAccountId == 'swap') or (fromAccount == 'linear-swap'): fromAccountId = 'linear-swap' else: toAccountId = 'linear-swap' # check if cross-margin or isolated symbol = self.safe_string(params, 'symbol') params = self.omit(params, 'symbol') if symbol is not None: symbol = self.market_id(symbol) request['margin-account'] = symbol else: request['margin-account'] = 'USDT' # cross-margin request['from'] = 'spot' if fromSpot else fromAccountId request['to'] = 'spot' if toSpot else toAccountId response = self.v2PrivatePostAccountTransfer(self.extend(request, params)) # # { # "code": "200", # "data": "660150061", # "message": "Succeed", # "success": True, # "print-log": True # } # return self.parse_transfer(response, currency) def fetch_isolated_borrow_rates(self, params={}) -> IsolatedBorrowRates: """ fetch the borrow interest rates of all currencies https://huobiapi.github.io/docs/spot/v1/en/#get-loan-interest-rate-and-quota-isolated :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a list of `isolated borrow rate structures ` """ self.load_markets() response = self.spotPrivateGetV1MarginLoanInfo(params) # # { # "status": "ok", # "data": [ # { # "symbol": "1inchusdt", # "currencies": [ # { # "currency": "1inch", # "interest-rate": "0.00098", # "min-loan-amt": "90.000000000000000000", # "max-loan-amt": "1000.000000000000000000", # "loanable-amt": "0.0", # "actual-rate": "0.00098" # }, # { # "currency": "usdt", # "interest-rate": "0.00098", # "min-loan-amt": "100.000000000000000000", # "max-loan-amt": "1000.000000000000000000", # "loanable-amt": "0.0", # "actual-rate": "0.00098" # } # ] # }, # ... # ] # } # data = self.safe_value(response, 'data', []) return self.parse_isolated_borrow_rates(data) def parse_isolated_borrow_rate(self, info: dict, market: Market = None) -> IsolatedBorrowRate: # # { # "symbol": "1inchusdt", # "currencies": [ # { # "currency": "1inch", # "interest-rate": "0.00098", # "min-loan-amt": "90.000000000000000000", # "max-loan-amt": "1000.000000000000000000", # "loanable-amt": "0.0", # "actual-rate": "0.00098" # }, # { # "currency": "usdt", # "interest-rate": "0.00098", # "min-loan-amt": "100.000000000000000000", # "max-loan-amt": "1000.000000000000000000", # "loanable-amt": "0.0", # "actual-rate": "0.00098" # } # ] # }, # marketId = self.safe_string(info, 'symbol') symbol = self.safe_symbol(marketId, market) currencies = self.safe_value(info, 'currencies', []) baseData = self.safe_value(currencies, 0) quoteData = self.safe_value(currencies, 1) baseId = self.safe_string(baseData, 'currency') quoteId = self.safe_string(quoteData, 'currency') return { 'symbol': symbol, 'base': self.safe_currency_code(baseId), 'baseRate': self.safe_number(baseData, 'actual-rate'), 'quote': self.safe_currency_code(quoteId), 'quoteRate': self.safe_number(quoteData, 'actual-rate'), 'period': 86400000, 'timestamp': None, 'datetime': None, 'info': info, } def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-historical-funding-rate https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-historical-funding-rate fetches historical funding rate prices :param str symbol: unified symbol of the market to fetch the funding rate history for :param int [since]: not used by huobi, but filtered internally by ccxt :param int [limit]: not used by huobi, but filtered internally by ccxt :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns dict[]: a list of `funding rate structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument') paginate = False paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate') if paginate: return self.fetch_paginated_call_cursor('fetchFundingRateHistory', symbol, since, limit, params, 'current_page', 'page_index', 1, 50) self.load_markets() market = self.market(symbol) request: dict = { 'contract_code': market['id'], } if limit is not None: request['page_size'] = limit else: request['page_size'] = 50 # max response = None if market['inverse']: response = self.contractPublicGetSwapApiV1SwapHistoricalFundingRate(self.extend(request, params)) elif market['linear']: response = self.contractPublicGetLinearSwapApiV1SwapHistoricalFundingRate(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchFundingRateHistory() supports inverse and linear swaps only') # # { # "status": "ok", # "data": { # "total_page": 62, # "current_page": 1, # "total_size": 1237, # "data": [ # { # "avg_premium_index": "-0.000208064395065541", # "funding_rate": "0.000100000000000000", # "realized_rate": "0.000100000000000000", # "funding_time": "1638921600000", # "contract_code": "BTC-USDT", # "symbol": "BTC", # "fee_asset": "USDT" # }, # ] # }, # "ts": 1638939294277 # } # data = self.safe_value(response, 'data') cursor = self.safe_value(data, 'current_page') result = self.safe_value(data, 'data', []) rates = [] for i in range(0, len(result)): entry = result[i] entry['current_page'] = cursor marketId = self.safe_string(entry, 'contract_code') symbolInner = self.safe_symbol(marketId) timestamp = self.safe_integer(entry, 'funding_time') rates.append({ 'info': entry, 'symbol': symbolInner, 'fundingRate': self.safe_number(entry, 'funding_rate'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), }) sorted = self.sort_by(rates, 'timestamp') return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit) def parse_funding_rate(self, contract, market: Market = None) -> FundingRate: # # { # "status": "ok", # "data": { # "estimated_rate": "0.000100000000000000", # "funding_rate": "0.000100000000000000", # "contract_code": "BCH-USD", # "symbol": "BCH", # "fee_asset": "BCH", # "funding_time": "1639094400000", # "next_funding_time": "1639123200000" # }, # "ts": 1639085854775 # } # nextFundingRate = self.safe_number(contract, 'estimated_rate') fundingTimestamp = self.safe_integer(contract, 'funding_time') nextFundingTimestamp = self.safe_integer(contract, 'next_funding_time') fundingTimeString = self.safe_string(contract, 'funding_time') nextFundingTimeString = self.safe_string(contract, 'next_funding_time') millisecondsInterval = Precise.string_sub(nextFundingTimeString, fundingTimeString) marketId = self.safe_string(contract, 'contract_code') symbol = self.safe_symbol(marketId, market) return { 'info': contract, 'symbol': symbol, 'markPrice': None, 'indexPrice': None, 'interestRate': None, 'estimatedSettlePrice': None, 'timestamp': None, 'datetime': None, 'fundingRate': self.safe_number(contract, 'funding_rate'), 'fundingTimestamp': fundingTimestamp, 'fundingDatetime': self.iso8601(fundingTimestamp), 'nextFundingRate': nextFundingRate, 'nextFundingTimestamp': nextFundingTimestamp, 'nextFundingDatetime': self.iso8601(nextFundingTimestamp), 'previousFundingRate': None, 'previousFundingTimestamp': None, 'previousFundingDatetime': None, 'interval': self.parse_funding_interval(millisecondsInterval), } def parse_funding_interval(self, interval): intervals: dict = { '3600000': '1h', '14400000': '4h', '28800000': '8h', '57600000': '16h', '86400000': '24h', } return self.safe_string(intervals, interval, interval) def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate: """ fetch the current funding rate https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-funding-rate https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-funding-rate :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `funding rate structure ` """ self.load_markets() market = self.market(symbol) request: dict = { 'contract_code': market['id'], } response = None if market['inverse']: response = self.contractPublicGetSwapApiV1SwapFundingRate(self.extend(request, params)) elif market['linear']: response = self.contractPublicGetLinearSwapApiV1SwapFundingRate(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchFundingRate() supports inverse and linear swaps only') # # { # "status": "ok", # "data": { # "estimated_rate": "0.000100000000000000", # "funding_rate": "0.000100000000000000", # "contract_code": "BTC-USDT", # "symbol": "BTC", # "fee_asset": "USDT", # "funding_time": "1603699200000", # "next_funding_time": "1603728000000" # }, # "ts": 1603696494714 # } # result = self.safe_value(response, 'data', {}) return self.parse_funding_rate(result, market) def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates: """ fetch the funding rate for multiple markets https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-a-batch-of-funding-rate https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-a-batch-of-funding-rate :param str[]|None symbols: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `funding rate structures `, indexed by market symbols """ self.load_markets() symbols = self.market_symbols(symbols) defaultSubType = self.safe_string(self.options, 'defaultSubType', 'linear') subType = None subType, params = self.handle_option_and_params(params, 'fetchFundingRates', 'subType', defaultSubType) if symbols is not None: firstSymbol = self.safe_string(symbols, 0) market = self.market(firstSymbol) isLinear = market['linear'] subType = 'linear' if isLinear else 'inverse' request: dict = { # 'contract_code': market['id'], } response = None if subType == 'linear': response = self.contractPublicGetLinearSwapApiV1SwapBatchFundingRate(self.extend(request, params)) elif subType == 'inverse': response = self.contractPublicGetSwapApiV1SwapBatchFundingRate(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchFundingRates() not support self market type') # # { # "status": "ok", # "data": [ # { # "estimated_rate": "0.000100000000000000", # "funding_rate": "0.000100000000000000", # "contract_code": "MANA-USDT", # "symbol": "MANA", # "fee_asset": "USDT", # "funding_time": "1643356800000", # "next_funding_time": "1643385600000", # "trade_partition":"USDT" # }, # ], # "ts": 1643346173103 # } # data = self.safe_value(response, 'data', []) return self.parse_funding_rates(data, symbols) def fetch_borrow_interest(self, code: Str = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[BorrowInterest]: """ fetch the interest owed by the user for borrowing currency for margin trading https://huobiapi.github.io/docs/spot/v1/en/#search-past-margin-orders-cross https://huobiapi.github.io/docs/spot/v1/en/#search-past-margin-orders-isolated :param str code: unified currency code :param str symbol: unified market symbol when fetch interest in isolated markets :param int [since]: the earliest time in ms to fetch borrrow interest for :param int [limit]: the maximum number of structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `borrow interest structures ` """ self.load_markets() marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchBorrowInterest', params) marginMode = 'cross' if (marginMode is None) else marginMode request: dict = {} if since is not None: request['start-date'] = self.yyyymmdd(since) if limit is not None: request['size'] = limit market = None response = None if marginMode == 'isolated': if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] response = self.privateGetMarginLoanOrders(self.extend(request, params)) else: # Cross if code is not None: currency = self.currency(code) request['currency'] = currency['id'] response = self.privateGetCrossMarginLoanOrders(self.extend(request, params)) # # { # "status":"ok", # "data":[ # { # "loan-balance":"0.100000000000000000", # "interest-balance":"0.000200000000000000", # "loan-amount":"0.100000000000000000", # "accrued-at":1511169724531, # "interest-amount":"0.000200000000000000", # "filled-points":"0.2", # "filled-ht":"0.2", # "currency":"btc", # "id":394, # "state":"accrual", # "account-id":17747, # "user-id":119913, # "created-at":1511169724531 # } # ] # } # data = self.safe_value(response, 'data') interest = self.parse_borrow_interests(data, market) return self.filter_by_currency_since_limit(interest, code, since, limit) def parse_borrow_interest(self, info: dict, market: Market = None) -> BorrowInterest: # isolated # { # "interest-rate":"0.000040830000000000", # "user-id":35930539, # "account-id":48916071, # "updated-at":1649320794195, # "deduct-rate":"1", # "day-interest-rate":"0.000980000000000000", # "hour-interest-rate":"0.000040830000000000", # "loan-balance":"100.790000000000000000", # "interest-balance":"0.004115260000000000", # "loan-amount":"100.790000000000000000", # "paid-coin":"0.000000000000000000", # "accrued-at":1649320794148, # "created-at":1649320794148, # "interest-amount":"0.004115260000000000", # "deduct-amount":"0", # "deduct-currency":"", # "paid-point":"0.000000000000000000", # "currency":"usdt", # "symbol":"ltcusdt", # "id":20242721, # } # # cross # { # "id":3416576, # "user-id":35930539, # "account-id":48956839, # "currency":"usdt", # "loan-amount":"102", # "loan-balance":"102", # "interest-amount":"0.00416466", # "interest-balance":"0.00416466", # "created-at":1649322735333, # "accrued-at":1649322735382, # "state":"accrual", # "filled-points":"0", # "filled-ht":"0" # } # marketId = self.safe_string(info, 'symbol') marginMode = 'cross' if (marketId is None) else 'isolated' market = self.safe_market(marketId) symbol = self.safe_string(market, 'symbol') timestamp = self.safe_integer(info, 'accrued-at') return { 'info': info, 'symbol': symbol, 'currency': self.safe_currency_code(self.safe_string(info, 'currency')), 'interest': self.safe_number(info, 'interest-amount'), 'interestRate': self.safe_number(info, 'interest-rate'), 'amountBorrowed': self.safe_number(info, 'loan-amount'), 'marginMode': marginMode, 'timestamp': timestamp, # Interest accrued time 'datetime': self.iso8601(timestamp), } def nonce(self): return self.milliseconds() - self.options['timeDifference'] def sign(self, path, api='public', method='GET', params={}, headers=None, body=None): url = '/' query = self.omit(params, self.extract_params(path)) if isinstance(api, str): # signing implementation for the old endpoints if (api == 'public') or (api == 'private'): url += self.version elif (api == 'v2Public') or (api == 'v2Private'): url += 'v2' url += '/' + self.implode_params(path, params) if api == 'private' or api == 'v2Private': self.check_required_credentials() timestamp = self.ymdhms(self.nonce(), 'T') request: dict = { 'SignatureMethod': 'HmacSHA256', 'SignatureVersion': '2', 'AccessKeyId': self.apiKey, 'Timestamp': timestamp, } if method != 'POST': request = self.extend(request, query) sortedRequest = self.keysort(request) auth = self.urlencode(sortedRequest, True) # True is a go only requirment # unfortunately, PHP demands double quotes for the escaped newline symbol payload = "\n".join([method, self.hostname, url, auth]) # eslint-disable-line quotes signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64') auth += '&' + self.urlencode({'Signature': signature}) url += '?' + auth if method == 'POST': body = self.json(query) headers = { 'Content-Type': 'application/json', } else: headers = { 'Content-Type': 'application/x-www-form-urlencoded', } else: if query: url += '?' + self.urlencode(query) url = self.implode_params(self.urls['api'][api], { 'hostname': self.hostname, }) + url else: # signing implementation for the new endpoints # type, access = api type = self.safe_string(api, 0) access = self.safe_string(api, 1) levelOneNestedPath = self.safe_string(api, 2) levelTwoNestedPath = self.safe_string(api, 3) hostname = None hostnames = self.safe_value(self.urls['hostnames'], type) if not isinstance(hostnames, str): hostnames = self.safe_value(hostnames, levelOneNestedPath) if (not isinstance(hostnames, str)) and (levelTwoNestedPath is not None): hostnames = self.safe_value(hostnames, levelTwoNestedPath) hostname = hostnames url += self.implode_params(path, params) if access == 'public': if query: url += '?' + self.urlencode(query) elif access == 'private': self.check_required_credentials() if method == 'POST': options = self.safe_value(self.options, 'broker', {}) id = self.safe_string(options, 'id', 'AA03022abc') if path.find('cancel') == -1 and path.endswith('order'): # swap order placement channelCode = self.safe_string(params, 'channel_code') if channelCode is None: params['channel_code'] = id elif path.endswith('orders/place'): # spot order placement clientOrderId = self.safe_string(params, 'client-order-id') if clientOrderId is None: params['client-order-id'] = id + self.uuid() timestamp = self.ymdhms(self.nonce(), 'T') request: dict = { 'SignatureMethod': 'HmacSHA256', 'SignatureVersion': '2', 'AccessKeyId': self.apiKey, 'Timestamp': timestamp, } # sorting needs such flow exactly, before urlencoding(more at: https://github.com/ccxt/ccxt/issues/24930 ) request = self.keysort(request) if method != 'POST': sortedQuery = self.keysort(query) request = self.extend(request, sortedQuery) auth = self.urlencode(request, True).replace('%2c', '%2C') # in c# it manually needs to be uppercased # unfortunately, PHP demands double quotes for the escaped newline symbol payload = "\n".join([method, hostname, url, auth]) # eslint-disable-line quotes signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64') auth += '&' + self.urlencode({'Signature': signature}) url += '?' + auth if method == 'POST': body = self.json(query) if len(body) == 2: body = '{}' headers = { 'Content-Type': 'application/json', } else: headers = { 'Content-Type': 'application/x-www-form-urlencoded', } url = self.implode_params(self.urls['api'][type], { 'hostname': hostname, }) + url return {'url': url, 'method': method, 'body': body, 'headers': headers} def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody): if response is None: return None # fallback to default error handler if 'status' in response: # # {"status":"error","err-code":"order-limitorder-amount-min-error","err-msg":"limit order amount error, min: `0.001`","data":null} # {"status":"ok","data":{"errors":[{"order_id":"1349442392365359104","err_code":1061,"err_msg":"The order does not exist."}],"successes":""},"ts":1741773744526} # status = self.safe_string(response, 'status') if status == 'error': code = self.safe_string_2(response, 'err-code', 'err_code') feedback = self.id + ' ' + body self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback) self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback) message = self.safe_string_2(response, 'err-msg', 'err_msg') self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback) raise ExchangeError(feedback) if 'code' in response: # {code: '1003', message: 'invalid signature'} feedback = self.id + ' ' + body code = self.safe_string(response, 'code') self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback) data = self.safe_dict(response, 'data') errorsList = self.safe_list(data, 'errors') if errorsList is not None: first = self.safe_dict(errorsList, 0) errcode = self.safe_string(first, 'err_code') errmessage = self.safe_string(first, 'err_msg') feedBack = self.id + ' ' + body self.throw_exactly_matched_exception(self.exceptions['exact'], errcode, feedBack) self.throw_exactly_matched_exception(self.exceptions['exact'], errmessage, feedBack) return None def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetch the history of funding payments paid and received on self account https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-account-financial-records-via-multiple-fields-new # linear swaps https://huobiapi.github.io/docs/dm/v1/en/#query-financial-records-via-multiple-fields-new # coin-m futures https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-financial-records-via-multiple-fields-new # coin-m swaps :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch funding history for :param int [limit]: the maximum number of funding history structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `funding history structure ` """ self.load_markets() market = self.market(symbol) marketType, query = self.handle_market_type_and_params('fetchFundingHistory', market, params) request: dict = { 'type': '30,31', } if since is not None: request['start_date'] = since response = None if marketType == 'swap': request['contract'] = market['id'] if market['linear']: # # { # "status": "ok", # "data": { # "financial_record": [ # { # "id": "1320088022", # "type": "30", # "amount": "0.004732510000000000", # "ts": "1641168019321", # "contract_code": "BTC-USDT", # "asset": "USDT", # "margin_account": "BTC-USDT", # "face_margin_account": '' # }, # ], # "remain_size": "0", # "next_id": null # }, # "ts": "1641189898425" # } # marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchFundingHistory', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': request['mar_acct'] = market['id'] else: request['mar_acct'] = market['quoteId'] response = self.contractPrivatePostLinearSwapApiV3SwapFinancialRecordExact(self.extend(request, query)) else: # # { # "code": 200, # "msg": "", # "data": [ # { # "query_id": 138798248, # "id": 117840, # "type": 5, # "amount": -0.024464850000000000, # "ts": 1638758435635, # "contract_code": "BTC-USDT-211210", # "asset": "USDT", # "margin_account": "USDT", # "face_margin_account": "" # } # ], # "ts": 1604312615051 # } # response = self.contractPrivatePostSwapApiV3SwapFinancialRecordExact(self.extend(request, query)) else: request['symbol'] = market['id'] response = self.contractPrivatePostApiV3ContractFinancialRecordExact(self.extend(request, query)) data = self.safe_list(response, 'data', []) return self.parse_incomes(data, market, since, limit) def set_leverage(self, leverage: int, symbol: Str = None, params={}): """ set the level of leverage for a market https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-switch-leverage https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-switch-leverage https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#switch-leverage https://huobiapi.github.io/docs/dm/v1/en/#switch-leverage # Coin-m futures :param float leverage: the rate of leverage :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: response from the exchange """ if symbol is None: raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument') self.load_markets() market = self.market(symbol) marketType, query = self.handle_market_type_and_params('setLeverage', market, params) request: dict = { 'lever_rate': leverage, } if marketType == 'future' and market['inverse']: request['symbol'] = market['settleId'] else: request['contract_code'] = market['id'] response = None if market['linear']: marginMode = None marginMode, params = self.handle_margin_mode_and_params('setLeverage', params) marginMode = 'cross' if (marginMode is None) else marginMode if marginMode == 'isolated': response = self.contractPrivatePostLinearSwapApiV1SwapSwitchLeverRate(self.extend(request, query)) elif marginMode == 'cross': response = self.contractPrivatePostLinearSwapApiV1SwapCrossSwitchLeverRate(self.extend(request, query)) else: raise NotSupported(self.id + ' setLeverage() not support self market type') # # { # "status": "ok", # "data": { # "contract_code": "BTC-USDT", # "lever_rate": "100", # "margin_mode": "isolated" # }, # "ts": "1641184710649" # } # else: if marketType == 'future': response = self.contractPrivatePostApiV1ContractSwitchLeverRate(self.extend(request, query)) elif marketType == 'swap': response = self.contractPrivatePostSwapApiV1SwapSwitchLeverRate(self.extend(request, query)) else: raise NotSupported(self.id + ' setLeverage() not support self market type') # # future # { # "status": "ok", # "data": {symbol: "BTC", lever_rate: 5}, # "ts": 1641184578678 # } # # swap # # { # "status": "ok", # "data": {contract_code: "BTC-USD", lever_rate: "5"}, # "ts": "1641184652979" # } # return response def parse_income(self, income, market: Market = None): # # { # "id": "1667161118", # "symbol": "BTC", # "type": "31", # "amount": "-2.11306593188E-7", # "ts": "1641139308983", # "contract_code": "BTC-USD" # } # marketId = self.safe_string(income, 'contract_code') symbol = self.safe_symbol(marketId, market) amount = self.safe_number(income, 'amount') timestamp = self.safe_integer(income, 'ts') id = self.safe_string(income, 'id') currencyId = self.safe_string_2(income, 'symbol', 'asset') code = self.safe_currency_code(currencyId) return { 'info': income, 'symbol': symbol, 'code': code, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'id': id, 'amount': amount, } def parse_position(self, position: dict, market: Market = None): # # { # "symbol": "BTC", # "contract_code": "BTC-USDT", # "volume": "1.000000000000000000", # "available": "1.000000000000000000", # "frozen": "0E-18", # "cost_open": "47162.000000000000000000", # "cost_hold": "47151.300000000000000000", # "profit_unreal": "0.007300000000000000", # "profit_rate": "-0.000144183876850008", # "lever_rate": "2", # "position_margin": "23.579300000000000000", # "direction": "buy", # "profit": "-0.003400000000000000", # "last_price": "47158.6", # "margin_asset": "USDT", # "margin_mode": "isolated", # "margin_account": "BTC-USDT", # "margin_balance": "24.973020070000000000", # "margin_position": "23.579300000000000000", # "margin_frozen": "0", # "margin_available": "1.393720070000000000", # "profit_real": "0E-18", # "risk_rate": "1.044107779705080303", # "withdraw_available": "1.386420070000000000000000000000000000", # "liquidation_price": "22353.229148614609571788", # "adjust_factor": "0.015000000000000000", # "margin_static": "24.965720070000000000" # } # market = self.safe_market(self.safe_string(position, 'contract_code')) symbol = market['symbol'] contracts = self.safe_string(position, 'volume') contractSize = self.safe_value(market, 'contractSize') contractSizeString = self.number_to_string(contractSize) entryPrice = self.safe_number(position, 'cost_open') initialMargin = self.safe_string(position, 'position_margin') rawSide = self.safe_string(position, 'direction') side = 'long' if (rawSide == 'buy') else 'short' unrealizedProfit = self.safe_number(position, 'profit_unreal') marginMode = self.safe_string(position, 'margin_mode') leverage = self.safe_string(position, 'lever_rate') percentage = Precise.string_mul(self.safe_string(position, 'profit_rate'), '100') lastPrice = self.safe_string(position, 'last_price') faceValue = Precise.string_mul(contracts, contractSizeString) notional = None if market['linear']: notional = Precise.string_mul(faceValue, lastPrice) else: notional = Precise.string_div(faceValue, lastPrice) marginMode = 'cross' intialMarginPercentage = Precise.string_div(initialMargin, notional) collateral = self.safe_string(position, 'margin_balance') liquidationPrice = self.safe_number(position, 'liquidation_price') adjustmentFactor = self.safe_string(position, 'adjust_factor') maintenanceMarginPercentage = Precise.string_div(adjustmentFactor, leverage) maintenanceMargin = Precise.string_mul(maintenanceMarginPercentage, notional) marginRatio = Precise.string_div(maintenanceMargin, collateral) return self.safe_position({ 'info': position, 'id': None, 'symbol': symbol, 'contracts': self.parse_number(contracts), 'contractSize': contractSize, 'entryPrice': entryPrice, 'collateral': self.parse_number(collateral), 'side': side, 'unrealizedPnl': unrealizedProfit, 'leverage': self.parse_number(leverage), 'percentage': self.parse_number(percentage), 'marginMode': marginMode, 'notional': self.parse_number(notional), 'markPrice': None, 'lastPrice': None, 'liquidationPrice': liquidationPrice, 'initialMargin': self.parse_number(initialMargin), 'initialMarginPercentage': self.parse_number(intialMarginPercentage), 'maintenanceMargin': self.parse_number(maintenanceMargin), 'maintenanceMarginPercentage': self.parse_number(maintenanceMarginPercentage), 'marginRatio': self.parse_number(marginRatio), 'timestamp': None, 'datetime': None, 'hedged': None, 'lastUpdateTimestamp': None, 'stopLossPrice': None, 'takeProfitPrice': None, }) def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]: """ fetch all open positions https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-query-user-39-s-position-information https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-query-user-s-position-information https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-user-s-position-information https://huobiapi.github.io/docs/dm/v1/en/#query-user-s-position-information :param str[] [symbols]: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.subType]: 'linear' or 'inverse' :param str [params.type]: *inverse only* 'future', or 'swap' :param str [params.marginMode]: *linear only* 'cross' or 'isolated' :returns dict[]: a list of `position structure ` """ self.load_markets() symbols = self.market_symbols(symbols) market = None if symbols is not None: symbolsLength = len(symbols) if symbolsLength > 0: first = self.safe_string(symbols, 0) market = self.market(first) marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchPositions', params, 'cross') subType = None subType, params = self.handle_sub_type_and_params('fetchPositions', market, params, 'linear') marketType = None marketType, params = self.handle_market_type_and_params('fetchPositions', market, params) if marketType == 'spot': marketType = 'future' response = None if subType == 'linear': if marginMode == 'isolated': response = self.contractPrivatePostLinearSwapApiV1SwapPositionInfo(params) elif marginMode == 'cross': response = self.contractPrivatePostLinearSwapApiV1SwapCrossPositionInfo(params) else: raise NotSupported(self.id + ' fetchPositions() not support self market type') # # { # "status": "ok", # "data": [ # { # "symbol": "BTC", # "contract_code": "BTC-USDT", # "volume": "1.000000000000000000", # "available": "1.000000000000000000", # "frozen": "0E-18", # "cost_open": "47162.000000000000000000", # "cost_hold": "47162.000000000000000000", # "profit_unreal": "0.047300000000000000", # "profit_rate": "0.002005852169119206", # "lever_rate": "2", # "position_margin": "23.604650000000000000", # "direction": "buy", # "profit": "0.047300000000000000", # "last_price": "47209.3", # "margin_asset": "USDT", # "margin_mode": "isolated", # "margin_account": "BTC-USDT" # } # ], # "ts": "1641108676768" # } # else: if marketType == 'future': response = self.contractPrivatePostApiV1ContractPositionInfo(params) elif marketType == 'swap': response = self.contractPrivatePostSwapApiV1SwapPositionInfo(params) else: raise NotSupported(self.id + ' fetchPositions() not support self market type') # # future # { # "status": "ok", # "data": [ # { # "symbol": "BTC", # "contract_code": "BTC220624", # "contract_type": "next_quarter", # "volume": "1.000000000000000000", # "available": "1.000000000000000000", # "frozen": "0E-18", # "cost_open": "49018.880000000009853343", # "cost_hold": "49018.880000000009853343", # "profit_unreal": "-8.62360608500000000000000000000000000000000000000E-7", # "profit_rate": "-0.000845439023678622", # "lever_rate": "2", # "position_margin": "0.001019583964880634", # "direction": "sell", # "profit": "-8.62360608500000000000000000000000000000000000000E-7", # "last_price": "49039.61" # } # ], # "ts": "1641109895199" # } # # swap # { # "status": "ok", # "data": [ # { # "symbol": "BTC", # "contract_code": "BTC-USD", # "volume": "1.000000000000000000", # "available": "1.000000000000000000", # "frozen": "0E-18", # "cost_open": "47150.000000000012353300", # "cost_hold": "47150.000000000012353300", # "profit_unreal": "0E-54", # "profit_rate": "-7.86E-16", # "lever_rate": "3", # "position_margin": "0.000706963591375044", # "direction": "buy", # "profit": "0E-54", # "last_price": "47150" # } # ], # "ts": "1641109636572" # } # data = self.safe_value(response, 'data', []) timestamp = self.safe_integer(response, 'ts') result = [] for i in range(0, len(data)): position = data[i] parsed = self.parse_position(position) result.append(self.extend(parsed, { 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), })) return self.filter_by_array_positions(result, 'symbol', symbols, False) def fetch_position(self, symbol: str, params={}): """ fetch data on a single open contract trade position https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-query-assets-and-positions https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-query-assets-and-positions https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-assets-and-positions https://huobiapi.github.io/docs/dm/v1/en/#query-assets-and-positions :param str symbol: unified market symbol of the market the position is held in, default is None :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `position structure ` """ self.load_markets() market = self.market(symbol) marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchPosition', params) marginMode = 'cross' if (marginMode is None) else marginMode marketType, query = self.handle_market_type_and_params('fetchPosition', market, params) request: dict = {} if market['future'] and market['inverse']: request['symbol'] = market['settleId'] else: if marginMode == 'cross': request['margin_account'] = 'USDT' # only allowed value request['contract_code'] = market['id'] response = None if market['linear']: if marginMode == 'isolated': response = self.contractPrivatePostLinearSwapApiV1SwapAccountPositionInfo(self.extend(request, query)) elif marginMode == 'cross': response = self.contractPrivatePostLinearSwapApiV1SwapCrossAccountPositionInfo(self.extend(request, query)) else: raise NotSupported(self.id + ' fetchPosition() not support self market type') # # isolated # # { # "status": "ok", # "data": [ # { # "positions": [], # "symbol": "BTC", # "margin_balance": 1.949728350000000000, # "margin_position": 0, # "margin_frozen": 0E-18, # "margin_available": 1.949728350000000000, # "profit_real": -0.050271650000000000, # "profit_unreal": 0, # "risk_rate": null, # "withdraw_available": 1.949728350000000000, # "liquidation_price": null, # "lever_rate": 20, # "adjust_factor": 0.150000000000000000, # "margin_static": 1.949728350000000000, # "contract_code": "BTC-USDT", # "margin_asset": "USDT", # "margin_mode": "isolated", # "margin_account": "BTC-USDT", # "trade_partition": "USDT", # "position_mode": "dual_side" # }, # ... opposite side position can be present here too(if hedge) # ], # "ts": 1653605008286 # } # # cross # # { # "status": "ok", # "data": { # "positions": [ # { # "symbol": "BTC", # "contract_code": "BTC-USDT", # "volume": "1.000000000000000000", # "available": "1.000000000000000000", # "frozen": "0E-18", # "cost_open": "29530.000000000000000000", # "cost_hold": "29530.000000000000000000", # "profit_unreal": "-0.010000000000000000", # "profit_rate": "-0.016931933626820200", # "lever_rate": "50", # "position_margin": "0.590400000000000000", # "direction": "buy", # "profit": "-0.010000000000000000", # "last_price": "29520", # "margin_asset": "USDT", # "margin_mode": "cross", # "margin_account": "USDT", # "contract_type": "swap", # "pair": "BTC-USDT", # "business_type": "swap", # "trade_partition": "USDT", # "position_mode": "dual_side" # }, # ... opposite side position can be present here too(if hedge) # ], # "futures_contract_detail": [ # { # "symbol": "BTC", # "contract_code": "BTC-USDT-220624", # "margin_position": "0", # "margin_frozen": "0E-18", # "margin_available": "1.497799766913531118", # "profit_unreal": "0", # "liquidation_price": null, # "lever_rate": "30", # "adjust_factor": "0.250000000000000000", # "contract_type": "quarter", # "pair": "BTC-USDT", # "business_type": "futures", # "trade_partition": "USDT" # }, # ... other items listed with different expiration(contract_code) # ], # "margin_mode": "cross", # "margin_account": "USDT", # "margin_asset": "USDT", # "margin_balance": "2.088199766913531118", # "margin_static": "2.098199766913531118", # "margin_position": "0.590400000000000000", # "margin_frozen": "0E-18", # "profit_real": "-0.016972710000000000", # "profit_unreal": "-0.010000000000000000", # "withdraw_available": "1.497799766913531118", # "risk_rate": "9.105496355562965147", # "contract_detail": [ # { # "symbol": "BTC", # "contract_code": "BTC-USDT", # "margin_position": "0.590400000000000000", # "margin_frozen": "0E-18", # "margin_available": "1.497799766913531118", # "profit_unreal": "-0.010000000000000000", # "liquidation_price": "27625.176468365024050352", # "lever_rate": "50", # "adjust_factor": "0.350000000000000000", # "contract_type": "swap", # "pair": "BTC-USDT", # "business_type": "swap", # "trade_partition": "USDT" # }, # ... all symbols listed # ], # "position_mode": "dual_side" # }, # "ts": "1653604697466" # } # else: if marketType == 'future': response = self.contractPrivatePostApiV1ContractAccountPositionInfo(self.extend(request, query)) elif marketType == 'swap': response = self.contractPrivatePostSwapApiV1SwapAccountPositionInfo(self.extend(request, query)) else: raise NotSupported(self.id + ' setLeverage() not support self market type') # # future, swap # # { # "status": "ok", # "data": [ # { # "symbol": "XRP", # "contract_code": "XRP-USD", # only present in swap # "margin_balance": 12.186361450698276582, # "margin_position": 5.036261079774375503, # "margin_frozen": 0E-18, # "margin_available": 7.150100370923901079, # "profit_real": -0.012672343876723438, # "profit_unreal": 0.163382354575000020, # "risk_rate": 2.344723929650649798, # "withdraw_available": 6.986718016348901059, # "liquidation_price": 0.271625200493799547, # "lever_rate": 5, # "adjust_factor": 0.075000000000000000, # "margin_static": 12.022979096123276562, # "positions": [ # { # "symbol": "XRP", # "contract_code": "XRP-USD", # # "contract_type": "self_week", # only present in future # "volume": 1.0, # "available": 1.0, # "frozen": 0E-18, # "cost_open": 0.394560000000000000, # "cost_hold": 0.394560000000000000, # "profit_unreal": 0.163382354575000020, # "profit_rate": 0.032232070910556005, # "lever_rate": 5, # "position_margin": 5.036261079774375503, # "direction": "buy", # "profit": 0.163382354575000020, # "last_price": 0.39712 # }, # ... opposite side position can be present here too(if hedge) # ] # } # ], # "ts": 1653600470199 # } # # cross usdt swap # # { # "status":"ok", # "data":{ # "positions":[], # "futures_contract_detail":[] # "margin_mode":"cross", # "margin_account":"USDT", # "margin_asset":"USDT", # "margin_balance":"1.000000000000000000", # "margin_static":"1.000000000000000000", # "margin_position":"0", # "margin_frozen":"1.000000000000000000", # "profit_real":"0E-18", # "profit_unreal":"0", # "withdraw_available":"0", # "risk_rate":"15.666666666666666666", # "contract_detail":[] # }, # "ts":"1645521118946" # } # data = self.safe_value(response, 'data') account = None if marginMode == 'cross': account = data else: account = self.safe_value(data, 0) omitted = self.omit(account, ['positions']) positions = self.safe_value(account, 'positions') position = None if market['future'] and market['inverse']: for i in range(0, len(positions)): entry = positions[i] if entry['contract_code'] == market['id']: position = entry break else: position = self.safe_value(positions, 0) timestamp = self.safe_integer(response, 'ts') parsed = self.parse_position(self.extend(position, omitted)) parsed['timestamp'] = timestamp parsed['datetime'] = self.iso8601(timestamp) return parsed def parse_ledger_entry_type(self, type): types: dict = { 'trade': 'trade', 'etf': 'trade', 'transact-fee': 'fee', 'fee-deduction': 'fee', 'transfer': 'transfer', 'credit': 'credit', 'liquidation': 'trade', 'interest': 'credit', 'deposit': 'deposit', 'withdraw': 'withdrawal', 'withdraw-fee': 'fee', 'exchange': 'exchange', 'other-types': 'transfer', 'rebate': 'rebate', } return self.safe_string(types, type, type) def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry: # # { # "accountId": 10000001, # "currency": "usdt", # "transactAmt": 10.000000000000000000, # "transactType": "transfer", # "transferType": "margin-transfer-out", # "transactId": 0, # "transactTime": 1629882331066, # "transferer": 28483123, # "transferee": 13496526 # } # currencyId = self.safe_string(item, 'currency') code = self.safe_currency_code(currencyId, currency) currency = self.safe_currency(currencyId, currency) id = self.safe_string(item, 'transactId') transferType = self.safe_string(item, 'transferType') timestamp = self.safe_integer(item, 'transactTime') account = self.safe_string(item, 'accountId') return self.safe_ledger_entry({ 'info': item, 'id': id, 'direction': self.safe_string(item, 'direction'), 'account': account, 'referenceId': id, 'referenceAccount': account, 'type': self.parse_ledger_entry_type(transferType), 'currency': code, 'amount': self.safe_number(item, 'transactAmt'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'before': None, 'after': None, 'status': None, 'fee': None, }, currency) def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]: """ fetch the history of changes, actions done by the user or operations that altered the balance of the user https://huobiapi.github.io/docs/spot/v1/en/#get-account-history :param str [code]: unified currency code, default is None :param int [since]: timestamp in ms of the earliest ledger entry, default is None :param int [limit]: max number of ledger entries to return, default is None :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: the latest time in ms to fetch entries for :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns dict: a `ledger structure ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate') if paginate: return self.fetch_paginated_call_dynamic('fetchLedger', code, since, limit, params, 500) accountId = self.fetch_account_id_by_type('spot', None, None, params) request: dict = { 'accountId': accountId, # 'currency': code, # 'transactTypes': 'all', # default all # 'startTime': 1546272000000, # 'endTime': 1546272000000, # 'sort': asc, # asc, desc # 'limit': 100, # range 1-500 # 'fromId': 323 # first record hasattr(self, ID) query for pagination } currency = None if code is not None: currency = self.currency(code) request['currency'] = currency['id'] if since is not None: request['startTime'] = since if limit is not None: request['limit'] = limit # max 500 request, params = self.handle_until_option('endTime', request, params) response = self.spotPrivateGetV2AccountLedger(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "accountId": 10000001, # "currency": "usdt", # "transactAmt": 10.000000000000000000, # "transactType": "transfer", # "transferType": "margin-transfer-out", # "transactId": 0, # "transactTime": 1629882331066, # "transferer": 28483123, # "transferee": 13496526 # }, # { # "accountId": 10000001, # "currency": "usdt", # "transactAmt": -10.000000000000000000, # "transactType": "transfer", # "transferType": "margin-transfer-in", # "transactId": 0, # "transactTime": 1629882096562, # "transferer": 13496526, # "transferee": 28483123 # } # ], # "nextId": 1624316679, # "ok": True # } # data = self.safe_value(response, 'data', []) return self.parse_ledger(data, currency, since, limit) def fetch_leverage_tiers(self, symbols: Strings = None, params={}) -> LeverageTiers: """ retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes :param str[]|None symbols: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `leverage tiers structures `, indexed by market symbols """ self.load_markets() response = self.contractPublicGetLinearSwapApiV1SwapAdjustfactor(params) # # { # "status": "ok", # "data": [ # { # "symbol": "MANA", # "contract_code": "MANA-USDT", # "margin_mode": "isolated", # "trade_partition": "USDT", # "list": [ # { # "lever_rate": 75, # "ladders": [ # { # "ladder": 0, # "min_size": 0, # "max_size": 999, # "adjust_factor": 0.7 # }, # ... # ] # } # ... # ] # }, # ... # ] # } # data = self.safe_list(response, 'data', []) return self.parse_leverage_tiers(data, symbols, 'contract_code') def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]: currencyId = self.safe_string(info, 'trade_partition') marketId = self.safe_string(info, 'contract_code') tiers = [] brackets = self.safe_list(info, 'list', []) for i in range(0, len(brackets)): item = brackets[i] leverage = self.safe_string(item, 'lever_rate') ladders = self.safe_list(item, 'ladders', []) for k in range(0, len(ladders)): bracket = ladders[k] adjustFactor = self.safe_string(bracket, 'adjust_factor') tiers.append({ 'tier': self.safe_integer(bracket, 'ladder'), 'symbol': self.safe_symbol(marketId, market, None, 'swap'), 'currency': self.safe_currency_code(currencyId), 'minNotional': self.safe_number(bracket, 'min_size'), 'maxNotional': self.safe_number(bracket, 'max_size'), 'maintenanceMarginRate': self.parse_number(Precise.string_div(adjustFactor, leverage)), 'maxLeverage': self.parse_number(leverage), 'info': bracket, }) return tiers def fetch_open_interest_history(self, symbol: str, timeframe='1h', since: Int = None, limit: Int = None, params={}): """ Retrieves the open interest history of a currency https://huobiapi.github.io/docs/dm/v1/en/#query-information-on-open-interest https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-information-on-open-interest https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-information-on-open-interest :param str symbol: Unified CCXT market symbol :param str timeframe: '1h', '4h', '12h', or '1d' :param int [since]: Not used by huobi api, but response parsed by CCXT :param int [limit]: Default:48,Data Range [1,200] :param dict [params]: Exchange specific parameters :param int [params.amount_type]: *required* Open interest unit. 1-cont,2-cryptocurrency :param int [params.pair]: eg BTC-USDT *Only for USDT-M* :returns dict: an array of `open interest structures ` """ if timeframe != '1h' and timeframe != '4h' and timeframe != '12h' and timeframe != '1d': raise BadRequest(self.id + ' fetchOpenInterestHistory cannot only use the 1h, 4h, 12h and 1d timeframe') self.load_markets() timeframes: dict = { '1h': '60min', '4h': '4hour', '12h': '12hour', '1d': '1day', } market = self.market(symbol) amountType = self.safe_integer_2(params, 'amount_type', 'amountType', 2) request: dict = { 'period': timeframes[timeframe], 'amount_type': amountType, } if limit is not None: request['size'] = limit response = None if market['future']: request['contract_type'] = self.safe_string(market['info'], 'contract_type') request['symbol'] = market['baseId'] # currency code on coin-m futures # coin-m futures response = self.contractPublicGetApiV1ContractHisOpenInterest(self.extend(request, params)) elif market['linear']: request['contract_type'] = 'swap' request['contract_code'] = market['id'] request['contract_code'] = market['id'] # USDT-M response = self.contractPublicGetLinearSwapApiV1SwapHisOpenInterest(self.extend(request, params)) else: request['contract_code'] = market['id'] # coin-m swaps response = self.contractPublicGetSwapApiV1SwapHisOpenInterest(self.extend(request, params)) # # contractPublicGetlinearSwapApiV1SwapHisOpenInterest # { # "status": "ok", # "data": { # "symbol": "BTC", # "tick": [ # { # "volume": "4385.4350000000000000", # "amount_type": "2", # "ts": "1648220400000", # "value": "194059884.1850000000000000" # }, # ... # ], # "contract_code": "BTC-USDT", # "business_type": "swap", # "pair": "BTC-USDT", # "contract_type": "swap", # "trade_partition": "USDT" # }, # "ts": "1648223733007" # } # # contractPublicGetSwapApiV1SwapHisOpenInterest # { # "status": "ok", # "data": { # "symbol": "CRV", # "tick": [ # { # "volume": 19174.0000000000000000, # "amount_type": 1, # "ts": 1648224000000 # }, # ... # ], # "contract_code": "CRV-USD" # }, # "ts": 1648226554260 # } # # contractPublicGetApiV1ContractHisOpenInterest # { # "status": "ok", # "data": { # "symbol": "BTC", # "contract_type": "self_week", # "tick": [ # { # "volume": "48419.0000000000000000", # "amount_type": 1, # "ts": 1648224000000 # }, # ... # ] # }, # "ts": 1648227062944 # } # data = self.safe_value(response, 'data') tick = self.safe_list(data, 'tick') return self.parse_open_interests_history(tick, market, since, limit) def fetch_open_interests(self, symbols: Strings = None, params={}): """ Retrieves the open interest for a list of symbols https://huobiapi.github.io/docs/dm/v1/en/#get-contract-open-interest-information https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-swap-open-interest-information https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-get-swap-open-interest-information :param str[] [symbols]: a list of unified CCXT market symbols :param dict [params]: exchange specific parameters :returns dict[]: a list of `open interest structures ` """ self.load_markets() symbols = self.market_symbols(symbols) market = None if symbols is not None: symbolsLength = len(symbols) if symbolsLength > 0: first = self.safe_string(symbols, 0) market = self.market(first) request: dict = {} subType = None subType, params = self.handle_sub_type_and_params('fetchPositions', market, params, 'linear') marketType = None marketType, params = self.handle_market_type_and_params('fetchPositions', market, params) response = None if marketType == 'future': response = self.contractPublicGetApiV1ContractOpenInterest(self.extend(request, params)) # # { # "status": "ok", # "data": [ # { # "volume": 118850.000000000000000000, # "amount": 635.502025211544374189, # "symbol": "BTC", # "contract_type": "self_week", # "contract_code": "BTC220930", # "trade_amount": 1470.9400749347598691119206024033947897351, # "trade_volume": 286286, # "trade_turnover": 28628600.000000000000000000 # } # ], # "ts": 1664337928805 # } # elif subType == 'inverse': response = self.contractPublicGetSwapApiV1SwapOpenInterest(self.extend(request, params)) # # { # "status": "ok", # "data": [ # { # "volume": 518018.000000000000000000, # "amount": 2769.675777407074725180, # "symbol": "BTC", # "contract_code": "BTC-USD", # "trade_amount": 9544.4032080046491323463688602729806842458, # "trade_volume": 1848448, # "trade_turnover": 184844800.000000000000000000 # } # ], # "ts": 1664337226028 # } # else: request['contract_type'] = 'swap' response = self.contractPublicGetLinearSwapApiV1SwapOpenInterest(self.extend(request, params)) # # { # "status": "ok", # "data": [ # { # "volume": 7192610.000000000000000000, # "amount": 7192.610000000000000000, # "symbol": "BTC", # "value": 134654290.332000000000000000, # "contract_code": "BTC-USDT", # "trade_amount": 70692.804, # "trade_volume": 70692804, # "trade_turnover": 1379302592.9518, # "business_type": "swap", # "pair": "BTC-USDT", # "contract_type": "swap", # "trade_partition": "USDT" # } # ], # "ts": 1664336503144 # } # data = self.safe_list(response, 'data', []) return self.parse_open_interests(data, symbols) def fetch_open_interest(self, symbol: str, params={}): """ Retrieves the open interest of a currency https://huobiapi.github.io/docs/dm/v1/en/#get-contract-open-interest-information https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-swap-open-interest-information https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-get-swap-open-interest-information :param str symbol: Unified CCXT market symbol :param dict [params]: exchange specific parameters :returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure: """ self.load_markets() market = self.market(symbol) if not market['contract']: raise BadRequest(self.id + ' fetchOpenInterest() supports contract markets only') if market['option']: raise NotSupported(self.id + ' fetchOpenInterest() does not currently support option markets') request: dict = { 'contract_code': market['id'], } response = None if market['future']: request['contract_type'] = self.safe_string(market['info'], 'contract_type') request['symbol'] = market['baseId'] # COIN-M futures response = self.contractPublicGetApiV1ContractOpenInterest(self.extend(request, params)) elif market['linear']: request['contract_type'] = 'swap' # USDT-M response = self.contractPublicGetLinearSwapApiV1SwapOpenInterest(self.extend(request, params)) else: # COIN-M swaps response = self.contractPublicGetSwapApiV1SwapOpenInterest(self.extend(request, params)) # # USDT-M contractPublicGetLinearSwapApiV1SwapOpenInterest # # { # "status": "ok", # "data": [ # { # "volume": 7192610.000000000000000000, # "amount": 7192.610000000000000000, # "symbol": "BTC", # "value": 134654290.332000000000000000, # "contract_code": "BTC-USDT", # "trade_amount": 70692.804, # "trade_volume": 70692804, # "trade_turnover": 1379302592.9518, # "business_type": "swap", # "pair": "BTC-USDT", # "contract_type": "swap", # "trade_partition": "USDT" # } # ], # "ts": 1664336503144 # } # # COIN-M Swap contractPublicGetSwapApiV1SwapOpenInterest # # { # "status": "ok", # "data": [ # { # "volume": 518018.000000000000000000, # "amount": 2769.675777407074725180, # "symbol": "BTC", # "contract_code": "BTC-USD", # "trade_amount": 9544.4032080046491323463688602729806842458, # "trade_volume": 1848448, # "trade_turnover": 184844800.000000000000000000 # } # ], # "ts": 1664337226028 # } # # COIN-M Futures contractPublicGetApiV1ContractOpenInterest # # { # "status": "ok", # "data": [ # { # "volume": 118850.000000000000000000, # "amount": 635.502025211544374189, # "symbol": "BTC", # "contract_type": "self_week", # "contract_code": "BTC220930", # "trade_amount": 1470.9400749347598691119206024033947897351, # "trade_volume": 286286, # "trade_turnover": 28628600.000000000000000000 # } # ], # "ts": 1664337928805 # } # data = self.safe_value(response, 'data', []) openInterest = self.parse_open_interest(data[0], market) timestamp = self.safe_integer(response, 'ts') openInterest['timestamp'] = timestamp openInterest['datetime'] = self.iso8601(timestamp) return openInterest def parse_open_interest(self, interest, market: Market = None): # # fetchOpenInterestHistory # # { # "volume": "4385.4350000000000000", # "amount_type": "2", # "ts": "1648220400000", # "value": "194059884.1850000000000000" # } # # fetchOpenInterest: USDT-M # # { # "volume": 7192610.000000000000000000, # "amount": 7192.610000000000000000, # "symbol": "BTC", # "value": 134654290.332000000000000000, # "contract_code": "BTC-USDT", # "trade_amount": 70692.804, # "trade_volume": 70692804, # "trade_turnover": 1379302592.9518, # "business_type": "swap", # "pair": "BTC-USDT", # "contract_type": "swap", # "trade_partition": "USDT" # } # # fetchOpenInterest: COIN-M Swap # # { # "volume": 518018.000000000000000000, # "amount": 2769.675777407074725180, # "symbol": "BTC", # "contract_code": "BTC-USD", # "trade_amount": 9544.4032080046491323463688602729806842458, # "trade_volume": 1848448, # "trade_turnover": 184844800.000000000000000000 # } # # fetchOpenInterest: COIN-M Futures # # { # "volume": 118850.000000000000000000, # "amount": 635.502025211544374189, # "symbol": "BTC", # "contract_type": "self_week", # "contract_code": "BTC220930", # "trade_amount": 1470.9400749347598691119206024033947897351, # "trade_volume": 286286, # "trade_turnover": 28628600.000000000000000000 # } # timestamp = self.safe_integer(interest, 'ts') amount = self.safe_number(interest, 'volume') value = self.safe_number(interest, 'value') marketId = self.safe_string(interest, 'contract_code') return self.safe_open_interest({ 'symbol': self.safe_symbol(marketId, market), 'baseVolume': amount, # deprecated 'quoteVolume': value, # deprecated 'openInterestAmount': amount, 'openInterestValue': value, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'info': interest, }, market) def borrow_isolated_margin(self, symbol: str, code: str, amount: float, params={}): """ create a loan to borrow margin https://huobiapi.github.io/docs/spot/v1/en/#request-a-margin-loan-isolated https://huobiapi.github.io/docs/spot/v1/en/#request-a-margin-loan-cross :param str symbol: unified market symbol, required for isolated margin :param str code: unified currency code of the currency to borrow :param float amount: the amount to borrow :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `margin loan structure ` """ self.load_markets() currency = self.currency(code) market = self.market(symbol) request: dict = { 'currency': currency['id'], 'amount': self.currency_to_precision(code, amount), 'symbol': market['id'], } response = self.privatePostMarginOrders(self.extend(request, params)) # # Isolated # # { # "data": 1000 # } # transaction = self.parse_margin_loan(response, currency) return self.extend(transaction, { 'amount': amount, 'symbol': symbol, }) def borrow_cross_margin(self, code: str, amount: float, params={}): """ create a loan to borrow margin https://huobiapi.github.io/docs/spot/v1/en/#request-a-margin-loan-isolated https://huobiapi.github.io/docs/spot/v1/en/#request-a-margin-loan-cross :param str code: unified currency code of the currency to borrow :param float amount: the amount to borrow :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `margin loan structure ` """ self.load_markets() currency = self.currency(code) request: dict = { 'currency': currency['id'], 'amount': self.currency_to_precision(code, amount), } response = self.privatePostCrossMarginOrders(self.extend(request, params)) # # Cross # # { # "status": "ok", # "data": null # } # transaction = self.parse_margin_loan(response, currency) return self.extend(transaction, { 'amount': amount, }) def repay_isolated_margin(self, symbol: str, code: str, amount, params={}): """ repay borrowed margin and interest https://huobiapi.github.io/docs/spot/v1/en/#repay-margin-loan-cross-isolated :param str symbol: unified market symbol :param str code: unified currency code of the currency to repay :param float amount: the amount to repay :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `margin loan structure ` """ self.load_markets() currency = self.currency(code) accountId = self.fetch_account_id_by_type('spot', 'isolated', symbol, params) request: dict = { 'currency': currency['id'], 'amount': self.currency_to_precision(code, amount), 'accountId': accountId, } response = self.v2PrivatePostAccountRepayment(self.extend(request, params)) # # { # "code":200, # "data": [ # { # "repayId":1174424, # "repayTime":1600747722018 # } # ] # } # data = self.safe_value(response, 'Data', []) loan = self.safe_value(data, 0) transaction = self.parse_margin_loan(loan, currency) return self.extend(transaction, { 'amount': amount, 'symbol': symbol, }) def repay_cross_margin(self, code: str, amount, params={}): """ repay borrowed margin and interest https://huobiapi.github.io/docs/spot/v1/en/#repay-margin-loan-cross-isolated :param str code: unified currency code of the currency to repay :param float amount: the amount to repay :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `margin loan structure ` """ self.load_markets() currency = self.currency(code) accountId = self.fetch_account_id_by_type('spot', 'cross', None, params) request: dict = { 'currency': currency['id'], 'amount': self.currency_to_precision(code, amount), 'accountId': accountId, } response = self.v2PrivatePostAccountRepayment(self.extend(request, params)) # # { # "code":200, # "data": [ # { # "repayId":1174424, # "repayTime":1600747722018 # } # ] # } # data = self.safe_value(response, 'Data', []) loan = self.safe_value(data, 0) transaction = self.parse_margin_loan(loan, currency) return self.extend(transaction, { 'amount': amount, }) def parse_margin_loan(self, info, currency: Currency = None): # # borrowMargin cross # # { # "status": "ok", # "data": null # } # # borrowMargin isolated # # { # "data": 1000 # } # # repayMargin # # { # "repayId":1174424, # "repayTime":1600747722018 # } # timestamp = self.safe_integer(info, 'repayTime') return { 'id': self.safe_string_2(info, 'repayId', 'data'), 'currency': self.safe_currency_code(None, currency), 'amount': None, 'symbol': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'info': info, } def fetch_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ Fetches historical settlement records https://huobiapi.github.io/docs/dm/v1/en/#query-historical-settlement-records-of-the-platform-interface https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-historical-settlement-records-of-the-platform-interface https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-historical-settlement-records-of-the-platform-interface :param str symbol: unified symbol of the market to fetch the settlement history for :param int [since]: timestamp in ms, value range = current time - 90 days,default = current time - 90 days :param int [limit]: page items, default 20, shall not exceed 50 :param dict [params]: exchange specific params :param int [params.until]: timestamp in ms, value range = start_time -> current time,default = current time :param int [params.page_index]: page index, default page 1 if not filled :param int [params.code]: unified currency code, can be used when symbol is None :returns dict[]: a list of `settlement history objects ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchSettlementHistory() requires a symbol argument') until = self.safe_integer(params, 'until') params = self.omit(params, ['until']) market = self.market(symbol) request: dict = {} if market['future']: request['symbol'] = market['baseId'] else: request['contract_code'] = market['id'] if since is not None: request['start_at'] = since if limit is not None: request['page_size'] = limit if until is not None: request['end_at'] = until response = None if market['swap']: if market['linear']: response = self.contractPublicGetLinearSwapApiV1SwapSettlementRecords(self.extend(request, params)) else: response = self.contractPublicGetSwapApiV1SwapSettlementRecords(self.extend(request, params)) else: response = self.contractPublicGetApiV1ContractSettlementRecords(self.extend(request, params)) # # linear swap, coin-m swap # # { # "status": "ok", # "data": { # "total_page": 14, # "current_page": 1, # "total_size": 270, # "settlement_record": [ # { # "symbol": "ADA", # "contract_code": "ADA-USDT", # "settlement_time": 1652313600000, # "clawback_ratio": 0E-18, # "settlement_price": 0.512303000000000000, # "settlement_type": "settlement", # "business_type": "swap", # "pair": "ADA-USDT", # "trade_partition": "USDT" # }, # ... # ], # "ts": 1652338693256 # } # # coin-m future # # { # "status": "ok", # "data": { # "total_page": 5, # "current_page": 1, # "total_size": 90, # "settlement_record": [ # { # "symbol": "FIL", # "settlement_time": 1652342400000, # "clawback_ratio": 0E-18, # "list": [ # { # "contract_code": "FIL220513", # "settlement_price": 7.016000000000000000, # "settlement_type": "settlement" # }, # ... # ] # }, # ] # } # } # data = self.safe_value(response, 'data') settlementRecord = self.safe_value(data, 'settlement_record') settlements = self.parse_settlements(settlementRecord, market) return self.sort_by(settlements, 'timestamp') def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}): """ fetch deposit and withdraw fees https://huobiapi.github.io/docs/spot/v1/en/#get-all-supported-currencies-v2 :param str[]|None codes: list of unified currency codes :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `fees structures ` """ self.load_markets() response = self.spotPublicGetV2ReferenceCurrencies(params) # # { # "code": 200, # "data": [ # { # "currency": "sxp", # "assetType": "1", # "chains": [ # { # "chain": "sxp", # "displayName": "ERC20", # "baseChain": "ETH", # "baseChainProtocol": "ERC20", # "isDynamic": True, # "numOfConfirmations": "12", # "numOfFastConfirmations": "12", # "depositStatus": "allowed", # "minDepositAmt": "0.23", # "withdrawStatus": "allowed", # "minWithdrawAmt": "0.23", # "withdrawPrecision": "8", # "maxWithdrawAmt": "227000.000000000000000000", # "withdrawQuotaPerDay": "227000.000000000000000000", # "withdrawQuotaPerYear": null, # "withdrawQuotaTotal": null, # "withdrawFeeType": "fixed", # "transactFeeWithdraw": "11.1653", # "addrWithTag": False, # "addrDepositTag": False # } # ], # "instStatus": "normal" # } # ] # } # data = self.safe_list(response, 'data') return self.parse_deposit_withdraw_fees(data, codes, 'currency') def parse_deposit_withdraw_fee(self, fee, currency: Currency = None): # # { # "currency": "sxp", # "assetType": "1", # "chains": [ # { # "chain": "sxp", # "displayName": "ERC20", # "baseChain": "ETH", # "baseChainProtocol": "ERC20", # "isDynamic": True, # "numOfConfirmations": "12", # "numOfFastConfirmations": "12", # "depositStatus": "allowed", # "minDepositAmt": "0.23", # "withdrawStatus": "allowed", # "minWithdrawAmt": "0.23", # "withdrawPrecision": "8", # "maxWithdrawAmt": "227000.000000000000000000", # "withdrawQuotaPerDay": "227000.000000000000000000", # "withdrawQuotaPerYear": null, # "withdrawQuotaTotal": null, # "withdrawFeeType": "fixed", # "transactFeeWithdraw": "11.1653", # "addrWithTag": False, # "addrDepositTag": False # } # ], # "instStatus": "normal" # } # chains = self.safe_value(fee, 'chains', []) result = self.deposit_withdraw_fee(fee) for j in range(0, len(chains)): chainEntry = chains[j] networkId = self.safe_string(chainEntry, 'chain') withdrawFeeType = self.safe_string(chainEntry, 'withdrawFeeType') networkCode = self.network_id_to_code(networkId) withdrawFee = None withdrawResult = None if withdrawFeeType == 'fixed': withdrawFee = self.safe_number(chainEntry, 'transactFeeWithdraw') withdrawResult = { 'fee': withdrawFee, 'percentage': False, } else: withdrawFee = self.safe_number(chainEntry, 'transactFeeRateWithdraw') withdrawResult = { 'fee': withdrawFee, 'percentage': True, } result['networks'][networkCode] = { 'withdraw': withdrawResult, 'deposit': { 'fee': None, 'percentage': None, }, } result = self.assign_default_deposit_withdraw_fees(result, currency) return result def parse_settlements(self, settlements, market): # # linear swap, coin-m swap, fetchSettlementHistory # # [ # { # "symbol": "ADA", # "contract_code": "ADA-USDT", # "settlement_time": 1652313600000, # "clawback_ratio": 0E-18, # "settlement_price": 0.512303000000000000, # "settlement_type": "settlement", # "business_type": "swap", # "pair": "ADA-USDT", # "trade_partition": "USDT" # }, # ... # ] # # coin-m future, fetchSettlementHistory # # [ # { # "symbol": "FIL", # "settlement_time": 1652342400000, # "clawback_ratio": 0E-18, # "list": [ # { # "contract_code": "FIL220513", # "settlement_price": 7.016000000000000000, # "settlement_type": "settlement" # }, # ... # ] # }, # ] # result = [] for i in range(0, len(settlements)): settlement = settlements[i] list = self.safe_value(settlement, 'list') if list is not None: timestamp = self.safe_integer(settlement, 'settlement_time') timestampDetails: dict = { 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), } for j in range(0, len(list)): item = list[j] parsedSettlement = self.parse_settlement(item, market) result.append(self.extend(parsedSettlement, timestampDetails)) else: result.append(self.parse_settlement(settlements[i], market)) return result def parse_settlement(self, settlement, market): # # linear swap, coin-m swap, fetchSettlementHistory # # { # "symbol": "ADA", # "contract_code": "ADA-USDT", # "settlement_time": 1652313600000, # "clawback_ratio": 0E-18, # "settlement_price": 0.512303000000000000, # "settlement_type": "settlement", # "business_type": "swap", # "pair": "ADA-USDT", # "trade_partition": "USDT" # } # # coin-m future, fetchSettlementHistory # # { # "contract_code": "FIL220513", # "settlement_price": 7.016000000000000000, # "settlement_type": "settlement" # } # timestamp = self.safe_integer(settlement, 'settlement_time') marketId = self.safe_string(settlement, 'contract_code') return { 'info': settlement, 'symbol': self.safe_symbol(marketId, market), 'price': self.safe_number(settlement, 'settlement_price'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), } def fetch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}): """ retrieves the public liquidations of a trading pair https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-query-liquidation-orders-new https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-liquidation-orders-new https://huobiapi.github.io/docs/dm/v1/en/#query-liquidation-order-information-new :param str symbol: unified CCXT market symbol :param int [since]: the earliest time in ms to fetch liquidations for :param int [limit]: the maximum number of liquidation structures to retrieve :param dict [params]: exchange specific parameters for the huobi api endpoint :param int [params.until]: timestamp in ms of the latest liquidation :param int [params.tradeType]: default 0, linear swap 0: all liquidated orders, 5: liquidated longs; 6: liquidated shorts, inverse swap and future 0: filled liquidated orders, 5: liquidated close orders, 6: liquidated open orders :returns dict: an array of `liquidation structures ` """ self.load_markets() market = self.market(symbol) tradeType = self.safe_integer(params, 'trade_type', 0) request: dict = { 'trade_type': tradeType, } if since is not None: request['start_time'] = since request, params = self.handle_until_option('end_time', request, params) response = None if market['swap']: request['contract'] = market['id'] if market['linear']: response = self.contractPublicGetLinearSwapApiV3SwapLiquidationOrders(self.extend(request, params)) else: response = self.contractPublicGetSwapApiV3SwapLiquidationOrders(self.extend(request, params)) elif market['future']: request['symbol'] = market['id'] response = self.contractPublicGetApiV3ContractLiquidationOrders(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchLiquidations() does not support ' + market['type'] + ' orders') # # { # "code": 200, # "msg": "", # "data": [ # { # "query_id": 452057, # "contract_code": "BTC-USDT-211210", # "symbol": "USDT", # "direction": "sell", # "offset": "close", # "volume": 479.000000000000000000, # "price": 51441.700000000000000000, # "created_at": 1638593647864, # "amount": 0.479000000000000000, # "trade_turnover": 24640.574300000000000000, # "business_type": "futures", # "pair": "BTC-USDT" # } # ], # "ts": 1604312615051 # } # data = self.safe_list(response, 'data', []) return self.parse_liquidations(data, market, since, limit) def parse_liquidation(self, liquidation, market: Market = None): # # { # "query_id": 452057, # "contract_code": "BTC-USDT-211210", # "symbol": "USDT", # "direction": "sell", # "offset": "close", # "volume": 479.000000000000000000, # "price": 51441.700000000000000000, # "created_at": 1638593647864, # "amount": 0.479000000000000000, # "trade_turnover": 24640.574300000000000000, # "business_type": "futures", # "pair": "BTC-USDT" # } # marketId = self.safe_string(liquidation, 'contract_code') timestamp = self.safe_integer(liquidation, 'created_at') return self.safe_liquidation({ 'info': liquidation, 'symbol': self.safe_symbol(marketId, market), 'contracts': self.safe_number(liquidation, 'volume'), 'contractSize': self.safe_number(market, 'contractSize'), 'price': self.safe_number(liquidation, 'price'), 'side': self.safe_string_lower(liquidation, 'direction'), 'baseValue': self.safe_number(liquidation, 'amount'), 'quoteValue': self.safe_number(liquidation, 'trade_turnover'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), }) def close_position(self, symbol: str, side: OrderSide = None, params={}) -> Order: """ closes open positions for a contract market, requires 'amount' in params, unlike other exchanges https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-place-lightning-close-order # USDT-M(isolated) https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-place-lightning-close-position # USDT-M(cross) https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#place-lightning-close-order # Coin-M swap https://huobiapi.github.io/docs/dm/v1/en/#place-flash-close-order # Coin-M futures :param str symbol: unified CCXT market symbol :param str side: 'buy' or 'sell', the side of the closing order, opposite side side :param dict [params]: extra parameters specific to the okx api endpoint :param str [params.clientOrderId]: client needs to provide unique API and have to maintain the API themselves afterwards. [1, 9223372036854775807] :param dict [params.marginMode]: 'cross' or 'isolated', required for linear markets EXCHANGE SPECIFIC PARAMETERS :param number [params.amount]: order quantity :param str [params.order_price_type]: 'lightning' by default, 'lightning_fok': lightning fok type, 'lightning_ioc': lightning ioc type 'market' by default, 'market': market order type, 'lightning_fok': lightning :returns dict: `an order structure ` """ self.load_markets() market = self.market(symbol) clientOrderId = self.safe_string(params, 'clientOrderId') if not market['contract']: raise BadRequest(self.id + ' closePosition() symbol supports contract markets only') self.check_required_argument('closePosition', side, 'side') request: dict = { 'contract_code': market['id'], 'direction': side, } if clientOrderId is not None: request['client_order_id'] = clientOrderId if market['inverse']: amount = self.safe_string_2(params, 'volume', 'amount') if amount is None: raise ArgumentsRequired(self.id + ' closePosition() requires an extra argument params["amount"] for inverse markets') request['volume'] = self.amount_to_precision(symbol, amount) params = self.omit(params, ['clientOrderId', 'volume', 'amount']) response = None if market['inverse']: # Coin-M if market['swap']: response = self.contractPrivatePostSwapApiV1SwapLightningClosePosition(self.extend(request, params)) else: # future response = self.contractPrivatePostApiV1LightningClosePosition(self.extend(request, params)) else: # USDT-M marginMode = None marginMode, params = self.handle_margin_mode_and_params('closePosition', params, 'cross') if marginMode == 'cross': response = self.contractPrivatePostLinearSwapApiV1SwapCrossLightningClosePosition(self.extend(request, params)) else: # isolated response = self.contractPrivatePostLinearSwapApiV1SwapLightningClosePosition(self.extend(request, params)) return self.parse_order(response, market) def set_position_mode(self, hedged: bool, symbol: Str = None, params={}): """ set hedged to True or False https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-switch-position-mode https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-switch-position-mode :param bool hedged: set to True to for hedged mode, must be set separately for each market in isolated margin mode, only valid for linear markets :param str [symbol]: unified market symbol, required for isolated margin mode :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: "cross"(default) or "isolated" :returns dict: response from the exchange """ self.load_markets() posMode = 'dual_side' if hedged else 'single_side' market = None if symbol is not None: market = self.market(symbol) marginMode = None marginMode, params = self.handle_margin_mode_and_params('setPositionMode', params, 'cross') request: dict = { 'position_mode': posMode, } response = None if (market is not None) and (market['inverse']): raise BadRequest(self.id + ' setPositionMode can only be used for linear markets') if marginMode == 'isolated': if symbol is None: raise ArgumentsRequired(self.id + ' setPositionMode requires a symbol argument for isolated margin mode') request['margin_account'] = market['id'] response = self.contractPrivatePostLinearSwapApiV1SwapSwitchPositionMode(self.extend(request, params)) # # { # "status": "ok", # "data": [ # { # "margin_account": "BTC-USDT", # "position_mode": "single_side" # } # ], # "ts": 1566899973811 # } # else: request['margin_account'] = 'USDT' response = self.contractPrivatePostLinearSwapApiV1SwapCrossSwitchPositionMode(self.extend(request, params)) # # { # "status": "ok", # "data": [ # { # "margin_account": "USDT", # "position_mode": "single_side" # } # ], # "ts": 1566899973811 # } # return response