3013 lines
124 KiB
Python
3013 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.base.exchange import Exchange
|
||
from ccxt.abstract.lbank import ImplicitAPI
|
||
import hashlib
|
||
from ccxt.base.types import Any, Balances, Currencies, Currency, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, TradingFees, 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 ArgumentsRequired
|
||
from ccxt.base.errors import BadRequest
|
||
from ccxt.base.errors import BadSymbol
|
||
from ccxt.base.errors import InsufficientFunds
|
||
from ccxt.base.errors import InvalidAddress
|
||
from ccxt.base.errors import InvalidOrder
|
||
from ccxt.base.errors import DuplicateOrderId
|
||
from ccxt.base.errors import NotSupported
|
||
from ccxt.base.errors import RateLimitExceeded
|
||
from ccxt.base.errors import InvalidNonce
|
||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
||
from ccxt.base.precise import Precise
|
||
|
||
|
||
class lbank(Exchange, ImplicitAPI):
|
||
|
||
def describe(self) -> Any:
|
||
return self.deep_extend(super(lbank, self).describe(), {
|
||
'id': 'lbank',
|
||
'name': 'LBank',
|
||
'countries': ['CN'],
|
||
'version': 'v2',
|
||
# 50 per second for making and cancelling orders 1000ms / 50 = 20
|
||
# 20 per second for all other requests, cost = 50 / 20 = 2.5
|
||
'rateLimit': 20,
|
||
'pro': True,
|
||
'has': {
|
||
'CORS': False,
|
||
'spot': True,
|
||
'margin': False,
|
||
'swap': None,
|
||
'future': False,
|
||
'option': False,
|
||
'addMargin': False,
|
||
'cancelAllOrders': True,
|
||
'cancelOrder': True,
|
||
'createMarketBuyOrderWithCost': True,
|
||
'createMarketOrderWithCost': False,
|
||
'createMarketSellOrderWithCost': False,
|
||
'createOrder': True,
|
||
'createReduceOnlyOrder': False,
|
||
'createStopLimitOrder': False,
|
||
'createStopMarketOrder': False,
|
||
'createStopOrder': False,
|
||
'fetchBalance': True,
|
||
'fetchBorrowRateHistories': False,
|
||
'fetchBorrowRateHistory': False,
|
||
'fetchClosedOrders': False,
|
||
'fetchCrossBorrowRate': False,
|
||
'fetchCrossBorrowRates': False,
|
||
'fetchCurrencies': True,
|
||
'fetchDepositAddress': True,
|
||
'fetchDepositAddresses': False,
|
||
'fetchDepositAddressesByNetwork': False,
|
||
'fetchDepositWithdrawFee': 'emulated',
|
||
'fetchDepositWithdrawFees': True,
|
||
'fetchFundingHistory': False,
|
||
'fetchFundingRate': False,
|
||
'fetchFundingRateHistory': False,
|
||
'fetchFundingRates': True,
|
||
'fetchIndexOHLCV': False,
|
||
'fetchIsolatedBorrowRate': False,
|
||
'fetchIsolatedBorrowRates': False,
|
||
'fetchIsolatedPositions': False,
|
||
'fetchLeverage': False,
|
||
'fetchLeverageTiers': False,
|
||
'fetchMarginMode': False,
|
||
'fetchMarkets': True,
|
||
'fetchMarkOHLCV': False,
|
||
'fetchMyTrades': True,
|
||
'fetchOHLCV': True,
|
||
'fetchOpenOrders': True,
|
||
'fetchOrder': True,
|
||
'fetchOrderBook': True,
|
||
'fetchOrders': True,
|
||
'fetchPosition': False,
|
||
'fetchPositionMode': False,
|
||
'fetchPositions': False,
|
||
'fetchPositionsRisk': False,
|
||
'fetchPremiumIndexOHLCV': False,
|
||
'fetchTicker': True,
|
||
'fetchTickers': True,
|
||
'fetchTime': True,
|
||
'fetchTrades': True,
|
||
'fetchTradingFees': True,
|
||
'fetchTransactionFees': True,
|
||
'reduceMargin': False,
|
||
'setLeverage': False,
|
||
'setMarginMode': False,
|
||
'setPositionMode': False,
|
||
'withdraw': True,
|
||
},
|
||
'timeframes': {
|
||
'1m': 'minute1',
|
||
'5m': 'minute5',
|
||
'15m': 'minute15',
|
||
'30m': 'minute30',
|
||
'1h': 'hour1',
|
||
'2h': 'hour2',
|
||
'4h': 'hour4',
|
||
'6h': 'hour6',
|
||
'8h': 'hour8',
|
||
'12h': 'hour12',
|
||
'1d': 'day1',
|
||
'1w': 'week1',
|
||
},
|
||
'urls': {
|
||
'logo': 'https://user-images.githubusercontent.com/1294454/38063602-9605e28a-3302-11e8-81be-64b1e53c4cfb.jpg',
|
||
'api': {
|
||
'rest': 'https://api.lbank.info',
|
||
'contract': 'https://lbkperp.lbank.com',
|
||
},
|
||
'api2': 'https://api.lbkex.com',
|
||
'www': 'https://www.lbank.com',
|
||
'doc': 'https://www.lbank.com/en-US/docs/index.html',
|
||
'fees': 'https://support.lbank.site/hc/en-gb/articles/900000535703-Trading-Fees-From-14-00-on-April-7-2020-UTC-8-',
|
||
'referral': 'https://www.lbank.com/login/?icode=7QCY',
|
||
},
|
||
'api': {
|
||
'spot': {
|
||
'public': {
|
||
'get': {
|
||
'currencyPairs': 2.5,
|
||
'accuracy': 2.5,
|
||
'usdToCny': 2.5,
|
||
'assetConfigs': 2.5,
|
||
'withdrawConfigs': 2.5 * 1.5, # frequently rate-limits, so increase self endpoint RL
|
||
'timestamp': 2.5,
|
||
'ticker/24hr': 2.5,
|
||
'ticker': 2.5,
|
||
'depth': 2.5,
|
||
'incrDepth': 2.5,
|
||
'trades': 2.5,
|
||
'kline': 2.5,
|
||
# new quote endpoints
|
||
'supplement/system_ping': 2.5,
|
||
'supplement/incrDepth': 2.5,
|
||
'supplement/trades': 2.5,
|
||
'supplement/ticker/price': 2.5,
|
||
'supplement/ticker/bookTicker': 2.5,
|
||
},
|
||
'post': {
|
||
'supplement/system_status': 2.5,
|
||
},
|
||
},
|
||
'private': {
|
||
'post': {
|
||
# account
|
||
'user_info': 2.5,
|
||
'subscribe/get_key': 2.5,
|
||
'subscribe/refresh_key': 2.5,
|
||
'subscribe/destroy_key': 2.5,
|
||
'get_deposit_address': 2.5,
|
||
'deposit_history': 2.5,
|
||
# order
|
||
'create_order': 1,
|
||
'batch_create_order': 1,
|
||
'cancel_order': 1,
|
||
'cancel_clientOrders': 1,
|
||
'orders_info': 2.5,
|
||
'orders_info_history': 2.5,
|
||
'order_transaction_detail': 2.5,
|
||
'transaction_history': 2.5,
|
||
'orders_info_no_deal': 2.5,
|
||
# withdraw
|
||
'withdraw': 2.5,
|
||
'withdrawCancel': 2.5,
|
||
'withdraws': 2.5,
|
||
'supplement/user_info': 2.5,
|
||
'supplement/withdraw': 2.5,
|
||
'supplement/deposit_history': 2.5,
|
||
'supplement/withdraws': 2.5,
|
||
'supplement/get_deposit_address': 2.5,
|
||
'supplement/asset_detail': 2.5,
|
||
'supplement/customer_trade_fee': 2.5,
|
||
'supplement/api_Restrictions': 2.5,
|
||
# new quote endpoints
|
||
'supplement/system_ping': 2.5,
|
||
# new order endpoints
|
||
'supplement/create_order_test': 1,
|
||
'supplement/create_order': 1,
|
||
'supplement/cancel_order': 1,
|
||
'supplement/cancel_order_by_symbol': 1,
|
||
'supplement/orders_info': 2.5,
|
||
'supplement/orders_info_no_deal': 2.5,
|
||
'supplement/orders_info_history': 2.5,
|
||
'supplement/user_info_account': 2.5,
|
||
'supplement/transaction_history': 2.5,
|
||
},
|
||
},
|
||
},
|
||
'contract': {
|
||
'public': {
|
||
'get': {
|
||
'cfd/openApi/v1/pub/getTime': 2.5,
|
||
'cfd/openApi/v1/pub/instrument': 2.5,
|
||
'cfd/openApi/v1/pub/marketData': 2.5,
|
||
'cfd/openApi/v1/pub/marketOrder': 2.5,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'fees': {
|
||
'trading': {
|
||
'maker': self.parse_number('0.001'),
|
||
'taker': self.parse_number('0.001'),
|
||
},
|
||
'funding': {
|
||
'withdraw': {},
|
||
},
|
||
},
|
||
'commonCurrencies': {
|
||
'XBT': 'XBT', # not BTC!
|
||
'HIT': 'Hiver',
|
||
'VET_ERC20': 'VEN',
|
||
'PNT': 'Penta',
|
||
},
|
||
'precisionMode': TICK_SIZE,
|
||
'options': {
|
||
'cacheSecretAsPem': True,
|
||
'createMarketBuyOrderRequiresPrice': True,
|
||
'fetchTrades': {
|
||
'method': 'spotPublicGetTrades', # or 'spotPublicGetTradesSupplement'
|
||
},
|
||
'fetchTransactionFees': { # DEPRECATED, please use fetchDepositWithdrawFees
|
||
'method': 'fetchPrivateTransactionFees', # or 'fetchPublicTransactionFees'
|
||
},
|
||
'fetchDepositWithdrawFees': {
|
||
'method': 'fetchPrivateDepositWithdrawFees', # or 'fetchPublicDepositWithdrawFees'
|
||
},
|
||
'fetchDepositAddress': {
|
||
'method': 'fetchDepositAddressDefault', # or fetchDepositAddressSupplement
|
||
},
|
||
'createOrder': {
|
||
'method': 'spotPrivatePostSupplementCreateOrder', # or spotPrivatePostCreateOrder
|
||
},
|
||
'fetchOrder': {
|
||
'method': 'fetchOrderSupplement', # or fetchOrderDefault
|
||
},
|
||
'fetchBalance': {
|
||
'method': 'spotPrivatePostSupplementUserInfo', # or spotPrivatePostSupplementUserInfoAccount or spotPrivatePostUserInfo
|
||
},
|
||
'networks': {
|
||
'ERC20': 'erc20',
|
||
'ETH': 'erc20',
|
||
'TRC20': 'trc20',
|
||
'TRX': 'trc20',
|
||
'OMNI': 'omni',
|
||
'ASA': 'asa',
|
||
'BEP20': 'bep20(bsc)',
|
||
'BSC': 'bep20(bsc)',
|
||
'HT': 'heco',
|
||
'BNB': 'bep2',
|
||
'BTC': 'btc',
|
||
'DOGE': 'dogecoin',
|
||
'MATIC': 'matic',
|
||
'POLYGON': 'matic',
|
||
'OEC': 'oec',
|
||
'BTCTRON': 'btctron',
|
||
'XRP': 'xrp',
|
||
# other unusual chains with number of listed currencies supported
|
||
# 'avax c-chain': 1,
|
||
# klay: 12,
|
||
# bta: 1,
|
||
# fantom: 1,
|
||
# celo: 1,
|
||
# sol: 2,
|
||
# zenith: 1,
|
||
# ftm: 5,
|
||
# bep20: 1,(single token with mis-named chain) SSS
|
||
# bitci: 1,
|
||
# sgb: 1,
|
||
# moonbeam: 1,
|
||
# ekta: 1,
|
||
# etl: 1,
|
||
# arbitrum: 1,
|
||
# tpc: 1,
|
||
# ptx: 1
|
||
# }
|
||
},
|
||
'networksById': {
|
||
'erc20': 'ERC20',
|
||
'trc20': 'TRC20',
|
||
'TRX': 'TRC20',
|
||
'bep20(bsc)': 'BEP20',
|
||
'bep20': 'BEP20',
|
||
},
|
||
'defaultNetworks': {
|
||
'USDT': 'TRC20',
|
||
},
|
||
},
|
||
'features': {
|
||
'default': {
|
||
'sandbox': False,
|
||
'createOrder': {
|
||
'marginMode': False,
|
||
'triggerPrice': False,
|
||
'triggerPriceType': None,
|
||
'triggerDirection': False,
|
||
'stopLossPrice': False,
|
||
'takeProfitPrice': False,
|
||
'attachedStopLossTakeProfit': None,
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': False,
|
||
'GTD': False,
|
||
},
|
||
'hedged': False,
|
||
'selfTradePrevention': False,
|
||
'trailing': False,
|
||
'leverage': False,
|
||
'marketBuyByCost': True,
|
||
'marketBuyRequiresPrice': False,
|
||
'iceberg': False,
|
||
},
|
||
'createOrders': None, # todo
|
||
'fetchMyTrades': {
|
||
'marginMode': False,
|
||
'limit': 100,
|
||
'daysBack': 100000, # todo
|
||
'untilDays': 2,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': False,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': False,
|
||
'limit': 200,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOrders': {
|
||
'marginMode': False,
|
||
'limit': 200,
|
||
'daysBack': None,
|
||
'untilDays': None,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchClosedOrders': None, # todo: through fetchOrders "status" -1: Cancelled 0: Unfilled 1: Partially filled 2: Completely filled 3: Partially filled has been cancelled 4: Cancellation is being processed
|
||
'fetchOHLCV': {
|
||
'limit': 2000,
|
||
},
|
||
},
|
||
'spot': {
|
||
'extends': 'default',
|
||
},
|
||
'swap': {
|
||
'linear': {
|
||
'extends': 'default',
|
||
},
|
||
'inverse': None,
|
||
},
|
||
'future': {
|
||
'linear': None,
|
||
'inverse': None,
|
||
},
|
||
},
|
||
})
|
||
|
||
def fetch_time(self, params={}) -> Int:
|
||
"""
|
||
fetches the current integer timestamp in milliseconds from the exchange server
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#get-timestamp
|
||
https://www.lbank.com/en-US/docs/contract.html#get-the-current-time
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns int: the current integer timestamp in milliseconds from the exchange server
|
||
"""
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchTime', None, params)
|
||
response = None
|
||
if type == 'swap':
|
||
response = self.contractPublicGetCfdOpenApiV1PubGetTime(params)
|
||
else:
|
||
response = self.spotPublicGetTimestamp(params)
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": 1691789627950,
|
||
# "error_code": 0,
|
||
# "ts": 1691789627950
|
||
# }
|
||
#
|
||
# swap
|
||
#
|
||
# {
|
||
# "data": 1691789627950,
|
||
# "error_code": 0,
|
||
# "msg": "Success",
|
||
# "result": "true",
|
||
# "success": True
|
||
# }
|
||
#
|
||
return self.safe_integer(response, 'data')
|
||
|
||
def fetch_currencies(self, params={}) -> Currencies:
|
||
"""
|
||
fetches all available currencies on an exchange
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an associative dictionary of currencies
|
||
"""
|
||
response = self.spotPublicGetWithdrawConfigs(params)
|
||
#
|
||
# {
|
||
# "msg": "Success",
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "amountScale": "4",
|
||
# "chain": "bep20(bsc)",
|
||
# "assetCode": "usdt",
|
||
# "min": "10",
|
||
# "transferAmtScale": "4",
|
||
# "canWithDraw": True,
|
||
# "fee": "0.0000",
|
||
# "minTransfer": "0.0001",
|
||
# "type": "1"
|
||
# },
|
||
# {
|
||
# "amountScale": "4",
|
||
# "chain": "trc20",
|
||
# "assetCode": "usdt",
|
||
# "min": "1",
|
||
# "transferAmtScale": "4",
|
||
# "canWithDraw": True,
|
||
# "fee": "1.0000",
|
||
# "minTransfer": "0.0001",
|
||
# "type": "1"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "error_code": "0",
|
||
# "ts": "1747973911431"
|
||
# }
|
||
#
|
||
currenciesData = self.safe_list(response, 'data', [])
|
||
grouped = self.group_by(currenciesData, 'assetCode')
|
||
groupedKeys = list(grouped.keys())
|
||
result: dict = {}
|
||
for i in range(0, len(groupedKeys)):
|
||
id = str((groupedKeys[i])) # some currencies are numeric
|
||
code = self.safe_currency_code(id)
|
||
networksRaw = grouped[id]
|
||
networks = {}
|
||
for j in range(0, len(networksRaw)):
|
||
networkEntry = networksRaw[j]
|
||
networkId = self.safe_string(networkEntry, 'chain')
|
||
if networkId is None:
|
||
networkId = self.safe_string(networkEntry, 'assetCode') # use type if networkId is not present
|
||
networkCode = self.network_id_to_code(networkId)
|
||
networks[networkCode] = {
|
||
'id': networkId,
|
||
'network': networkCode,
|
||
'limits': {
|
||
'withdraw': {
|
||
'min': self.safe_number(networkEntry, 'min'),
|
||
'max': None,
|
||
},
|
||
'deposit': {
|
||
'min': self.safe_number(networkEntry, 'minTransfer'),
|
||
'max': None,
|
||
},
|
||
},
|
||
'active': None,
|
||
'deposit': None,
|
||
'withdraw': self.safe_bool(networkEntry, 'canWithDraw'),
|
||
'fee': self.safe_number(networkEntry, 'fee'),
|
||
'precision': self.parse_number(self.parse_precision(self.safe_string(networkEntry, 'transferAmtScale'))),
|
||
'info': networkEntry,
|
||
}
|
||
result[code] = self.safe_currency_structure({
|
||
'id': id,
|
||
'code': code,
|
||
'precision': None,
|
||
'type': None,
|
||
'name': None,
|
||
'active': None,
|
||
'deposit': None,
|
||
'withdraw': None,
|
||
'fee': None,
|
||
'limits': {
|
||
'withdraw': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'deposit': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'networks': networks,
|
||
'info': networksRaw,
|
||
})
|
||
return result
|
||
|
||
def fetch_markets(self, params={}) -> List[Market]:
|
||
"""
|
||
retrieves data on all markets for lbank
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#trading-pairs
|
||
https://www.lbank.com/en-US/docs/contract.html#query-contract-information-list
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: an array of objects representing market data
|
||
"""
|
||
marketsPromises = [
|
||
self.fetch_spot_markets(params),
|
||
self.fetch_swap_markets(params),
|
||
]
|
||
resolvedMarkets = marketsPromises
|
||
return self.array_concat(resolvedMarkets[0], resolvedMarkets[1])
|
||
|
||
def fetch_spot_markets(self, params={}):
|
||
response = self.spotPublicGetAccuracy(params)
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "symbol": "btc_usdt",
|
||
# "quantityAccuracy": "4",
|
||
# "minTranQua": "0.0001",
|
||
# "priceAccuracy": "2"
|
||
# },
|
||
# ],
|
||
# "error_code": 0,
|
||
# "ts": 1691560288484
|
||
# }
|
||
#
|
||
data = self.safe_value(response, 'data', [])
|
||
result = []
|
||
for i in range(0, len(data)):
|
||
market = data[i]
|
||
marketId = self.safe_string(market, 'symbol')
|
||
parts = marketId.split('_')
|
||
baseId = parts[0]
|
||
quoteId = parts[1]
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
symbol = base + '/' + quote
|
||
result.append({
|
||
'id': marketId,
|
||
'symbol': symbol,
|
||
'base': base,
|
||
'quote': quote,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settle': None,
|
||
'settleId': None,
|
||
'type': 'spot',
|
||
'spot': True,
|
||
'margin': False,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': False,
|
||
'active': True,
|
||
'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, 'quantityAccuracy'))),
|
||
'price': self.parse_number(self.parse_precision(self.safe_string(market, 'priceAccuracy'))),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(market, 'minTranQua'),
|
||
'max': None,
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': None,
|
||
'info': market,
|
||
})
|
||
return result
|
||
|
||
def fetch_swap_markets(self, params={}):
|
||
request: dict = {
|
||
'productGroup': 'SwapU',
|
||
}
|
||
response = self.contractPublicGetCfdOpenApiV1PubInstrument(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "data": [
|
||
# {
|
||
# "priceLimitUpperValue": 0.2,
|
||
# "symbol": "BTCUSDT",
|
||
# "volumeTick": 0.0001,
|
||
# "indexPrice": "29707.70200000",
|
||
# "minOrderVolume": "0.0001",
|
||
# "priceTick": 0.1,
|
||
# "maxOrderVolume": "30.0",
|
||
# "baseCurrency": "BTC",
|
||
# "volumeMultiple": 1.0,
|
||
# "exchangeID": "Exchange",
|
||
# "priceCurrency": "USDT",
|
||
# "priceLimitLowerValue": 0.2,
|
||
# "clearCurrency": "USDT",
|
||
# "symbolName": "BTCUSDT",
|
||
# "defaultLeverage": 20.0,
|
||
# "minOrderCost": "5.0"
|
||
# },
|
||
# ],
|
||
# "error_code": 0,
|
||
# "msg": "Success",
|
||
# "result": "true",
|
||
# "success": True
|
||
# }
|
||
#
|
||
data = self.safe_value(response, 'data', [])
|
||
result = []
|
||
for i in range(0, len(data)):
|
||
market = data[i]
|
||
marketId = self.safe_string(market, 'symbol')
|
||
baseId = self.safe_string(market, 'baseCurrency')
|
||
settleId = self.safe_string(market, 'clearCurrency')
|
||
quoteId = settleId
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
settle = self.safe_currency_code(settleId)
|
||
symbol = base + '/' + quote + ':' + settle
|
||
result.append({
|
||
'id': marketId,
|
||
'symbol': symbol,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': settle,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': settleId,
|
||
'type': 'swap',
|
||
'spot': False,
|
||
'margin': False,
|
||
'swap': True,
|
||
'future': False,
|
||
'option': False,
|
||
'active': True,
|
||
'contract': True,
|
||
'linear': True,
|
||
'inverse': False,
|
||
'contractSize': self.safe_number(market, 'volumeMultiple'),
|
||
'expiry': None,
|
||
'expiryDatetime': None,
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'amount': self.safe_number(market, 'volumeTick'),
|
||
'price': self.safe_number(market, 'priceTick'),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(market, 'minOrderVolume'),
|
||
'max': self.safe_number(market, 'maxOrderVolume'),
|
||
},
|
||
'price': {
|
||
'min': self.safe_number(market, 'priceLimitLowerValue'),
|
||
'max': self.safe_number(market, 'priceLimitUpperValue'),
|
||
},
|
||
'cost': {
|
||
'min': self.safe_number(market, 'minOrderCost'),
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': None,
|
||
'info': market,
|
||
})
|
||
return result
|
||
|
||
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
||
#
|
||
# spot: fetchTicker, fetchTickers
|
||
#
|
||
# {
|
||
# "symbol": "btc_usdt",
|
||
# "ticker": {
|
||
# "high": "29695.57",
|
||
# "vol": "6890.2789",
|
||
# "low": "29110",
|
||
# "change": "0.58",
|
||
# "turnover": "202769821.06",
|
||
# "latest": "29405.98"
|
||
# },
|
||
# "timestamp": :1692064274908
|
||
# }
|
||
#
|
||
# swap: fetchTickers
|
||
#
|
||
# {
|
||
# "prePositionFeeRate": "0.000053",
|
||
# "volume": "2435.459",
|
||
# "symbol": "BTCUSDT",
|
||
# "highestPrice": "29446.5",
|
||
# "lowestPrice": "29362.9",
|
||
# "openPrice": "29419.5",
|
||
# "markedPrice": "29385.1",
|
||
# "turnover": "36345526.2438402",
|
||
# "lastPrice": "29387.0"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(ticker, 'timestamp')
|
||
marketId = self.safe_string(ticker, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market)
|
||
tickerData = self.safe_value(ticker, 'ticker', {})
|
||
market = self.safe_market(marketId, market)
|
||
data = ticker if (market['contract']) else tickerData
|
||
return self.safe_ticker({
|
||
'symbol': symbol,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'high': self.safe_string_2(data, 'high', 'highestPrice'),
|
||
'low': self.safe_string_2(data, 'low', 'lowestPrice'),
|
||
'bid': None,
|
||
'bidVolume': None,
|
||
'ask': None,
|
||
'askVolume': None,
|
||
'vwap': None,
|
||
'open': self.safe_string(data, 'openPrice'),
|
||
'close': None,
|
||
'last': self.safe_string_2(data, 'latest', 'lastPrice'),
|
||
'previousClose': None,
|
||
'change': None,
|
||
'percentage': self.safe_string(data, 'change'),
|
||
'average': None,
|
||
'baseVolume': self.safe_string_2(data, 'vol', 'volume'),
|
||
'quoteVolume': self.safe_string(data, 'turnover'),
|
||
'info': ticker,
|
||
}, market)
|
||
|
||
def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
||
"""
|
||
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#query-current-market-data-new
|
||
|
||
:param str symbol: unified symbol of the market to fetch the ticker for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['swap']:
|
||
responseForSwap = self.fetch_tickers([market['symbol']], params)
|
||
return self.safe_value(responseForSwap, market['symbol'])
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = self.spotPublicGetTicker24hr(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "symbol": "btc_usdt",
|
||
# "ticker": {
|
||
# "high": "29695.57",
|
||
# "vol": "6890.2789",
|
||
# "low": "29110",
|
||
# "change": "0.58",
|
||
# "turnover": "202769821.06",
|
||
# "latest": "29405.98"
|
||
# },
|
||
# "timestamp": :1692064274908
|
||
# }
|
||
# ],
|
||
# "error_code": 0,
|
||
# "ts": :1692064276872
|
||
# }
|
||
#
|
||
data = self.safe_value(response, 'data', [])
|
||
first = self.safe_dict(data, 0, {})
|
||
return self.parse_ticker(first, market)
|
||
|
||
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
||
"""
|
||
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#query-current-market-data-new
|
||
https://www.lbank.com/en-US/docs/contract.html#query-contract-market-list
|
||
|
||
: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>`
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
if symbols is not None:
|
||
symbols = self.market_symbols(symbols)
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength > 0:
|
||
market = self.market(symbols[0])
|
||
request: dict = {}
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchTickers', market, params)
|
||
response = None
|
||
if type == 'swap':
|
||
request['productGroup'] = 'SwapU'
|
||
response = self.contractPublicGetCfdOpenApiV1PubMarketData(self.extend(request, params))
|
||
else:
|
||
request['symbol'] = 'all'
|
||
response = self.spotPublicGetTicker24hr(self.extend(request, params))
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "symbol": "btc_usdt",
|
||
# "ticker": {
|
||
# "high": "29695.57",
|
||
# "vol": "6890.2789",
|
||
# "low": "29110",
|
||
# "change": "0.58",
|
||
# "turnover": "202769821.06",
|
||
# "latest": "29405.98"
|
||
# },
|
||
# "timestamp": :1692064274908
|
||
# }
|
||
# ],
|
||
# "error_code": 0,
|
||
# "ts": :1692064276872
|
||
# }
|
||
#
|
||
# swap
|
||
#
|
||
# {
|
||
# "data": [
|
||
# {
|
||
# "prePositionFeeRate": "0.000053",
|
||
# "volume": "2435.459",
|
||
# "symbol": "BTCUSDT",
|
||
# "highestPrice": "29446.5",
|
||
# "lowestPrice": "29362.9",
|
||
# "openPrice": "29419.5",
|
||
# "markedPrice": "29385.1",
|
||
# "turnover": "36345526.2438402",
|
||
# "lastPrice": "29387.0"
|
||
# },
|
||
# ],
|
||
# "error_code": 0,
|
||
# "msg": "Success",
|
||
# "result": "true",
|
||
# "success": True
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_tickers(data, symbols)
|
||
|
||
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
||
"""
|
||
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#query-market-depth
|
||
https://www.lbank.com/en-US/docs/contract.html#get-handicap
|
||
|
||
:param str symbol: unified symbol of the market to fetch the order book for
|
||
:param int [limit]: the maximum amount of order book entries to return
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if limit is None:
|
||
limit = 60
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchOrderBook', market, params)
|
||
response = None
|
||
if type == 'swap':
|
||
request['depth'] = limit
|
||
response = self.contractPublicGetCfdOpenApiV1PubMarketOrder(self.extend(request, params))
|
||
else:
|
||
request['size'] = limit
|
||
response = self.spotPublicGetDepth(self.extend(request, params))
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": {
|
||
# "asks": [
|
||
# ["29243.37", "2.8783"],
|
||
# ["29243.39", "2.2842"],
|
||
# ["29243.4", "0.0337"]
|
||
# ],
|
||
# "bids": [
|
||
# ["29243.36", "1.5258"],
|
||
# ["29243.34", "0.8218"],
|
||
# ["29243.28", "1.285"]
|
||
# ],
|
||
# "timestamp": :1692157328820
|
||
# },
|
||
# "error_code": 0,
|
||
# "ts": :1692157328820
|
||
# }
|
||
#
|
||
# swap
|
||
#
|
||
# {
|
||
# "data": {
|
||
# "symbol": "BTCUSDT",
|
||
# "asks": [
|
||
# {
|
||
# "volume": "14.6535",
|
||
# "price": "29234.2",
|
||
# "orders": "1"
|
||
# },
|
||
# ],
|
||
# "bids": [
|
||
# {
|
||
# "volume": "13.4899",
|
||
# "price": "29234.1",
|
||
# "orders": "4"
|
||
# },
|
||
# ]
|
||
# },
|
||
# "error_code": 0,
|
||
# "msg": "Success",
|
||
# "result": "true",
|
||
# "success": True
|
||
# }
|
||
#
|
||
orderbook = self.safe_value(response, 'data', {})
|
||
timestamp = self.milliseconds()
|
||
if market['swap']:
|
||
return self.parse_order_book(orderbook, market['symbol'], timestamp, 'bids', 'asks', 'price', 'volume')
|
||
return self.parse_order_book(orderbook, market['symbol'], timestamp, 'bids', 'asks')
|
||
|
||
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
||
#
|
||
# fetchTrades(old) spotPublicGetTrades
|
||
#
|
||
# {
|
||
# "date_ms":1647021989789,
|
||
# "amount":0.0028,
|
||
# "price":38804.2,
|
||
# "type":"buy",
|
||
# "tid":"52d5616ee35c43019edddebe59b3e094"
|
||
# }
|
||
#
|
||
#
|
||
# fetchTrades(new) spotPublicGetTradesSupplement
|
||
#
|
||
# {
|
||
# "quoteQty":1675.048485,
|
||
# "price":0.127545,
|
||
# "qty":13133,
|
||
# "id":"3589541dc22e4357b227283650f714e2",
|
||
# "time":1648058297110,
|
||
# "isBuyerMaker":false
|
||
# }
|
||
#
|
||
# fetchMyTrades(private)
|
||
#
|
||
# {
|
||
# "orderUuid":"38b4e7a4-14f6-45fd-aba1-1a37024124a0",
|
||
# "tradeFeeRate":0.0010000000,
|
||
# "dealTime":1648500944496,
|
||
# "dealQuantity":30.00000000000000000000,
|
||
# "tradeFee":0.00453300000000000000,
|
||
# "txUuid":"11f3850cc6214ea3b495adad3a032794",
|
||
# "dealPrice":0.15111300000000000000,
|
||
# "dealVolumePrice":4.53339000000000000000,
|
||
# "tradeType":"sell_market"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer_2(trade, 'date_ms', 'time')
|
||
if timestamp is None:
|
||
timestamp = self.safe_integer(trade, 'dealTime')
|
||
amountString = self.safe_string_2(trade, 'amount', 'qty')
|
||
if amountString is None:
|
||
amountString = self.safe_string(trade, 'dealQuantity')
|
||
priceString = self.safe_string(trade, 'price')
|
||
if priceString is None:
|
||
priceString = self.safe_string(trade, 'dealPrice')
|
||
costString = self.safe_string(trade, 'quoteQty')
|
||
if costString is None:
|
||
costString = self.safe_string(trade, 'dealVolumePrice')
|
||
side = self.safe_string_2(trade, 'tradeType', 'type')
|
||
type = None
|
||
takerOrMaker = None
|
||
if side is not None:
|
||
parts = side.split('_')
|
||
side = self.safe_string(parts, 0)
|
||
typePart = self.safe_string(parts, 1)
|
||
type = 'limit'
|
||
takerOrMaker = 'taker'
|
||
if typePart is not None:
|
||
if typePart == 'market':
|
||
type = 'market'
|
||
elif typePart == 'maker':
|
||
takerOrMaker = 'maker'
|
||
id = self.safe_string_2(trade, 'tid', 'id')
|
||
if id is None:
|
||
id = self.safe_string(trade, 'txUuid')
|
||
order = self.safe_string(trade, 'orderUuid')
|
||
symbol = self.safe_symbol(None, market)
|
||
fee = None
|
||
feeCost = self.safe_string(trade, 'tradeFee')
|
||
if feeCost is not None:
|
||
fee = {
|
||
'cost': feeCost,
|
||
'currency': market['base'] if (side == 'buy') else market['quote'],
|
||
'rate': self.safe_string(trade, 'tradeFeeRate'),
|
||
}
|
||
return self.safe_trade({
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': symbol,
|
||
'id': id,
|
||
'order': order,
|
||
'type': type,
|
||
'takerOrMaker': takerOrMaker,
|
||
'side': side,
|
||
'price': priceString,
|
||
'amount': amountString,
|
||
'cost': costString,
|
||
'fee': fee,
|
||
'info': trade,
|
||
}, market)
|
||
|
||
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
||
"""
|
||
get the list of most recent trades for a particular symbol
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#query-historical-transactions
|
||
https://www.lbank.com/en-US/docs/index.html#recent-transactions-list
|
||
|
||
:param str symbol: unified symbol of the market to fetch trades for
|
||
:param int [since]: timestamp in ms of the earliest trade to fetch
|
||
:param int [limit]: the maximum amount of trades to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
if since is not None:
|
||
request['time'] = since
|
||
if limit is not None:
|
||
request['size'] = min(limit, 600)
|
||
else:
|
||
request['size'] = 600 # max
|
||
options = self.safe_value(self.options, 'fetchTrades', {})
|
||
defaultMethod = self.safe_string(options, 'method', 'spotPublicGetTrades')
|
||
method = self.safe_string(params, 'method', defaultMethod)
|
||
params = self.omit(params, 'method')
|
||
response = None
|
||
if method == 'spotPublicGetSupplementTrades':
|
||
response = self.spotPublicGetSupplementTrades(self.extend(request, params))
|
||
else:
|
||
response = self.spotPublicGetTrades(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":"true",
|
||
# "data": [
|
||
# {
|
||
# "date_ms":1647021989789,
|
||
# "amount":0.0028,
|
||
# "price":38804.2,
|
||
# "type":"buy",
|
||
# "tid":"52d5616ee35c43019edddebe59b3e094"
|
||
# }
|
||
# ],
|
||
# "error_code":0,
|
||
# "ts":1647021999308
|
||
# }
|
||
#
|
||
trades = self.safe_list(response, 'data', [])
|
||
return self.parse_trades(trades, market, since, limit)
|
||
|
||
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
||
#
|
||
# [
|
||
# 1482311500, # timestamp
|
||
# 5423.23, # open
|
||
# 5472.80, # high
|
||
# 5516.09, # low
|
||
# 5462, # close
|
||
# 234.3250 # volume
|
||
# ],
|
||
#
|
||
return [
|
||
self.safe_timestamp(ohlcv, 0), # timestamp
|
||
self.safe_number(ohlcv, 1), # open
|
||
self.safe_number(ohlcv, 2), # high
|
||
self.safe_number(ohlcv, 3), # low
|
||
self.safe_number(ohlcv, 4), # close
|
||
self.safe_number(ohlcv, 5), # volume
|
||
]
|
||
|
||
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
||
"""
|
||
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#query-k-bar-data
|
||
|
||
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
||
:param str timeframe: the length of time each candle represents
|
||
:param int [since]: timestamp in ms of the earliest candle to fetch
|
||
:param int [limit]: the maximum amount of candles to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
||
"""
|
||
# endpoint doesnt work
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if limit is None:
|
||
limit = 100
|
||
else:
|
||
limit = min(limit, 2000)
|
||
if since is None:
|
||
duration = self.parse_timeframe(timeframe)
|
||
since = self.milliseconds() - (duration * 1000 * limit)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'type': self.safe_string(self.timeframes, timeframe, timeframe),
|
||
'time': self.parse_to_int(since / 1000),
|
||
'size': min(limit + 1, 2000), # max 2000
|
||
}
|
||
response = self.spotPublicGetKline(self.extend(request, params))
|
||
ohlcvs = self.safe_list(response, 'data', [])
|
||
#
|
||
#
|
||
# [
|
||
# [
|
||
# 1482311500,
|
||
# 5423.23,
|
||
# 5472.80,
|
||
# 5516.09,
|
||
# 5462,
|
||
# 234.3250
|
||
# ],
|
||
# [
|
||
# 1482311400,
|
||
# 5432.52,
|
||
# 5459.87,
|
||
# 5414.30,
|
||
# 5428.23,
|
||
# 213.7329
|
||
# ]
|
||
# ]
|
||
#
|
||
return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
|
||
|
||
def parse_balance(self, response) -> Balances:
|
||
#
|
||
# spotPrivatePostUserInfo
|
||
#
|
||
# {
|
||
# "toBtc": {
|
||
# "egc:": "0",
|
||
# "iog": "0",
|
||
# "ksm": "0",
|
||
# },
|
||
# "freeze": {
|
||
# "egc": "0",
|
||
# "iog": "0",
|
||
# "ksm": "0" ,
|
||
# },
|
||
# "asset": {
|
||
# "egc": "0",
|
||
# "iog": "0",
|
||
# "ksm": "0",
|
||
# },
|
||
# "free": {
|
||
# "egc": "0",
|
||
# "iog": "0",
|
||
# "ksm": "0",
|
||
# }
|
||
# }
|
||
#
|
||
# spotPrivatePostSupplementUserInfoAccount
|
||
#
|
||
# {
|
||
# "balances":[
|
||
# {
|
||
# "asset":"lbk",
|
||
# "free":"0",
|
||
# "locked":"0"
|
||
# }, ...
|
||
# ]
|
||
# }
|
||
#
|
||
# spotPrivatePostSupplementUserInfo
|
||
#
|
||
# [
|
||
# {
|
||
# "usableAmt":"31.45130723",
|
||
# "assetAmt":"31.45130723",
|
||
# "networkList":[
|
||
# {
|
||
# "isDefault":true,
|
||
# "withdrawFeeRate":"",
|
||
# "name":"bep20(bsc)",
|
||
# "withdrawMin":30,
|
||
# "minLimit":0.0001,
|
||
# "minDeposit":0.0001,
|
||
# "feeAssetCode":"doge",
|
||
# "withdrawFee":"30",
|
||
# "type":1,
|
||
# "coin":"doge",
|
||
# "network":"bsc"
|
||
# },
|
||
# {
|
||
# "isDefault":false,
|
||
# "withdrawFeeRate":"",
|
||
# "name":"dogecoin",
|
||
# "withdrawMin":10,
|
||
# "minLimit":0.0001,
|
||
# "minDeposit":10,
|
||
# "feeAssetCode":"doge",
|
||
# "withdrawFee":"10",
|
||
# "type":1,
|
||
# "coin":"doge",
|
||
# "network":"dogecoin"
|
||
# }
|
||
# ],
|
||
# "freezeAmt":"0",
|
||
# "coin":"doge"
|
||
# }, ...
|
||
# ]
|
||
#
|
||
timestamp = self.safe_integer(response, 'ts')
|
||
result: dict = {
|
||
'info': response,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
data = self.safe_value(response, 'data')
|
||
# from spotPrivatePostUserInfo
|
||
toBtc = self.safe_value(data, 'toBtc')
|
||
if toBtc is not None:
|
||
used = self.safe_value(data, 'freeze', {})
|
||
free = self.safe_value(data, 'free', {})
|
||
currencies = list(free.keys())
|
||
for i in range(0, len(currencies)):
|
||
currencyId = currencies[i]
|
||
code = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
account['used'] = self.safe_string(used, currencyId)
|
||
account['free'] = self.safe_string(free, currencyId)
|
||
result[code] = account
|
||
return self.safe_balance(result)
|
||
# from spotPrivatePostSupplementUserInfoAccount
|
||
balances = self.safe_value(data, 'balances')
|
||
if balances is not None:
|
||
for i in range(0, len(balances)):
|
||
item = balances[i]
|
||
currencyId = self.safe_string(item, 'asset')
|
||
codeInner = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
account['free'] = self.safe_string(item, 'free')
|
||
account['used'] = self.safe_string(item, 'locked')
|
||
result[codeInner] = account
|
||
return self.safe_balance(result)
|
||
# from spotPrivatePostSupplementUserInfo
|
||
isArray = isinstance(data, list)
|
||
if isArray is True:
|
||
for i in range(0, len(data)):
|
||
item = data[i]
|
||
currencyId = self.safe_string(item, 'coin')
|
||
codeInner = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
account['free'] = self.safe_string(item, 'usableAmt')
|
||
account['used'] = self.safe_string(item, 'freezeAmt')
|
||
result[codeInner] = account
|
||
return self.safe_balance(result)
|
||
return None
|
||
|
||
def parse_funding_rate(self, ticker, market: Market = None) -> FundingRate:
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "highestPrice": "69495.5",
|
||
# "underlyingPrice": "68455.904",
|
||
# "lowestPrice": "68182.1",
|
||
# "openPrice": "68762.4",
|
||
# "positionFeeRate": "0.0001",
|
||
# "volume": "33534.2858",
|
||
# "markedPrice": "68434.1",
|
||
# "turnover": "1200636218.210558",
|
||
# "positionFeeTime": "28800",
|
||
# "lastPrice": "68427.3",
|
||
# "nextFeeTime": "1730736000000",
|
||
# "fundingRate": "0.0001",
|
||
# }
|
||
marketId = self.safe_string(ticker, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market)
|
||
markPrice = self.safe_number(ticker, 'markedPrice')
|
||
indexPrice = self.safe_number(ticker, 'underlyingPrice')
|
||
fundingRate = self.safe_number(ticker, 'fundingRate')
|
||
fundingTime = self.safe_integer(ticker, 'nextFeeTime')
|
||
positionFeeTime = self.safe_integer(ticker, 'positionFeeTime')
|
||
intervalString = None
|
||
if positionFeeTime is not None:
|
||
interval = self.parse_to_int(positionFeeTime / 60 / 60)
|
||
intervalString = str(interval) + 'h'
|
||
return {
|
||
'info': ticker,
|
||
'symbol': symbol,
|
||
'markPrice': markPrice,
|
||
'indexPrice': indexPrice,
|
||
'fundingRate': fundingRate,
|
||
'fundingTimestamp': fundingTime,
|
||
'fundingDatetime': self.iso8601(fundingTime),
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'nextFundingRate': None,
|
||
'nextFundingTimestamp': None,
|
||
'nextFundingDatetime': None,
|
||
'previousFundingRate': None,
|
||
'previousFundingTimestamp': None,
|
||
'previousFundingDatetime': None,
|
||
'interval': intervalString,
|
||
}
|
||
|
||
def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
|
||
"""
|
||
fetch the current funding rate
|
||
|
||
https://www.lbank.com/en-US/docs/contract.html#query-contract-market-list
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
responseForSwap = self.fetch_funding_rates([market['symbol']], params)
|
||
return self.safe_value(responseForSwap, market['symbol'])
|
||
|
||
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
|
||
"""
|
||
fetch the funding rate for multiple markets
|
||
|
||
https://www.lbank.com/en-US/docs/contract.html#query-contract-market-list
|
||
|
||
:param str[]|None symbols: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a dictionary of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rates-structure>`, indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols)
|
||
request: dict = {
|
||
'productGroup': 'SwapU',
|
||
}
|
||
response = self.contractPublicGetCfdOpenApiV1PubMarketData(self.extend(request, params))
|
||
# {
|
||
# "data": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "highestPrice": "69495.5",
|
||
# "underlyingPrice": "68455.904",
|
||
# "lowestPrice": "68182.1",
|
||
# "openPrice": "68762.4",
|
||
# "positionFeeRate": "0.0001",
|
||
# "volume": "33534.2858",
|
||
# "markedPrice": "68434.1",
|
||
# "turnover": "1200636218.210558",
|
||
# "positionFeeTime": "28800",
|
||
# "lastPrice": "68427.3",
|
||
# "nextFeeTime": "1730736000000",
|
||
# "fundingRate": "0.0001",
|
||
# }
|
||
# ],
|
||
# "error_code": "0",
|
||
# "msg": "Success",
|
||
# "result": "true",
|
||
# "success": True,
|
||
# }
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_funding_rates(data, symbols)
|
||
|
||
def fetch_balance(self, params={}) -> Balances:
|
||
"""
|
||
query for balance and get the amount of funds available for trading or funds locked in orders
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#asset-information
|
||
https://www.lbank.com/en-US/docs/index.html#account-information
|
||
https://www.lbank.com/en-US/docs/index.html#get-all-coins-information
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
||
"""
|
||
self.load_markets()
|
||
options = self.safe_value(self.options, 'fetchBalance', {})
|
||
defaultMethod = self.safe_string(options, 'method', 'spotPrivatePostSupplementUserInfo')
|
||
method = self.safe_string(params, 'method', defaultMethod)
|
||
response = None
|
||
if method == 'spotPrivatePostSupplementUserInfoAccount':
|
||
response = self.spotPrivatePostSupplementUserInfoAccount()
|
||
elif method == 'spotPrivatePostUserInfo':
|
||
response = self.spotPrivatePostUserInfo()
|
||
else:
|
||
response = self.spotPrivatePostSupplementUserInfo()
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "usableAmt": "14.36",
|
||
# "assetAmt": "14.36",
|
||
# "networkList": [
|
||
# {
|
||
# "isDefault": False,
|
||
# "withdrawFeeRate": "",
|
||
# "name": "erc20",
|
||
# "withdrawMin": 30,
|
||
# "minLimit": 0.0001,
|
||
# "minDeposit": 20,
|
||
# "feeAssetCode": "usdt",
|
||
# "withdrawFee": "30",
|
||
# "type": 1,
|
||
# "coin": "usdt",
|
||
# "network": "eth"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "freezeAmt": "0",
|
||
# "coin": "ada"
|
||
# }
|
||
# ],
|
||
# "code": 0
|
||
# }
|
||
#
|
||
return self.parse_balance(response)
|
||
|
||
def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
|
||
#
|
||
# {
|
||
# "symbol":"skt_usdt",
|
||
# "makerCommission":"0.10",
|
||
# "takerCommission":"0.10"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(fee, 'symbol')
|
||
symbol = self.safe_symbol(marketId)
|
||
return {
|
||
'info': fee,
|
||
'symbol': symbol,
|
||
'maker': self.safe_number(fee, 'makerCommission'),
|
||
'taker': self.safe_number(fee, 'takerCommission'),
|
||
'percentage': None,
|
||
'tierBased': None,
|
||
}
|
||
|
||
def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
||
"""
|
||
fetch the trading fees for a market
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#transaction-fee-rate-query
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
market = self.market(symbol)
|
||
result = self.fetch_trading_fees(self.extend(params, {'category': market['id']}))
|
||
return self.safe_dict(result, symbol)
|
||
|
||
def fetch_trading_fees(self, params={}) -> TradingFees:
|
||
"""
|
||
fetch the trading fees for multiple markets
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#transaction-fee-rate-query
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
response = self.spotPrivatePostSupplementCustomerTradeFee(self.extend(request, params))
|
||
fees = self.safe_value(response, 'data', [])
|
||
result: dict = {}
|
||
for i in range(0, len(fees)):
|
||
fee = self.parse_trading_fee(fees[i])
|
||
symbol = fee['symbol']
|
||
result[symbol] = fee
|
||
return result
|
||
|
||
def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
|
||
"""
|
||
create a market buy order by providing the symbol and cost
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#place-order
|
||
https://www.lbank.com/en-US/docs/index.html#place-an-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only')
|
||
params['createMarketBuyOrderRequiresPrice'] = False
|
||
return self.create_order(symbol, 'market', 'buy', cost, None, params)
|
||
|
||
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
create a trade order
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#place-order
|
||
https://www.lbank.com/en-US/docs/index.html#place-an-order
|
||
|
||
: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
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
clientOrderId = self.safe_string_2(params, 'custom_id', 'clientOrderId')
|
||
postOnly = self.safe_bool(params, 'postOnly', False)
|
||
timeInForce = self.safe_string_upper(params, 'timeInForce')
|
||
params = self.omit(params, ['custom_id', 'clientOrderId', 'timeInForce', 'postOnly'])
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
ioc = (timeInForce == 'IOC')
|
||
fok = (timeInForce == 'FOK')
|
||
maker = (postOnly or (timeInForce == 'PO'))
|
||
if (type == 'market') and (ioc or fok or maker):
|
||
raise InvalidOrder(self.id + ' createOrder() does not allow market FOK, IOC, or postOnly orders. Only limit IOC, FOK, and postOnly orders are allowed')
|
||
if type == 'limit':
|
||
request['type'] = side
|
||
request['price'] = self.price_to_precision(symbol, price)
|
||
request['amount'] = self.amount_to_precision(symbol, amount)
|
||
if ioc:
|
||
request['type'] = side + '_' + 'ioc'
|
||
elif fok:
|
||
request['type'] = side + '_' + 'fok'
|
||
elif maker:
|
||
request['type'] = side + '_' + 'maker'
|
||
elif type == 'market':
|
||
if side == 'sell':
|
||
request['type'] = side + '_' + 'market'
|
||
request['amount'] = self.amount_to_precision(symbol, amount)
|
||
elif side == 'buy':
|
||
request['type'] = side + '_' + 'market'
|
||
quoteAmount = None
|
||
createMarketBuyOrderRequiresPrice = True
|
||
createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
|
||
cost = self.safe_number(params, 'cost')
|
||
params = self.omit(params, 'cost')
|
||
if cost is not None:
|
||
quoteAmount = self.cost_to_precision(symbol, cost)
|
||
elif createMarketBuyOrderRequiresPrice:
|
||
if price is None:
|
||
raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend in the amount argument')
|
||
else:
|
||
amountString = self.number_to_string(amount)
|
||
priceString = self.number_to_string(price)
|
||
costRequest = Precise.string_mul(amountString, priceString)
|
||
quoteAmount = self.cost_to_precision(symbol, costRequest)
|
||
else:
|
||
quoteAmount = self.cost_to_precision(symbol, amount)
|
||
# market buys require filling the price param instead of the amount param, for market buys the price is treated cost by lbank
|
||
request['price'] = quoteAmount
|
||
if clientOrderId is not None:
|
||
request['custom_id'] = clientOrderId
|
||
options = self.safe_value(self.options, 'createOrder', {})
|
||
defaultMethod = self.safe_string(options, 'method', 'spotPrivatePostSupplementCreateOrder')
|
||
method = self.safe_string(params, 'method', defaultMethod)
|
||
params = self.omit(params, 'method')
|
||
response = None
|
||
if method == 'spotPrivatePostCreateOrder':
|
||
response = self.spotPrivatePostCreateOrder(self.extend(request, params))
|
||
else:
|
||
response = self.spotPrivatePostSupplementCreateOrder(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":{
|
||
# "symbol":"doge_usdt",
|
||
# "order_id":"0cf8a3de-4597-4296-af45-be7abaa06b07"
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648162321043
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data', {})
|
||
return self.safe_order({
|
||
'id': self.safe_string(result, 'order_id'),
|
||
'info': result,
|
||
}, market)
|
||
|
||
def parse_order_status(self, status: Str):
|
||
statuses: dict = {
|
||
'-1': 'canceled', # canceled
|
||
'0': 'open', # not traded
|
||
'1': 'open', # partial deal
|
||
'2': 'closed', # complete deal
|
||
'3': 'canceled', # filled partially and cancelled
|
||
'4': 'closed', # disposal processing
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_order(self, order: dict, market: Market = None) -> Order:
|
||
#
|
||
# fetchOrderSupplement(private)
|
||
#
|
||
# {
|
||
# "cummulativeQuoteQty":0,
|
||
# "symbol":"doge_usdt",
|
||
# "executedQty":0,
|
||
# "orderId":"53d2d53e-70fb-4398-b722-f48571a5f61e",
|
||
# "origQty":1E+2,
|
||
# "price":0.05,
|
||
# "clientOrderId":null,
|
||
# "origQuoteOrderQty":5,
|
||
# "updateTime":1648163406000,
|
||
# "time":1648163139387,
|
||
# "type":"buy_maker",
|
||
# "status":-1
|
||
# }
|
||
#
|
||
#
|
||
# fetchOrderDefault(private)
|
||
#
|
||
# {
|
||
# "symbol":"shib_usdt",
|
||
# "amount":1,
|
||
# "create_time":1649367863356,
|
||
# "price":0.0000246103,
|
||
# "avg_price":0.00002466180000000104,
|
||
# "type":"buy_market",
|
||
# "order_id":"abe8b92d-86d9-4d6d-b71e-d14f5fb53ddf",
|
||
# "custom_id": "007", # field only present if user creates it at order time
|
||
# "deal_amount":40548.54065802,
|
||
# "status":2
|
||
# }
|
||
#
|
||
# fetchOpenOrders(private)
|
||
#
|
||
# {
|
||
# "cummulativeQuoteQty":0,
|
||
# "symbol":"doge_usdt",
|
||
# "executedQty":0,
|
||
# "orderId":"73878edf-008d-4e4c-8041-df1f1b2cd8bb",
|
||
# "origQty":100,
|
||
# "price":0.05,
|
||
# "origQuoteOrderQty":5,
|
||
# "updateTime":1648501762000,
|
||
# "time":1648501762353,
|
||
# "type":"buy",
|
||
# "status":0
|
||
# }
|
||
#
|
||
# fetchOrders(private)
|
||
#
|
||
# {
|
||
# "cummulativeQuoteQty":0,
|
||
# "symbol":"doge_usdt",
|
||
# "executedQty":0,
|
||
# "orderId":"2cadc7cc-b5f6-486b-a5b4-d6ac49a9c186",
|
||
# "origQty":100,
|
||
# "price":0.05,
|
||
# "origQuoteOrderQty":5,
|
||
# "updateTime":1648501384000,
|
||
# "time":1648501363889,
|
||
# "type":"buy",
|
||
# "status":-1
|
||
# }
|
||
#
|
||
# cancelOrder
|
||
#
|
||
# {
|
||
# "executedQty":0.0,
|
||
# "price":0.05,
|
||
# "origQty":100.0,
|
||
# "tradeType":"buy",
|
||
# "status":0
|
||
# }
|
||
#
|
||
# cancelAllOrders
|
||
#
|
||
# {
|
||
# "executedQty":0.00000000000000000000,
|
||
# "orderId":"293ef71b-3e67-4962-af93-aa06990a045f",
|
||
# "price":0.05000000000000000000,
|
||
# "origQty":100.00000000000000000000,
|
||
# "tradeType":"buy",
|
||
# "status":0
|
||
# }
|
||
#
|
||
id = self.safe_string_2(order, 'orderId', 'order_id')
|
||
clientOrderId = self.safe_string_2(order, 'clientOrderId', 'custom_id')
|
||
timestamp = self.safe_integer_2(order, 'time', 'create_time')
|
||
rawStatus = self.safe_string(order, 'status')
|
||
marketId = self.safe_string(order, 'symbol')
|
||
market = self.safe_market(marketId, market)
|
||
timeInForce = None
|
||
postOnly = False
|
||
type = 'limit'
|
||
rawType = self.safe_string_2(order, 'type', 'tradeType') # buy, sell, buy_market, sell_market, buy_maker,sell_maker,buy_ioc,sell_ioc, buy_fok, sell_fok
|
||
parts = rawType.split('_')
|
||
side = self.safe_string(parts, 0)
|
||
typePart = self.safe_string(parts, 1) # market, maker, ioc, fok or None(limit)
|
||
if typePart == 'market':
|
||
type = 'market'
|
||
if typePart == 'maker':
|
||
postOnly = True
|
||
timeInForce = 'PO'
|
||
if typePart == 'ioc':
|
||
timeInForce = 'IOC'
|
||
if typePart == 'fok':
|
||
timeInForce = 'FOK'
|
||
price = self.safe_string(order, 'price')
|
||
costString = self.safe_string(order, 'cummulativeQuoteQty')
|
||
amountString = None
|
||
if rawType != 'buy_market':
|
||
amountString = self.safe_string_2(order, 'origQty', 'amount')
|
||
filledString = self.safe_string_2(order, 'executedQty', 'deal_amount')
|
||
return self.safe_order({
|
||
'id': id,
|
||
'clientOrderId': clientOrderId,
|
||
'datetime': self.iso8601(timestamp),
|
||
'timestamp': timestamp,
|
||
'lastTradeTimestamp': None,
|
||
'status': self.parse_order_status(rawStatus),
|
||
'symbol': market['symbol'],
|
||
'type': type,
|
||
'timeInForce': timeInForce,
|
||
'postOnly': postOnly,
|
||
'side': side,
|
||
'price': price,
|
||
'triggerPrice': None,
|
||
'cost': costString,
|
||
'amount': amountString,
|
||
'filled': filledString,
|
||
'remaining': None,
|
||
'trades': None,
|
||
'fee': None,
|
||
'info': order,
|
||
'average': None,
|
||
}, market)
|
||
|
||
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
fetches information on an order made by the user
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#query-order
|
||
https://www.lbank.com/en-US/docs/index.html#query-order-new
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
method = self.safe_string(params, 'method')
|
||
if method is None:
|
||
options = self.safe_value(self.options, 'fetchOrder', {})
|
||
method = self.safe_string(options, 'method', 'fetchOrderSupplement')
|
||
if method == 'fetchOrderSupplement':
|
||
return self.fetch_order_supplement(id, symbol, params)
|
||
return self.fetch_order_default(id, symbol, params)
|
||
|
||
def fetch_order_supplement(self, id: str, symbol: Str = None, params={}):
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'orderId': id,
|
||
}
|
||
response = self.spotPrivatePostSupplementOrdersInfo(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":{
|
||
# "cummulativeQuoteQty":0,
|
||
# "symbol":"doge_usdt",
|
||
# "executedQty":0,
|
||
# "orderId":"53d2d53e-70fb-4398-b722-f48571a5f61e",
|
||
# "origQty":1E+2,
|
||
# "price":0.05,
|
||
# "clientOrderId":null,
|
||
# "origQuoteOrderQty":5,
|
||
# "updateTime":1648163406000,
|
||
# "time":1648163139387,
|
||
# "type":"buy_maker",
|
||
# "status":-1
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648164471827
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'data', {})
|
||
return self.parse_order(result)
|
||
|
||
def fetch_order_default(self, id: str, symbol: Str = None, params={}):
|
||
# Id can be a list of ids delimited by a comma
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'order_id': id,
|
||
}
|
||
response = self.spotPrivatePostOrdersInfo(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":[
|
||
# {
|
||
# "symbol":"doge_usdt",
|
||
# "amount":18,
|
||
# "create_time":1647455223186,
|
||
# "price":0,
|
||
# "avg_price":0.113344,
|
||
# "type":"sell_market",
|
||
# "order_id":"d4ca1ddd-40d9-42c1-9717-5de435865bec",
|
||
# "deal_amount":18,
|
||
# "status":2
|
||
# }
|
||
# ],
|
||
# "error_code":0,
|
||
# "ts":1647455270776
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data', [])
|
||
numOrders = len(result)
|
||
if numOrders == 1:
|
||
return self.parse_order(result[0])
|
||
else:
|
||
# parsedOrders = []
|
||
# for i in range(0, numOrders):
|
||
# parsedOrder = self.parse_order(result[i])
|
||
# parsedOrders.append(parsedOrder)
|
||
# }
|
||
# return parsedOrders
|
||
raise BadRequest(self.id + ' fetchOrder() can only fetch one order at a time')
|
||
|
||
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch all trades made by the user
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#past-transaction-details
|
||
|
||
: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 trade 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')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
since = self.safe_value(params, 'start_date', since)
|
||
params = self.omit(params, 'start_date')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
# 'start_date' Start time yyyy-mm-dd, the maximum is today, the default is yesterday
|
||
# 'end_date' Finish time yyyy-mm-dd, the maximum is today, the default is today
|
||
# 'The start': and end date of the query window is up to 2 days
|
||
# 'from' Initial transaction number inquiring
|
||
# 'direct' inquire direction,The default is the 'next' which is the positive sequence of dealing time,the 'prev' is inverted order of dealing time
|
||
# 'size' Query the number of defaults to 100
|
||
}
|
||
if limit is not None:
|
||
request['size'] = limit
|
||
if since is not None:
|
||
request['start_date'] = self.ymd(since, '-') # max query 2 days ago
|
||
request['end_date'] = self.ymd(since + 86400000, '-') # will cover 2 days
|
||
response = self.spotPrivatePostTransactionHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":[
|
||
# {
|
||
# "orderUuid":"38b4e7a4-14f6-45fd-aba1-1a37024124a0",
|
||
# "tradeFeeRate":0.0010000000,
|
||
# "dealTime":1648500944496,
|
||
# "dealQuantity":30.00000000000000000000,
|
||
# "tradeFee":0.00453300000000000000,
|
||
# "txUuid":"11f3850cc6214ea3b495adad3a032794",
|
||
# "dealPrice":0.15111300000000000000,
|
||
# "dealVolumePrice":4.53339000000000000000,
|
||
# "tradeType":"sell_market"
|
||
# }
|
||
# ],
|
||
# "error_code":0,
|
||
# "ts":1648509742164
|
||
# }
|
||
#
|
||
trades = self.safe_list(response, 'data', [])
|
||
return self.parse_trades(trades, market, since, limit)
|
||
|
||
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple orders made by the user
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#query-all-orders
|
||
|
||
: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>`
|
||
"""
|
||
# default query is for canceled and completely filled orders
|
||
# does not return open orders unless specified explicitly
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if limit is None:
|
||
limit = 100
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'current_page': 1,
|
||
'page_length': limit,
|
||
# 'status' -1: Cancelled, 0: Unfilled, 1: Partially filled, 2: Completely filled, 3: Partially filled and cancelled, 4: Cancellation is being processed
|
||
}
|
||
response = self.spotPrivatePostSupplementOrdersInfoHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":{
|
||
# "total":1,
|
||
# "page_length":100,
|
||
# "orders":[
|
||
# {
|
||
# "cummulativeQuoteQty":0,
|
||
# "symbol":"doge_usdt",
|
||
# "executedQty":0,
|
||
# "orderId":"2cadc7cc-b5f6-486b-a5b4-d6ac49a9c186",
|
||
# "origQty":100,
|
||
# "price":0.05,
|
||
# "origQuoteOrderQty":5,
|
||
# "updateTime":1648501384000,
|
||
# "time":1648501363889,
|
||
# "type":"buy",
|
||
# "status":-1
|
||
# }, ...
|
||
# ],
|
||
# "current_page":1
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648505706348
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data', {})
|
||
orders = self.safe_list(result, 'orders', [])
|
||
return self.parse_orders(orders, market, since, limit)
|
||
|
||
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetch all unfilled currently open orders
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#current-pending-order
|
||
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch open orders for
|
||
:param int [limit]: the maximum number of open order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if limit is None:
|
||
limit = 100
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'current_page': 1,
|
||
'page_length': limit,
|
||
}
|
||
response = self.spotPrivatePostSupplementOrdersInfoNoDeal(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":{
|
||
# "total":1,
|
||
# "page_length":100,
|
||
# "orders":[
|
||
# {
|
||
# "cummulativeQuoteQty":0,
|
||
# "symbol":"doge_usdt",
|
||
# "executedQty":0,
|
||
# "orderId":"73878edf-008d-4e4c-8041-df1f1b2cd8bb",
|
||
# "origQty":100,
|
||
# "price":0.05,
|
||
# "origQuoteOrderQty":5,
|
||
# "updateTime":1648501762000,
|
||
# "time":1648501762353,
|
||
# "type":"buy",
|
||
# "status":0
|
||
# }, ...
|
||
# ],
|
||
# "current_page":1
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648506110196
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data', {})
|
||
orders = self.safe_list(result, 'orders', [])
|
||
return self.parse_orders(orders, market, since, limit)
|
||
|
||
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
cancels an open order
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#cancel-order-new
|
||
|
||
: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>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
clientOrderId = self.safe_string_2(params, 'origClientOrderId', 'clientOrderId')
|
||
params = self.omit(params, ['origClientOrderId', 'clientOrderId'])
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'orderId': id,
|
||
}
|
||
if clientOrderId is not None:
|
||
request['origClientOrderId'] = clientOrderId
|
||
response = self.spotPrivatePostSupplementCancelOrder(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":{
|
||
# "executedQty":0.0,
|
||
# "price":0.05,
|
||
# "origQty":100.0,
|
||
# "tradeType":"buy",
|
||
# "status":0
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648501286196
|
||
# }
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_order(data)
|
||
|
||
def cancel_all_orders(self, symbol: Str = None, params={}):
|
||
"""
|
||
cancel all open orders in a market
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#cancel-all-pending-orders-for-a-single-trading-pair
|
||
|
||
:param str symbol: unified market symbol of the market to cancel orders in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = self.spotPrivatePostSupplementCancelOrderBySymbol(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":"true",
|
||
# "data":[
|
||
# {
|
||
# "executedQty":0.00000000000000000000,
|
||
# "orderId":"293ef71b-3e67-4962-af93-aa06990a045f",
|
||
# "price":0.05000000000000000000,
|
||
# "origQty":100.00000000000000000000,
|
||
# "tradeType":"buy",
|
||
# "status":0
|
||
# },
|
||
# ],
|
||
# "error_code":0,
|
||
# "ts":1648506641469
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_orders(data)
|
||
|
||
def get_network_code_for_currency(self, currencyCode, params):
|
||
defaultNetworks = self.safe_value(self.options, 'defaultNetworks')
|
||
defaultNetwork = self.safe_string_upper(defaultNetworks, currencyCode)
|
||
networks = self.safe_value(self.options, 'networks', {})
|
||
network = self.safe_string_upper(params, 'network', defaultNetwork) # self line allows the user to specify either ERC20 or ETH
|
||
network = self.safe_string(networks, network, network) # handle ERC20>ETH alias
|
||
return network
|
||
|
||
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
fetch the deposit address for a currency associated with self account
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#get-deposit-address
|
||
https://www.lbank.com/en-US/docs/index.html#the-user-obtains-the-deposit-address
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
self.load_markets()
|
||
options = self.safe_value(self.options, 'fetchDepositAddress', {})
|
||
defaultMethod = self.safe_string(options, 'method', 'fetchDepositAddressDefault')
|
||
method = self.safe_string(params, 'method', defaultMethod)
|
||
params = self.omit(params, 'method')
|
||
response = None
|
||
if method == 'fetchDepositAddressSupplement':
|
||
response = self.fetch_deposit_address_supplement(code, params)
|
||
else:
|
||
response = self.fetch_deposit_address_default(code, params)
|
||
return response
|
||
|
||
def fetch_deposit_address_default(self, code: str, params={}) -> DepositAddress:
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'assetCode': currency['id'],
|
||
}
|
||
network = self.get_network_code_for_currency(code, params)
|
||
if network is not None:
|
||
request['netWork'] = network # ... yes, really lol
|
||
params = self.omit(params, 'network')
|
||
response = self.spotPrivatePostGetDepositAddress(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":{
|
||
# "assetCode":"usdt",
|
||
# "address":"0xc85689d37ca650bf2f2161364cdedee21eb6ca53",
|
||
# "memo":null,
|
||
# "netWork":"bep20(bsc)"
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648075865103
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data')
|
||
address = self.safe_string(result, 'address')
|
||
tag = self.safe_string(result, 'memo')
|
||
return {
|
||
'info': response,
|
||
'currency': code,
|
||
'network': self.network_id_to_code(self.safe_string(result, 'netWork')),
|
||
'address': address,
|
||
'tag': tag,
|
||
}
|
||
|
||
def fetch_deposit_address_supplement(self, code: str, params={}) -> DepositAddress:
|
||
# returns the address for whatever the default network is...
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
}
|
||
networks = self.safe_value(self.options, 'networks')
|
||
network = self.safe_string_upper(params, 'network')
|
||
network = self.safe_string(networks, network, network)
|
||
if network is not None:
|
||
request['networkName'] = network
|
||
params = self.omit(params, 'network')
|
||
response = self.spotPrivatePostSupplementGetDepositAddress(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data":{
|
||
# "address":"TDxtabCC8iQwaxUUrPcE4WL2jArGAfvQ5A",
|
||
# "memo":null,
|
||
# "coin":"usdt"
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648073818880
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data')
|
||
address = self.safe_string(result, 'address')
|
||
tag = self.safe_string(result, 'memo')
|
||
return {
|
||
'info': response,
|
||
'currency': code,
|
||
'network': None,
|
||
'address': address,
|
||
'tag': tag,
|
||
}
|
||
|
||
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
||
"""
|
||
make a withdrawal
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#withdrawal
|
||
|
||
:param str code: unified currency code
|
||
:param float amount: the amount to withdraw
|
||
:param str address: the address to withdraw to
|
||
:param str tag:
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
||
self.check_address(address)
|
||
self.load_markets()
|
||
fee = self.safe_string(params, 'fee')
|
||
params = self.omit(params, 'fee')
|
||
# The relevant coin network fee can be found by calling fetchDepositWithdrawFees(), note: if no network param is supplied then the default network will be used, self can also be found in fetchDepositWithdrawFees().
|
||
self.check_required_argument('withdraw', fee, 'fee')
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'address': address,
|
||
'coin': currency['id'],
|
||
'amount': amount,
|
||
'fee': fee, # the correct coin-network fee must be supplied, which can be found by calling fetchDepositWithdrawFees(private)
|
||
# 'networkName': defaults to the defaultNetwork of the coin which can be found in the /supplement/user_info endpoint
|
||
# 'memo': memo: memo word of bts and dct
|
||
# 'mark': Withdrawal Notes
|
||
# 'name': Remarks of the address. After hasattr(self, filling) parameter, it will be added to the withdrawal address book of the currency.
|
||
# 'withdrawOrderId': withdrawOrderId
|
||
# 'type': type=1 is for intra-site transfer
|
||
}
|
||
if tag is not None:
|
||
request['memo'] = tag
|
||
network = self.safe_string_upper_2(params, 'network', 'networkName')
|
||
params = self.omit(params, ['network', 'networkName'])
|
||
networks = self.safe_value(self.options, 'networks')
|
||
networkId = self.safe_string(networks, network, network)
|
||
if networkId is not None:
|
||
request['networkName'] = networkId
|
||
response = self.spotPrivatePostSupplementWithdraw(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data": {
|
||
# "fee":10.00000000000000000000,
|
||
# "withdrawId":1900376
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1648992501414
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data', {})
|
||
return {
|
||
'info': result,
|
||
'id': self.safe_string(result, 'withdrawId'),
|
||
}
|
||
|
||
def parse_transaction_status(self, status, type):
|
||
statuses: dict = {
|
||
'deposit': {
|
||
'1': 'pending',
|
||
'2': 'ok',
|
||
'3': 'failed',
|
||
'4': 'canceled',
|
||
'5': 'transfer',
|
||
},
|
||
'withdrawal': {
|
||
'1': 'pending',
|
||
'2': 'canceled',
|
||
'3': 'failed',
|
||
'4': 'ok',
|
||
},
|
||
}
|
||
return self.safe_string(self.safe_value(statuses, type, {}), status, status)
|
||
|
||
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
||
#
|
||
# fetchDeposits(private)
|
||
#
|
||
# {
|
||
# "insertTime":1649012310000,
|
||
# "amount":9.00000000000000000000,
|
||
# "address":"TYASr5UV6HEcXatwdFQfmLVUqQQQMUxHLS",
|
||
# "networkName":"trc20",
|
||
# "txId":"081e4e9351dd0274922168da5f2d14ea6c495b1c3b440244f4a6dd9fe196bf2b",
|
||
# "coin":"usdt",
|
||
# "status":"2"
|
||
# }
|
||
#
|
||
#
|
||
# fetchWithdrawals(private)
|
||
#
|
||
# {
|
||
# "amount":2.00000000000000000000,
|
||
# "address":"TBjrW5JHDyPZjFc5nrRMhRWUDaJmhGhmD6",
|
||
# "fee":1.00000000000000000000,
|
||
# "networkName":"trc20",
|
||
# "coid":"usdt",
|
||
# "transferType":"数字资产提现",
|
||
# "txId":"47eeee2763ad49b8817524dacfa7d092fb58f8b0ab7e5d25473314df1a793c3d",
|
||
# "id":1902194,
|
||
# "applyTime":1649014002000,
|
||
# "status":"4"
|
||
# }
|
||
#
|
||
id = self.safe_string(transaction, 'id')
|
||
type = None
|
||
if id is None:
|
||
type = 'deposit'
|
||
else:
|
||
type = 'withdrawal'
|
||
txid = self.safe_string(transaction, 'txId')
|
||
timestamp = self.safe_integer_2(transaction, 'insertTime', 'applyTime')
|
||
address = self.safe_string(transaction, 'address')
|
||
addressFrom = None
|
||
addressTo = None
|
||
if type == 'deposit':
|
||
addressFrom = address
|
||
else:
|
||
addressTo = address
|
||
amount = self.safe_number(transaction, 'amount')
|
||
currencyId = self.safe_string_2(transaction, 'coin', 'coid')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
status = self.parse_transaction_status(self.safe_string(transaction, 'status'), type)
|
||
fee = None
|
||
feeCost = self.safe_number(transaction, 'fee')
|
||
if feeCost is not None:
|
||
fee = {
|
||
'cost': feeCost,
|
||
'currency': code,
|
||
}
|
||
return {
|
||
'info': transaction,
|
||
'id': id,
|
||
'txid': txid,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'network': self.network_id_to_code(self.safe_string(transaction, 'networkName')),
|
||
'address': address,
|
||
'addressTo': addressTo,
|
||
'addressFrom': addressFrom,
|
||
'tag': None,
|
||
'tagTo': None,
|
||
'tagFrom': None,
|
||
'type': type,
|
||
'amount': amount,
|
||
'currency': code,
|
||
'status': status,
|
||
'updated': None,
|
||
'comment': None,
|
||
'internal': (status == 'transfer'),
|
||
'fee': fee,
|
||
}
|
||
|
||
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
fetch all deposits made to an account
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#get-recharge-history
|
||
|
||
:param str code: unified currency code
|
||
:param int [since]: the earliest time in ms to fetch deposits for
|
||
:param int [limit]: the maximum number of deposits structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
# 'status': Recharge status: ("1","Applying"),("2","Recharge successful"),("3","Recharge failed"),("4","Already Cancel"),("5", "Transfer")
|
||
# 'endTime': end time, timestamp in milliseconds, default now
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
response = self.spotPrivatePostSupplementDepositHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data": {
|
||
# "total":1,
|
||
# "depositOrders": [
|
||
# {
|
||
# "insertTime":1649012310000,
|
||
# "amount":9.00000000000000000000,
|
||
# "address":"TYASr5UV6HEcXatwdFQfmLVUqQQQMUxHLS",
|
||
# "networkName":"trc20",
|
||
# "txId":"081e4e9351dd0274922168da5f2d14ea6c495b1c3b440244f4a6dd9fe196bf2b",
|
||
# "coin":"usdt",
|
||
# "status":"2"
|
||
# },
|
||
# ],
|
||
# "page_length":20,
|
||
# "current_page":1
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1649719721758
|
||
# }
|
||
#
|
||
data = self.safe_value(response, 'data', {})
|
||
deposits = self.safe_list(data, 'depositOrders', [])
|
||
return self.parse_transactions(deposits, currency, since, limit)
|
||
|
||
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
fetch all withdrawals made from an account
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#get-withdrawal-history
|
||
|
||
: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>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
# 'status': Recharge status: ("1","Applying"),("2","Recharge successful"),("3","Recharge failed"),("4","Already Cancel"),("5", "Transfer")
|
||
# 'endTime': end time, timestamp in milliseconds, default now
|
||
# 'withdrawOrderId': Custom withdrawal id
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
response = self.spotPrivatePostSupplementWithdraws(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result":true,
|
||
# "data": {
|
||
# "total":1,
|
||
# "withdraws": [
|
||
# {
|
||
# "amount":2.00000000000000000000,
|
||
# "address":"TBjrW5JHDyPZjFc5nrRMhRWUDaJmhGhmD6",
|
||
# "fee":1.00000000000000000000,
|
||
# "networkName":"trc20",
|
||
# "coid":"usdt",
|
||
# "transferType":"数字资产提现",
|
||
# "txId":"47eeee2763ad49b8817524dacfa7d092fb58f8b0ab7e5d25473314df1a793c3d",
|
||
# "id":1902194,
|
||
# "applyTime":1649014002000,
|
||
# "status":"4"
|
||
# },
|
||
# ],
|
||
# "page_length":20,
|
||
# "current_page":1
|
||
# },
|
||
# "error_code":0,
|
||
# "ts":1649720362362
|
||
# }
|
||
#
|
||
data = self.safe_value(response, 'data', {})
|
||
withdraws = self.safe_list(data, 'withdraws', [])
|
||
return self.parse_transactions(withdraws, currency, since, limit)
|
||
|
||
def fetch_transaction_fees(self, codes: Strings = None, params={}):
|
||
"""
|
||
@deprecated
|
||
please use fetchDepositWithdrawFees instead
|
||
:param str[]|None codes: not used by lbank fetchTransactionFees()
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
# private only returns information for currencies with non-zero balance
|
||
self.load_markets()
|
||
isAuthorized = self.check_required_credentials(False)
|
||
result = None
|
||
if isAuthorized is True:
|
||
options = self.safe_value(self.options, 'fetchTransactionFees', {})
|
||
defaultMethod = self.safe_string(options, 'method', 'fetchPrivateTransactionFees')
|
||
method = self.safe_string(params, 'method', defaultMethod)
|
||
params = self.omit(params, 'method')
|
||
if method == 'fetchPublicTransactionFees':
|
||
result = self.fetch_public_transaction_fees(params)
|
||
else:
|
||
result = self.fetch_private_transaction_fees(params)
|
||
else:
|
||
result = self.fetch_public_transaction_fees(params)
|
||
return result
|
||
|
||
def fetch_private_transaction_fees(self, params={}):
|
||
# complete response
|
||
# incl. for coins which None in public method
|
||
self.load_markets()
|
||
response = self.spotPrivatePostSupplementUserInfo()
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "usableAmt": "14.36",
|
||
# "assetAmt": "14.36",
|
||
# "networkList": [
|
||
# {
|
||
# "isDefault": False,
|
||
# "withdrawFeeRate": "",
|
||
# "name": "erc20",
|
||
# "withdrawMin": 30,
|
||
# "minLimit": 0.0001,
|
||
# "minDeposit": 20,
|
||
# "feeAssetCode": "usdt",
|
||
# "withdrawFee": "30",
|
||
# "type": 1,
|
||
# "coin": "usdt",
|
||
# "network": "eth"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "freezeAmt": "0",
|
||
# "coin": "ada"
|
||
# }
|
||
# ],
|
||
# "code": 0
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data', [])
|
||
withdrawFees: dict = {}
|
||
for i in range(0, len(result)):
|
||
entry = result[i]
|
||
currencyId = self.safe_string(entry, 'coin')
|
||
code = self.safe_currency_code(currencyId)
|
||
networkList = self.safe_value(entry, 'networkList', [])
|
||
withdrawFees[code] = {}
|
||
for j in range(0, len(networkList)):
|
||
networkEntry = networkList[j]
|
||
fee = self.safe_number(networkEntry, 'withdrawFee')
|
||
if fee is not None:
|
||
networkCode = self.network_id_to_code(self.safe_string(networkEntry, 'name'))
|
||
withdrawFees[code][networkCode] = fee
|
||
return {
|
||
'withdraw': withdrawFees,
|
||
'deposit': {},
|
||
'info': response,
|
||
}
|
||
|
||
def fetch_public_transaction_fees(self, params={}):
|
||
# extremely incomplete response
|
||
# vast majority fees None
|
||
self.load_markets()
|
||
code = self.safe_string_2(params, 'coin', 'assetCode')
|
||
params = self.omit(params, ['coin', 'assetCode'])
|
||
request: dict = {}
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['assetCode'] = currency['id']
|
||
response = self.spotPublicGetWithdrawConfigs(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "amountScale": "4",
|
||
# "chain": "heco",
|
||
# "assetCode": "lbk",
|
||
# "min": "200",
|
||
# "transferAmtScale": "4",
|
||
# "canWithDraw": True,
|
||
# "fee": "100",
|
||
# "minTransfer": "0.0001",
|
||
# "type": "1"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "error_code": "0",
|
||
# "ts": "1663364435973"
|
||
# }
|
||
#
|
||
result = self.safe_value(response, 'data', [])
|
||
withdrawFees: dict = {}
|
||
for i in range(0, len(result)):
|
||
item = result[i]
|
||
canWithdraw = self.safe_value(item, 'canWithDraw')
|
||
if canWithdraw == 'true':
|
||
currencyId = self.safe_string(item, 'assetCode')
|
||
codeInner = self.safe_currency_code(currencyId)
|
||
network = self.network_id_to_code(self.safe_string(item, 'chain'))
|
||
if network is None:
|
||
network = codeInner
|
||
fee = self.safe_string(item, 'fee')
|
||
if withdrawFees[codeInner] is None:
|
||
withdrawFees[codeInner] = {}
|
||
withdrawFees[codeInner][network] = self.parse_number(fee)
|
||
return {
|
||
'withdraw': withdrawFees,
|
||
'deposit': {},
|
||
'info': response,
|
||
}
|
||
|
||
def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
||
"""
|
||
when using private endpoint, only returns information for currencies with non-zero balance, use public method by specifying self.options['fetchDepositWithdrawFees']['method'] = 'fetchPublicDepositWithdrawFees'
|
||
|
||
https://www.lbank.com/en-US/docs/index.html#get-all-coins-information
|
||
https://www.lbank.com/en-US/docs/index.html#withdrawal-configurations
|
||
|
||
:param str[] [codes]: array of unified currency codes
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
self.load_markets()
|
||
isAuthorized = self.check_required_credentials(False)
|
||
response = None
|
||
if isAuthorized is True:
|
||
options = self.safe_value(self.options, 'fetchDepositWithdrawFees', {})
|
||
defaultMethod = self.safe_string(options, 'method', 'fetchPrivateDepositWithdrawFees')
|
||
method = self.safe_string(params, 'method', defaultMethod)
|
||
params = self.omit(params, 'method')
|
||
if method == 'fetchPublicDepositWithdrawFees':
|
||
response = self.fetch_public_deposit_withdraw_fees(codes, params)
|
||
else:
|
||
response = self.fetch_private_deposit_withdraw_fees(codes, params)
|
||
else:
|
||
response = self.fetch_public_deposit_withdraw_fees(codes, params)
|
||
return response
|
||
|
||
def fetch_private_deposit_withdraw_fees(self, codes=None, params={}):
|
||
# complete response
|
||
# incl. for coins which None in public method
|
||
self.load_markets()
|
||
response = self.spotPrivatePostSupplementUserInfo(params)
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "usableAmt": "14.36",
|
||
# "assetAmt": "14.36",
|
||
# "networkList": [
|
||
# {
|
||
# "isDefault": False,
|
||
# "withdrawFeeRate": "",
|
||
# "name": "erc20",
|
||
# "withdrawMin": 30,
|
||
# "minLimit": 0.0001,
|
||
# "minDeposit": 20,
|
||
# "feeAssetCode": "usdt",
|
||
# "withdrawFee": "30",
|
||
# "type": 1,
|
||
# "coin": "usdt",
|
||
# "network": "eth"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "freezeAmt": "0",
|
||
# "coin": "ada"
|
||
# }
|
||
# ],
|
||
# "code": 0
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_deposit_withdraw_fees(data, codes, 'coin')
|
||
|
||
def fetch_public_deposit_withdraw_fees(self, codes=None, params={}):
|
||
# extremely incomplete response
|
||
# vast majority fees None
|
||
self.load_markets()
|
||
request: dict = {}
|
||
response = self.spotPublicGetWithdrawConfigs(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "result": "true",
|
||
# "data": [
|
||
# {
|
||
# "amountScale": "4",
|
||
# "chain": "heco",
|
||
# "assetCode": "lbk",
|
||
# "min": "200",
|
||
# "transferAmtScale": "4",
|
||
# "canWithDraw": True,
|
||
# "fee": "100",
|
||
# "minTransfer": "0.0001",
|
||
# "type": "1"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "error_code": "0",
|
||
# "ts": "1663364435973"
|
||
# }
|
||
#
|
||
data = self.safe_value(response, 'data', [])
|
||
return self.parse_public_deposit_withdraw_fees(data, codes)
|
||
|
||
def parse_public_deposit_withdraw_fees(self, response, codes=None):
|
||
#
|
||
# [
|
||
# {
|
||
# "amountScale": "4",
|
||
# "chain": "heco",
|
||
# "assetCode": "lbk",
|
||
# "min": "200",
|
||
# "transferAmtScale": "4",
|
||
# "canWithDraw": True,
|
||
# "fee": "100",
|
||
# "minTransfer": "0.0001",
|
||
# "type": "1"
|
||
# },
|
||
# ...
|
||
# ]
|
||
#
|
||
result: dict = {}
|
||
for i in range(0, len(response)):
|
||
fee = response[i]
|
||
canWithdraw = self.safe_value(fee, 'canWithDraw')
|
||
if canWithdraw is True:
|
||
currencyId = self.safe_string(fee, 'assetCode')
|
||
code = self.safe_currency_code(currencyId)
|
||
if codes is None or self.in_array(code, codes):
|
||
withdrawFee = self.safe_number(fee, 'fee')
|
||
if withdrawFee is not None:
|
||
resultValue = self.safe_value(result, code)
|
||
if resultValue is None:
|
||
result[code] = self.deposit_withdraw_fee([fee])
|
||
else:
|
||
resultCodeInfo = result[code]['info']
|
||
resultCodeInfo.append(fee)
|
||
networkCode = self.network_id_to_code(self.safe_string(fee, 'chain'))
|
||
if networkCode is not None:
|
||
result[code]['networks'][networkCode] = {
|
||
'withdraw': {
|
||
'fee': withdrawFee,
|
||
'percentage': None,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
}
|
||
else:
|
||
result[code]['withdraw'] = {
|
||
'fee': withdrawFee,
|
||
'percentage': None,
|
||
}
|
||
return result
|
||
|
||
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
||
#
|
||
# * only used for fetchPrivateDepositWithdrawFees
|
||
#
|
||
# {
|
||
# "usableAmt": "14.36",
|
||
# "assetAmt": "14.36",
|
||
# "networkList": [
|
||
# {
|
||
# "isDefault": False,
|
||
# "withdrawFeeRate": "",
|
||
# "name": "erc20",
|
||
# "withdrawMin": 30,
|
||
# "minLimit": 0.0001,
|
||
# "minDeposit": 20,
|
||
# "feeAssetCode": "usdt",
|
||
# "withdrawFee": "30",
|
||
# "type": 1,
|
||
# "coin": "usdt",
|
||
# "network": "eth"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "freezeAmt": "0",
|
||
# "coin": "ada"
|
||
# }
|
||
#
|
||
result = self.deposit_withdraw_fee(fee)
|
||
networkList = self.safe_value(fee, 'networkList', [])
|
||
for j in range(0, len(networkList)):
|
||
networkEntry = networkList[j]
|
||
networkCode = self.network_id_to_code(self.safe_string(networkEntry, 'name'))
|
||
withdrawFee = self.safe_number(networkEntry, 'withdrawFee')
|
||
isDefault = self.safe_value(networkEntry, 'isDefault')
|
||
if withdrawFee is not None:
|
||
if isDefault:
|
||
result['withdraw'] = {
|
||
'fee': withdrawFee,
|
||
'percentage': None,
|
||
}
|
||
result['networks'][networkCode] = {
|
||
'withdraw': {
|
||
'fee': withdrawFee,
|
||
'percentage': None,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
}
|
||
return result
|
||
|
||
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
||
query = self.omit(params, self.extract_params(path))
|
||
url = self.urls['api']['rest'] + '/' + self.version + '/' + self.implode_params(path, params)
|
||
# Every spot endpoint ends with ".do"
|
||
if api[0] == 'spot':
|
||
url += '.do'
|
||
else:
|
||
url = self.urls['api']['contract'] + '/' + self.implode_params(path, params)
|
||
if api[1] == 'public':
|
||
if query:
|
||
url += '?' + self.urlencode(self.keysort(query))
|
||
else:
|
||
self.check_required_credentials()
|
||
timestamp = str(self.milliseconds())
|
||
echostr = self.uuid22() + self.uuid16()
|
||
query = self.extend({
|
||
'api_key': self.apiKey,
|
||
}, query)
|
||
signatureMethod = None
|
||
if len(self.secret) > 32:
|
||
signatureMethod = 'RSA'
|
||
else:
|
||
signatureMethod = 'HmacSHA256'
|
||
auth = self.rawencode(self.keysort(self.extend({
|
||
'echostr': echostr,
|
||
'signature_method': signatureMethod,
|
||
'timestamp': timestamp,
|
||
}, query)))
|
||
encoded = self.encode(auth)
|
||
hash = self.hash(encoded, 'md5')
|
||
uppercaseHash = hash.upper()
|
||
sign = None
|
||
if signatureMethod == 'RSA':
|
||
cacheSecretAsPem = self.safe_bool(self.options, 'cacheSecretAsPem', True)
|
||
pem = None
|
||
if cacheSecretAsPem:
|
||
pem = self.safe_value(self.options, 'pem')
|
||
if pem is None:
|
||
pem = self.convert_secret_to_pem(self.encode(self.secret))
|
||
self.options['pem'] = pem
|
||
else:
|
||
pem = self.convert_secret_to_pem(self.encode(self.secret))
|
||
sign = self.rsa(uppercaseHash, pem, 'sha256')
|
||
elif signatureMethod == 'HmacSHA256':
|
||
sign = self.hmac(self.encode(uppercaseHash), self.encode(self.secret), hashlib.sha256)
|
||
query['sign'] = sign
|
||
body = self.urlencode(self.keysort(query))
|
||
headers = {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'timestamp': timestamp,
|
||
'signature_method': signatureMethod,
|
||
'echostr': echostr,
|
||
}
|
||
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
||
|
||
def convert_secret_to_pem(self, secret):
|
||
lineLength = 64
|
||
secretLength = len(secret) - 0
|
||
numLines = self.parse_to_int(secretLength / lineLength)
|
||
numLines = self.sum(numLines, 1)
|
||
pem = "-----BEGIN PRIVATE KEY-----\n" # eslint-disable-line
|
||
for i in range(0, numLines):
|
||
start = i * lineLength
|
||
end = self.sum(start, lineLength)
|
||
pem += self.secret[start:end] + "\n" # eslint-disable-line
|
||
return pem + '-----END PRIVATE KEY-----'
|
||
|
||
def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
||
if response is None:
|
||
return None
|
||
success = self.safe_value(response, 'result')
|
||
if success == 'false' or not success:
|
||
errorCode = self.safe_string(response, 'error_code')
|
||
message = self.safe_string({
|
||
'10000': 'Internal error',
|
||
'10001': 'The required parameters can not be empty',
|
||
'10002': 'Validation failed',
|
||
'10003': 'Invalid parameter',
|
||
'10004': 'Request too frequent',
|
||
'10005': 'Secret key does not exist',
|
||
'10006': 'User does not exist',
|
||
'10007': 'Invalid signature',
|
||
'10008': 'Invalid Trading Pair',
|
||
'10009': 'Price and/or Amount are required for limit order',
|
||
'10010': 'Price and/or Amount must be less than minimum requirement',
|
||
# '10011': 'Market orders can not be missing the amount of the order',
|
||
# '10012': 'market sell orders can not be missing orders',
|
||
'10013': 'The amount is too small',
|
||
'10014': 'Insufficient amount of money in the account',
|
||
'10015': 'Invalid order type',
|
||
'10016': 'Insufficient account balance',
|
||
'10017': 'Server Error',
|
||
'10018': 'Page size should be between 1 and 50',
|
||
'10019': 'Cancel NO more than 3 orders in one request',
|
||
'10020': 'Volume < 0.001',
|
||
'10021': 'Price < 0.01',
|
||
'10022': 'Invalid authorization',
|
||
'10023': 'Market Order is not supported yet',
|
||
'10024': 'User cannot trade on self pair',
|
||
'10025': 'Order has been filled',
|
||
'10026': 'Order has been cancelld',
|
||
'10027': 'Order is cancelling',
|
||
'10028': 'Wrong query time',
|
||
'10029': 'from is not in the query time',
|
||
'10030': 'from do not match the transaction type of inqury',
|
||
'10031': 'echostr length must be valid and length must be from 30 to 40',
|
||
'10033': 'Failed to create order',
|
||
'10036': 'customID duplicated',
|
||
'10100': 'Has no privilege to withdraw',
|
||
'10101': 'Invalid fee rate to withdraw',
|
||
'10102': 'Too little to withdraw',
|
||
'10103': 'Exceed daily limitation of withdraw',
|
||
'10104': 'Cancel was rejected',
|
||
'10105': 'Request has been cancelled',
|
||
'10106': 'None trade time',
|
||
'10107': 'Start price exception',
|
||
'10108': 'can not create order',
|
||
'10109': 'wallet address is not mapping',
|
||
'10110': 'transfer fee is not mapping',
|
||
'10111': 'mount > 0',
|
||
'10112': 'fee is too lower',
|
||
'10113': 'transfer fee is 0',
|
||
'10600': 'intercepted by replay attacks filter, check timestamp',
|
||
'10601': 'Interface closed unavailable',
|
||
'10701': 'invalid asset code',
|
||
'10702': 'not allowed deposit',
|
||
}, errorCode, self.json(response))
|
||
ErrorClass = self.safe_value({
|
||
'10001': BadRequest,
|
||
'10002': AuthenticationError,
|
||
'10003': BadRequest,
|
||
'10004': RateLimitExceeded,
|
||
'10005': AuthenticationError,
|
||
'10006': AuthenticationError,
|
||
'10007': AuthenticationError,
|
||
'10008': BadSymbol,
|
||
'10009': InvalidOrder,
|
||
'10010': InvalidOrder,
|
||
'10013': InvalidOrder,
|
||
'10014': InsufficientFunds,
|
||
'10015': InvalidOrder,
|
||
'10016': InsufficientFunds,
|
||
'10017': ExchangeError,
|
||
'10018': BadRequest,
|
||
'10019': BadRequest,
|
||
'10020': BadRequest,
|
||
'10021': InvalidOrder,
|
||
'10022': PermissionDenied, # 'Invalid authorization',
|
||
'10023': InvalidOrder, # 'Market Order is not supported yet',
|
||
'10024': PermissionDenied, # 'User cannot trade on self pair',
|
||
'10025': InvalidOrder, # 'Order has been filled',
|
||
'10026': InvalidOrder, # 'Order has been cancelled',
|
||
'10027': InvalidOrder, # 'Order is cancelling',
|
||
'10028': BadRequest, # 'Wrong query time',
|
||
'10029': BadRequest, # 'from is not in the query time',
|
||
'10030': BadRequest, # 'from do not match the transaction type of inqury',
|
||
'10031': InvalidNonce, # 'echostr length must be valid and length must be from 30 to 40',
|
||
'10033': ExchangeError, # 'Failed to create order',
|
||
'10036': DuplicateOrderId, # 'customID duplicated',
|
||
'10100': PermissionDenied, # 'Has no privilege to withdraw',
|
||
'10101': BadRequest, # 'Invalid fee rate to withdraw',
|
||
'10102': InsufficientFunds, # 'Too little to withdraw',
|
||
'10103': ExchangeError, # 'Exceed daily limitation of withdraw',
|
||
'10104': ExchangeError, # 'Cancel was rejected',
|
||
'10105': ExchangeError, # 'Request has been cancelled',
|
||
'10106': BadRequest, # 'None trade time',
|
||
'10107': BadRequest, # 'Start price exception',
|
||
'10108': ExchangeError, # 'can not create order',
|
||
'10109': InvalidAddress, # 'wallet address is not mapping',
|
||
'10110': ExchangeError, # 'transfer fee is not mapping',
|
||
'10111': BadRequest, # 'mount > 0',
|
||
'10112': BadRequest, # 'fee is too lower',
|
||
'10113': BadRequest, # 'transfer fee is 0',
|
||
'10600': BadRequest, # 'intercepted by replay attacks filter, check timestamp',
|
||
'10601': ExchangeError, # 'Interface closed unavailable',
|
||
'10701': BadSymbol, # 'invalid asset code',
|
||
'10702': PermissionDenied, # 'not allowed deposit',
|
||
}, errorCode, ExchangeError)
|
||
raise ErrorClass(message)
|
||
return None
|