2526 lines
124 KiB
Python
2526 lines
124 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.async_support.base.exchange import Exchange
|
|
from ccxt.abstract.tokocrypto import ImplicitAPI
|
|
import hashlib
|
|
import json
|
|
from ccxt.base.types import Any, Balances, Currency, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
|
|
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 AccountSuspended
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import MarginModeAlreadySet
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.errors import OrderImmediatelyFillable
|
|
from ccxt.base.errors import OrderNotFillable
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import DDoSProtection
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.errors import ExchangeNotAvailable
|
|
from ccxt.base.errors import OnMaintenance
|
|
from ccxt.base.errors import InvalidNonce
|
|
from ccxt.base.errors import RequestTimeout
|
|
from ccxt.base.errors import BadResponse
|
|
from ccxt.base.decimal_to_precision import TRUNCATE
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class tokocrypto(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(tokocrypto, self).describe(), {
|
|
'id': 'tokocrypto',
|
|
'name': 'Tokocrypto',
|
|
'countries': ['ID'], # Indonesia
|
|
'certified': False,
|
|
'pro': False,
|
|
'version': 'v1',
|
|
# new metainfo interface
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': True,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': None,
|
|
'borrowMargin': None,
|
|
'cancelAllOrders': False,
|
|
'cancelOrder': True,
|
|
'cancelOrders': None,
|
|
'createDepositAddress': False,
|
|
'createMarketBuyOrderWithCost': True,
|
|
'createMarketOrderWithCost': False,
|
|
'createMarketSellOrderWithCost': False,
|
|
'createOrder': True,
|
|
'createReduceOnlyOrder': None,
|
|
'createStopLimitOrder': True,
|
|
'createStopMarketOrder': True,
|
|
'createStopOrder': True,
|
|
'fetchAccounts': False,
|
|
'fetchBalance': True,
|
|
'fetchBidsAsks': True,
|
|
'fetchBorrowInterest': None,
|
|
'fetchBorrowRateHistories': None,
|
|
'fetchBorrowRateHistory': None,
|
|
'fetchCanceledOrders': False,
|
|
'fetchClosedOrder': False,
|
|
'fetchClosedOrders': 'emulated',
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': False,
|
|
'fetchDeposit': False,
|
|
'fetchDepositAddress': True,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDepositAddressesByNetwork': False,
|
|
'fetchDeposits': True,
|
|
'fetchDepositsWithdrawals': False,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingRate': False,
|
|
'fetchFundingRateHistory': False,
|
|
'fetchFundingRates': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchL3OrderBook': False,
|
|
'fetchLedger': None,
|
|
'fetchLeverage': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchMarketLeverageTiers': 'emulated',
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenOrder': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrderBooks': False,
|
|
'fetchOrders': True,
|
|
'fetchOrderTrades': False,
|
|
'fetchPosition': False,
|
|
'fetchPositions': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchStatus': False,
|
|
'fetchTicker': False,
|
|
'fetchTickers': False,
|
|
'fetchTime': True,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': False,
|
|
'fetchTradingLimits': False,
|
|
'fetchTransactionFee': False,
|
|
'fetchTransactionFees': False,
|
|
'fetchTransactions': False,
|
|
'fetchTransfers': False,
|
|
'fetchWithdrawal': False,
|
|
'fetchWithdrawals': True,
|
|
'fetchWithdrawalWhitelist': False,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'signIn': False,
|
|
'transfer': False,
|
|
'withdraw': True,
|
|
},
|
|
'timeframes': {
|
|
'1m': '1m',
|
|
'3m': '3m',
|
|
'5m': '5m',
|
|
'15m': '15m',
|
|
'30m': '30m',
|
|
'1h': '1h',
|
|
'2h': '2h',
|
|
'4h': '4h',
|
|
'6h': '6h',
|
|
'8h': '8h',
|
|
'12h': '12h',
|
|
'1d': '1d',
|
|
'3d': '3d',
|
|
'1w': '1w',
|
|
'1M': '1M',
|
|
},
|
|
'urls': {
|
|
'logo': 'https://user-images.githubusercontent.com/1294454/183870484-d3398d0c-f6a1-4cce-91b8-d58792308716.jpg',
|
|
'api': {
|
|
'rest': {
|
|
'public': 'https://www.tokocrypto.com',
|
|
'binance': 'https://api.binance.com/api/v3',
|
|
'private': 'https://www.tokocrypto.com',
|
|
},
|
|
},
|
|
'www': 'https://tokocrypto.com',
|
|
# 'referral': 'https://www.binance.us/?ref=35005074',
|
|
'doc': 'https://www.tokocrypto.com/apidocs/',
|
|
'fees': 'https://www.tokocrypto.com/fees/newschedule',
|
|
},
|
|
'api': {
|
|
'binance': {
|
|
'get': {
|
|
'ping': 1,
|
|
'time': 1,
|
|
'depth': {'cost': 1, 'byLimit': [[100, 1], [500, 5], [1000, 10], [5000, 50]]},
|
|
'trades': 1,
|
|
'aggTrades': 1,
|
|
'historicalTrades': 5,
|
|
'klines': 1,
|
|
'ticker/24hr': {'cost': 1, 'noSymbol': 40},
|
|
'ticker/price': {'cost': 1, 'noSymbol': 2},
|
|
'ticker/bookTicker': {'cost': 1, 'noSymbol': 2},
|
|
'exchangeInfo': 10,
|
|
},
|
|
'put': {
|
|
'userDataStream': 1,
|
|
},
|
|
'post': {
|
|
'userDataStream': 1,
|
|
},
|
|
'delete': {
|
|
'userDataStream': 1,
|
|
},
|
|
},
|
|
'public': {
|
|
'get': {
|
|
'open/v1/common/time': 1,
|
|
'open/v1/common/symbols': 1,
|
|
# all the actual symbols are type 1
|
|
'open/v1/market/depth': 1, # when symbol type is not 1
|
|
'open/v1/market/trades': 1, # when symbol type is not 1
|
|
'open/v1/market/agg-trades': 1, # when symbol type is not 1
|
|
'open/v1/market/klines': 1, # when symbol type is not 1
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'open/v1/orders/detail': 1,
|
|
'open/v1/orders': 1,
|
|
'open/v1/account/spot': 1,
|
|
'open/v1/account/spot/asset': 1,
|
|
'open/v1/orders/trades': 1,
|
|
'open/v1/withdraws': 1,
|
|
'open/v1/deposits': 1,
|
|
'open/v1/deposits/address': 1,
|
|
},
|
|
'post': {
|
|
'open/v1/orders': 1,
|
|
'open/v1/orders/cancel': 1,
|
|
'open/v1/orders/oco': 1,
|
|
'open/v1/withdraws': 1,
|
|
'open/v1/user-data-stream': 1,
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'tierBased': True,
|
|
'percentage': True,
|
|
'taker': self.parse_number('0.0075'), # 0.1% trading fee, zero fees for all trading pairs before November 1
|
|
'maker': self.parse_number('0.0075'), # 0.1% trading fee, zero fees for all trading pairs before November 1
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'options': {
|
|
# 'fetchTradesMethod': 'binanceGetTrades', # binanceGetTrades, binanceGetAggTrades
|
|
'createMarketBuyOrderRequiresPrice': True,
|
|
'defaultTimeInForce': 'GTC', # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
|
|
# 'defaultType': 'spot', # 'spot', 'future', 'margin', 'delivery'
|
|
'hasAlreadyAuthenticatedSuccessfully': False,
|
|
'warnOnFetchOpenOrdersWithoutSymbol': True,
|
|
# 'fetchPositions': 'positionRisk', # or 'account'
|
|
'recvWindow': 5 * 1000, # 5 sec, binance default
|
|
'timeDifference': 0, # the difference between system clock and Binance clock
|
|
'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
|
|
'newOrderRespType': {
|
|
'market': 'FULL', # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
|
|
'limit': 'FULL', # we change it from 'ACK' by default to 'FULL'(returns immediately if limit is not hit)
|
|
},
|
|
'quoteOrderQty': False, # whether market orders support amounts in quote currency
|
|
'networks': {
|
|
'ERC20': 'ETH',
|
|
'TRC20': 'TRX',
|
|
'BEP2': 'BNB',
|
|
'BEP20': 'BSC',
|
|
'OMNI': 'OMNI',
|
|
'EOS': 'EOS',
|
|
'SPL': 'SOL',
|
|
},
|
|
'reverseNetworks': {
|
|
'tronscan.org': 'TRC20',
|
|
'etherscan.io': 'ERC20',
|
|
'bscscan.com': 'BSC',
|
|
'explorer.binance.org': 'BEP2',
|
|
'bithomp.com': 'XRP',
|
|
'bloks.io': 'EOS',
|
|
'stellar.expert': 'XLM',
|
|
'blockchair.com/bitcoin': 'BTC',
|
|
'blockchair.com/bitcoin-cash': 'BCH',
|
|
'blockchair.com/ecash': 'XEC',
|
|
'explorer.litecoin.net': 'LTC',
|
|
'explorer.avax.network': 'AVAX',
|
|
'solscan.io': 'SOL',
|
|
'polkadot.subscan.io': 'DOT',
|
|
'dashboard.internetcomputer.org': 'ICP',
|
|
'explorer.chiliz.com': 'CHZ',
|
|
'cardanoscan.io': 'ADA',
|
|
'mainnet.theoan.com': 'AION',
|
|
'algoexplorer.io': 'ALGO',
|
|
'explorer.ambrosus.com': 'AMB',
|
|
'viewblock.io/zilliqa': 'ZIL',
|
|
'viewblock.io/arweave': 'AR',
|
|
'explorer.ark.io': 'ARK',
|
|
'atomscan.com': 'ATOM',
|
|
'www.mintscan.io': 'CTK',
|
|
'explorer.bitcoindiamond.org': 'BCD',
|
|
'btgexplorer.com': 'BTG',
|
|
'bts.ai': 'BTS',
|
|
'explorer.celo.org': 'CELO',
|
|
'explorer.nervos.org': 'CKB',
|
|
'cerebro.cortexlabs.ai': 'CTXC',
|
|
'chainz.cryptoid.info': 'VIA',
|
|
'explorer.dcrdata.org': 'DCR',
|
|
'digiexplorer.info': 'DGB',
|
|
'dock.subscan.io': 'DOCK',
|
|
'dogechain.info': 'DOGE',
|
|
'explorer.elrond.com': 'EGLD',
|
|
'blockscout.com': 'ETC',
|
|
'explore-fetchhub.fetch.ai': 'FET',
|
|
'filfox.info': 'FIL',
|
|
'fio.bloks.io': 'FIO',
|
|
'explorer.firo.org': 'FIRO',
|
|
'neoscan.io': 'NEO',
|
|
'ftmscan.com': 'FTM',
|
|
'explorer.gochain.io': 'GO',
|
|
'block.gxb.io': 'GXS',
|
|
'hash-hash.info': 'HBAR',
|
|
'www.hiveblockexplorer.com': 'HIVE',
|
|
'explorer.helium.com': 'HNT',
|
|
'tracker.icon.foundation': 'ICX',
|
|
'www.iostabc.com': 'IOST',
|
|
'explorer.iota.org': 'IOTA',
|
|
'iotexscan.io': 'IOTX',
|
|
'irishub.iobscan.io': 'IRIS',
|
|
'kava.mintscan.io': 'KAVA',
|
|
'scope.klaytn.com': 'KLAY',
|
|
'kmdexplorer.io': 'KMD',
|
|
'kusama.subscan.io': 'KSM',
|
|
'explorer.lto.network': 'LTO',
|
|
'polygonscan.com': 'POLYGON',
|
|
'explorer.ont.io': 'ONT',
|
|
'minaexplorer.com': 'MINA',
|
|
'nanolooker.com': 'NANO',
|
|
'explorer.nebulas.io': 'NAS',
|
|
'explorer.nbs.plus': 'NBS',
|
|
'explorer.nebl.io': 'NEBL',
|
|
'nulscan.io': 'NULS',
|
|
'nxscan.com': 'NXS',
|
|
'explorer.harmony.one': 'ONE',
|
|
'explorer.poa.network': 'POA',
|
|
'qtum.info': 'QTUM',
|
|
'explorer.rsk.co': 'RSK',
|
|
'www.oasisscan.com': 'ROSE',
|
|
'ravencoin.network': 'RVN',
|
|
'sc.tokenview.com': 'SC',
|
|
'secretnodes.com': 'SCRT',
|
|
'explorer.skycoin.com': 'SKY',
|
|
'steemscan.com': 'STEEM',
|
|
'explorer.stacks.co': 'STX',
|
|
'www.thetascan.io': 'THETA',
|
|
'scan.tomochain.com': 'TOMO',
|
|
'explore.vechain.org': 'VET',
|
|
'explorer.vite.net': 'VITE',
|
|
'www.wanscan.org': 'WAN',
|
|
'wavesexplorer.com': 'WAVES',
|
|
'wax.eosx.io': 'WAXP',
|
|
'waltonchain.pro': 'WTC',
|
|
'chain.nem.ninja': 'XEM',
|
|
'verge-blockchain.info': 'XVG',
|
|
'explorer.yoyow.org': 'YOYOW',
|
|
'explorer.zcha.in': 'ZEC',
|
|
'explorer.zensystem.io': 'ZEN',
|
|
},
|
|
'impliedNetworks': {
|
|
'ETH': {'ERC20': 'ETH'},
|
|
'TRX': {'TRC20': 'TRX'},
|
|
},
|
|
'legalMoney': {
|
|
'MXN': True,
|
|
'UGX': True,
|
|
'SEK': True,
|
|
'CHF': True,
|
|
'VND': True,
|
|
'AED': True,
|
|
'DKK': True,
|
|
'KZT': True,
|
|
'HUF': True,
|
|
'PEN': True,
|
|
'PHP': True,
|
|
'USD': True,
|
|
'TRY': True,
|
|
'EUR': True,
|
|
'NGN': True,
|
|
'PLN': True,
|
|
'BRL': True,
|
|
'ZAR': True,
|
|
'KES': True,
|
|
'ARS': True,
|
|
'RUB': True,
|
|
'AUD': True,
|
|
'NOK': True,
|
|
'CZK': True,
|
|
'GBP': True,
|
|
'UAH': True,
|
|
'GHS': True,
|
|
'HKD': True,
|
|
'CAD': True,
|
|
'INR': True,
|
|
'JPY': True,
|
|
'NZD': True,
|
|
},
|
|
},
|
|
# https://binance-docs.github.io/apidocs/spot/en/#error-codes-2
|
|
'exceptions': {
|
|
'exact': {
|
|
'System is under maintenance.': OnMaintenance, # {"code":1,"msg":"System is under maintenance."}
|
|
'System abnormality': ExchangeError, # {"code":-1000,"msg":"System abnormality"}
|
|
'You are not authorized to execute self request.': PermissionDenied, # {"msg":"You are not authorized to execute self request."}
|
|
'API key does not exist': AuthenticationError,
|
|
'Order would trigger immediately.': OrderImmediatelyFillable,
|
|
'Stop price would trigger immediately.': OrderImmediatelyFillable, # {"code":-2010,"msg":"Stop price would trigger immediately."}
|
|
'Order would immediately match and take.': OrderImmediatelyFillable, # {"code":-2010,"msg":"Order would immediately match and take."}
|
|
'Account has insufficient balance for requested action.': InsufficientFunds,
|
|
'Rest API trading is not enabled.': ExchangeNotAvailable,
|
|
"You don't have permission.": PermissionDenied, # {"msg":"You don't have permission.","success":false}
|
|
'Market is closed.': ExchangeNotAvailable, # {"code":-1013,"msg":"Market is closed."}
|
|
'Too many requests. Please try again later.': DDoSProtection, # {"msg":"Too many requests. Please try again later.","success":false}
|
|
'This action disabled is on self account.': AccountSuspended, # {"code":-2010,"msg":"This action disabled is on self account."}
|
|
'-1000': ExchangeNotAvailable, # {"code":-1000,"msg":"An unknown error occured while processing the request."}
|
|
'-1001': ExchangeNotAvailable, # {"code":-1001,"msg":"'Internal error; unable to process your request. Please try again.'"}
|
|
'-1002': AuthenticationError, # {"code":-1002,"msg":"'You are not authorized to execute self request.'"}
|
|
'-1003': RateLimitExceeded, # {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."}
|
|
'-1004': DDoSProtection, # {"code":-1004,"msg":"Server is busy, please wait and try again"}
|
|
'-1005': PermissionDenied, # {"code":-1005,"msg":"No such IP has been white listed"}
|
|
'-1006': BadResponse, # {"code":-1006,"msg":"An unexpected response was received from the message bus. Execution status unknown."}
|
|
'-1007': RequestTimeout, # {"code":-1007,"msg":"Timeout waiting for response from backend server. Send status unknown; execution status unknown."}
|
|
'-1010': BadResponse, # {"code":-1010,"msg":"ERROR_MSG_RECEIVED."}
|
|
'-1011': PermissionDenied, # {"code":-1011,"msg":"This IP cannot access self route."}
|
|
'-1013': InvalidOrder, # {"code":-1013,"msg":"createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL"}
|
|
'-1014': InvalidOrder, # {"code":-1014,"msg":"Unsupported order combination."}
|
|
'-1015': RateLimitExceeded, # {"code":-1015,"msg":"'Too many new orders; current limit is %s orders per %s.'"}
|
|
'-1016': ExchangeNotAvailable, # {"code":-1016,"msg":"'This service is no longer available.',"}
|
|
'-1020': BadRequest, # {"code":-1020,"msg":"'This operation is not supported.'"}
|
|
'-1021': InvalidNonce, # {"code":-1021,"msg":"'your time is ahead of server'"}
|
|
'-1022': AuthenticationError, # {"code":-1022,"msg":"Signature for self request is not valid."}
|
|
'-1023': BadRequest, # {"code":-1023,"msg":"Start time is greater than end time."}
|
|
'-1099': AuthenticationError, # {"code":-1099,"msg":"Not found, authenticated, or authorized"}
|
|
'-1100': BadRequest, # {"code":-1100,"msg":"createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'"}
|
|
'-1101': BadRequest, # {"code":-1101,"msg":"Too many parameters; expected %s and received %s."}
|
|
'-1102': BadRequest, # {"code":-1102,"msg":"Param %s or %s must be sent, but both were empty"}
|
|
'-1103': BadRequest, # {"code":-1103,"msg":"An unknown parameter was sent."}
|
|
'-1104': BadRequest, # {"code":-1104,"msg":"Not all sent parameters were read, read 8 parameters but was sent 9"}
|
|
'-1105': BadRequest, # {"code":-1105,"msg":"Parameter %s was empty."}
|
|
'-1106': BadRequest, # {"code":-1106,"msg":"Parameter %s sent when not required."}
|
|
'-1108': BadRequest, # {"code":-1108,"msg":"Invalid asset."}
|
|
'-1109': AuthenticationError, # {"code":-1109,"msg":"Invalid account."}
|
|
'-1110': BadRequest, # {"code":-1110,"msg":"Invalid symbolType."}
|
|
'-1111': BadRequest, # {"code":-1111,"msg":"Precision is over the maximum defined for self asset."}
|
|
'-1112': InvalidOrder, # {"code":-1112,"msg":"No orders on book for symbol."}
|
|
'-1113': BadRequest, # {"code":-1113,"msg":"Withdrawal amount must be negative."}
|
|
'-1114': BadRequest, # {"code":-1114,"msg":"TimeInForce parameter sent when not required."}
|
|
'-1115': BadRequest, # {"code":-1115,"msg":"Invalid timeInForce."}
|
|
'-1116': BadRequest, # {"code":-1116,"msg":"Invalid orderType."}
|
|
'-1117': BadRequest, # {"code":-1117,"msg":"Invalid side."}
|
|
'-1118': BadRequest, # {"code":-1118,"msg":"New client order ID was empty."}
|
|
'-1119': BadRequest, # {"code":-1119,"msg":"Original client order ID was empty."}
|
|
'-1120': BadRequest, # {"code":-1120,"msg":"Invalid interval."}
|
|
'-1121': BadSymbol, # {"code":-1121,"msg":"Invalid symbol."}
|
|
'-1125': AuthenticationError, # {"code":-1125,"msg":"This listenKey does not exist."}
|
|
'-1127': BadRequest, # {"code":-1127,"msg":"More than %s hours between startTime and endTime."}
|
|
'-1128': BadRequest, # {"code":-1128,"msg":"{"code":-1128,"msg":"Combination of optional parameters invalid."}"}
|
|
'-1130': BadRequest, # {"code":-1130,"msg":"Data sent for paramter %s is not valid."}
|
|
'-1131': BadRequest, # {"code":-1131,"msg":"recvWindow must be less than 60000"}
|
|
'-1136': BadRequest, # {"code":-1136,"msg":"Invalid newOrderRespType"}
|
|
'-2008': AuthenticationError, # {"code":-2008,"msg":"Invalid Api-Key ID."}
|
|
'-2010': ExchangeError, # {"code":-2010,"msg":"generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc..."}
|
|
'-2011': OrderNotFound, # {"code":-2011,"msg":"cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'"}
|
|
'-2013': OrderNotFound, # {"code":-2013,"msg":"fetchOrder(1, 'BTC/USDT') -> 'Order does not exist'"}
|
|
'-2014': AuthenticationError, # {"code":-2014,"msg":"API-key format invalid."}
|
|
'-2015': AuthenticationError, # {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
|
|
'-2016': BadRequest, # {"code":-2016,"msg":"No trading window could be found for the symbol. Try ticker/24hrs instead."}
|
|
'-2018': InsufficientFunds, # {"code":-2018,"msg":"Balance is insufficient"}
|
|
'-2019': InsufficientFunds, # {"code":-2019,"msg":"Margin is insufficient."}
|
|
'-2020': OrderNotFillable, # {"code":-2020,"msg":"Unable to fill."}
|
|
'-2021': OrderImmediatelyFillable, # {"code":-2021,"msg":"Order would immediately trigger."}
|
|
'-2022': InvalidOrder, # {"code":-2022,"msg":"ReduceOnly Order is rejected."}
|
|
'-2023': InsufficientFunds, # {"code":-2023,"msg":"User in liquidation mode now."}
|
|
'-2024': InsufficientFunds, # {"code":-2024,"msg":"Position is not sufficient."}
|
|
'-2025': InvalidOrder, # {"code":-2025,"msg":"Reach max open order limit."}
|
|
'-2026': InvalidOrder, # {"code":-2026,"msg":"This OrderType is not supported when reduceOnly."}
|
|
'-2027': InvalidOrder, # {"code":-2027,"msg":"Exceeded the maximum allowable position at current leverage."}
|
|
'-2028': InsufficientFunds, # {"code":-2028,"msg":"Leverage is smaller than permitted: insufficient margin balance"}
|
|
'-3000': ExchangeError, # {"code":-3000,"msg":"Internal server error."}
|
|
'-3001': AuthenticationError, # {"code":-3001,"msg":"Please enable 2FA first."}
|
|
'-3002': BadSymbol, # {"code":-3002,"msg":"We don't have self asset."}
|
|
'-3003': BadRequest, # {"code":-3003,"msg":"Margin account does not exist."}
|
|
'-3004': ExchangeError, # {"code":-3004,"msg":"Trade not allowed."}
|
|
'-3005': InsufficientFunds, # {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."}
|
|
'-3006': InsufficientFunds, # {"code":-3006,"msg":"Your borrow amount has exceed maximum borrow amount."}
|
|
'-3007': ExchangeError, # {"code":-3007,"msg":"You have pending transaction, please try again later.."}
|
|
'-3008': InsufficientFunds, # {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."}
|
|
'-3009': BadRequest, # {"code":-3009,"msg":"This asset are not allowed to transfer into margin account currently."}
|
|
'-3010': ExchangeError, # {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."}
|
|
'-3011': BadRequest, # {"code":-3011,"msg":"Your input date is invalid."}
|
|
'-3012': ExchangeError, # {"code":-3012,"msg":"Borrow is banned for self asset."}
|
|
'-3013': BadRequest, # {"code":-3013,"msg":"Borrow amount less than minimum borrow amount."}
|
|
'-3014': AccountSuspended, # {"code":-3014,"msg":"Borrow is banned for self account."}
|
|
'-3015': ExchangeError, # {"code":-3015,"msg":"Repay amount exceeds borrow amount."}
|
|
'-3016': BadRequest, # {"code":-3016,"msg":"Repay amount less than minimum repay amount."}
|
|
'-3017': ExchangeError, # {"code":-3017,"msg":"This asset are not allowed to transfer into margin account currently."}
|
|
'-3018': AccountSuspended, # {"code":-3018,"msg":"Transferring in has been banned for self account."}
|
|
'-3019': AccountSuspended, # {"code":-3019,"msg":"Transferring out has been banned for self account."}
|
|
'-3020': InsufficientFunds, # {"code":-3020,"msg":"Transfer out amount exceeds max amount."}
|
|
'-3021': BadRequest, # {"code":-3021,"msg":"Margin account are not allowed to trade self trading pair."}
|
|
'-3022': AccountSuspended, # {"code":-3022,"msg":"You account's trading is banned."}
|
|
'-3023': BadRequest, # {"code":-3023,"msg":"You can't transfer out/place order under current margin level."}
|
|
'-3024': ExchangeError, # {"code":-3024,"msg":"The unpaid debt is too small after self repayment."}
|
|
'-3025': BadRequest, # {"code":-3025,"msg":"Your input date is invalid."}
|
|
'-3026': BadRequest, # {"code":-3026,"msg":"Your input param is invalid."}
|
|
'-3027': BadSymbol, # {"code":-3027,"msg":"Not a valid margin asset."}
|
|
'-3028': BadSymbol, # {"code":-3028,"msg":"Not a valid margin pair."}
|
|
'-3029': ExchangeError, # {"code":-3029,"msg":"Transfer failed."}
|
|
'-3036': AccountSuspended, # {"code":-3036,"msg":"This account is not allowed to repay."}
|
|
'-3037': ExchangeError, # {"code":-3037,"msg":"PNL is clearing. Wait a second."}
|
|
'-3038': BadRequest, # {"code":-3038,"msg":"Listen key not found."}
|
|
'-3041': InsufficientFunds, # {"code":-3041,"msg":"Balance is not enough"}
|
|
'-3042': BadRequest, # {"code":-3042,"msg":"PriceIndex not available for self margin pair."}
|
|
'-3043': BadRequest, # {"code":-3043,"msg":"Transferring in not allowed."}
|
|
'-3044': DDoSProtection, # {"code":-3044,"msg":"System busy."}
|
|
'-3045': ExchangeError, # {"code":-3045,"msg":"The system doesn't have enough asset now."}
|
|
'-3999': ExchangeError, # {"code":-3999,"msg":"This function is only available for invited users."}
|
|
'-4001': BadRequest, # {"code":-4001 ,"msg":"Invalid operation."}
|
|
'-4002': BadRequest, # {"code":-4002 ,"msg":"Invalid get."}
|
|
'-4003': BadRequest, # {"code":-4003 ,"msg":"Your input email is invalid."}
|
|
'-4004': AuthenticationError, # {"code":-4004,"msg":"You don't login or auth."}
|
|
'-4005': RateLimitExceeded, # {"code":-4005 ,"msg":"Too many new requests."}
|
|
'-4006': BadRequest, # {"code":-4006 ,"msg":"Support main account only."}
|
|
'-4007': BadRequest, # {"code":-4007 ,"msg":"Address validation is not passed."}
|
|
'-4008': BadRequest, # {"code":-4008 ,"msg":"Address tag validation is not passed."}
|
|
'-4010': BadRequest, # {"code":-4010 ,"msg":"White list mail has been confirmed."} # [TODO] possible bug: it should probably be "has not been confirmed"
|
|
'-4011': BadRequest, # {"code":-4011 ,"msg":"White list mail is invalid."}
|
|
'-4012': BadRequest, # {"code":-4012 ,"msg":"White list is not opened."}
|
|
'-4013': AuthenticationError, # {"code":-4013 ,"msg":"2FA is not opened."}
|
|
'-4014': PermissionDenied, # {"code":-4014 ,"msg":"Withdraw is not allowed within 2 min login."}
|
|
'-4015': ExchangeError, # {"code":-4015 ,"msg":"Withdraw is limited."}
|
|
'-4016': PermissionDenied, # {"code":-4016 ,"msg":"Within 24 hours after password modification, withdrawal is prohibited."}
|
|
'-4017': PermissionDenied, # {"code":-4017 ,"msg":"Within 24 hours after the release of 2FA, withdrawal is prohibited."}
|
|
'-4018': BadSymbol, # {"code":-4018,"msg":"We don't have self asset."}
|
|
'-4019': BadSymbol, # {"code":-4019,"msg":"Current asset is not open for withdrawal."}
|
|
'-4021': BadRequest, # {"code":-4021,"msg":"Asset withdrawal must be an %s multiple of %s."}
|
|
'-4022': BadRequest, # {"code":-4022,"msg":"Not less than the minimum pick-up quantity %s."}
|
|
'-4023': ExchangeError, # {"code":-4023,"msg":"Within 24 hours, the withdrawal exceeds the maximum amount."}
|
|
'-4024': InsufficientFunds, # {"code":-4024,"msg":"You don't have self asset."}
|
|
'-4025': InsufficientFunds, # {"code":-4025,"msg":"The number of hold asset is less than zero."}
|
|
'-4026': InsufficientFunds, # {"code":-4026,"msg":"You have insufficient balance."}
|
|
'-4027': ExchangeError, # {"code":-4027,"msg":"Failed to obtain tranId."}
|
|
'-4028': BadRequest, # {"code":-4028,"msg":"The amount of withdrawal must be greater than the Commission."}
|
|
'-4029': BadRequest, # {"code":-4029,"msg":"The withdrawal record does not exist."}
|
|
'-4030': ExchangeError, # {"code":-4030,"msg":"Confirmation of successful asset withdrawal. [TODO] possible bug in docs"}
|
|
'-4031': ExchangeError, # {"code":-4031,"msg":"Cancellation failed."}
|
|
'-4032': ExchangeError, # {"code":-4032,"msg":"Withdraw verification exception."}
|
|
'-4033': BadRequest, # {"code":-4033,"msg":"Illegal address."}
|
|
'-4034': ExchangeError, # {"code":-4034,"msg":"The address is suspected of fake."}
|
|
'-4035': PermissionDenied, # {"code":-4035,"msg":"This address is not on the whitelist. Please join and try again."}
|
|
'-4036': BadRequest, # {"code":-4036,"msg":"The new address needs to be withdrawn in {0} hours."}
|
|
'-4037': ExchangeError, # {"code":-4037,"msg":"Re-sending Mail failed."}
|
|
'-4038': ExchangeError, # {"code":-4038,"msg":"Please try again in 5 minutes."}
|
|
'-4039': BadRequest, # {"code":-4039,"msg":"The user does not exist."}
|
|
'-4040': BadRequest, # {"code":-4040,"msg":"This address not charged."}
|
|
'-4041': ExchangeError, # {"code":-4041,"msg":"Please try again in one minute."}
|
|
'-4042': ExchangeError, # {"code":-4042,"msg":"This asset cannot get deposit address again."}
|
|
'-4043': BadRequest, # {"code":-4043,"msg":"More than 100 recharge addresses were used in 24 hours."}
|
|
'-4044': BadRequest, # {"code":-4044,"msg":"This is a blacklist country."}
|
|
'-4045': ExchangeError, # {"code":-4045,"msg":"Failure to acquire assets."}
|
|
'-4046': AuthenticationError, # {"code":-4046,"msg":"Agreement not confirmed."}
|
|
'-4047': BadRequest, # {"code":-4047,"msg":"Time interval must be within 0-90 days"}
|
|
'-5001': BadRequest, # {"code":-5001,"msg":"Don't allow transfer to micro assets."}
|
|
'-5002': InsufficientFunds, # {"code":-5002,"msg":"You have insufficient balance."}
|
|
'-5003': InsufficientFunds, # {"code":-5003,"msg":"You don't have self asset."}
|
|
'-5004': BadRequest, # {"code":-5004,"msg":"The residual balances of %s have exceeded 0.001BTC, Please re-choose."}
|
|
'-5005': InsufficientFunds, # {"code":-5005,"msg":"The residual balances of %s is too low, Please re-choose."}
|
|
'-5006': BadRequest, # {"code":-5006,"msg":"Only transfer once in 24 hours."}
|
|
'-5007': BadRequest, # {"code":-5007,"msg":"Quantity must be greater than zero."}
|
|
'-5008': InsufficientFunds, # {"code":-5008,"msg":"Insufficient amount of returnable assets."}
|
|
'-5009': BadRequest, # {"code":-5009,"msg":"Product does not exist."}
|
|
'-5010': ExchangeError, # {"code":-5010,"msg":"Asset transfer fail."}
|
|
'-5011': BadRequest, # {"code":-5011,"msg":"future account not exists."}
|
|
'-5012': ExchangeError, # {"code":-5012,"msg":"Asset transfer is in pending."}
|
|
'-5013': InsufficientFunds, # {"code":-5013,"msg":"Asset transfer failed: insufficient balance""} # undocumented
|
|
'-5021': BadRequest, # {"code":-5021,"msg":"This parent sub have no relation"}
|
|
'-6001': BadRequest, # {"code":-6001,"msg":"Daily product not exists."}
|
|
'-6003': BadRequest, # {"code":-6003,"msg":"Product not exist or you don't have permission"}
|
|
'-6004': ExchangeError, # {"code":-6004,"msg":"Product not in purchase status"}
|
|
'-6005': InvalidOrder, # {"code":-6005,"msg":"Smaller than min purchase limit"}
|
|
'-6006': BadRequest, # {"code":-6006,"msg":"Redeem amount error"}
|
|
'-6007': BadRequest, # {"code":-6007,"msg":"Not in redeem time"}
|
|
'-6008': BadRequest, # {"code":-6008,"msg":"Product not in redeem status"}
|
|
'-6009': RateLimitExceeded, # {"code":-6009,"msg":"Request frequency too high"}
|
|
'-6011': BadRequest, # {"code":-6011,"msg":"Exceeding the maximum num allowed to purchase per user"}
|
|
'-6012': InsufficientFunds, # {"code":-6012,"msg":"Balance not enough"}
|
|
'-6013': ExchangeError, # {"code":-6013,"msg":"Purchasing failed"}
|
|
'-6014': BadRequest, # {"code":-6014,"msg":"Exceed up-limit allowed to purchased"}
|
|
'-6015': BadRequest, # {"code":-6015,"msg":"Empty request body"}
|
|
'-6016': BadRequest, # {"code":-6016,"msg":"Parameter err"}
|
|
'-6017': BadRequest, # {"code":-6017,"msg":"Not in whitelist"}
|
|
'-6018': BadRequest, # {"code":-6018,"msg":"Asset not enough"}
|
|
'-6019': AuthenticationError, # {"code":-6019,"msg":"Need confirm"}
|
|
'-6020': BadRequest, # {"code":-6020,"msg":"Project not exists"}
|
|
'-7001': BadRequest, # {"code":-7001,"msg":"Date range is not supported."}
|
|
'-7002': BadRequest, # {"code":-7002,"msg":"Data request type is not supported."}
|
|
'-9000': InsufficientFunds, # {"code":-9000,"msg":"user have no avaliable amount"}"
|
|
'-10017': BadRequest, # {"code":-10017,"msg":"Repay amount should not be larger than liability."}
|
|
'-11008': InsufficientFunds, # {"code":-11008,"msg":"Exceeding the account's maximum borrowable limit."} # undocumented
|
|
'-12014': RateLimitExceeded, # {"code":-12014,"msg":"More than 1 request in 3 seconds"}
|
|
'-13000': BadRequest, # {"code":-13000,"msg":"Redeption of the token is forbiden now"}
|
|
'-13001': BadRequest, # {"code":-13001,"msg":"Exceeds individual 24h redemption limit of the token"}
|
|
'-13002': BadRequest, # {"code":-13002,"msg":"Exceeds total 24h redemption limit of the token"}
|
|
'-13003': BadRequest, # {"code":-13003,"msg":"Subscription of the token is forbiden now"}
|
|
'-13004': BadRequest, # {"code":-13004,"msg":"Exceeds individual 24h subscription limit of the token"}
|
|
'-13005': BadRequest, # {"code":-13005,"msg":"Exceeds total 24h subscription limit of the token"}
|
|
'-13006': InvalidOrder, # {"code":-13006,"msg":"Subscription amount is too small"}
|
|
'-13007': AuthenticationError, # {"code":-13007,"msg":"The Agreement is not signed"}
|
|
'-21001': BadRequest, # {"code":-21001,"msg":"USER_IS_NOT_UNIACCOUNT"}
|
|
'-21002': BadRequest, # {"code":-21002,"msg":"UNI_ACCOUNT_CANT_TRANSFER_FUTURE"}
|
|
'-21003': BadRequest, # {"code":-21003,"msg":"NET_ASSET_MUST_LTE_RATIO"}
|
|
'100001003': BadRequest, # {"code":100001003,"msg":"Verification failed"} # undocumented
|
|
'2202': InsufficientFunds, # {"code":2202,"msg":"Insufficient balance","data":{"code":-2010,"msg":"Account has insufficient balance for requested action."},"timestamp":1662733681161}
|
|
'3210': InvalidOrder, # {"code":3210,"msg":"The total volume is too low","data":{"code":-1013,"msg":"Filter failure: MIN_NOTIONAL"},"timestamp":1662734704462}
|
|
'3203': InvalidOrder, # {"code":3203,"msg":"Incorrect Order Quantity","timestamp":1662734809758}
|
|
'3211': InvalidOrder, # {"code":3211,"msg":"The total volume must be greater than 10","timestamp":1662739358179}
|
|
'3207': InvalidOrder, # {"code":3207,"msg":"The price cannot be lower than 12.18","timestamp":1662739502856}
|
|
'3218': OrderNotFound, # {"code":3218,"msg":"Order does not exist","timestamp":1662739749275}
|
|
},
|
|
'broad': {
|
|
'has no operation privilege': PermissionDenied,
|
|
'MAX_POSITION': InvalidOrder, # {"code":-2010,"msg":"Filter failure: MAX_POSITION"}
|
|
},
|
|
},
|
|
'features': {
|
|
'spot': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerDirection': False,
|
|
'triggerPriceType': None,
|
|
'stopLossPrice': False, # todo
|
|
'takeProfitPrice': False, # todo
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyByCost': True,
|
|
'marketBuyRequiresPrice': True,
|
|
'selfTradePrevention': True, # todo
|
|
'iceberg': True, # todo
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000, # todo
|
|
'untilDays': 100000, # todo
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchClosedOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000, # todo
|
|
'daysBackCanceled': 1, # todo
|
|
'untilDays': 100000, # todo
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOHLCV': {
|
|
'limit': 1000,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
})
|
|
|
|
def nonce(self):
|
|
return self.milliseconds() - self.options['timeDifference']
|
|
|
|
async def fetch_time(self, params={}) -> Int:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#check-server-time
|
|
|
|
fetches the current integer timestamp in milliseconds from the exchange server
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int: the current integer timestamp in milliseconds from the exchange server
|
|
"""
|
|
response = await self.publicGetOpenV1CommonTime(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "Success",
|
|
# "data": null,
|
|
# "timestamp": 1737378074159
|
|
# }
|
|
#
|
|
return self.safe_integer(response, 'timestamp')
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#get-all-supported-trading-symbol
|
|
|
|
retrieves data on all markets for tokocrypto
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = await self.publicGetOpenV1CommonSymbols(params)
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"Success",
|
|
# "data":{
|
|
# "list":[
|
|
# {
|
|
# "type":1,
|
|
# "symbol":"1INCH_BTC",
|
|
# "baseAsset":"1INCH",
|
|
# "basePrecision":8,
|
|
# "quoteAsset":"BTC",
|
|
# "quotePrecision":8,
|
|
# "filters":[
|
|
# {"filterType":"PRICE_FILTER","minPrice":"0.00000001","maxPrice":"1000.00000000","tickSize":"0.00000001","applyToMarket":false},
|
|
# {"filterType":"PERCENT_PRICE","multiplierUp":5,"multiplierDown":0.2,"avgPriceMins":"5","applyToMarket":false},
|
|
# {"filterType":"LOT_SIZE","minQty":"0.10000000","maxQty":"90000000.00000000","stepSize":"0.10000000","applyToMarket":false},
|
|
# {"filterType":"MIN_NOTIONAL","avgPriceMins":"5","minNotional":"0.00010000","applyToMarket":true},
|
|
# {"filterType":"ICEBERG_PARTS","applyToMarket":false,"limit":"10"},
|
|
# {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"79460.14117231","stepSize":"0.00000000","applyToMarket":false},
|
|
# {"filterType":"TRAILING_DELTA","applyToMarket":false},
|
|
# {"filterType":"MAX_NUM_ORDERS","applyToMarket":false},
|
|
# {"filterType":"MAX_NUM_ALGO_ORDERS","applyToMarket":false,"maxNumAlgoOrders":"5"}
|
|
# ],
|
|
# "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"],
|
|
# "icebergEnable":1,
|
|
# "ocoEnable":1,
|
|
# "spotTradingEnable":1,
|
|
# "marginTradingEnable":1,
|
|
# "permissions":["SPOT","MARGIN"]
|
|
# },
|
|
# ]
|
|
# },
|
|
# "timestamp":1659492212507
|
|
# }
|
|
#
|
|
if self.options['adjustForTimeDifference']:
|
|
await self.load_time_difference()
|
|
data = self.safe_value(response, 'data', {})
|
|
list = self.safe_value(data, 'list', [])
|
|
result = []
|
|
for i in range(0, len(list)):
|
|
market = list[i]
|
|
baseId = self.safe_string(market, 'baseAsset')
|
|
quoteId = self.safe_string(market, 'quoteAsset')
|
|
id = self.safe_string(market, 'symbol')
|
|
lowercaseId = self.safe_string_lower(market, 'symbol')
|
|
settleId = self.safe_string(market, 'marginAsset')
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
settle = self.safe_currency_code(settleId)
|
|
symbol = base + '/' + quote
|
|
filters = self.safe_value(market, 'filters', [])
|
|
filtersByType = self.index_by(filters, 'filterType')
|
|
status = self.safe_string(market, 'spotTradingEnable')
|
|
active = (status == '1')
|
|
permissions = self.safe_value(market, 'permissions', [])
|
|
for j in range(0, len(permissions)):
|
|
if permissions[j] == 'TRD_GRP_003':
|
|
active = False
|
|
break
|
|
isMarginTradingAllowed = self.safe_bool(market, 'isMarginTradingAllowed', False)
|
|
entry: dict = {
|
|
'id': id,
|
|
'lowercaseId': lowercaseId,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': 'spot',
|
|
'spot': True,
|
|
'margin': isMarginTradingAllowed,
|
|
'swap': False,
|
|
'future': False,
|
|
'delivery': False,
|
|
'option': False,
|
|
'active': active,
|
|
'contract': False,
|
|
'linear': None,
|
|
'inverse': None,
|
|
'contractSize': None,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'quantityPrecision'))),
|
|
'price': self.parse_number(self.parse_precision(self.safe_string(market, 'pricePrecision'))),
|
|
'base': self.parse_number(self.parse_precision(self.safe_string(market, 'baseAssetPrecision'))),
|
|
'quote': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None,
|
|
'info': market,
|
|
}
|
|
if 'PRICE_FILTER' in filtersByType:
|
|
filter = self.safe_value(filtersByType, 'PRICE_FILTER', {})
|
|
entry['precision']['price'] = self.safe_number(filter, 'tickSize')
|
|
# PRICE_FILTER reports zero values for maxPrice
|
|
# since they updated filter types in November 2018
|
|
# https://github.com/ccxt/ccxt/issues/4286
|
|
# therefore limits['price']['max'] doesn't have any meaningful value except None
|
|
entry['limits']['price'] = {
|
|
'min': self.safe_number(filter, 'minPrice'),
|
|
'max': self.safe_number(filter, 'maxPrice'),
|
|
}
|
|
entry['precision']['price'] = filter['tickSize']
|
|
if 'LOT_SIZE' in filtersByType:
|
|
filter = self.safe_value(filtersByType, 'LOT_SIZE', {})
|
|
entry['precision']['amount'] = self.safe_number(filter, 'stepSize')
|
|
entry['limits']['amount'] = {
|
|
'min': self.safe_number(filter, 'minQty'),
|
|
'max': self.safe_number(filter, 'maxQty'),
|
|
}
|
|
if 'MARKET_LOT_SIZE' in filtersByType:
|
|
filter = self.safe_value(filtersByType, 'MARKET_LOT_SIZE', {})
|
|
entry['limits']['market'] = {
|
|
'min': self.safe_number(filter, 'minQty'),
|
|
'max': self.safe_number(filter, 'maxQty'),
|
|
}
|
|
if 'MIN_NOTIONAL' in filtersByType:
|
|
filter = self.safe_value(filtersByType, 'MIN_NOTIONAL', {})
|
|
entry['limits']['cost']['min'] = self.safe_number_2(filter, 'minNotional', 'notional')
|
|
result.append(entry)
|
|
return result
|
|
|
|
async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#order-book
|
|
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
: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
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {}
|
|
if limit is not None:
|
|
request['limit'] = limit # default 100, max 5000, see https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#order-book
|
|
response = None
|
|
if market['quote'] == 'USDT':
|
|
request['symbol'] = market['baseId'] + market['quoteId']
|
|
response = await self.binanceGetDepth(self.extend(request, params))
|
|
else:
|
|
request['symbol'] = market['id']
|
|
response = await self.publicGetOpenV1MarketDepth(self.extend(request, params))
|
|
#
|
|
# future
|
|
#
|
|
# {
|
|
# "lastUpdateId":333598053905,
|
|
# "E":1618631511986,
|
|
# "T":1618631511964,
|
|
# "bids":[
|
|
# ["2493.56","20.189"],
|
|
# ["2493.54","1.000"],
|
|
# ["2493.51","0.005"]
|
|
# ],
|
|
# "asks":[
|
|
# ["2493.57","0.877"],
|
|
# ["2493.62","0.063"],
|
|
# ["2493.71","12.054"],
|
|
# ]
|
|
# }
|
|
# type not 1
|
|
# {
|
|
# "code":0,
|
|
# "msg":"Success",
|
|
# "data":{
|
|
# "lastUpdateId":3204783,
|
|
# "bids":[],
|
|
# "asks": []
|
|
# },
|
|
# "timestamp":1692262634599
|
|
# }
|
|
data = self.safe_value(response, 'data', response)
|
|
timestamp = self.safe_integer_2(response, 'T', 'timestamp')
|
|
orderbook = self.parse_order_book(data, symbol, timestamp)
|
|
orderbook['nonce'] = self.safe_integer(data, 'lastUpdateId')
|
|
return orderbook
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# aggregate trades
|
|
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
|
|
#
|
|
# {
|
|
# "a": 26129, # Aggregate tradeId
|
|
# "p": "0.01633102", # Price
|
|
# "q": "4.70443515", # Quantity
|
|
# "f": 27781, # First tradeId
|
|
# "l": 27781, # Last tradeId
|
|
# "T": 1498793709153, # Timestamp
|
|
# "m": True, # Was the buyer the maker?
|
|
# "M": True # Was the trade the best price match?
|
|
# }
|
|
#
|
|
# recent public trades and old public trades
|
|
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list
|
|
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data
|
|
#
|
|
# {
|
|
# "id": 28457,
|
|
# "price": "4.00000100",
|
|
# "qty": "12.00000000",
|
|
# "time": 1499865549590,
|
|
# "isBuyerMaker": True,
|
|
# "isBestMatch": True
|
|
# }
|
|
#
|
|
# private trades
|
|
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data
|
|
#
|
|
# {
|
|
# "symbol": "BNBBTC",
|
|
# "id": 28457,
|
|
# "orderId": 100234,
|
|
# "price": "4.00000100",
|
|
# "qty": "12.00000000",
|
|
# "commission": "10.10000000",
|
|
# "commissionAsset": "BNB",
|
|
# "time": 1499865549590,
|
|
# "isBuyer": True,
|
|
# "isMaker": False,
|
|
# "isBestMatch": True
|
|
# }
|
|
#
|
|
# futures trades
|
|
# https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data
|
|
#
|
|
# {
|
|
# "accountId": 20,
|
|
# "buyer": False,
|
|
# "commission": "-0.07819010",
|
|
# "commissionAsset": "USDT",
|
|
# "counterPartyId": 653,
|
|
# "id": 698759,
|
|
# "maker": False,
|
|
# "orderId": 25851813,
|
|
# "price": "7819.01",
|
|
# "qty": "0.002",
|
|
# "quoteQty": "0.01563",
|
|
# "realizedPnl": "-0.91539999",
|
|
# "side": "SELL",
|
|
# "symbol": "BTCUSDT",
|
|
# "time": 1569514978020
|
|
# }
|
|
# {
|
|
# "symbol": "BTCUSDT",
|
|
# "id": 477128891,
|
|
# "orderId": 13809777875,
|
|
# "side": "SELL",
|
|
# "price": "38479.55",
|
|
# "qty": "0.001",
|
|
# "realizedPnl": "-0.00009534",
|
|
# "marginAsset": "USDT",
|
|
# "quoteQty": "38.47955",
|
|
# "commission": "-0.00076959",
|
|
# "commissionAsset": "USDT",
|
|
# "time": 1612733566708,
|
|
# "positionSide": "BOTH",
|
|
# "maker": True,
|
|
# "buyer": False
|
|
# }
|
|
#
|
|
# {respType: FULL}
|
|
#
|
|
# {
|
|
# "price": "4000.00000000",
|
|
# "qty": "1.00000000",
|
|
# "commission": "4.00000000",
|
|
# "commissionAsset": "USDT",
|
|
# "tradeId": "1234",
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer_2(trade, 'T', 'time')
|
|
price = self.safe_string_2(trade, 'p', 'price')
|
|
amount = self.safe_string_2(trade, 'q', 'qty')
|
|
cost = self.safe_string_2(trade, 'quoteQty', 'baseQty') # inverse futures
|
|
marketId = self.safe_string(trade, 'symbol')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
id = self.safe_string_2(trade, 't', 'a')
|
|
id = self.safe_string_2(trade, 'id', 'tradeId', id)
|
|
side: Str = None
|
|
orderId = self.safe_string(trade, 'orderId')
|
|
buyerMaker = self.safe_value_2(trade, 'm', 'isBuyerMaker')
|
|
takerOrMaker: Str = None
|
|
if buyerMaker is not None:
|
|
side = 'sell' if buyerMaker else 'buy' # self is reversed intentionally
|
|
takerOrMaker = 'taker'
|
|
elif 'side' in trade:
|
|
side = self.safe_string_lower(trade, 'side')
|
|
else:
|
|
if 'isBuyer' in trade:
|
|
side = 'buy' if trade['isBuyer'] else 'sell' # self is a True side
|
|
fee = None
|
|
if 'commission' in trade:
|
|
fee = {
|
|
'cost': self.safe_string(trade, 'commission'),
|
|
'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAsset')),
|
|
}
|
|
if 'isMaker' in trade:
|
|
takerOrMaker = 'maker' if trade['isMaker'] else 'taker'
|
|
if 'maker' in trade:
|
|
takerOrMaker = 'maker' if trade['maker'] else 'taker'
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'id': id,
|
|
'order': orderId,
|
|
'type': None,
|
|
'side': side,
|
|
'takerOrMaker': takerOrMaker,
|
|
'price': price,
|
|
'amount': amount,
|
|
'cost': cost,
|
|
'fee': fee,
|
|
}, market)
|
|
|
|
async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#recent-trades-list
|
|
https://www.tokocrypto.com/apidocs/#compressedaggregate-trades-list
|
|
|
|
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>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': self.get_market_id_by_type(market),
|
|
# 'fromId': 123, # ID to get aggregate trades from INCLUSIVE.
|
|
# 'startTime': 456, # Timestamp in ms to get aggregate trades from INCLUSIVE.
|
|
# 'endTime': 789, # Timestamp in ms to get aggregate trades until INCLUSIVE.
|
|
# 'limit': 500, # default = 500, maximum = 1000
|
|
}
|
|
if market['quote'] != 'USDT':
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
responseInner = self.publicGetOpenV1MarketTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "success",
|
|
# "data": {
|
|
# "list": [
|
|
# {
|
|
# "id": 28457,
|
|
# "price": "4.00000100",
|
|
# "qty": "12.00000000",
|
|
# "time": 1499865549590,
|
|
# "isBuyerMaker": True,
|
|
# "isBestMatch": True
|
|
# }
|
|
# ]
|
|
# },
|
|
# "timestamp": 1571921637091
|
|
# }
|
|
#
|
|
data = self.safe_dict(responseInner, 'data', {})
|
|
list = self.safe_list(data, 'list', [])
|
|
return self.parse_trades(list, market, since, limit)
|
|
if limit is not None:
|
|
request['limit'] = limit # default = 500, maximum = 1000
|
|
defaultMethod = 'binanceGetTrades'
|
|
method = self.safe_string(self.options, 'fetchTradesMethod', defaultMethod)
|
|
response = None
|
|
if (method == 'binanceGetAggTrades') and (since is not None):
|
|
request['startTime'] = since
|
|
# https://github.com/ccxt/ccxt/issues/6400
|
|
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
|
|
request['endTime'] = self.sum(since, 3600000)
|
|
response = await self.binanceGetAggTrades(self.extend(request, params))
|
|
else:
|
|
response = await self.binanceGetTrades(self.extend(request, params))
|
|
#
|
|
# Caveats:
|
|
# - default limit(500) applies only if no other parameters set, trades up
|
|
# to the maximum limit may be returned to satisfy other parameters
|
|
# - if both limit and time window is set and time window contains more
|
|
# trades than the limit then the last trades from the window are returned
|
|
# - 'tradeId' accepted and returned by self method is "aggregate" trade id
|
|
# which is different from actual trade id
|
|
# - setting both fromId and time window results in error
|
|
#
|
|
# aggregate trades
|
|
#
|
|
# [
|
|
# {
|
|
# "a": 26129, # Aggregate tradeId
|
|
# "p": "0.01633102", # Price
|
|
# "q": "4.70443515", # Quantity
|
|
# "f": 27781, # First tradeId
|
|
# "l": 27781, # Last tradeId
|
|
# "T": 1498793709153, # Timestamp
|
|
# "m": True, # Was the buyer the maker?
|
|
# "M": True # Was the trade the best price match?
|
|
# }
|
|
# ]
|
|
#
|
|
# recent public trades and historical public trades
|
|
#
|
|
# [
|
|
# {
|
|
# "id": 28457,
|
|
# "price": "4.00000100",
|
|
# "qty": "12.00000000",
|
|
# "time": 1499865549590,
|
|
# "isBuyerMaker": True,
|
|
# "isBestMatch": True
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_trades(response, market, since, limit)
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "symbol": "ETHBTC",
|
|
# "priceChange": "0.00068700",
|
|
# "priceChangePercent": "2.075",
|
|
# "weightedAvgPrice": "0.03342681",
|
|
# "prevClosePrice": "0.03310300",
|
|
# "lastPrice": "0.03378900",
|
|
# "lastQty": "0.07700000",
|
|
# "bidPrice": "0.03378900",
|
|
# "bidQty": "7.16800000",
|
|
# "askPrice": "0.03379000",
|
|
# "askQty": "24.00000000",
|
|
# "openPrice": "0.03310200",
|
|
# "highPrice": "0.03388900",
|
|
# "lowPrice": "0.03306900",
|
|
# "volume": "205478.41000000",
|
|
# "quoteVolume": "6868.48826294",
|
|
# "openTime": 1601469986932,
|
|
# "closeTime": 1601556386932,
|
|
# "firstId": 196098772,
|
|
# "lastId": 196186315,
|
|
# "count": 87544
|
|
# }
|
|
#
|
|
# coinm
|
|
# {
|
|
# "baseVolume": "214549.95171161",
|
|
# "closeTime": "1621965286847",
|
|
# "count": "1283779",
|
|
# "firstId": "152560106",
|
|
# "highPrice": "39938.3",
|
|
# "lastId": "153843955",
|
|
# "lastPrice": "37993.4",
|
|
# "lastQty": "1",
|
|
# "lowPrice": "36457.2",
|
|
# "openPrice": "37783.4",
|
|
# "openTime": "1621878840000",
|
|
# "pair": "BTCUSD",
|
|
# "priceChange": "210.0",
|
|
# "priceChangePercent": "0.556",
|
|
# "symbol": "BTCUSD_PERP",
|
|
# "volume": "81990451",
|
|
# "weightedAvgPrice": "38215.08713747"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(ticker, 'closeTime')
|
|
marketId = self.safe_string(ticker, 'symbol')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
last = self.safe_string(ticker, 'lastPrice')
|
|
isCoinm = ('baseVolume' in ticker)
|
|
baseVolume = None
|
|
quoteVolume = None
|
|
if isCoinm:
|
|
baseVolume = self.safe_string(ticker, 'baseVolume')
|
|
quoteVolume = self.safe_string(ticker, 'volume')
|
|
else:
|
|
baseVolume = self.safe_string(ticker, 'volume')
|
|
quoteVolume = self.safe_string(ticker, 'quoteVolume')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_string(ticker, 'highPrice'),
|
|
'low': self.safe_string(ticker, 'lowPrice'),
|
|
'bid': self.safe_string(ticker, 'bidPrice'),
|
|
'bidVolume': self.safe_string(ticker, 'bidQty'),
|
|
'ask': self.safe_string(ticker, 'askPrice'),
|
|
'askVolume': self.safe_string(ticker, 'askQty'),
|
|
'vwap': self.safe_string(ticker, 'weightedAvgPrice'),
|
|
'open': self.safe_string(ticker, 'openPrice'),
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': self.safe_string(ticker, 'prevClosePrice'), # previous day close
|
|
'change': self.safe_string(ticker, 'priceChange'),
|
|
'percentage': self.safe_string(ticker, 'priceChangePercent'),
|
|
'average': None,
|
|
'baseVolume': baseVolume,
|
|
'quoteVolume': quoteVolume,
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
|
|
https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics
|
|
|
|
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
|
:param str[]|None 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>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.binanceGetTicker24hr(params)
|
|
return self.parse_tickers(response, symbols)
|
|
|
|
def get_market_id_by_type(self, market):
|
|
if market['quote'] == 'USDT':
|
|
return market['baseId'] + market['quoteId']
|
|
return market['id']
|
|
|
|
async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
|
|
https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics
|
|
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
: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>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['baseId'] + market['quoteId'],
|
|
}
|
|
response = await self.binanceGetTicker24hr(self.extend(request, params))
|
|
if isinstance(response, list):
|
|
firstTicker = self.safe_dict(response, 0, {})
|
|
return self.parse_ticker(firstTicker, market)
|
|
return self.parse_ticker(response, market)
|
|
|
|
async def fetch_bids_asks(self, symbols: Strings = None, params={}):
|
|
"""
|
|
|
|
https://binance-docs.github.io/apidocs/spot/en/#symbol-order-book-ticker
|
|
|
|
fetches the bid and ask price and volume for multiple markets
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the bids and asks for, all markets 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>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.binanceGetTickerBookTicker(params)
|
|
return self.parse_tickers(response, symbols)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
# when api method = publicGetKlines or fapiPublicGetKlines or dapiPublicGetKlines
|
|
# [
|
|
# 1591478520000, # open time
|
|
# "0.02501300", # open
|
|
# "0.02501800", # high
|
|
# "0.02500000", # low
|
|
# "0.02500000", # close
|
|
# "22.19000000", # volume
|
|
# 1591478579999, # close time
|
|
# "0.55490906", # quote asset volume
|
|
# 40, # number of trades
|
|
# "10.92900000", # taker buy base asset volume
|
|
# "0.27336462", # taker buy quote asset volume
|
|
# "0" # ignore
|
|
# ]
|
|
#
|
|
# when api method = fapiPublicGetMarkPriceKlines or fapiPublicGetIndexPriceKlines
|
|
# [
|
|
# [
|
|
# 1591256460000, # Open time
|
|
# "9653.29201333", # Open
|
|
# "9654.56401333", # High
|
|
# "9653.07367333", # Low
|
|
# "9653.07367333", # Close(or latest price)
|
|
# "0", # Ignore
|
|
# 1591256519999, # Close time
|
|
# "0", # Ignore
|
|
# 60, # Number of bisic data
|
|
# "0", # Ignore
|
|
# "0", # Ignore
|
|
# "0" # Ignore
|
|
# ]
|
|
# ]
|
|
#
|
|
return [
|
|
self.safe_integer(ohlcv, 0),
|
|
self.safe_number(ohlcv, 1),
|
|
self.safe_number(ohlcv, 2),
|
|
self.safe_number(ohlcv, 3),
|
|
self.safe_number(ohlcv, 4),
|
|
self.safe_number(ohlcv, 5),
|
|
]
|
|
|
|
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
|
|
https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data
|
|
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
: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 str [params.price]: "mark" or "index" for mark price and index price candles
|
|
:param int [params.until]: timestamp in ms of the latest candle to fetch
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
# binance docs say that the default limit 500, max 1500 for futures, max 1000 for spot markets
|
|
# the reality is that the time range wider than 500 candles won't work right
|
|
defaultLimit = 500
|
|
maxLimit = 1500
|
|
price = self.safe_string(params, 'price')
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['price', 'until'])
|
|
limit = defaultLimit if (limit is None) else min(limit, maxLimit)
|
|
request: dict = {
|
|
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
'limit': limit,
|
|
}
|
|
if price == 'index':
|
|
request['pair'] = market['id'] # Index price takes self argument instead of symbol
|
|
else:
|
|
request['symbol'] = self.get_market_id_by_type(market)
|
|
# duration = self.parse_timeframe(timeframe)
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
response = None
|
|
if market['quote'] == 'USDT':
|
|
response = await self.binanceGetKlines(self.extend(request, params))
|
|
else:
|
|
response = await self.publicGetOpenV1MarketKlines(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [1591478520000,"0.02501300","0.02501800","0.02500000","0.02500000","22.19000000",1591478579999,"0.55490906",40,"10.92900000","0.27336462","0"],
|
|
# [1591478580000,"0.02499600","0.02500900","0.02499400","0.02500300","21.34700000",1591478639999,"0.53370468",24,"7.53800000","0.18850725","0"],
|
|
# [1591478640000,"0.02500800","0.02501100","0.02500300","0.02500800","154.14200000",1591478699999,"3.85405839",97,"5.32300000","0.13312641","0"],
|
|
# ]
|
|
#
|
|
data = self.safe_list(response, 'data', response)
|
|
return self.parse_ohlcvs(data, market, timeframe, since, limit)
|
|
|
|
async def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#account-information-signed
|
|
|
|
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 str [params.type]: 'future', 'delivery', 'savings', 'funding', or 'spot'
|
|
:param str [params.marginMode]: 'cross' or 'isolated', for margin trading, uses self.options.defaultMarginMode if not passed, defaults to None/None/None
|
|
:param str[]|None [params.symbols]: unified market symbols, only used in isolated margin mode
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
|
|
type = self.safe_string(params, 'type', defaultType)
|
|
defaultMarginMode = self.safe_string_2(self.options, 'marginMode', 'defaultMarginMode')
|
|
marginMode = self.safe_string_lower(params, 'marginMode', defaultMarginMode)
|
|
request: dict = {}
|
|
response = await self.privateGetOpenV1AccountSpot(self.extend(request, params))
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"Success",
|
|
# "data":{
|
|
# "makerCommission":"0.00100000",
|
|
# "takerCommission":"0.00100000",
|
|
# "buyerCommission":"0.00000000",
|
|
# "sellerCommission":"0.00000000",
|
|
# "canTrade":1,
|
|
# "canWithdraw":1,
|
|
# "canDeposit":1,
|
|
# "status":1,
|
|
# "accountAssets":[
|
|
# {"asset":"1INCH","free":"0","locked":"0"},
|
|
# {"asset":"AAVE","free":"0","locked":"0"},
|
|
# {"asset":"ACA","free":"0","locked":"0"}
|
|
# ],
|
|
# },
|
|
# "timestamp":1659666786943
|
|
# }
|
|
#
|
|
return self.parse_balance_custom(response, type, marginMode)
|
|
|
|
def parse_balance_custom(self, response, type=None, marginMode=None):
|
|
timestamp = self.safe_integer(response, 'updateTime')
|
|
result: dict = {
|
|
'info': response,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
data = self.safe_value(response, 'data', {})
|
|
balances = self.safe_value(data, 'accountAssets', [])
|
|
for i in range(0, len(balances)):
|
|
balance = balances[i]
|
|
currencyId = self.safe_string(balance, 'asset')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(balance, 'free')
|
|
account['used'] = self.safe_string(balance, 'locked')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'-2': 'open',
|
|
'0': 'open', # NEW
|
|
'1': 'open', # PARTIALLY_FILLED
|
|
'2': 'closed', # FILLED
|
|
'3': 'canceled', # CANCELED
|
|
'4': 'canceling', # PENDING_CANCEL(currently unused)
|
|
'5': 'rejected', # REJECTED
|
|
'6': 'expired', # EXPIRED
|
|
'NEW': 'open',
|
|
'PARTIALLY_FILLED': 'open',
|
|
'FILLED': 'closed',
|
|
'CANCELED': 'canceled',
|
|
'PENDING_CANCEL': 'canceling', # currently unused
|
|
'REJECTED': 'rejected',
|
|
'EXPIRED': 'expired',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "symbol": "LTCBTC",
|
|
# "orderId": 1,
|
|
# "clientOrderId": "myOrder1",
|
|
# "price": "0.1",
|
|
# "origQty": "1.0",
|
|
# "executedQty": "0.0",
|
|
# "cummulativeQuoteQty": "0.0",
|
|
# "status": "NEW",
|
|
# "timeInForce": "GTC",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "stopPrice": "0.0",
|
|
# "icebergQty": "0.0",
|
|
# "time": 1499827319559,
|
|
# "updateTime": 1499827319559,
|
|
# "isWorking": True
|
|
# }
|
|
# createOrder
|
|
# {
|
|
# "orderId": "145265071",
|
|
# "bOrderListId": "0",
|
|
# "clientId": "49c09c3c2cd54419a59c05441f517b3c",
|
|
# "bOrderId": "35247529",
|
|
# "symbol": "USDT_BIDR",
|
|
# "symbolType": "1",
|
|
# "side": "0",
|
|
# "type": "1",
|
|
# "price": "11915",
|
|
# "origQty": "2",
|
|
# "origQuoteQty": "23830.00",
|
|
# "executedQty": "0.00000000",
|
|
# "executedPrice": "0",
|
|
# "executedQuoteQty": "0.00",
|
|
# "timeInForce": "1",
|
|
# "stopPrice": "0",
|
|
# "icebergQty": "0",
|
|
# "status": "0",
|
|
# "createTime": "1662711074372"
|
|
# }
|
|
#
|
|
# createOrder with {"newOrderRespType": "FULL"}
|
|
#
|
|
# {
|
|
# "symbol": "BTCUSDT",
|
|
# "orderId": 5403233939,
|
|
# "orderListId": -1,
|
|
# "clientOrderId": "x-R4BD3S825e669e75b6c14f69a2c43e",
|
|
# "transactTime": 1617151923742,
|
|
# "price": "0.00000000",
|
|
# "origQty": "0.00050000",
|
|
# "executedQty": "0.00050000",
|
|
# "cummulativeQuoteQty": "29.47081500",
|
|
# "status": "FILLED",
|
|
# "timeInForce": "GTC",
|
|
# "type": "MARKET",
|
|
# "side": "BUY",
|
|
# "fills": [
|
|
# {
|
|
# "price": "58941.63000000",
|
|
# "qty": "0.00050000",
|
|
# "commission": "0.00007050",
|
|
# "commissionAsset": "BNB",
|
|
# "tradeId": 737466631
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# delivery
|
|
#
|
|
# {
|
|
# "orderId": "18742727411",
|
|
# "symbol": "ETHUSD_PERP",
|
|
# "pair": "ETHUSD",
|
|
# "status": "FILLED",
|
|
# "clientOrderId": "x-xcKtGhcu3e2d1503fdd543b3b02419",
|
|
# "price": "0",
|
|
# "avgPrice": "4522.14",
|
|
# "origQty": "1",
|
|
# "executedQty": "1",
|
|
# "cumBase": "0.00221134",
|
|
# "timeInForce": "GTC",
|
|
# "type": "MARKET",
|
|
# "reduceOnly": False,
|
|
# "closePosition": False,
|
|
# "side": "SELL",
|
|
# "positionSide": "BOTH",
|
|
# "stopPrice": "0",
|
|
# "workingType": "CONTRACT_PRICE",
|
|
# "priceProtect": False,
|
|
# "origType": "MARKET",
|
|
# "time": "1636061952660",
|
|
# "updateTime": "1636061952660"
|
|
# }
|
|
#
|
|
status = self.parse_order_status(self.safe_string(order, 'status'))
|
|
marketId = self.safe_string(order, 'symbol')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
filled = self.safe_string(order, 'executedQty', '0')
|
|
timestamp = self.safe_integer(order, 'createTime')
|
|
average = self.safe_string(order, 'avgPrice')
|
|
price = self.safe_string_2(order, 'price', 'executedPrice')
|
|
amount = self.safe_string(order, 'origQty')
|
|
# - Spot/Margin market: cummulativeQuoteQty
|
|
# Note self is not the actual cost, since Binance futures uses leverage to calculate margins.
|
|
cost = self.safe_string_n(order, ['cummulativeQuoteQty', 'cumQuote', 'executedQuoteQty', 'cumBase'])
|
|
id = self.safe_string(order, 'orderId')
|
|
type = self.parse_order_type(self.safe_string_lower(order, 'type'))
|
|
side = self.safe_string_lower(order, 'side')
|
|
if side == '0':
|
|
side = 'buy'
|
|
elif side == '1':
|
|
side = 'sell'
|
|
fills = self.safe_value(order, 'fills', [])
|
|
clientOrderId = self.safe_string_2(order, 'clientOrderId', 'clientId')
|
|
timeInForce = self.safe_string(order, 'timeInForce')
|
|
if timeInForce == 'GTX':
|
|
# GTX means "Good Till Crossing" and is an equivalent way of saying Post Only
|
|
timeInForce = 'PO'
|
|
postOnly = (type == 'limit_maker') or (timeInForce == 'PO')
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': id,
|
|
'clientOrderId': clientOrderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': timeInForce,
|
|
'postOnly': postOnly,
|
|
'reduceOnly': self.safe_value(order, 'reduceOnly'),
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': self.parse_number(self.omit_zero(self.safe_string(order, 'stopPrice'))),
|
|
'amount': amount,
|
|
'cost': cost,
|
|
'average': average,
|
|
'filled': filled,
|
|
'remaining': None,
|
|
'status': status,
|
|
'fee': None,
|
|
'trades': fills,
|
|
}, market)
|
|
|
|
def parse_order_type(self, status):
|
|
statuses: dict = {
|
|
'2': 'market',
|
|
'1': 'limit',
|
|
'4': 'limit',
|
|
'7': 'limit',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://www.tokocrypto.com/apidocs/#new-order--signed
|
|
|
|
: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 of currency you want to trade in units of 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 at which a trigger order would be triggered
|
|
:param float [params.cost]: for spot market buy orders, the quote quantity that can be used alternative for the amount
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clientId')
|
|
postOnly = self.safe_bool(params, 'postOnly', False)
|
|
# only supported for spot/margin api
|
|
if postOnly:
|
|
type = 'LIMIT_MAKER'
|
|
params = self.omit(params, ['clientId', 'clientOrderId'])
|
|
initialUppercaseType = type.upper()
|
|
uppercaseType = initialUppercaseType
|
|
triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
|
|
if triggerPrice is not None:
|
|
params = self.omit(params, ['triggerPrice', 'stopPrice'])
|
|
if uppercaseType == 'MARKET':
|
|
uppercaseType = 'STOP_LOSS'
|
|
elif uppercaseType == 'LIMIT':
|
|
uppercaseType = 'STOP_LOSS_LIMIT'
|
|
validOrderTypes = self.safe_value(market['info'], 'orderTypes')
|
|
if not self.in_array(uppercaseType, validOrderTypes):
|
|
if initialUppercaseType != uppercaseType:
|
|
raise InvalidOrder(self.id + ' triggerPrice parameter is not allowed for ' + symbol + ' ' + type + ' orders')
|
|
else:
|
|
raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type for the ' + symbol + ' market')
|
|
reverseOrderTypeMapping: dict = {
|
|
'LIMIT': 1,
|
|
'MARKET': 2,
|
|
'STOP_LOSS': 3,
|
|
'STOP_LOSS_LIMIT': 4,
|
|
'TAKE_PROFIT': 5,
|
|
'TAKE_PROFIT_LIMIT': 6,
|
|
'LIMIT_MAKER': 7,
|
|
}
|
|
request: dict = {
|
|
'symbol': market['baseId'] + '_' + market['quoteId'],
|
|
'type': self.safe_string(reverseOrderTypeMapping, uppercaseType),
|
|
}
|
|
if side == 'buy':
|
|
request['side'] = 0
|
|
elif side == 'sell':
|
|
request['side'] = 1
|
|
if clientOrderId is None:
|
|
broker = self.safe_value(self.options, 'broker')
|
|
if broker is not None:
|
|
brokerId = self.safe_string(broker, 'marketType')
|
|
if brokerId is not None:
|
|
request['clientId'] = brokerId + self.uuid22()
|
|
else:
|
|
request['clientId'] = clientOrderId
|
|
# additional required fields depending on the order type
|
|
priceIsRequired = False
|
|
triggerPriceIsRequired = False
|
|
quantityIsRequired = False
|
|
#
|
|
# spot/margin
|
|
#
|
|
# LIMIT timeInForce, quantity, price
|
|
# MARKET quantity or quoteOrderQty
|
|
# STOP_LOSS quantity, stopPrice
|
|
# STOP_LOSS_LIMIT timeInForce, quantity, price, stopPrice
|
|
# TAKE_PROFIT quantity, stopPrice
|
|
# TAKE_PROFIT_LIMIT timeInForce, quantity, price, stopPrice
|
|
# LIMIT_MAKER quantity, price
|
|
#
|
|
if uppercaseType == 'MARKET':
|
|
if side == 'buy':
|
|
precision = market['precision']['price']
|
|
quoteAmount = None
|
|
createMarketBuyOrderRequiresPrice = True
|
|
createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
|
|
cost = self.safe_number_2(params, 'cost', 'quoteOrderQty')
|
|
params = self.omit(params, ['cost', 'quoteOrderQty'])
|
|
if cost is not None:
|
|
quoteAmount = 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(quote quantity) in the amount argument')
|
|
else:
|
|
amountString = self.number_to_string(amount)
|
|
priceString = self.number_to_string(price)
|
|
quoteAmount = Precise.string_mul(amountString, priceString)
|
|
else:
|
|
quoteAmount = amount
|
|
request['quoteOrderQty'] = self.decimal_to_precision(quoteAmount, TRUNCATE, precision, self.precisionMode)
|
|
else:
|
|
quantityIsRequired = True
|
|
elif uppercaseType == 'LIMIT':
|
|
priceIsRequired = True
|
|
quantityIsRequired = True
|
|
elif (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT'):
|
|
triggerPriceIsRequired = True
|
|
quantityIsRequired = True
|
|
if market['linear'] or market['inverse']:
|
|
priceIsRequired = True
|
|
elif (uppercaseType == 'STOP_LOSS_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
|
quantityIsRequired = True
|
|
triggerPriceIsRequired = True
|
|
priceIsRequired = True
|
|
elif uppercaseType == 'LIMIT_MAKER':
|
|
priceIsRequired = True
|
|
quantityIsRequired = True
|
|
if quantityIsRequired:
|
|
request['quantity'] = self.amount_to_precision(symbol, amount)
|
|
if priceIsRequired:
|
|
if price is None:
|
|
raise InvalidOrder(self.id + ' createOrder() requires a price argument for a ' + type + ' order')
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
if triggerPriceIsRequired:
|
|
if triggerPrice is None:
|
|
raise InvalidOrder(self.id + ' createOrder() requires a triggerPrice extra param for a ' + type + ' order')
|
|
else:
|
|
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
|
|
response = await self.privatePostOpenV1Orders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "Success",
|
|
# "data": {
|
|
# "orderId": 145264846,
|
|
# "bOrderListId": 0,
|
|
# "clientId": "4ee2ab5e55e74b358eaf98079c670d17",
|
|
# "bOrderId": 35247499,
|
|
# "symbol": "USDT_BIDR",
|
|
# "symbolType": 1,
|
|
# "side": 0,
|
|
# "type": 1,
|
|
# "price": "11915",
|
|
# "origQty": "2",
|
|
# "origQuoteQty": "23830.00",
|
|
# "executedQty": "0.00000000",
|
|
# "executedPrice": "0",
|
|
# "executedQuoteQty": "0.00",
|
|
# "timeInForce": 1,
|
|
# "stopPrice": 0,
|
|
# "icebergQty": "0",
|
|
# "status": 0,
|
|
# "createTime": 1662710994848
|
|
# },
|
|
# "timestamp": 1662710994975
|
|
# }
|
|
#
|
|
rawOrder = self.safe_dict(response, 'data', {})
|
|
return self.parse_order(rawOrder, market)
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#query-order-signed
|
|
|
|
fetches information on an order made by the user
|
|
: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>`
|
|
"""
|
|
request: dict = {
|
|
'orderId': id,
|
|
}
|
|
response = await self.privateGetOpenV1Orders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "Success",
|
|
# "data": {
|
|
# "list": [{
|
|
# "orderId": "145221985",
|
|
# "clientId": "201515331fd64d03aedbe687a38152e3",
|
|
# "bOrderId": "35239632",
|
|
# "bOrderListId": "0",
|
|
# "symbol": "USDT_BIDR",
|
|
# "symbolType": 1,
|
|
# "side": 0,
|
|
# "type": 1,
|
|
# "price": "11907",
|
|
# "origQty": "2",
|
|
# "origQuoteQty": "23814",
|
|
# "executedQty": "0",
|
|
# "executedPrice": "0",
|
|
# "executedQuoteQty": "0",
|
|
# "timeInForce": 1,
|
|
# "stopPrice": "0",
|
|
# "icebergQty": "0",
|
|
# "status": 0,
|
|
# "createTime": 1662699360000
|
|
# }]
|
|
# },
|
|
# "timestamp": 1662710056523
|
|
# }
|
|
#
|
|
data = self.safe_value(response, 'data', {})
|
|
list = self.safe_value(data, 'list', [])
|
|
rawOrder = self.safe_dict(list, 0, {})
|
|
return self.parse_order(rawOrder)
|
|
|
|
async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#all-orders-signed
|
|
|
|
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
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
# 'type': -1, # -1 = all, 1 = open, 2 = closed
|
|
# 'side': 1, # or 2
|
|
# 'startTime': since,
|
|
# 'endTime': self.milliseconds(),
|
|
# 'fromId': 'starting order ID', # if defined, the "direct" field becomes mandatory
|
|
# 'direct': 'prev', # prev, next
|
|
# 'limit': 500, # default 500, max 1000
|
|
}
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.privateGetOpenV1Orders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "success",
|
|
# "data": {
|
|
# "list": [
|
|
# {
|
|
# "orderId": "4", # order id
|
|
# "bOrderId": "100001", # binance order id
|
|
# "bOrderListId": -1, # Unless part of an OCO, the value will always be -1.
|
|
# "clientId": "1aa4f99ad7bc4fab903395afd25d0597", # client custom order id
|
|
# "symbol": "ADA_USDT",
|
|
# "symbolType": 1,
|
|
# "side": 1,
|
|
# "type": 1,
|
|
# "price": "0.1",
|
|
# "origQty": "10",
|
|
# "origQuoteQty": "1",
|
|
# "executedQty": "0",
|
|
# "executedPrice": "0",
|
|
# "executedQuoteQty": "0",
|
|
# "timeInForce": 1,
|
|
# "stopPrice": "0.0000000000000000",
|
|
# "icebergQty": "0.0000000000000000",
|
|
# "status": 0,
|
|
# "isWorking": 0,
|
|
# "createTime": 1572692016811
|
|
# }
|
|
# ]
|
|
# },
|
|
# "timestamp": 1572860756458
|
|
# }
|
|
#
|
|
data = self.safe_value(response, 'data', {})
|
|
orders = self.safe_list(data, 'list', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#all-orders-signed
|
|
|
|
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 orders structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {'type': 1} # -1 = all, 1 = open, 2 = closed
|
|
return await self.fetch_orders(symbol, since, limit, self.extend(request, params))
|
|
|
|
async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#all-orders-signed
|
|
|
|
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
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {'type': 2} # -1 = all, 1 = open, 2 = closed
|
|
return await self.fetch_orders(symbol, since, limit, self.extend(request, params))
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#cancel-order-signed
|
|
|
|
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
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {
|
|
'orderId': id,
|
|
}
|
|
response = await self.privatePostOpenV1OrdersCancel(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "Success",
|
|
# "data": {
|
|
# "orderId": "145221985",
|
|
# "bOrderListId": "0",
|
|
# "clientId": "201515331fd64d03aedbe687a38152e3",
|
|
# "bOrderId": "35239632",
|
|
# "symbol": "USDT_BIDR",
|
|
# "symbolType": 1,
|
|
# "type": 1,
|
|
# "side": 0,
|
|
# "price": "11907.0000000000000000",
|
|
# "origQty": "2.0000000000000000",
|
|
# "origQuoteQty": "23814.0000000000000000",
|
|
# "executedPrice": "0.0000000000000000",
|
|
# "executedQty": "0.00000000",
|
|
# "executedQuoteQty": "0.00",
|
|
# "timeInForce": 1,
|
|
# "stopPrice": "0.0000000000000000",
|
|
# "icebergQty": "0.0000000000000000",
|
|
# "status": 3
|
|
# },
|
|
# "timestamp": 1662710683634
|
|
# }
|
|
#
|
|
rawOrder = self.safe_dict(response, 'data', {})
|
|
return self.parse_order(rawOrder)
|
|
|
|
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#account-trade-list-signed
|
|
|
|
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
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
endTime = self.safe_integer_2(params, 'until', 'endTime')
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if endTime is not None:
|
|
request['endTime'] = endTime
|
|
params = self.omit(params, ['endTime', 'until'])
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.privateGetOpenV1OrdersTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "success",
|
|
# "data": {
|
|
# "list": [
|
|
# {
|
|
# "tradeId": "3",
|
|
# "orderId": "2",
|
|
# "symbol": "ADA_USDT",
|
|
# "price": "0.04398",
|
|
# "qty": "250",
|
|
# "quoteQty": "10.995",
|
|
# "commission": "0.25",
|
|
# "commissionAsset": "ADA",
|
|
# "isBuyer": 1,
|
|
# "isMaker": 0,
|
|
# "isBestMatch": 1,
|
|
# "time": "1572920872276"
|
|
# }
|
|
# ]
|
|
# },
|
|
# "timestamp": 1573723498893
|
|
# }
|
|
#
|
|
data = self.safe_value(response, 'data', {})
|
|
trades = self.safe_list(data, 'list', [])
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://www.tokocrypto.com/apidocs/#deposit-address-signed
|
|
|
|
: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>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
# 'network': 'ETH', # 'BSC', 'XMR', you can get network and isDefault in networkList in the response of sapiGetCapitalConfigDetail
|
|
}
|
|
networks = self.safe_value(self.options, 'networks', {})
|
|
network = self.safe_string_upper(params, 'network') # self line allows the user to specify either ERC20 or ETH
|
|
network = self.safe_string(networks, network, network) # handle ERC20>ETH alias
|
|
if network is not None:
|
|
request['network'] = network
|
|
params = self.omit(params, 'network')
|
|
# has support for the 'network' parameter
|
|
# https://binance-docs.github.io/apidocs/spot/en/#deposit-address-supporting-network-user_data
|
|
response = await self.privateGetOpenV1DepositsAddress(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"Success",
|
|
# "data":{
|
|
# "uid":"182395",
|
|
# "asset":"USDT",
|
|
# "network":"ETH",
|
|
# "address":"0x101a925704f6ff13295ab8dd7a60988d116aaedf",
|
|
# "addressTag":"",
|
|
# "status":1
|
|
# },
|
|
# "timestamp":1660685915746
|
|
# }
|
|
#
|
|
data = self.safe_value(response, 'data', {})
|
|
address = self.safe_string(data, 'address')
|
|
tag = self.safe_string(data, 'addressTag', '')
|
|
if len(tag) == 0:
|
|
tag = None
|
|
self.check_address(address)
|
|
return {
|
|
'info': response,
|
|
'currency': code,
|
|
'network': self.safe_string(data, 'network'),
|
|
'address': address,
|
|
'tag': tag,
|
|
}
|
|
|
|
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#deposit-history-signed
|
|
|
|
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
|
|
:param int [params.until]: the latest time in ms to fetch deposits for
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = None
|
|
request: dict = {}
|
|
until = self.safe_integer(params, 'until')
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['coin'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
# max 3 months range https://github.com/ccxt/ccxt/issues/6495
|
|
endTime = self.sum(since, 7776000000)
|
|
if until is not None:
|
|
endTime = min(endTime, until)
|
|
request['endTime'] = endTime
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.privateGetOpenV1Deposits(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"Success",
|
|
# "data":{
|
|
# "list":[
|
|
# {
|
|
# "id":5167969,
|
|
# "asset":"BIDR",
|
|
# "network":"BSC",
|
|
# "address":"0x101a925704f6ff13295ab8dd7a60988d116aaedf",
|
|
# "addressTag":"",
|
|
# "txId":"113409337867",
|
|
# "amount":"15000",
|
|
# "transferType":1,
|
|
# "status":1,
|
|
# "insertTime":"1659429390000"
|
|
# },
|
|
# ]
|
|
# },
|
|
# "timestamp":1659758865998
|
|
# }
|
|
#
|
|
data = self.safe_value(response, 'data', {})
|
|
deposits = self.safe_list(data, 'list', [])
|
|
return self.parse_transactions(deposits, currency, since, limit)
|
|
|
|
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#withdraw-signed
|
|
|
|
fetch all withdrawals made from an account
|
|
: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>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {}
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['coin'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
# max 3 months range https://github.com/ccxt/ccxt/issues/6495
|
|
request['endTime'] = self.sum(since, 7776000000)
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.privateGetOpenV1Withdraws(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"Success",
|
|
# "data":{
|
|
# "list":[
|
|
# {
|
|
# "id":4245859,
|
|
# "clientId":"198",
|
|
# "asset":"BIDR",
|
|
# "network":"BSC",
|
|
# "address":"0xff1c75149cc492e7d5566145b859fcafc900b6e9",
|
|
# "addressTag":"",
|
|
# "amount":"10000",
|
|
# "fee":"0",
|
|
# "txId":"113501794501",
|
|
# "transferType":1,
|
|
# "status":10,
|
|
# "createTime":1659521314413
|
|
# }
|
|
# ]
|
|
# },
|
|
# "timestamp":1659759062187
|
|
# }
|
|
#
|
|
data = self.safe_value(response, 'data', {})
|
|
withdrawals = self.safe_list(data, 'list', [])
|
|
return self.parse_transactions(withdrawals, currency, since, limit)
|
|
|
|
def parse_transaction_status_by_type(self, status, type=None):
|
|
statusesByType: dict = {
|
|
'deposit': {
|
|
'0': 'pending',
|
|
'1': 'ok',
|
|
},
|
|
'withdrawal': {
|
|
'0': 'pending', # Email Sent
|
|
'1': 'canceled', # Cancelled(different from 1 = ok in deposits)
|
|
'2': 'pending', # Awaiting Approval
|
|
'3': 'failed', # Rejected
|
|
'4': 'pending', # Processing
|
|
'5': 'failed', # Failure
|
|
'10': 'ok', # Completed
|
|
},
|
|
}
|
|
statuses = self.safe_value(statusesByType, type, {})
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# fetchDeposits
|
|
#
|
|
# {
|
|
# "id": 5167969,
|
|
# "asset": "BIDR",
|
|
# "network": "BSC",
|
|
# "address": "0x101a925704f6ff13295ab8dd7a60988d116aaedf",
|
|
# "addressTag": "",
|
|
# "txId": "113409337867",
|
|
# "amount": "15000",
|
|
# "transferType": 1,
|
|
# "status": 1,
|
|
# "insertTime": "1659429390000"
|
|
# }
|
|
#
|
|
# fetchWithdrawals
|
|
#
|
|
# {
|
|
# "id": 4245859,
|
|
# "clientId": "198",
|
|
# "asset": "BIDR",
|
|
# "network": "BSC",
|
|
# "address": "0xff1c75149cc492e7d5566145b859fcafc900b6e9",
|
|
# "addressTag": "",
|
|
# "amount": "10000",
|
|
# "fee": "0",
|
|
# "txId": "113501794501",
|
|
# "transferType": 1,
|
|
# "status": 10,
|
|
# "createTime": 1659521314413
|
|
# }
|
|
#
|
|
# withdraw
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "成功",
|
|
# "data": {
|
|
# "withdrawId":"12"
|
|
# },
|
|
# "timestamp": 1571745049095
|
|
# }
|
|
#
|
|
address = self.safe_string(transaction, 'address')
|
|
tag = self.safe_string(transaction, 'addressTag') # set but unused
|
|
if tag is not None:
|
|
if len(tag) < 1:
|
|
tag = None
|
|
txid = self.safe_string(transaction, 'txId')
|
|
if (txid is not None) and (txid.find('Internal transfer ') >= 0):
|
|
txid = txid[18:]
|
|
currencyId = self.safe_string_2(transaction, 'coin', 'fiatCurrency')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
timestamp = None
|
|
insertTime = self.safe_integer(transaction, 'insertTime')
|
|
createTime = self.safe_integer_2(transaction, 'createTime', 'timestamp')
|
|
type = self.safe_string(transaction, 'type')
|
|
if type is None:
|
|
if (insertTime is not None) and (createTime is None):
|
|
type = 'deposit'
|
|
timestamp = insertTime
|
|
elif (insertTime is None) and (createTime is not None):
|
|
type = 'withdrawal'
|
|
timestamp = createTime
|
|
feeCost = self.safe_number_2(transaction, 'transactionFee', 'totalFee')
|
|
fee = {
|
|
'currency': None,
|
|
'cost': None,
|
|
'rate': None,
|
|
}
|
|
if feeCost is not None:
|
|
fee['currency'] = code
|
|
fee['cost'] = feeCost
|
|
internalRaw = self.safe_integer(transaction, 'transferType')
|
|
internal = False
|
|
if internalRaw is not None:
|
|
internal = True
|
|
id = self.safe_string(transaction, 'id')
|
|
if id is None:
|
|
data = self.safe_value(transaction, 'data', {})
|
|
id = self.safe_string(data, 'withdrawId')
|
|
type = 'withdrawal'
|
|
return {
|
|
'info': transaction,
|
|
'id': id,
|
|
'txid': txid,
|
|
'type': type,
|
|
'currency': code,
|
|
'network': self.safe_string(transaction, 'network'),
|
|
'amount': self.safe_number(transaction, 'amount'),
|
|
'status': self.parse_transaction_status_by_type(self.safe_string(transaction, 'status'), type),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'address': address,
|
|
'addressFrom': None,
|
|
'addressTo': address,
|
|
'tag': tag,
|
|
'tagFrom': None,
|
|
'tagTo': tag,
|
|
'updated': self.safe_integer_2(transaction, 'successTime', 'updateTime'),
|
|
'comment': None,
|
|
'internal': internal,
|
|
'fee': fee,
|
|
}
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
|
|
https://www.tokocrypto.com/apidocs/#withdraw-signed
|
|
|
|
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)
|
|
await self.load_markets()
|
|
self.check_address(address)
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
# 'clientId': 'string', # # client's custom id for withdraw order, server does not check it's uniqueness, automatically generated if not sent
|
|
# 'network': 'string',
|
|
'address': address,
|
|
# 'addressTag': 'string', # for coins like XRP, XMR, etc
|
|
'amount': self.number_to_string(amount),
|
|
}
|
|
if tag is not None:
|
|
request['addressTag'] = tag
|
|
networkCode, query = self.handle_network_code_and_params(params)
|
|
networkId = self.network_code_to_id(networkCode)
|
|
if networkId is not None:
|
|
request['network'] = networkId.upper()
|
|
response = await self.privatePostOpenV1Withdraws(self.extend(request, query))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "成功",
|
|
# "data": {
|
|
# "withdrawId":"12"
|
|
# },
|
|
# "timestamp": 1571745049095
|
|
# }
|
|
#
|
|
return self.parse_transaction(response, currency)
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
if not (api in self.urls['api']['rest']):
|
|
raise NotSupported(self.id + ' does not have a testnet/sandbox URL for ' + api + ' endpoints')
|
|
url = self.urls['api']['rest'][api]
|
|
url += '/' + path
|
|
if api == 'wapi':
|
|
url += '.html'
|
|
userDataStream = (path == 'userDataStream') or (path == 'listenKey')
|
|
if userDataStream:
|
|
if self.apiKey:
|
|
# v1 special case for userDataStream
|
|
headers = {
|
|
'X-MBX-APIKEY': self.apiKey,
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
}
|
|
if method != 'GET':
|
|
body = self.urlencode(params)
|
|
else:
|
|
raise AuthenticationError(self.id + ' userDataStream endpoint requires `apiKey` credential')
|
|
elif (api == 'private') or (api == 'sapi' and path != 'system/status') or (api == 'sapiV3') or (api == 'wapi' and path != 'systemStatus') or (api == 'dapiPrivate') or (api == 'dapiPrivateV2') or (api == 'fapiPrivate') or (api == 'fapiPrivateV2'):
|
|
self.check_required_credentials()
|
|
query = None
|
|
defaultRecvWindow = self.safe_integer(self.options, 'recvWindow')
|
|
extendedParams = self.extend({
|
|
'timestamp': self.nonce(),
|
|
}, params)
|
|
if defaultRecvWindow is not None:
|
|
extendedParams['recvWindow'] = defaultRecvWindow
|
|
recvWindow = self.safe_integer(params, 'recvWindow')
|
|
if recvWindow is not None:
|
|
extendedParams['recvWindow'] = recvWindow
|
|
if (api == 'sapi') and (path == 'asset/dust'):
|
|
query = self.urlencode_with_array_repeat(extendedParams)
|
|
elif (path == 'batchOrders') or (path.find('sub-account') >= 0) or (path == 'capital/withdraw/apply') or (path.find('staking') >= 0):
|
|
query = self.rawencode(extendedParams)
|
|
else:
|
|
query = self.urlencode(extendedParams)
|
|
signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256)
|
|
query += '&' + 'signature=' + signature
|
|
headers = {
|
|
'X-MBX-APIKEY': self.apiKey,
|
|
}
|
|
if (method == 'GET') or (method == 'DELETE') or (api == 'wapi'):
|
|
url += '?' + query
|
|
else:
|
|
body = query
|
|
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
else:
|
|
if params:
|
|
url += '?' + self.urlencode(params)
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
if (code == 418) or (code == 429):
|
|
raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
|
|
# error response in a form: {"code": -1013, "msg": "Invalid quantity."}
|
|
# following block cointains legacy checks against message patterns in "msg" property
|
|
# will switch "code" checks eventually, when we know all of them
|
|
if code >= 400:
|
|
if body.find('Price * QTY is zero or less') >= 0:
|
|
raise InvalidOrder(self.id + ' order cost = amount * price is zero or less ' + body)
|
|
if body.find('LOT_SIZE') >= 0:
|
|
raise InvalidOrder(self.id + ' order amount should be evenly divisible by lot size ' + body)
|
|
if body.find('PRICE_FILTER') >= 0:
|
|
raise InvalidOrder(self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid value in general, use self.price_to_precision(symbol, amount) ' + body)
|
|
if response is None:
|
|
return None # fallback to default error handler
|
|
# check success value for wapi endpoints
|
|
# response in format {'msg': 'The coin does not exist.', 'success': True/false}
|
|
success = self.safe_bool(response, 'success', True)
|
|
if not success:
|
|
messageInner = self.safe_string(response, 'msg')
|
|
parsedMessage = None
|
|
if messageInner is not None:
|
|
try:
|
|
parsedMessage = json.loads(messageInner)
|
|
except Exception as e:
|
|
# do nothing
|
|
parsedMessage = None
|
|
if parsedMessage is not None:
|
|
response = parsedMessage
|
|
message = self.safe_string(response, 'msg')
|
|
if message is not None:
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, self.id + ' ' + message)
|
|
# checks against error codes
|
|
error = self.safe_string(response, 'code')
|
|
if error is not None:
|
|
# https://github.com/ccxt/ccxt/issues/6501
|
|
# https://github.com/ccxt/ccxt/issues/7742
|
|
if (error == '200') or Precise.string_equals(error, '0'):
|
|
return None
|
|
# a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
|
|
# despite that their message is very confusing, it is raised by Binance
|
|
# on a temporary ban, the API key is valid, but disabled for a while
|
|
if (error == '-2015') and self.options['hasAlreadyAuthenticatedSuccessfully']:
|
|
raise DDoSProtection(self.id + ' ' + body)
|
|
feedback = self.id + ' ' + body
|
|
if message == 'No need to change margin type.':
|
|
# not an error
|
|
# https://github.com/ccxt/ccxt/issues/11268
|
|
# https://github.com/ccxt/ccxt/pull/11624
|
|
# POST https://fapi.binance.com/fapi/v1/marginType 400 Bad Request
|
|
# binanceusdm {"code":-4046,"msg":"No need to change margin type."}
|
|
raise MarginModeAlreadySet(feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
|
|
raise ExchangeError(feedback)
|
|
if not success:
|
|
raise ExchangeError(self.id + ' ' + body)
|
|
return None
|
|
|
|
def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
|
|
if ('noCoin' in config) and not ('coin' in params):
|
|
return config['noCoin']
|
|
elif ('noSymbol' in config) and not ('symbol' in params):
|
|
return config['noSymbol']
|
|
elif ('noPoolId' in config) and not ('poolId' in params):
|
|
return config['noPoolId']
|
|
elif ('byLimit' in config) and ('limit' in params):
|
|
limit = params['limit']
|
|
byLimit = config['byLimit']
|
|
for i in range(0, len(byLimit)):
|
|
entry = byLimit[i]
|
|
if limit <= entry[0]:
|
|
return entry[1]
|
|
return self.safe_integer(config, 'cost', 1)
|