Files
ccxt_with_mt5/ccxt/htx.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

8983 lines
434 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 dont 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 <https://docs.ccxt.com/#/?id=exchange-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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-book-structure>` 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, step15merged depth data 0-5,14-15, when step is 0depth 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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=public-trades>`
"""
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 <https://docs.ccxt.com/#/?id=account-structure>` 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 <https://docs.ccxt.com/#/?id=account-structure>` 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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 limit3 opponent4 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 <https://docs.ccxt.com/#/?id=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('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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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('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 <https://docs.ccxt.com/#/?id=address-structure>` 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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
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 <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=isolated-borrow-rate-structure>`
"""
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 <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=funding-rates-structure>`, 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 <https://docs.ccxt.com/#/?id=borrow-interest-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=ledger>`
"""
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 <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, 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]: Default48Data Range [1,200]
:param dict [params]: Exchange specific parameters
:param int [params.amount_type]: *required* Open interest unit. 1-cont2-cryptocurrency
:param int [params.pair]: eg BTC-USDT *Only for USDT-M*
:returns dict: an array of `open interest structures <https://docs.ccxt.com/#/?id=open-interest-structure>`
"""
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 <https://docs.ccxt.com/#/?id=open-interest-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)
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 daysdefault = 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 timedefault = 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 <https://docs.ccxt.com/#/?id=settlement-history-structure>`
"""
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 <https://docs.ccxt.com/#/?id=fee-structure>`
"""
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 <https://docs.ccxt.com/#/?id=liquidation-structure>`
"""
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 <https://docs.ccxt.com/#/?id=position-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