8983 lines
434 KiB
Python
8983 lines
434 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
||
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
||
|
||
from ccxt.base.exchange import Exchange
|
||
from ccxt.abstract.htx import ImplicitAPI
|
||
import hashlib
|
||
from ccxt.base.types import Account, Any, Balances, BorrowInterest, Currencies, Currency, DepositAddress, Int, IsolatedBorrowRate, IsolatedBorrowRates, LedgerEntry, LeverageTier, LeverageTiers, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, TransferEntry
|
||
from typing import List
|
||
from ccxt.base.errors import ExchangeError
|
||
from ccxt.base.errors import AuthenticationError
|
||
from ccxt.base.errors import PermissionDenied
|
||
from ccxt.base.errors import AccountNotEnabled
|
||
from ccxt.base.errors import ArgumentsRequired
|
||
from ccxt.base.errors import BadRequest
|
||
from ccxt.base.errors import BadSymbol
|
||
from ccxt.base.errors import InsufficientFunds
|
||
from ccxt.base.errors import InvalidOrder
|
||
from ccxt.base.errors import OrderNotFound
|
||
from ccxt.base.errors import NotSupported
|
||
from ccxt.base.errors import OperationFailed
|
||
from ccxt.base.errors import RateLimitExceeded
|
||
from ccxt.base.errors import ExchangeNotAvailable
|
||
from ccxt.base.errors import OnMaintenance
|
||
from ccxt.base.errors import RequestTimeout
|
||
from ccxt.base.decimal_to_precision import TRUNCATE
|
||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
||
from ccxt.base.precise import Precise
|
||
|
||
|
||
class htx(Exchange, ImplicitAPI):
|
||
|
||
def describe(self) -> Any:
|
||
return self.deep_extend(super(htx, self).describe(), {
|
||
'id': 'htx',
|
||
'name': 'HTX',
|
||
'countries': ['CN'],
|
||
'rateLimit': 100,
|
||
'userAgent': self.userAgents['chrome100'],
|
||
'certified': True,
|
||
'version': 'v1',
|
||
'hostname': 'api.huobi.pro', # api.testnet.huobi.pro
|
||
'pro': True,
|
||
'has': {
|
||
'CORS': None,
|
||
'spot': True,
|
||
'margin': True,
|
||
'swap': True,
|
||
'future': True,
|
||
'option': None,
|
||
'addMargin': None,
|
||
'borrowCrossMargin': True,
|
||
'borrowIsolatedMargin': True,
|
||
'cancelAllOrders': True,
|
||
'cancelAllOrdersAfter': True,
|
||
'cancelOrder': True,
|
||
'cancelOrders': True,
|
||
'closeAllPositions': False,
|
||
'closePosition': True,
|
||
'createDepositAddress': None,
|
||
'createMarketBuyOrderWithCost': True,
|
||
'createMarketOrderWithCost': False,
|
||
'createMarketSellOrderWithCost': False,
|
||
'createOrder': True,
|
||
'createOrders': True,
|
||
'createReduceOnlyOrder': False,
|
||
'createStopLimitOrder': True,
|
||
'createStopLossOrder': True,
|
||
'createStopMarketOrder': True,
|
||
'createStopOrder': True,
|
||
'createTakeProfitOrder': True,
|
||
'createTrailingPercentOrder': True,
|
||
'createTriggerOrder': True,
|
||
'fetchAccounts': True,
|
||
'fetchBalance': True,
|
||
'fetchBidsAsks': None,
|
||
'fetchBorrowInterest': True,
|
||
'fetchBorrowRateHistories': None,
|
||
'fetchBorrowRateHistory': None,
|
||
'fetchCanceledOrders': None,
|
||
'fetchClosedOrder': None,
|
||
'fetchClosedOrders': True,
|
||
'fetchCrossBorrowRate': False,
|
||
'fetchCrossBorrowRates': False,
|
||
'fetchCurrencies': True,
|
||
'fetchDeposit': None,
|
||
'fetchDepositAddress': True,
|
||
'fetchDepositAddresses': None,
|
||
'fetchDepositAddressesByNetwork': True,
|
||
'fetchDeposits': True,
|
||
'fetchDepositWithdrawFee': 'emulated',
|
||
'fetchDepositWithdrawFees': True,
|
||
'fetchFundingHistory': True,
|
||
'fetchFundingRate': True,
|
||
'fetchFundingRateHistory': True,
|
||
'fetchFundingRates': True,
|
||
'fetchIndexOHLCV': True,
|
||
'fetchIsolatedBorrowRate': False,
|
||
'fetchIsolatedBorrowRates': True,
|
||
'fetchL3OrderBook': None,
|
||
'fetchLastPrices': True,
|
||
'fetchLedger': True,
|
||
'fetchLedgerEntry': None,
|
||
'fetchLeverage': False,
|
||
'fetchLeverageTiers': True,
|
||
'fetchLiquidations': True,
|
||
'fetchMarginAdjustmentHistory': False,
|
||
'fetchMarketLeverageTiers': 'emulated',
|
||
'fetchMarkets': True,
|
||
'fetchMarkOHLCV': True,
|
||
'fetchMyLiquidations': False,
|
||
'fetchMyTrades': True,
|
||
'fetchOHLCV': True,
|
||
'fetchOpenInterest': True,
|
||
'fetchOpenInterestHistory': True,
|
||
'fetchOpenInterests': True,
|
||
'fetchOpenOrder': None,
|
||
'fetchOpenOrders': True,
|
||
'fetchOrder': True,
|
||
'fetchOrderBook': True,
|
||
'fetchOrderBooks': None,
|
||
'fetchOrders': True,
|
||
'fetchOrderTrades': True,
|
||
'fetchPosition': True,
|
||
'fetchPositionHistory': 'emulated',
|
||
'fetchPositions': True,
|
||
'fetchPositionsHistory': False,
|
||
'fetchPositionsRisk': False,
|
||
'fetchPremiumIndexOHLCV': True,
|
||
'fetchSettlementHistory': True,
|
||
'fetchStatus': True,
|
||
'fetchTicker': True,
|
||
'fetchTickers': True,
|
||
'fetchTime': True,
|
||
'fetchTrades': True,
|
||
'fetchTradingFee': True,
|
||
'fetchTradingFees': False,
|
||
'fetchTradingLimits': True,
|
||
'fetchTransactionFee': None,
|
||
'fetchTransactionFees': None,
|
||
'fetchTransactions': None,
|
||
'fetchTransfers': None,
|
||
'fetchWithdrawAddresses': True,
|
||
'fetchWithdrawal': None,
|
||
'fetchWithdrawals': True,
|
||
'fetchWithdrawalWhitelist': None,
|
||
'reduceMargin': None,
|
||
'repayCrossMargin': True,
|
||
'repayIsolatedMargin': True,
|
||
'setLeverage': True,
|
||
'setMarginMode': False,
|
||
'setPositionMode': True,
|
||
'signIn': None,
|
||
'transfer': True,
|
||
'withdraw': True,
|
||
},
|
||
'timeframes': {
|
||
'1m': '1min',
|
||
'5m': '5min',
|
||
'15m': '15min',
|
||
'30m': '30min',
|
||
'1h': '60min',
|
||
'4h': '4hour',
|
||
'1d': '1day',
|
||
'1w': '1week',
|
||
'1M': '1mon',
|
||
'1y': '1year',
|
||
},
|
||
'urls': {
|
||
# 'test': {
|
||
# 'market': 'https://api.testnet.huobi.pro',
|
||
# 'public': 'https://api.testnet.huobi.pro',
|
||
# 'private': 'https://api.testnet.huobi.pro',
|
||
# },
|
||
'logo': 'https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg',
|
||
'hostnames': {
|
||
'contract': 'api.hbdm.com',
|
||
'spot': 'api.huobi.pro',
|
||
'status': {
|
||
'spot': 'status.huobigroup.com',
|
||
'future': {
|
||
'inverse': 'status-dm.huobigroup.com',
|
||
'linear': 'status-linear-swap.huobigroup.com', # USDT-Margined Contracts
|
||
},
|
||
'swap': {
|
||
'inverse': 'status-swap.huobigroup.com',
|
||
'linear': 'status-linear-swap.huobigroup.com', # USDT-Margined Contracts
|
||
},
|
||
},
|
||
# recommended for AWS
|
||
# 'contract': 'api.hbdm.vn',
|
||
# 'spot': 'api-aws.huobi.pro',
|
||
},
|
||
'api': {
|
||
'status': 'https://{hostname}',
|
||
'contract': 'https://{hostname}',
|
||
'spot': 'https://{hostname}',
|
||
'public': 'https://{hostname}',
|
||
'private': 'https://{hostname}',
|
||
'v2Public': 'https://{hostname}',
|
||
'v2Private': 'https://{hostname}',
|
||
},
|
||
'www': 'https://www.huobi.com',
|
||
'referral': {
|
||
'url': 'https://www.htx.com.vc/invite/en-us/1h?invite_code=6rmm2223',
|
||
'discount': 0.15,
|
||
},
|
||
'doc': [
|
||
'https://huobiapi.github.io/docs/spot/v1/en/',
|
||
'https://huobiapi.github.io/docs/dm/v1/en/',
|
||
'https://huobiapi.github.io/docs/coin_margined_swap/v1/en/',
|
||
'https://huobiapi.github.io/docs/usdt_swap/v1/en/',
|
||
'https://www.huobi.com/en-us/opend/newApiPages/',
|
||
],
|
||
'fees': 'https://www.huobi.com/about/fee/',
|
||
},
|
||
'api': {
|
||
# ------------------------------------------------------------
|
||
# old api definitions
|
||
'v2Public': {
|
||
'get': {
|
||
'reference/currencies': 1, # 币链参考信息
|
||
'market-status': 1, # 获取当前市场状态
|
||
},
|
||
},
|
||
'v2Private': {
|
||
'get': {
|
||
'account/ledger': 1,
|
||
'account/withdraw/quota': 1,
|
||
'account/withdraw/address': 1, # 提币地址查询(限母用户可用)
|
||
'account/deposit/address': 1,
|
||
'account/repayment': 5, # 还币交易记录查询
|
||
'reference/transact-fee-rate': 1,
|
||
'account/asset-valuation': 0.2, # 获取账户资产估值
|
||
'point/account': 5, # 点卡余额查询
|
||
'sub-user/user-list': 1, # 获取子用户列表
|
||
'sub-user/user-state': 1, # 获取特定子用户的用户状态
|
||
'sub-user/account-list': 1, # 获取特定子用户的账户列表
|
||
'sub-user/deposit-address': 1, # 子用户充币地址查询
|
||
'sub-user/query-deposit': 1, # 子用户充币记录查询
|
||
'user/api-key': 1, # 母子用户API key信息查询
|
||
'user/uid': 1, # 母子用户获取用户UID
|
||
'algo-orders/opening': 1, # 查询未触发OPEN策略委托
|
||
'algo-orders/history': 1, # 查询策略委托历史
|
||
'algo-orders/specific': 1, # 查询特定策略委托
|
||
'c2c/offers': 1, # 查询借入借出订单
|
||
'c2c/offer': 1, # 查询特定借入借出订单及其交易记录
|
||
'c2c/transactions': 1, # 查询借入借出交易记录
|
||
'c2c/repayment': 1, # 查询还币交易记录
|
||
'c2c/account': 1, # 查询账户余额
|
||
'etp/reference': 1, # 基础参考信息
|
||
'etp/transactions': 5, # 获取杠杆ETP申赎记录
|
||
'etp/transaction': 5, # 获取特定杠杆ETP申赎记录
|
||
'etp/rebalance': 1, # 获取杠杆ETP调仓记录
|
||
'etp/limit': 1, # 获取ETP持仓限额
|
||
},
|
||
'post': {
|
||
'account/transfer': 1,
|
||
'account/repayment': 5, # 归还借币(全仓逐仓通用)
|
||
'point/transfer': 5, # 点卡划转
|
||
'sub-user/management': 1, # 冻结/解冻子用户
|
||
'sub-user/creation': 1, # 子用户创建
|
||
'sub-user/tradable-market': 1, # 设置子用户交易权限
|
||
'sub-user/transferability': 1, # 设置子用户资产转出权限
|
||
'sub-user/api-key-generation': 1, # 子用户API key创建
|
||
'sub-user/api-key-modification': 1, # 修改子用户API key
|
||
'sub-user/api-key-deletion': 1, # 删除子用户API key
|
||
'sub-user/deduct-mode': 1, # 设置子用户手续费抵扣模式
|
||
'algo-orders': 1, # 策略委托下单
|
||
'algo-orders/cancel-all-after': 1, # 自动撤销订单
|
||
'algo-orders/cancellation': 1, # 策略委托(触发前)撤单
|
||
'c2c/offer': 1, # 借入借出下单
|
||
'c2c/cancellation': 1, # 借入借出撤单
|
||
'c2c/cancel-all': 1, # 撤销所有借入借出订单
|
||
'c2c/repayment': 1, # 还币
|
||
'c2c/transfer': 1, # 资产划转
|
||
'etp/creation': 5, # 杠杆ETP换入
|
||
'etp/redemption': 5, # 杠杆ETP换出
|
||
'etp/{transactId}/cancel': 10, # 杠杆ETP单个撤单
|
||
'etp/batch-cancel': 50, # 杠杆ETP批量撤单
|
||
},
|
||
},
|
||
'public': {
|
||
'get': {
|
||
'common/symbols': 1, # 查询系统支持的所有交易对
|
||
'common/currencys': 1, # 查询系统支持的所有币种
|
||
'common/timestamp': 1, # 查询系统当前时间
|
||
'common/exchange': 1, # order limits
|
||
'settings/currencys': 1, # ?language=en-US
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
'account/accounts': 0.2, # 查询当前用户的所有账户(即account-id)
|
||
'account/accounts/{id}/balance': 0.2, # 查询指定账户的余额
|
||
'account/accounts/{sub-uid}': 1,
|
||
'account/history': 4,
|
||
'cross-margin/loan-info': 1,
|
||
'margin/loan-info': 1, # 查询借币币息率及额度
|
||
'fee/fee-rate/get': 1,
|
||
'order/openOrders': 0.4,
|
||
'order/orders': 0.4,
|
||
'order/orders/{id}': 0.4, # 查询某个订单详情
|
||
'order/orders/{id}/matchresults': 0.4, # 查询某个订单的成交明细
|
||
'order/orders/getClientOrder': 0.4,
|
||
'order/history': 1, # 查询当前委托、历史委托
|
||
'order/matchresults': 1, # 查询当前成交、历史成交
|
||
# 'dw/withdraw-virtual/addresses', # 查询虚拟币提现地址(Deprecated)
|
||
'query/deposit-withdraw': 1,
|
||
# 'margin/loan-info', # duplicate
|
||
'margin/loan-orders': 0.2, # 借贷订单
|
||
'margin/accounts/balance': 0.2, # 借贷账户详情
|
||
'cross-margin/loan-orders': 1, # 查询借币订单
|
||
'cross-margin/accounts/balance': 1, # 借币账户详情
|
||
'points/actions': 1,
|
||
'points/orders': 1,
|
||
'subuser/aggregate-balance': 10,
|
||
'stable-coin/exchange_rate': 1,
|
||
'stable-coin/quote': 1,
|
||
},
|
||
'post': {
|
||
'account/transfer': 1, # 资产划转(该节点为母用户和子用户进行资产划转的通用接口。)
|
||
'futures/transfer': 1,
|
||
'order/batch-orders': 0.4,
|
||
'order/orders/place': 0.2, # 创建并执行一个新订单(一步下单, 推荐使用)
|
||
'order/orders/submitCancelClientOrder': 0.2,
|
||
'order/orders/batchCancelOpenOrders': 0.4,
|
||
# 'order/orders', # 创建一个新的订单请求 (仅创建订单,不执行下单)
|
||
# 'order/orders/{id}/place', # 执行一个订单 (仅执行已创建的订单)
|
||
'order/orders/{id}/submitcancel': 0.2, # 申请撤销一个订单请求
|
||
'order/orders/batchcancel': 0.4, # 批量撤销订单
|
||
# 'dw/balance/transfer', # 资产划转
|
||
'dw/withdraw/api/create': 1, # 申请提现虚拟币
|
||
# 'dw/withdraw-virtual/create', # 申请提现虚拟币
|
||
# 'dw/withdraw-virtual/{id}/place', # 确认申请虚拟币提现(Deprecated)
|
||
'dw/withdraw-virtual/{id}/cancel': 1, # 申请取消提现虚拟币
|
||
'dw/transfer-in/margin': 10, # 现货账户划入至借贷账户
|
||
'dw/transfer-out/margin': 10, # 借贷账户划出至现货账户
|
||
'margin/orders': 10, # 申请借贷
|
||
'margin/orders/{id}/repay': 10, # 归还借贷
|
||
'cross-margin/transfer-in': 1, # 资产划转
|
||
'cross-margin/transfer-out': 1, # 资产划转
|
||
'cross-margin/orders': 1, # 申请借币
|
||
'cross-margin/orders/{id}/repay': 1, # 归还借币
|
||
'stable-coin/exchange': 1,
|
||
'subuser/transfer': 10,
|
||
},
|
||
},
|
||
# ------------------------------------------------------------
|
||
# new api definitions
|
||
# 'https://status.huobigroup.com/api/v2/summary.json': 1,
|
||
# 'https://status-dm.huobigroup.com/api/v2/summary.json': 1,
|
||
# 'https://status-swap.huobigroup.com/api/v2/summary.json': 1,
|
||
# 'https://status-linear-swap.huobigroup.com/api/v2/summary.json': 1,
|
||
'status': {
|
||
'public': {
|
||
'spot': {
|
||
'get': {
|
||
'api/v2/summary.json': 1,
|
||
},
|
||
},
|
||
'future': {
|
||
'inverse': {
|
||
'get': {
|
||
'api/v2/summary.json': 1,
|
||
},
|
||
},
|
||
'linear': {
|
||
'get': {
|
||
'api/v2/summary.json': 1,
|
||
},
|
||
},
|
||
},
|
||
'swap': {
|
||
'inverse': {
|
||
'get': {
|
||
'api/v2/summary.json': 1,
|
||
},
|
||
},
|
||
'linear': {
|
||
'get': {
|
||
'api/v2/summary.json': 1,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'spot': {
|
||
'public': {
|
||
'get': {
|
||
'v2/market-status': 1,
|
||
'v1/common/symbols': 1,
|
||
'v1/common/currencys': 1,
|
||
'v2/settings/common/currencies': 1,
|
||
'v2/reference/currencies': 1,
|
||
'v1/common/timestamp': 1,
|
||
'v1/common/exchange': 1, # order limits
|
||
'v1/settings/common/chains': 1,
|
||
'v1/settings/common/currencys': 1,
|
||
'v1/settings/common/symbols': 1,
|
||
'v2/settings/common/symbols': 1,
|
||
'v1/settings/common/market-symbols': 1,
|
||
# Market Data
|
||
'market/history/candles': 1,
|
||
'market/history/kline': 1,
|
||
'market/detail/merged': 1,
|
||
'market/tickers': 1,
|
||
'market/detail': 1,
|
||
'market/depth': 1,
|
||
'market/trade': 1,
|
||
'market/history/trade': 1,
|
||
'market/etp': 1, # Get real-time equity of leveraged ETP
|
||
# ETP
|
||
'v2/etp/reference': 1,
|
||
'v2/etp/rebalance': 1,
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
# Account
|
||
'v1/account/accounts': 0.2,
|
||
'v1/account/accounts/{account-id}/balance': 0.2,
|
||
'v2/account/valuation': 1,
|
||
'v2/account/asset-valuation': 0.2,
|
||
'v1/account/history': 4,
|
||
'v2/account/ledger': 1,
|
||
'v2/point/account': 5,
|
||
# Wallet(Deposit and Withdraw)
|
||
'v2/account/deposit/address': 1,
|
||
'v2/account/withdraw/quota': 1,
|
||
'v2/account/withdraw/address': 1,
|
||
'v2/reference/currencies': 1,
|
||
'v1/query/deposit-withdraw': 1,
|
||
'v1/query/withdraw/client-order-id': 1,
|
||
# Sub user management
|
||
'v2/user/api-key': 1,
|
||
'v2/user/uid': 1,
|
||
'v2/sub-user/user-list': 1,
|
||
'v2/sub-user/user-state': 1,
|
||
'v2/sub-user/account-list': 1,
|
||
'v2/sub-user/deposit-address': 1,
|
||
'v2/sub-user/query-deposit': 1,
|
||
'v1/subuser/aggregate-balance': 10,
|
||
'v1/account/accounts/{sub-uid}': 1,
|
||
# Trading
|
||
'v1/order/openOrders': 0.4,
|
||
'v1/order/orders/{order-id}': 0.4,
|
||
'v1/order/orders/getClientOrder': 0.4,
|
||
'v1/order/orders/{order-id}/matchresult': 0.4,
|
||
'v1/order/orders/{order-id}/matchresults': 0.4,
|
||
'v1/order/orders': 0.4,
|
||
'v1/order/history': 1,
|
||
'v1/order/matchresults': 1,
|
||
'v2/reference/transact-fee-rate': 1,
|
||
# Conditional Order
|
||
'v2/algo-orders/opening': 1,
|
||
'v2/algo-orders/history': 1,
|
||
'v2/algo-orders/specific': 1,
|
||
# Margin Loan(Cross/Isolated)
|
||
'v1/margin/loan-info': 1,
|
||
'v1/margin/loan-orders': 0.2,
|
||
'v1/margin/accounts/balance': 0.2,
|
||
'v1/cross-margin/loan-info': 1,
|
||
'v1/cross-margin/loan-orders': 1,
|
||
'v1/cross-margin/accounts/balance': 1,
|
||
'v2/account/repayment': 5,
|
||
# Stable Coin Exchange
|
||
'v1/stable-coin/quote': 1,
|
||
'v1/stable_coin/exchange_rate': 1,
|
||
# ETP
|
||
'v2/etp/transactions': 5,
|
||
'v2/etp/transaction': 5,
|
||
'v2/etp/limit': 1,
|
||
},
|
||
'post': {
|
||
# Account
|
||
'v1/account/transfer': 1,
|
||
'v1/futures/transfer': 1, # future transfers
|
||
'v2/point/transfer': 5,
|
||
'v2/account/transfer': 1, # swap transfers
|
||
# Wallet(Deposit and Withdraw)
|
||
'v1/dw/withdraw/api/create': 1,
|
||
'v1/dw/withdraw-virtual/{withdraw-id}/cancel': 1,
|
||
# Sub user management
|
||
'v2/sub-user/deduct-mode': 1,
|
||
'v2/sub-user/creation': 1,
|
||
'v2/sub-user/management': 1,
|
||
'v2/sub-user/tradable-market': 1,
|
||
'v2/sub-user/transferability': 1,
|
||
'v2/sub-user/api-key-generation': 1,
|
||
'v2/sub-user/api-key-modification': 1,
|
||
'v2/sub-user/api-key-deletion': 1,
|
||
'v1/subuser/transfer': 10,
|
||
'v1/trust/user/active/credit': 10,
|
||
# Trading
|
||
'v1/order/orders/place': 0.2,
|
||
'v1/order/batch-orders': 0.4,
|
||
'v1/order/auto/place': 0.2,
|
||
'v1/order/orders/{order-id}/submitcancel': 0.2,
|
||
'v1/order/orders/submitCancelClientOrder': 0.2,
|
||
'v1/order/orders/batchCancelOpenOrders': 0.4,
|
||
'v1/order/orders/batchcancel': 0.4,
|
||
'v2/algo-orders/cancel-all-after': 1,
|
||
# Conditional Order
|
||
'v2/algo-orders': 1,
|
||
'v2/algo-orders/cancellation': 1,
|
||
# Margin Loan(Cross/Isolated)
|
||
'v2/account/repayment': 5,
|
||
'v1/dw/transfer-in/margin': 10,
|
||
'v1/dw/transfer-out/margin': 10,
|
||
'v1/margin/orders': 10,
|
||
'v1/margin/orders/{order-id}/repay': 10,
|
||
'v1/cross-margin/transfer-in': 1,
|
||
'v1/cross-margin/transfer-out': 1,
|
||
'v1/cross-margin/orders': 1,
|
||
'v1/cross-margin/orders/{order-id}/repay': 1,
|
||
# Stable Coin Exchange
|
||
'v1/stable-coin/exchange': 1,
|
||
# ETP
|
||
'v2/etp/creation': 5,
|
||
'v2/etp/redemption': 5,
|
||
'v2/etp/{transactId}/cancel': 10,
|
||
'v2/etp/batch-cancel': 50,
|
||
},
|
||
},
|
||
},
|
||
'contract': {
|
||
'public': {
|
||
'get': {
|
||
'api/v1/timestamp': 1,
|
||
'heartbeat/': 1, # backslash is not a typo
|
||
# Future Market Data interface
|
||
'api/v1/contract_contract_info': 1,
|
||
'api/v1/contract_index': 1,
|
||
'api/v1/contract_query_elements': 1,
|
||
'api/v1/contract_price_limit': 1,
|
||
'api/v1/contract_open_interest': 1,
|
||
'api/v1/contract_delivery_price': 1,
|
||
'market/depth': 1,
|
||
'market/bbo': 1,
|
||
'market/history/kline': 1,
|
||
'index/market/history/mark_price_kline': 1,
|
||
'market/detail/merged': 1,
|
||
'market/detail/batch_merged': 1,
|
||
'v2/market/detail/batch_merged': 1,
|
||
'market/trade': 1,
|
||
'market/history/trade': 1,
|
||
'api/v1/contract_risk_info': 1,
|
||
'api/v1/contract_insurance_fund': 1,
|
||
'api/v1/contract_adjustfactor': 1,
|
||
'api/v1/contract_his_open_interest': 1,
|
||
'api/v1/contract_ladder_margin': 1,
|
||
'api/v1/contract_api_state': 1,
|
||
'api/v1/contract_elite_account_ratio': 1,
|
||
'api/v1/contract_elite_position_ratio': 1,
|
||
'api/v1/contract_liquidation_orders': 1,
|
||
'api/v1/contract_settlement_records': 1,
|
||
'index/market/history/index': 1,
|
||
'index/market/history/basis': 1,
|
||
'api/v1/contract_estimated_settlement_price': 1,
|
||
'api/v3/contract_liquidation_orders': 1,
|
||
# Swap Market Data interface
|
||
'swap-api/v1/swap_contract_info': 1,
|
||
'swap-api/v1/swap_index': 1,
|
||
'swap-api/v1/swap_query_elements': 1,
|
||
'swap-api/v1/swap_price_limit': 1,
|
||
'swap-api/v1/swap_open_interest': 1,
|
||
'swap-ex/market/depth': 1,
|
||
'swap-ex/market/bbo': 1,
|
||
'swap-ex/market/history/kline': 1,
|
||
'index/market/history/swap_mark_price_kline': 1,
|
||
'swap-ex/market/detail/merged': 1,
|
||
'v2/swap-ex/market/detail/batch_merged': 1,
|
||
'index/market/history/swap_premium_index_kline': 1,
|
||
'swap-ex/market/detail/batch_merged': 1,
|
||
'swap-ex/market/trade': 1,
|
||
'swap-ex/market/history/trade': 1,
|
||
'swap-api/v1/swap_risk_info': 1,
|
||
'swap-api/v1/swap_insurance_fund': 1,
|
||
'swap-api/v1/swap_adjustfactor': 1,
|
||
'swap-api/v1/swap_his_open_interest': 1,
|
||
'swap-api/v1/swap_ladder_margin': 1,
|
||
'swap-api/v1/swap_api_state': 1,
|
||
'swap-api/v1/swap_elite_account_ratio': 1,
|
||
'swap-api/v1/swap_elite_position_ratio': 1,
|
||
'swap-api/v1/swap_estimated_settlement_price': 1,
|
||
'swap-api/v1/swap_liquidation_orders': 1,
|
||
'swap-api/v1/swap_settlement_records': 1,
|
||
'swap-api/v1/swap_funding_rate': 1,
|
||
'swap-api/v1/swap_batch_funding_rate': 1,
|
||
'swap-api/v1/swap_historical_funding_rate': 1,
|
||
'swap-api/v3/swap_liquidation_orders': 1,
|
||
'index/market/history/swap_estimated_rate_kline': 1,
|
||
'index/market/history/swap_basis': 1,
|
||
# Swap Market Data interface
|
||
'linear-swap-api/v1/swap_contract_info': 1,
|
||
'linear-swap-api/v1/swap_index': 1,
|
||
'linear-swap-api/v1/swap_query_elements': 1,
|
||
'linear-swap-api/v1/swap_price_limit': 1,
|
||
'linear-swap-api/v1/swap_open_interest': 1,
|
||
'linear-swap-ex/market/depth': 1,
|
||
'linear-swap-ex/market/bbo': 1,
|
||
'linear-swap-ex/market/history/kline': 1,
|
||
'index/market/history/linear_swap_mark_price_kline': 1,
|
||
'linear-swap-ex/market/detail/merged': 1,
|
||
'linear-swap-ex/market/detail/batch_merged': 1,
|
||
'v2/linear-swap-ex/market/detail/batch_merged': 1,
|
||
'linear-swap-ex/market/trade': 1,
|
||
'linear-swap-ex/market/history/trade': 1,
|
||
'linear-swap-api/v1/swap_risk_info': 1,
|
||
'swap-api/v1/linear-swap-api/v1/swap_insurance_fund': 1,
|
||
'linear-swap-api/v1/swap_adjustfactor': 1,
|
||
'linear-swap-api/v1/swap_cross_adjustfactor': 1,
|
||
'linear-swap-api/v1/swap_his_open_interest': 1,
|
||
'linear-swap-api/v1/swap_ladder_margin': 1,
|
||
'linear-swap-api/v1/swap_cross_ladder_margin': 1,
|
||
'linear-swap-api/v1/swap_api_state': 1,
|
||
'linear-swap-api/v1/swap_cross_transfer_state': 1,
|
||
'linear-swap-api/v1/swap_cross_trade_state': 1,
|
||
'linear-swap-api/v1/swap_elite_account_ratio': 1,
|
||
'linear-swap-api/v1/swap_elite_position_ratio': 1,
|
||
'linear-swap-api/v1/swap_liquidation_orders': 1,
|
||
'linear-swap-api/v1/swap_settlement_records': 1,
|
||
'linear-swap-api/v1/swap_funding_rate': 1,
|
||
'linear-swap-api/v1/swap_batch_funding_rate': 1,
|
||
'linear-swap-api/v1/swap_historical_funding_rate': 1,
|
||
'linear-swap-api/v3/swap_liquidation_orders': 1,
|
||
'index/market/history/linear_swap_premium_index_kline': 1,
|
||
'index/market/history/linear_swap_estimated_rate_kline': 1,
|
||
'index/market/history/linear_swap_basis': 1,
|
||
'linear-swap-api/v1/swap_estimated_settlement_price': 1,
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
# Future Account Interface
|
||
'api/v1/contract_sub_auth_list': 1,
|
||
'api/v1/contract_api_trading_status': 1,
|
||
# Swap Account Interface
|
||
'swap-api/v1/swap_sub_auth_list': 1,
|
||
'swap-api/v1/swap_api_trading_status': 1,
|
||
# Swap Account Interface
|
||
'linear-swap-api/v1/swap_sub_auth_list': 1,
|
||
'linear-swap-api/v1/swap_api_trading_status': 1,
|
||
'linear-swap-api/v1/swap_cross_position_side': 1,
|
||
'linear-swap-api/v1/swap_position_side': 1,
|
||
'linear-swap-api/v3/unified_account_info': 1,
|
||
'linear-swap-api/v3/fix_position_margin_change_record': 1,
|
||
'linear-swap-api/v3/swap_unified_account_type': 1,
|
||
'linear-swap-api/v3/linear_swap_overview_account_info': 1,
|
||
},
|
||
'post': {
|
||
# Future Account Interface
|
||
'api/v1/contract_balance_valuation': 1,
|
||
'api/v1/contract_account_info': 1,
|
||
'api/v1/contract_position_info': 1,
|
||
'api/v1/contract_sub_auth': 1,
|
||
'api/v1/contract_sub_account_list': 1,
|
||
'api/v1/contract_sub_account_info_list': 1,
|
||
'api/v1/contract_sub_account_info': 1,
|
||
'api/v1/contract_sub_position_info': 1,
|
||
'api/v1/contract_financial_record': 1,
|
||
'api/v1/contract_financial_record_exact': 1,
|
||
'api/v1/contract_user_settlement_records': 1,
|
||
'api/v1/contract_order_limit': 1,
|
||
'api/v1/contract_fee': 1,
|
||
'api/v1/contract_transfer_limit': 1,
|
||
'api/v1/contract_position_limit': 1,
|
||
'api/v1/contract_account_position_info': 1,
|
||
'api/v1/contract_master_sub_transfer': 1,
|
||
'api/v1/contract_master_sub_transfer_record': 1,
|
||
'api/v1/contract_available_level_rate': 1,
|
||
'api/v3/contract_financial_record': 1,
|
||
'api/v3/contract_financial_record_exact': 1,
|
||
# Future Trade Interface
|
||
'api/v1/contract-cancel-after': 1,
|
||
'api/v1/contract_order': 1,
|
||
'api/v1/contract_batchorder': 1,
|
||
'api/v1/contract_cancel': 1,
|
||
'api/v1/contract_cancelall': 1,
|
||
'api/v1/contract_switch_lever_rate': 30,
|
||
'api/v1/lightning_close_position': 1,
|
||
'api/v1/contract_order_info': 1,
|
||
'api/v1/contract_order_detail': 1,
|
||
'api/v1/contract_openorders': 1,
|
||
'api/v1/contract_hisorders': 1,
|
||
'api/v1/contract_hisorders_exact': 1,
|
||
'api/v1/contract_matchresults': 1,
|
||
'api/v1/contract_matchresults_exact': 1,
|
||
'api/v3/contract_hisorders': 1,
|
||
'api/v3/contract_hisorders_exact': 1,
|
||
'api/v3/contract_matchresults': 1,
|
||
'api/v3/contract_matchresults_exact': 1,
|
||
# Contract Strategy Order Interface
|
||
'api/v1/contract_trigger_order': 1,
|
||
'api/v1/contract_trigger_cancel': 1,
|
||
'api/v1/contract_trigger_cancelall': 1,
|
||
'api/v1/contract_trigger_openorders': 1,
|
||
'api/v1/contract_trigger_hisorders': 1,
|
||
'api/v1/contract_tpsl_order': 1,
|
||
'api/v1/contract_tpsl_cancel': 1,
|
||
'api/v1/contract_tpsl_cancelall': 1,
|
||
'api/v1/contract_tpsl_openorders': 1,
|
||
'api/v1/contract_tpsl_hisorders': 1,
|
||
'api/v1/contract_relation_tpsl_order': 1,
|
||
'api/v1/contract_track_order': 1,
|
||
'api/v1/contract_track_cancel': 1,
|
||
'api/v1/contract_track_cancelall': 1,
|
||
'api/v1/contract_track_openorders': 1,
|
||
'api/v1/contract_track_hisorders': 1,
|
||
# Swap Account Interface
|
||
'swap-api/v1/swap_balance_valuation': 1,
|
||
'swap-api/v1/swap_account_info': 1,
|
||
'swap-api/v1/swap_position_info': 1,
|
||
'swap-api/v1/swap_account_position_info': 1,
|
||
'swap-api/v1/swap_sub_auth': 1,
|
||
'swap-api/v1/swap_sub_account_list': 1,
|
||
'swap-api/v1/swap_sub_account_info_list': 1,
|
||
'swap-api/v1/swap_sub_account_info': 1,
|
||
'swap-api/v1/swap_sub_position_info': 1,
|
||
'swap-api/v1/swap_financial_record': 1,
|
||
'swap-api/v1/swap_financial_record_exact': 1,
|
||
'swap-api/v1/swap_user_settlement_records': 1,
|
||
'swap-api/v1/swap_available_level_rate': 1,
|
||
'swap-api/v1/swap_order_limit': 1,
|
||
'swap-api/v1/swap_fee': 1,
|
||
'swap-api/v1/swap_transfer_limit': 1,
|
||
'swap-api/v1/swap_position_limit': 1,
|
||
'swap-api/v1/swap_master_sub_transfer': 1,
|
||
'swap-api/v1/swap_master_sub_transfer_record': 1,
|
||
'swap-api/v3/swap_financial_record': 1,
|
||
'swap-api/v3/swap_financial_record_exact': 1,
|
||
# Swap Trade Interface
|
||
'swap-api/v1/swap-cancel-after': 1,
|
||
'swap-api/v1/swap_order': 1,
|
||
'swap-api/v1/swap_batchorder': 1,
|
||
'swap-api/v1/swap_cancel': 1,
|
||
'swap-api/v1/swap_cancelall': 1,
|
||
'swap-api/v1/swap_lightning_close_position': 1,
|
||
'swap-api/v1/swap_switch_lever_rate': 30,
|
||
'swap-api/v1/swap_order_info': 1,
|
||
'swap-api/v1/swap_order_detail': 1,
|
||
'swap-api/v1/swap_openorders': 1,
|
||
'swap-api/v1/swap_hisorders': 1,
|
||
'swap-api/v1/swap_hisorders_exact': 1,
|
||
'swap-api/v1/swap_matchresults': 1,
|
||
'swap-api/v1/swap_matchresults_exact': 1,
|
||
'swap-api/v3/swap_matchresults': 1,
|
||
'swap-api/v3/swap_matchresults_exact': 1,
|
||
'swap-api/v3/swap_hisorders': 1,
|
||
'swap-api/v3/swap_hisorders_exact': 1,
|
||
# Swap Strategy Order Interface
|
||
'swap-api/v1/swap_trigger_order': 1,
|
||
'swap-api/v1/swap_trigger_cancel': 1,
|
||
'swap-api/v1/swap_trigger_cancelall': 1,
|
||
'swap-api/v1/swap_trigger_openorders': 1,
|
||
'swap-api/v1/swap_trigger_hisorders': 1,
|
||
'swap-api/v1/swap_tpsl_order': 1,
|
||
'swap-api/v1/swap_tpsl_cancel': 1,
|
||
'swap-api/v1/swap_tpsl_cancelall': 1,
|
||
'swap-api/v1/swap_tpsl_openorders': 1,
|
||
'swap-api/v1/swap_tpsl_hisorders': 1,
|
||
'swap-api/v1/swap_relation_tpsl_order': 1,
|
||
'swap-api/v1/swap_track_order': 1,
|
||
'swap-api/v1/swap_track_cancel': 1,
|
||
'swap-api/v1/swap_track_cancelall': 1,
|
||
'swap-api/v1/swap_track_openorders': 1,
|
||
'swap-api/v1/swap_track_hisorders': 1,
|
||
# Swap Account Interface
|
||
'linear-swap-api/v1/swap_lever_position_limit': 1,
|
||
'linear-swap-api/v1/swap_cross_lever_position_limit': 1,
|
||
'linear-swap-api/v1/swap_balance_valuation': 1,
|
||
'linear-swap-api/v1/swap_account_info': 1,
|
||
'linear-swap-api/v1/swap_cross_account_info': 1,
|
||
'linear-swap-api/v1/swap_position_info': 1,
|
||
'linear-swap-api/v1/swap_cross_position_info': 1,
|
||
'linear-swap-api/v1/swap_account_position_info': 1,
|
||
'linear-swap-api/v1/swap_cross_account_position_info': 1,
|
||
'linear-swap-api/v1/swap_sub_auth': 1,
|
||
'linear-swap-api/v1/swap_sub_account_list': 1,
|
||
'linear-swap-api/v1/swap_cross_sub_account_list': 1,
|
||
'linear-swap-api/v1/swap_sub_account_info_list': 1,
|
||
'linear-swap-api/v1/swap_cross_sub_account_info_list': 1,
|
||
'linear-swap-api/v1/swap_sub_account_info': 1,
|
||
'linear-swap-api/v1/swap_cross_sub_account_info': 1,
|
||
'linear-swap-api/v1/swap_sub_position_info': 1,
|
||
'linear-swap-api/v1/swap_cross_sub_position_info': 1,
|
||
'linear-swap-api/v1/swap_financial_record': 1,
|
||
'linear-swap-api/v1/swap_financial_record_exact': 1,
|
||
'linear-swap-api/v1/swap_user_settlement_records': 1,
|
||
'linear-swap-api/v1/swap_cross_user_settlement_records': 1,
|
||
'linear-swap-api/v1/swap_available_level_rate': 1,
|
||
'linear-swap-api/v1/swap_cross_available_level_rate': 1,
|
||
'linear-swap-api/v1/swap_order_limit': 1,
|
||
'linear-swap-api/v1/swap_fee': 1,
|
||
'linear-swap-api/v1/swap_transfer_limit': 1,
|
||
'linear-swap-api/v1/swap_cross_transfer_limit': 1,
|
||
'linear-swap-api/v1/swap_position_limit': 1,
|
||
'linear-swap-api/v1/swap_cross_position_limit': 1,
|
||
'linear-swap-api/v1/swap_master_sub_transfer': 1,
|
||
'linear-swap-api/v1/swap_master_sub_transfer_record': 1,
|
||
'linear-swap-api/v1/swap_transfer_inner': 1,
|
||
'linear-swap-api/v3/swap_financial_record': 1,
|
||
'linear-swap-api/v3/swap_financial_record_exact': 1,
|
||
# Swap Trade Interface
|
||
'linear-swap-api/v1/swap_order': 1,
|
||
'linear-swap-api/v1/swap_cross_order': 1,
|
||
'linear-swap-api/v1/swap_batchorder': 1,
|
||
'linear-swap-api/v1/swap_cross_batchorder': 1,
|
||
'linear-swap-api/v1/swap_cancel': 1,
|
||
'linear-swap-api/v1/swap_cross_cancel': 1,
|
||
'linear-swap-api/v1/swap_cancelall': 1,
|
||
'linear-swap-api/v1/swap_cross_cancelall': 1,
|
||
'linear-swap-api/v1/swap_switch_lever_rate': 30,
|
||
'linear-swap-api/v1/swap_cross_switch_lever_rate': 30,
|
||
'linear-swap-api/v1/swap_lightning_close_position': 1,
|
||
'linear-swap-api/v1/swap_cross_lightning_close_position': 1,
|
||
'linear-swap-api/v1/swap_order_info': 1,
|
||
'linear-swap-api/v1/swap_cross_order_info': 1,
|
||
'linear-swap-api/v1/swap_order_detail': 1,
|
||
'linear-swap-api/v1/swap_cross_order_detail': 1,
|
||
'linear-swap-api/v1/swap_openorders': 1,
|
||
'linear-swap-api/v1/swap_cross_openorders': 1,
|
||
'linear-swap-api/v1/swap_hisorders': 1,
|
||
'linear-swap-api/v1/swap_cross_hisorders': 1,
|
||
'linear-swap-api/v1/swap_hisorders_exact': 1,
|
||
'linear-swap-api/v1/swap_cross_hisorders_exact': 1,
|
||
'linear-swap-api/v1/swap_matchresults': 1,
|
||
'linear-swap-api/v1/swap_cross_matchresults': 1,
|
||
'linear-swap-api/v1/swap_matchresults_exact': 1,
|
||
'linear-swap-api/v1/swap_cross_matchresults_exact': 1,
|
||
'linear-swap-api/v1/linear-cancel-after': 1,
|
||
'linear-swap-api/v1/swap_switch_position_mode': 1,
|
||
'linear-swap-api/v1/swap_cross_switch_position_mode': 1,
|
||
'linear-swap-api/v3/swap_matchresults': 1,
|
||
'linear-swap-api/v3/swap_cross_matchresults': 1,
|
||
'linear-swap-api/v3/swap_matchresults_exact': 1,
|
||
'linear-swap-api/v3/swap_cross_matchresults_exact': 1,
|
||
'linear-swap-api/v3/swap_hisorders': 1,
|
||
'linear-swap-api/v3/swap_cross_hisorders': 1,
|
||
'linear-swap-api/v3/swap_hisorders_exact': 1,
|
||
'linear-swap-api/v3/swap_cross_hisorders_exact': 1,
|
||
'linear-swap-api/v3/fix_position_margin_change': 1,
|
||
'linear-swap-api/v3/swap_switch_account_type': 1,
|
||
'linear-swap-api/v3/linear_swap_fee_switch': 1,
|
||
# Swap Strategy Order Interface
|
||
'linear-swap-api/v1/swap_trigger_order': 1,
|
||
'linear-swap-api/v1/swap_cross_trigger_order': 1,
|
||
'linear-swap-api/v1/swap_trigger_cancel': 1,
|
||
'linear-swap-api/v1/swap_cross_trigger_cancel': 1,
|
||
'linear-swap-api/v1/swap_trigger_cancelall': 1,
|
||
'linear-swap-api/v1/swap_cross_trigger_cancelall': 1,
|
||
'linear-swap-api/v1/swap_trigger_openorders': 1,
|
||
'linear-swap-api/v1/swap_cross_trigger_openorders': 1,
|
||
'linear-swap-api/v1/swap_trigger_hisorders': 1,
|
||
'linear-swap-api/v1/swap_cross_trigger_hisorders': 1,
|
||
'linear-swap-api/v1/swap_tpsl_order': 1,
|
||
'linear-swap-api/v1/swap_cross_tpsl_order': 1,
|
||
'linear-swap-api/v1/swap_tpsl_cancel': 1,
|
||
'linear-swap-api/v1/swap_cross_tpsl_cancel': 1,
|
||
'linear-swap-api/v1/swap_tpsl_cancelall': 1,
|
||
'linear-swap-api/v1/swap_cross_tpsl_cancelall': 1,
|
||
'linear-swap-api/v1/swap_tpsl_openorders': 1,
|
||
'linear-swap-api/v1/swap_cross_tpsl_openorders': 1,
|
||
'linear-swap-api/v1/swap_tpsl_hisorders': 1,
|
||
'linear-swap-api/v1/swap_cross_tpsl_hisorders': 1,
|
||
'linear-swap-api/v1/swap_relation_tpsl_order': 1,
|
||
'linear-swap-api/v1/swap_cross_relation_tpsl_order': 1,
|
||
'linear-swap-api/v1/swap_track_order': 1,
|
||
'linear-swap-api/v1/swap_cross_track_order': 1,
|
||
'linear-swap-api/v1/swap_track_cancel': 1,
|
||
'linear-swap-api/v1/swap_cross_track_cancel': 1,
|
||
'linear-swap-api/v1/swap_track_cancelall': 1,
|
||
'linear-swap-api/v1/swap_cross_track_cancelall': 1,
|
||
'linear-swap-api/v1/swap_track_openorders': 1,
|
||
'linear-swap-api/v1/swap_cross_track_openorders': 1,
|
||
'linear-swap-api/v1/swap_track_hisorders': 1,
|
||
'linear-swap-api/v1/swap_cross_track_hisorders': 1,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'fees': {
|
||
'trading': {
|
||
'feeSide': 'get',
|
||
'tierBased': False,
|
||
'percentage': True,
|
||
'maker': self.parse_number('0.002'),
|
||
'taker': self.parse_number('0.002'),
|
||
},
|
||
},
|
||
'exceptions': {
|
||
'broad': {
|
||
'contract is restricted of closing positions on API. Please contact customer service': OnMaintenance,
|
||
'maintain': OnMaintenance,
|
||
'API key has no permission': PermissionDenied, # {"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: API key has no permission [API Key没有权限]","data":null}
|
||
},
|
||
'exact': {
|
||
# err-code
|
||
'403': AuthenticationError, # {"status":"error","err_code":403,"err_msg":"Incorrect Access key [Access key错误]","ts":1652774224344}
|
||
'1010': AccountNotEnabled, # {"status":"error","err_code":1010,"err_msg":"Account doesnt exist.","ts":1648137970490}
|
||
'1003': AuthenticationError, # {code: '1003', message: 'invalid signature'}
|
||
'1013': BadSymbol, # {"status":"error","err_code":1013,"err_msg":"This contract symbol doesnt exist.","ts":1640550459583}
|
||
'1017': OrderNotFound, # {"status":"error","err_code":1017,"err_msg":"Order doesnt exist.","ts":1640550859242}
|
||
'1034': InvalidOrder, # {"status":"error","err_code":1034,"err_msg":"Incorrect field of order price type.","ts":1643802870182}
|
||
'1036': InvalidOrder, # {"status":"error","err_code":1036,"err_msg":"Incorrect field of open long form.","ts":1643802518986}
|
||
'1039': InvalidOrder, # {"status":"error","err_code":1039,"err_msg":"Buy price must be lower than 39270.9USDT. Sell price must exceed 37731USDT.","ts":1643802374403}
|
||
'1041': InvalidOrder, # {"status":"error","err_code":1041,"err_msg":"The order amount exceeds the limit(170000Cont), please modify and order again.","ts":1643802784940}
|
||
'1047': InsufficientFunds, # {"status":"error","err_code":1047,"err_msg":"Insufficient margin available.","ts":1643802672652}
|
||
'1048': InsufficientFunds, # {"status":"error","err_code":1048,"err_msg":"Insufficient close amount available.","ts":1652772408864}
|
||
'1061': OrderNotFound, # {"status":"ok","data":{"errors":[{"order_id":"1349442392365359104","err_code":1061,"err_msg":"The order does not exist."}],"successes":""},"ts":1741773744526}
|
||
'1051': InvalidOrder, # {"status":"error","err_code":1051,"err_msg":"No orders to cancel.","ts":1652552125876}
|
||
'1066': BadSymbol, # {"status":"error","err_code":1066,"err_msg":"The symbol field cannot be empty. Please re-enter.","ts":1640550819147}
|
||
'1067': InvalidOrder, # {"status":"error","err_code":1067,"err_msg":"The client_order_id field is invalid. Please re-enter.","ts":1643802119413}
|
||
'1094': InvalidOrder, # {"status":"error","err_code":1094,"err_msg":"The leverage cannot be empty, please switch the leverage or contact customer service","ts":1640496946243}
|
||
'1220': AccountNotEnabled, # {"status":"error","err_code":1220,"err_msg":"You don’t have access permission have not opened contracts trading.","ts":1645096660718}
|
||
'1303': BadRequest, # {"code":1303,"data":null,"message":"Each transfer-out cannot be less than 5USDT.","success":false,"print-log":true}
|
||
'1461': InvalidOrder, # {"status":"error","err_code":1461,"err_msg":"Current positions have triggered position limits(5000USDT). Please modify.","ts":1652554651234}
|
||
'4007': BadRequest, # {"code":"4007","msg":"Unified account special interface, non - one account is not available","data":null,"ts":"1698413427651"}'
|
||
'bad-request': BadRequest,
|
||
'validation-format-error': BadRequest, # {"status":"error","err-code":"validation-format-error","err-msg":"Format Error: order-id.","data":null}
|
||
'validation-constraints-required': BadRequest, # {"status":"error","err-code":"validation-constraints-required","err-msg":"Field is missing: client-order-id.","data":null}
|
||
'base-date-limit-error': BadRequest, # {"status":"error","err-code":"base-date-limit-error","err-msg":"date less than system limit","data":null}
|
||
'api-not-support-temp-addr': PermissionDenied, # {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null}
|
||
'timeout': RequestTimeout, # {"ts":1571653730865,"status":"error","err-code":"timeout","err-msg":"Request Timeout"}
|
||
'gateway-internal-error': ExchangeNotAvailable, # {"status":"error","err-code":"gateway-internal-error","err-msg":"Failed to load data. Try again later.","data":null}
|
||
'account-frozen-balance-insufficient-error': InsufficientFunds, # {"status":"error","err-code":"account-frozen-balance-insufficient-error","err-msg":"trade account balance is not enough, left: `0.0027`","data":null}
|
||
'invalid-amount': InvalidOrder, # eg "Paramemter `amount` is invalid."
|
||
'order-limitorder-amount-min-error': InvalidOrder, # limit order amount error, min: `0.001`
|
||
'order-limitorder-amount-max-error': InvalidOrder, # market order amount error, max: `1000000`
|
||
'order-marketorder-amount-min-error': InvalidOrder, # market order amount error, min: `0.01`
|
||
'order-limitorder-price-min-error': InvalidOrder, # limit order price error
|
||
'order-limitorder-price-max-error': InvalidOrder, # limit order price error
|
||
'order-stop-order-hit-trigger': InvalidOrder, # {"status":"error","err-code":"order-stop-order-hit-trigger","err-msg":"Orders that are triggered immediately are not supported.","data":null}
|
||
'order-value-min-error': InvalidOrder, # {"status":"error","err-code":"order-value-min-error","err-msg":"Order total cannot be lower than: 1 USDT","data":null}
|
||
'order-invalid-price': InvalidOrder, # {"status":"error","err-code":"order-invalid-price","err-msg":"invalid price","data":null}
|
||
'order-holding-limit-failed': InvalidOrder, # {"status":"error","err-code":"order-holding-limit-failed","err-msg":"Order failed, exceeded the holding limit of self currency","data":null}
|
||
'order-orderprice-precision-error': InvalidOrder, # {"status":"error","err-code":"order-orderprice-precision-error","err-msg":"order price precision error, scale: `4`","data":null}
|
||
'order-etp-nav-price-max-error': InvalidOrder, # {"status":"error","err-code":"order-etp-nav-price-max-error","err-msg":"Order price cannot be higher than 5% of NAV","data":null}
|
||
'order-orderstate-error': OrderNotFound, # canceling an already canceled order
|
||
'order-queryorder-invalid': OrderNotFound, # querying a non-existent order
|
||
'order-update-error': ExchangeNotAvailable, # undocumented error
|
||
'api-signature-check-failed': AuthenticationError,
|
||
'api-signature-not-valid': AuthenticationError, # {"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: Incorrect Access key [Access key错误]","data":null}
|
||
'base-record-invalid': OrderNotFound, # https://github.com/ccxt/ccxt/issues/5750
|
||
'base-symbol-trade-disabled': BadSymbol, # {"status":"error","err-code":"base-symbol-trade-disabled","err-msg":"Trading is disabled for self symbol","data":null}
|
||
'base-symbol-error': BadSymbol, # {"status":"error","err-code":"base-symbol-error","err-msg":"The symbol is invalid","data":null}
|
||
'system-maintenance': OnMaintenance, # {"status": "error", "err-code": "system-maintenance", "err-msg": "System is in maintenance!", "data": null}
|
||
'base-request-exceed-frequency-limit': RateLimitExceeded, # {"status":"error","err-code":"base-request-exceed-frequency-limit","err-msg":"Frequency of requests has exceeded the limit, please try again later","data":null}
|
||
# err-msg
|
||
'invalid symbol': BadSymbol, # {"ts":1568813334794,"status":"error","err-code":"invalid-parameter","err-msg":"invalid symbol"}
|
||
'symbol trade not open now': BadSymbol, # {"ts":1576210479343,"status":"error","err-code":"invalid-parameter","err-msg":"symbol trade not open now"}
|
||
'require-symbol': BadSymbol, # {"status":"error","err-code":"require-symbol","err-msg":"Parameter `symbol` is required.","data":null},
|
||
'invalid-address': BadRequest, # {"status":"error","err-code":"invalid-address","err-msg":"Invalid address.","data":null},
|
||
'base-currency-chain-error': BadRequest, # {"status":"error","err-code":"base-currency-chain-error","err-msg":"The current currency chain does not exist","data":null},
|
||
'dw-insufficient-balance': InsufficientFunds, # {"status":"error","err-code":"dw-insufficient-balance","err-msg":"Insufficient balance. You can only transfer `12.3456` at most.","data":null}
|
||
'base-withdraw-fee-error': BadRequest, # {"status":"error","err-code":"base-withdraw-fee-error","err-msg":"withdrawal fee is not within limits","data":null}
|
||
'dw-withdraw-min-limit': BadRequest, # {"status":"error","err-code":"dw-withdraw-min-limit","err-msg":"The withdrawal amount is less than the minimum limit.","data":null}
|
||
'request limit': RateLimitExceeded, # {"ts":1687004814731,"status":"error","err-code":"invalid-parameter","err-msg":"request limit"}
|
||
},
|
||
},
|
||
'precisionMode': TICK_SIZE,
|
||
'options': {
|
||
'include_OS_certificates': False, # temporarily leave self, remove in future
|
||
'fetchMarkets': {
|
||
'types': {
|
||
'spot': True,
|
||
'linear': True,
|
||
'inverse': True,
|
||
},
|
||
},
|
||
'timeDifference': 0, # the difference between system clock and exchange clock
|
||
'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
|
||
'fetchOHLCV': {
|
||
'useHistoricalEndpointForSpot': True,
|
||
},
|
||
'withdraw': {
|
||
'includeFee': False,
|
||
},
|
||
'defaultType': 'spot', # spot, future, swap
|
||
'defaultSubType': 'linear', # inverse, linear
|
||
'defaultNetwork': 'ERC20',
|
||
'defaultNetworks': {
|
||
'ETH': 'ERC20',
|
||
'BTC': 'BTC',
|
||
'USDT': 'TRC20',
|
||
},
|
||
'networks': {
|
||
# by displaynames
|
||
'TRC20': 'TRX', # TRON for mainnet
|
||
'BTC': 'BTC',
|
||
'ERC20': 'ETH', # ETH for mainnet
|
||
'SOL': 'SOLANA',
|
||
'HRC20': 'HECO',
|
||
'BEP20': 'BSC',
|
||
'XMR': 'XMR',
|
||
'LTC': 'LTC',
|
||
'XRP': 'XRP',
|
||
'XLM': 'XLM',
|
||
'CRONOS': 'CRO',
|
||
'CRO': 'CRO',
|
||
'GLMR': 'GLMR',
|
||
'POLYGON': 'MATIC',
|
||
'MATIC': 'MATIC',
|
||
'BTT': 'BTT',
|
||
'CUBE': 'CUBE',
|
||
'IOST': 'IOST',
|
||
'NEO': 'NEO',
|
||
'KLAY': 'KLAY',
|
||
'EOS': 'EOS',
|
||
'THETA': 'THETA',
|
||
'NAS': 'NAS',
|
||
'NULS': 'NULS',
|
||
'QTUM': 'QTUM',
|
||
'FTM': 'FTM',
|
||
'CELO': 'CELO',
|
||
'DOGE': 'DOGE',
|
||
'DOGECHAIN': 'DOGECHAIN',
|
||
'NEAR': 'NEAR',
|
||
'STEP': 'STEP',
|
||
'BITCI': 'BITCI',
|
||
'CARDANO': 'ADA',
|
||
'ADA': 'ADA',
|
||
'ETC': 'ETC',
|
||
'LUK': 'LUK',
|
||
'MINEPLEX': 'MINEPLEX',
|
||
'DASH': 'DASH',
|
||
'ZEC': 'ZEC',
|
||
'IOTA': 'IOTA',
|
||
'NEON3': 'NEON3',
|
||
'XEM': 'XEM',
|
||
'HC': 'HC',
|
||
'LSK': 'LSK',
|
||
'DCR': 'DCR',
|
||
'BTG': 'BTG',
|
||
'STEEM': 'STEEM',
|
||
'BTS': 'BTS',
|
||
'ICX': 'ICX',
|
||
'WAVES': 'WAVES',
|
||
'CMT': 'CMT',
|
||
'BTM': 'BTM',
|
||
'VET': 'VET',
|
||
'XZC': 'XZC',
|
||
'ACT': 'ACT',
|
||
'SMT': 'SMT',
|
||
'BCD': 'BCD',
|
||
'WAX': 'WAX1',
|
||
'WICC': 'WICC',
|
||
'ELF': 'ELF',
|
||
'ZIL': 'ZIL',
|
||
'ELA': 'ELA',
|
||
'BCX': 'BCX',
|
||
'SBTC': 'SBTC',
|
||
'BIFI': 'BIFI',
|
||
'CTXC': 'CTXC',
|
||
'WAN': 'WAN',
|
||
'POLYX': 'POLYX',
|
||
'PAI': 'PAI',
|
||
'WTC': 'WTC',
|
||
'DGB': 'DGB',
|
||
'XVG': 'XVG',
|
||
'AAC': 'AAC',
|
||
'AE': 'AE',
|
||
'SEELE': 'SEELE',
|
||
'BCV': 'BCV',
|
||
'GRS': 'GRS',
|
||
'ARDR': 'ARDR',
|
||
'NANO': 'NANO',
|
||
'ZEN': 'ZEN',
|
||
'RBTC': 'RBTC',
|
||
'BSV': 'BSV',
|
||
'GAS': 'GAS',
|
||
'XTZ': 'XTZ',
|
||
'LAMB': 'LAMB',
|
||
'CVNT1': 'CVNT1',
|
||
'DOCK': 'DOCK',
|
||
'SC': 'SC',
|
||
'KMD': 'KMD',
|
||
'ETN': 'ETN',
|
||
'TOP': 'TOP',
|
||
'IRIS': 'IRIS',
|
||
'UGAS': 'UGAS',
|
||
'TT': 'TT',
|
||
'NEWTON': 'NEWTON',
|
||
'VSYS': 'VSYS',
|
||
'FSN': 'FSN',
|
||
'BHD': 'BHD',
|
||
'ONE': 'ONE',
|
||
'EM': 'EM',
|
||
'CKB': 'CKB',
|
||
'EOSS': 'EOSS',
|
||
'HIVE': 'HIVE',
|
||
'RVN': 'RVN',
|
||
'DOT': 'DOT',
|
||
'KSM': 'KSM',
|
||
'BAND': 'BAND',
|
||
'OEP4': 'OEP4',
|
||
'NBS': 'NBS',
|
||
'FIS': 'FIS',
|
||
'AR': 'AR',
|
||
'HBAR': 'HBAR',
|
||
'FIL': 'FIL',
|
||
'MASS': 'MASS',
|
||
'KAVA': 'KAVA',
|
||
'XYM': 'XYM',
|
||
'ENJ': 'ENJ',
|
||
'CRUST': 'CRUST',
|
||
'ICP': 'ICP',
|
||
'CSPR': 'CSPR',
|
||
'FLOW': 'FLOW',
|
||
'IOTX': 'IOTX',
|
||
'LAT': 'LAT',
|
||
'APT': 'APT',
|
||
'XCH': 'XCH',
|
||
'MINA': 'MINA',
|
||
'XEC': 'ECASH',
|
||
'XPRT': 'XPRT',
|
||
'CCA': 'ACA',
|
||
'AOTI': 'COTI',
|
||
'AKT': 'AKT',
|
||
'ARS': 'ARS',
|
||
'ASTR': 'ASTR',
|
||
'AZERO': 'AZERO',
|
||
'BLD': 'BLD',
|
||
'BRISE': 'BRISE',
|
||
'CORE': 'CORE',
|
||
'DESO': 'DESO',
|
||
'DFI': 'DFI',
|
||
'EGLD': 'EGLD',
|
||
'ERG': 'ERG',
|
||
'ETHF': 'ETHFAIR',
|
||
'ETHW': 'ETHW',
|
||
'EVMOS': 'EVMOS',
|
||
'FIO': 'FIO',
|
||
'FLR': 'FLR',
|
||
'FINSCHIA': 'FINSCHIA',
|
||
'KMA': 'KMA',
|
||
'KYVE': 'KYVE',
|
||
'MEV': 'MEV',
|
||
'MOVR': 'MOVR',
|
||
'NODL': 'NODL',
|
||
'OAS': 'OAS',
|
||
'OSMO': 'OSMO',
|
||
'PAYCOIN': 'PAYCOIN',
|
||
'POKT': 'POKT',
|
||
'PYG': 'PYG',
|
||
'REI': 'REI',
|
||
'SCRT': 'SCRT',
|
||
'SDN': 'SDN',
|
||
'SEI': 'SEI',
|
||
'SGB': 'SGB',
|
||
'SUI': 'SUI',
|
||
'SXP': 'SOLAR',
|
||
'SYS': 'SYS',
|
||
'TENET': 'TENET',
|
||
'TON': 'TON',
|
||
'UNQ': 'UNQ',
|
||
'UYU': 'UYU',
|
||
'WEMIX': 'WEMIX',
|
||
'XDC': 'XDC',
|
||
'XPLA': 'XPLA',
|
||
# todo: below
|
||
# 'LUNC': 'LUNC',
|
||
# 'TERRA': 'TERRA', # tbd
|
||
# 'LUNA': 'LUNA', tbd
|
||
# 'FCT2': 'FCT2',
|
||
# FIL-0X ?
|
||
# 'COSMOS': 'ATOM1',
|
||
# 'ATOM': 'ATOM1',
|
||
# 'CRO': 'CRO',
|
||
# 'OP': ['OPTIMISM', 'OPTIMISMETH']
|
||
# 'ARB': ['ARB', 'ARBITRUMETH']
|
||
# 'CHZ': ['CHZ', 'CZH'],
|
||
# todo: AVAXCCHAIN CCHAIN AVAX
|
||
# 'ALGO': ['ALGO', 'ALGOUSDT']
|
||
# 'ONT': ['ONT', 'ONTOLOGY'],
|
||
# 'BCC': 'BCC', BCH's somewhat chain
|
||
# 'DBC1': 'DBC1',
|
||
},
|
||
# https://github.com/ccxt/ccxt/issues/5376
|
||
'fetchOrdersByStatesMethod': 'spot_private_get_v1_order_orders', # 'spot_private_get_v1_order_history' # https://github.com/ccxt/ccxt/pull/5392
|
||
'createMarketBuyOrderRequiresPrice': True,
|
||
'language': 'en-US',
|
||
'broker': {
|
||
'id': 'AA03022abc',
|
||
},
|
||
'accountsByType': {
|
||
'spot': 'pro',
|
||
'funding': 'pro',
|
||
'future': 'futures',
|
||
},
|
||
'accountsById': {
|
||
'spot': 'spot',
|
||
'margin': 'margin',
|
||
'otc': 'otc',
|
||
'point': 'point',
|
||
'super-margin': 'super-margin',
|
||
'investment': 'investment',
|
||
'borrow': 'borrow',
|
||
'grid-trading': 'grid-trading',
|
||
'deposit-earning': 'deposit-earning',
|
||
'otc-options': 'otc-options',
|
||
},
|
||
'typesByAccount': {
|
||
'pro': 'spot',
|
||
'futures': 'future',
|
||
},
|
||
'spot': {
|
||
'stopOrderTypes': {
|
||
'stop-limit': True,
|
||
'buy-stop-limit': True,
|
||
'sell-stop-limit': True,
|
||
'stop-limit-fok': True,
|
||
'buy-stop-limit-fok': True,
|
||
'sell-stop-limit-fok': True,
|
||
},
|
||
'limitOrderTypes': {
|
||
'limit': True,
|
||
'buy-limit': True,
|
||
'sell-limit': True,
|
||
'ioc': True,
|
||
'buy-ioc': True,
|
||
'sell-ioc': True,
|
||
'limit-maker': True,
|
||
'buy-limit-maker': True,
|
||
'sell-limit-maker': True,
|
||
'stop-limit': True,
|
||
'buy-stop-limit': True,
|
||
'sell-stop-limit': True,
|
||
'limit-fok': True,
|
||
'buy-limit-fok': True,
|
||
'sell-limit-fok': True,
|
||
'stop-limit-fok': True,
|
||
'buy-stop-limit-fok': True,
|
||
'sell-stop-limit-fok': True,
|
||
},
|
||
},
|
||
},
|
||
'commonCurrencies': {
|
||
# https://github.com/ccxt/ccxt/issues/6081
|
||
# https://github.com/ccxt/ccxt/issues/3365
|
||
# https://github.com/ccxt/ccxt/issues/2873
|
||
'NGL': 'GFNGL',
|
||
'GET': 'THEMIS', # conflict with GET(Guaranteed Entrance Token, GET Protocol)
|
||
'GTC': 'GAMECOM', # conflict with Gitcoin and Gastrocoin
|
||
'HIT': 'HITCHAIN',
|
||
# https://github.com/ccxt/ccxt/issues/7399
|
||
# https://coinmarketcap.com/currencies/pnetwork/
|
||
# https://coinmarketcap.com/currencies/penta/markets/
|
||
# https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/
|
||
'PNT': 'PENTA',
|
||
'SBTC': 'SUPERBITCOIN',
|
||
'SOUL': 'SOULSAVER',
|
||
'BIFI': 'BITCOINFILE', # conflict with Beefy.Finance https://github.com/ccxt/ccxt/issues/8706
|
||
'FUD': 'FTX Users Debt',
|
||
},
|
||
'features': {
|
||
'spot': {
|
||
'sandbox': True,
|
||
'createOrder': {
|
||
'marginMode': True,
|
||
'triggerPrice': True,
|
||
'triggerDirection': True,
|
||
'triggerPriceType': None,
|
||
'stopLossPrice': False, # todo: add support by triggerprice
|
||
'takeProfitPrice': False,
|
||
'attachedStopLossTakeProfit': None,
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': True,
|
||
'GTD': False,
|
||
},
|
||
'hedged': False,
|
||
'trailing': False,
|
||
'iceberg': False,
|
||
'selfTradePrevention': True, # todo implement
|
||
'leverage': True, # todo implement
|
||
'marketBuyByCost': True,
|
||
'marketBuyRequiresPrice': True,
|
||
},
|
||
'createOrders': {
|
||
'max': 10,
|
||
},
|
||
'fetchMyTrades': {
|
||
'marginMode': False,
|
||
'limit': 500,
|
||
'daysBack': 120,
|
||
'untilDays': 2,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': False,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': False,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'limit': 500,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrders': {
|
||
'marginMode': False,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'limit': 500,
|
||
'untilDays': 2,
|
||
'daysBack': 180,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchClosedOrders': {
|
||
'marginMode': False,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'untilDays': 2,
|
||
'limit': 500,
|
||
'daysBack': 180,
|
||
'daysBackCanceled': 1 / 12,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOHLCV': {
|
||
'limit': 1000, # 2000 for non-historical
|
||
},
|
||
},
|
||
'forDerivatives': {
|
||
'extends': 'spot',
|
||
'createOrder': {
|
||
'stopLossPrice': True,
|
||
'takeProfitPrice': True,
|
||
'trailing': True,
|
||
'hedged': True,
|
||
# 'leverage': True, # todo
|
||
},
|
||
'createOrders': {
|
||
'max': 25,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': True,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': True,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'limit': 50,
|
||
},
|
||
'fetchOrders': {
|
||
'marginMode': True,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'limit': 50,
|
||
'daysBack': 90,
|
||
},
|
||
'fetchClosedOrders': {
|
||
'marginMode': True,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'untilDays': 2,
|
||
'limit': 50,
|
||
'daysBack': 90,
|
||
'daysBackCanceled': 1 / 12,
|
||
},
|
||
'fetchOHLCV': {
|
||
'limit': 2000,
|
||
},
|
||
},
|
||
'swap': {
|
||
'linear': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
'inverse': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
},
|
||
'future': {
|
||
'linear': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
'inverse': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
},
|
||
},
|
||
})
|
||
|
||
def fetch_status(self, params={}):
|
||
"""
|
||
the latest known information on the availability of the exchange API
|
||
|
||
https://huobiapi.github.io/docs/spot/v1/en/#get-system-status
|
||
https://huobiapi.github.io/docs/dm/v1/en/#get-system-status
|
||
https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-system-status
|
||
https://huobiapi.github.io/docs/usdt_swap/v1/en/#get-system-status
|
||
https://huobiapi.github.io/docs/usdt_swap/v1/en/#query-whether-the-system-is-available # contractPublicGetHeartbeat
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `status structure <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, step15(merged depth data 0-5,14-15, when step is 0,depth data will not be merged
|
||
# to get depth data within step 20, use step6, step7, step8, step9, step10, step11, step12, step13(merged depth data 7-13), when step is 6, depth data will not be merged
|
||
#
|
||
'type': 'step0',
|
||
# 'symbol': market['id'], # spot, future
|
||
# 'contract_code': market['id'], # swap
|
||
}
|
||
response = None
|
||
if market['linear']:
|
||
request['contract_code'] = market['id']
|
||
response = self.contractPublicGetLinearSwapExMarketDepth(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if market['future']:
|
||
request['symbol'] = market['id']
|
||
response = self.contractPublicGetMarketDepth(self.extend(request, params))
|
||
elif market['swap']:
|
||
request['contract_code'] = market['id']
|
||
response = self.contractPublicGetSwapExMarketDepth(self.extend(request, params))
|
||
else:
|
||
if limit is not None:
|
||
# Valid depths are 5, 10, 20 or empty https://huobiapi.github.io/docs/spot/v1/en/#get-market-depth
|
||
if (limit != 5) and (limit != 10) and (limit != 20) and (limit != 150):
|
||
raise BadRequest(self.id + ' fetchOrderBook() limit argument must be None, 5, 10, 20, or 150, default is 150')
|
||
# only set the depth if it is not 150
|
||
# 150 is the implicit default on the exchange side for step0 and no orderbook aggregation
|
||
# it is not accepted by the exchange if you set it explicitly
|
||
if limit != 150:
|
||
request['depth'] = limit
|
||
request['symbol'] = market['id']
|
||
response = self.spotPublicGetMarketDepth(self.extend(request, params))
|
||
#
|
||
# spot, future, swap
|
||
#
|
||
# {
|
||
# "status": "ok",
|
||
# "ch": "market.btcusdt.depth.step0",
|
||
# "ts": 1583474832790,
|
||
# "tick": {
|
||
# "bids": [
|
||
# [9100.290000000000000000, 0.200000000000000000],
|
||
# [9099.820000000000000000, 0.200000000000000000],
|
||
# [9099.610000000000000000, 0.205000000000000000],
|
||
# ],
|
||
# "asks": [
|
||
# [9100.640000000000000000, 0.005904000000000000],
|
||
# [9101.010000000000000000, 0.287311000000000000],
|
||
# [9101.030000000000000000, 0.012121000000000000],
|
||
# ],
|
||
# "ch":"market.BTC-USD.depth.step0",
|
||
# "ts":1583474832008,
|
||
# "id":1637554816,
|
||
# "mrid":121654491624,
|
||
# "version":104999698781
|
||
# }
|
||
# }
|
||
#
|
||
if 'tick' in response:
|
||
if not response['tick']:
|
||
raise BadSymbol(self.id + ' fetchOrderBook() returned empty response: ' + self.json(response))
|
||
tick = self.safe_value(response, 'tick')
|
||
timestamp = self.safe_integer(tick, 'ts', self.safe_integer(response, 'ts'))
|
||
result = self.parse_order_book(tick, symbol, timestamp)
|
||
result['nonce'] = self.safe_integer(tick, 'version')
|
||
return result
|
||
raise ExchangeError(self.id + ' fetchOrderBook() returned unrecognized response: ' + self.json(response))
|
||
|
||
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
||
#
|
||
# spot fetchTrades(public)
|
||
#
|
||
# {
|
||
# "amount": 0.010411000000000000,
|
||
# "trade-id": 102090736910,
|
||
# "ts": 1583497692182,
|
||
# "id": 10500517034273194594947,
|
||
# "price": 9096.050000000000000000,
|
||
# "direction": "sell"
|
||
# }
|
||
#
|
||
# spot fetchMyTrades(private)
|
||
#
|
||
# {
|
||
# "symbol": "swftcbtc",
|
||
# "fee-currency": "swftc",
|
||
# "filled-fees": "0",
|
||
# "source": "spot-api",
|
||
# "id": 83789509854000,
|
||
# "type": "buy-limit",
|
||
# "order-id": 83711103204909,
|
||
# 'filled-points': "0.005826843283532154",
|
||
# "fee-deduct-currency": "ht",
|
||
# 'filled-amount': "45941.53",
|
||
# "price": "0.0000001401",
|
||
# "created-at": 1597933260729,
|
||
# "match-id": 100087455560,
|
||
# "role": "maker",
|
||
# "trade-id": 100050305348
|
||
# }
|
||
#
|
||
# linear swap isolated margin fetchOrder details
|
||
#
|
||
# {
|
||
# "trade_id": 131560927,
|
||
# "trade_price": 13059.800000000000000000,
|
||
# "trade_volume": 1.000000000000000000,
|
||
# "trade_turnover": 13.059800000000000000,
|
||
# "trade_fee": -0.005223920000000000,
|
||
# "created_at": 1603703614715,
|
||
# "role": "taker",
|
||
# "fee_asset": "USDT",
|
||
# "profit": 0,
|
||
# "real_profit": 0,
|
||
# "id": "131560927-770334322963152896-1"
|
||
# }
|
||
#
|
||
# inverse swap cross margin fetchMyTrades
|
||
#
|
||
# {
|
||
# "contract_type":"swap",
|
||
# "pair":"O3-USDT",
|
||
# "business_type":"swap",
|
||
# "query_id":652123190,
|
||
# "match_id":28306009409,
|
||
# "order_id":941137865226903553,
|
||
# "symbol":"O3",
|
||
# "contract_code":"O3-USDT",
|
||
# "direction":"sell",
|
||
# "offset":"open",
|
||
# "trade_volume":100.000000000000000000,
|
||
# "trade_price":0.398500000000000000,
|
||
# "trade_turnover":39.850000000000000000,
|
||
# "trade_fee":-0.007970000000000000,
|
||
# "offset_profitloss":0E-18,
|
||
# "create_date":1644426352999,
|
||
# "role":"Maker",
|
||
# "order_source":"api",
|
||
# "order_id_str":"941137865226903553",
|
||
# "id":"28306009409-941137865226903553-1",
|
||
# "fee_asset":"USDT",
|
||
# "margin_mode":"cross",
|
||
# "margin_account":"USDT",
|
||
# "real_profit":0E-18,
|
||
# "trade_partition":"USDT"
|
||
# }
|
||
#
|
||
marketId = self.safe_string_2(trade, 'contract_code', 'symbol')
|
||
market = self.safe_market(marketId, market)
|
||
symbol = market['symbol']
|
||
timestamp = self.safe_integer_2(trade, 'ts', 'created-at')
|
||
timestamp = self.safe_integer_2(trade, 'created_at', 'create_date', timestamp)
|
||
order = self.safe_string_2(trade, 'order-id', 'order_id')
|
||
side = self.safe_string(trade, 'direction')
|
||
type = self.safe_string(trade, 'type')
|
||
if type is not None:
|
||
typeParts = type.split('-')
|
||
side = typeParts[0]
|
||
type = typeParts[1]
|
||
takerOrMaker = self.safe_string_lower(trade, 'role')
|
||
priceString = self.safe_string_2(trade, 'price', 'trade_price')
|
||
amountString = self.safe_string_2(trade, 'filled-amount', 'amount')
|
||
amountString = self.safe_string(trade, 'trade_volume', amountString)
|
||
costString = self.safe_string(trade, 'trade_turnover')
|
||
fee = None
|
||
feeCost = self.safe_string(trade, 'filled-fees')
|
||
if feeCost is None:
|
||
feeCost = Precise.string_neg(self.safe_string(trade, 'trade_fee'))
|
||
feeCurrencyId = self.safe_string_2(trade, 'fee-currency', 'fee_asset')
|
||
feeCurrency = self.safe_currency_code(feeCurrencyId)
|
||
filledPoints = self.safe_string(trade, 'filled-points')
|
||
if filledPoints is not None:
|
||
if (feeCost is None) or Precise.string_equals(feeCost, '0'):
|
||
feeDeductCurrency = self.safe_string(trade, 'fee-deduct-currency')
|
||
if feeDeductCurrency is not None:
|
||
feeCost = filledPoints
|
||
feeCurrency = self.safe_currency_code(feeDeductCurrency)
|
||
if feeCost is not None:
|
||
fee = {
|
||
'cost': feeCost,
|
||
'currency': feeCurrency,
|
||
}
|
||
# htx's multi-market trade-id is a bit complex to parse accordingly.
|
||
# - for `id` which contains hyphen, it would be the unique id, eg. xxxxxx-1, xxxxxx-2(self happens mostly for contract markets)
|
||
# - otherwise the least priority is given to the `id` key
|
||
id: Str = None
|
||
safeId = self.safe_string(trade, 'id')
|
||
if safeId is not None and safeId.find('-') >= 0:
|
||
id = safeId
|
||
else:
|
||
id = self.safe_string_n(trade, ['trade_id', 'trade-id', 'id'])
|
||
return self.safe_trade({
|
||
'id': id,
|
||
'info': trade,
|
||
'order': order,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': symbol,
|
||
'type': type,
|
||
'side': side,
|
||
'takerOrMaker': takerOrMaker,
|
||
'price': priceString,
|
||
'amount': amountString,
|
||
'cost': costString,
|
||
'fee': fee,
|
||
}, market)
|
||
|
||
def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch all the trades made from a single order
|
||
|
||
https://huobiapi.github.io/docs/spot/v1/en/#get-the-match-result-of-an-order
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch trades for
|
||
:param int [limit]: the maximum number of trades to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `trade structures <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 limit,3 opponent,4 lightning, 5 trigger order, 6 pst_only, 7 optimal_5, 8 optimal_10, 9 optimal_20, 10 fok, 11 ioc
|
||
# POST /swap-api/v3/swap_hisorders inverse swap ------------------
|
||
# POST /linear-swap-api/v3/swap_hisorders linear isolated --------
|
||
# POST /linear-swap-api/v3/swap_cross_hisorders linear cross -----
|
||
'trade_type': 0, # 0:All; 1: Open long; 2: Open short; 3: Close short; 4: Close long; 5: Liquidate long positions; 6: Liquidate short positions, 17:buy(one-way mode), 18:sell(one-way mode)
|
||
'status': '0', # support multiple query seperated by ',',such as '3,4,5', 0: all. 3. Have sumbmitted the orders; 4. Orders partially matched; 5. Orders cancelled with partially matched; 6. Orders fully matched; 7. Orders cancelled
|
||
}
|
||
response = None
|
||
trigger = self.safe_bool_2(params, 'stop', 'trigger')
|
||
stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit')
|
||
trailing = self.safe_bool(params, 'trailing', False)
|
||
params = self.omit(params, ['stop', 'stopLossTakeProfit', 'trailing', 'trigger'])
|
||
if trigger or stopLossTakeProfit or trailing:
|
||
if limit is not None:
|
||
request['page_size'] = limit
|
||
request['contract_code'] = market['id']
|
||
request['create_date'] = 90
|
||
else:
|
||
if since is not None:
|
||
request['start_time'] = since # max 90 days back
|
||
# request['end_time'] = since + 172800000 # 48 hours window
|
||
request['contract'] = market['id']
|
||
request['type'] = 1 # 1:All Orders,2:Order in Finished Status
|
||
request, params = self.handle_until_option('end_time', request, params)
|
||
if market['linear']:
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchContractOrders', params)
|
||
marginMode = 'cross' if (marginMode is None) else marginMode
|
||
if marginMode == 'isolated':
|
||
if trigger:
|
||
response = self.contractPrivatePostLinearSwapApiV1SwapTriggerHisorders(self.extend(request, params))
|
||
elif stopLossTakeProfit:
|
||
response = self.contractPrivatePostLinearSwapApiV1SwapTpslHisorders(self.extend(request, params))
|
||
elif trailing:
|
||
response = self.contractPrivatePostLinearSwapApiV1SwapTrackHisorders(self.extend(request, params))
|
||
else:
|
||
response = self.contractPrivatePostLinearSwapApiV3SwapHisorders(self.extend(request, params))
|
||
elif marginMode == 'cross':
|
||
if trigger:
|
||
response = self.contractPrivatePostLinearSwapApiV1SwapCrossTriggerHisorders(self.extend(request, params))
|
||
elif stopLossTakeProfit:
|
||
response = self.contractPrivatePostLinearSwapApiV1SwapCrossTpslHisorders(self.extend(request, params))
|
||
elif trailing:
|
||
response = self.contractPrivatePostLinearSwapApiV1SwapCrossTrackHisorders(self.extend(request, params))
|
||
else:
|
||
response = self.contractPrivatePostLinearSwapApiV3SwapCrossHisorders(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if market['swap']:
|
||
if trigger:
|
||
response = self.contractPrivatePostSwapApiV1SwapTriggerHisorders(self.extend(request, params))
|
||
elif stopLossTakeProfit:
|
||
response = self.contractPrivatePostSwapApiV1SwapTpslHisorders(self.extend(request, params))
|
||
elif trailing:
|
||
response = self.contractPrivatePostSwapApiV1SwapTrackHisorders(self.extend(request, params))
|
||
else:
|
||
response = self.contractPrivatePostSwapApiV3SwapHisorders(self.extend(request, params))
|
||
elif market['future']:
|
||
request['symbol'] = market['settleId']
|
||
if trigger:
|
||
response = self.contractPrivatePostApiV1ContractTriggerHisorders(self.extend(request, params))
|
||
elif stopLossTakeProfit:
|
||
response = self.contractPrivatePostApiV1ContractTpslHisorders(self.extend(request, params))
|
||
elif trailing:
|
||
response = self.contractPrivatePostApiV1ContractTrackHisorders(self.extend(request, params))
|
||
else:
|
||
response = self.contractPrivatePostApiV3ContractHisorders(self.extend(request, params))
|
||
#
|
||
# future and swap
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "ok",
|
||
# "data": [
|
||
# {
|
||
# "direction": "buy",
|
||
# "offset": "open",
|
||
# "volume": 1.000000000000000000,
|
||
# "price": 25000.000000000000000000,
|
||
# "profit": 0E-18,
|
||
# "pair": "BTC-USDT",
|
||
# "query_id": 47403349100,
|
||
# "order_id": 1103683465337593856,
|
||
# "contract_code": "BTC-USDT-230505",
|
||
# "symbol": "BTC",
|
||
# "lever_rate": 5,
|
||
# "create_date": 1683180243577,
|
||
# "order_source": "web",
|
||
# "canceled_source": "web",
|
||
# "order_price_type": 1,
|
||
# "order_type": 1,
|
||
# "margin_frozen": 0E-18,
|
||
# "trade_volume": 0E-18,
|
||
# "trade_turnover": 0E-18,
|
||
# "fee": 0E-18,
|
||
# "trade_avg_price": 0,
|
||
# "status": 7,
|
||
# "order_id_str": "1103683465337593856",
|
||
# "fee_asset": "USDT",
|
||
# "fee_amount": 0,
|
||
# "fee_quote_amount": 0,
|
||
# "liquidation_type": "0",
|
||
# "margin_asset": "USDT",
|
||
# "margin_mode": "cross",
|
||
# "margin_account": "USDT",
|
||
# "update_time": 1683180352034,
|
||
# "is_tpsl": 0,
|
||
# "real_profit": 0,
|
||
# "trade_partition": "USDT",
|
||
# "reduce_only": 0,
|
||
# "contract_type": "self_week",
|
||
# "business_type": "futures"
|
||
# }
|
||
# ],
|
||
# "ts": 1683239909141
|
||
# }
|
||
#
|
||
# trigger
|
||
#
|
||
# {
|
||
# "status": "ok",
|
||
# "data": {
|
||
# "orders": [
|
||
# {
|
||
# "contract_type": "swap",
|
||
# "business_type": "swap",
|
||
# "pair": "BTC-USDT",
|
||
# "symbol": "BTC",
|
||
# "contract_code": "BTC-USDT",
|
||
# "trigger_type": "le",
|
||
# "volume": 1.000000000000000000,
|
||
# "order_type": 1,
|
||
# "direction": "buy",
|
||
# "offset": "open",
|
||
# "lever_rate": 1,
|
||
# "order_id": 1103670703588327424,
|
||
# "order_id_str": "1103670703588327424",
|
||
# "relation_order_id": "-1",
|
||
# "order_price_type": "limit",
|
||
# "status": 6,
|
||
# "order_source": "web",
|
||
# "trigger_price": 25000.000000000000000000,
|
||
# "triggered_price": null,
|
||
# "order_price": 24000.000000000000000000,
|
||
# "created_at": 1683177200945,
|
||
# "triggered_at": null,
|
||
# "order_insert_at": 0,
|
||
# "canceled_at": 1683179075234,
|
||
# "fail_code": null,
|
||
# "fail_reason": null,
|
||
# "margin_mode": "cross",
|
||
# "margin_account": "USDT",
|
||
# "update_time": 1683179075958,
|
||
# "trade_partition": "USDT",
|
||
# "reduce_only": 0
|
||
# },
|
||
# ],
|
||
# "total_page": 1,
|
||
# "current_page": 1,
|
||
# "total_size": 2
|
||
# },
|
||
# "ts": 1683239702792
|
||
# }
|
||
#
|
||
# stop-loss and take-profit
|
||
#
|
||
# {
|
||
# "status": "ok",
|
||
# "data": {
|
||
# "orders": [
|
||
# {
|
||
# "contract_type": "swap",
|
||
# "business_type": "swap",
|
||
# "pair": "BTC-USDT",
|
||
# "symbol": "BTC",
|
||
# "contract_code": "BTC-USDT",
|
||
# "margin_mode": "cross",
|
||
# "margin_account": "USDT",
|
||
# "volume": 1.000000000000000000,
|
||
# "order_type": 1,
|
||
# "tpsl_order_type": "sl",
|
||
# "direction": "sell",
|
||
# "order_id": 1103680386844839936,
|
||
# "order_id_str": "1103680386844839936",
|
||
# "order_source": "web",
|
||
# "trigger_type": "le",
|
||
# "trigger_price": 25000.000000000000000000,
|
||
# "created_at": 1683179509613,
|
||
# "order_price_type": "market",
|
||
# "status": 11,
|
||
# "source_order_id": null,
|
||
# "relation_tpsl_order_id": "-1",
|
||
# "canceled_at": 0,
|
||
# "fail_code": null,
|
||
# "fail_reason": null,
|
||
# "triggered_price": null,
|
||
# "relation_order_id": "-1",
|
||
# "update_time": 1683179968231,
|
||
# "order_price": 0E-18,
|
||
# "trade_partition": "USDT"
|
||
# },
|
||
# ],
|
||
# "total_page": 1,
|
||
# "current_page": 1,
|
||
# "total_size": 2
|
||
# },
|
||
# "ts": 1683229230233
|
||
# }
|
||
#
|
||
orders = self.safe_value(response, 'data')
|
||
if not isinstance(orders, list):
|
||
orders = self.safe_value(orders, 'orders', [])
|
||
return self.parse_orders(orders, market, since, limit)
|
||
|
||
def fetch_closed_contract_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
request: dict = {
|
||
'status': '5,6,7', # comma separated, 0 all, 3 submitted orders, 4 partially matched, 5 partially cancelled, 6 fully matched and closed, 7 canceled
|
||
}
|
||
return self.fetch_contract_orders(symbol, since, limit, self.extend(request, params))
|
||
|
||
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
|
||
https://huobiapi.github.io/docs/spot/v1/en/#search-past-orders
|
||
https://huobiapi.github.io/docs/spot/v1/en/#search-historical-orders-within-48-hours
|
||
https://huobiapi.github.io/docs/usdt_swap/v1/en/#isolated-get-history-orders-new
|
||
https://huobiapi.github.io/docs/usdt_swap/v1/en/#cross-get-history-orders-new
|
||
https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#get-history-orders-new
|
||
https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#query-history-orders-via-multiple-fields-new
|
||
|
||
fetches information on multiple orders made by the user
|
||
:param str symbol: unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param bool [params.trigger]: *contract only* if the orders are trigger trigger orders or not
|
||
:param bool [params.stopLossTakeProfit]: *contract only* if the orders are stop-loss or take-profit orders
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.trailing]: *contract only* set to True if you want to fetch trailing stop orders
|
||
:returns Order[]: a list of `order structures <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]: Default:48,Data Range [1,200]
|
||
:param dict [params]: Exchange specific parameters
|
||
:param int [params.amount_type]: *required* Open interest unit. 1-cont,2-cryptocurrency
|
||
:param int [params.pair]: eg BTC-USDT *Only for USDT-M*
|
||
:returns dict: an array of `open interest structures <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 days,default = current time - 90 days
|
||
:param int [limit]: page items, default 20, shall not exceed 50
|
||
:param dict [params]: exchange specific params
|
||
:param int [params.until]: timestamp in ms, value range = start_time -> current time,default = current time
|
||
:param int [params.page_index]: page index, default page 1 if not filled
|
||
:param int [params.code]: unified currency code, can be used when symbol is None
|
||
:returns dict[]: a list of `settlement history objects <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
|