1771 lines
81 KiB
Python
1771 lines
81 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
from ccxt.async_support.base.exchange import Exchange
|
|
from ccxt.abstract.latoken import ImplicitAPI
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction, TransferEntry
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import AccountSuspended
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.errors import ExchangeNotAvailable
|
|
from ccxt.base.errors import InvalidNonce
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
|
|
|
|
class latoken(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(latoken, self).describe(), {
|
|
'id': 'latoken',
|
|
'name': 'Latoken',
|
|
'countries': ['KY'], # Cayman Islands
|
|
'version': 'v2',
|
|
'rateLimit': 1000,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'borrowMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createOrder': True,
|
|
'createPostOnlyOrder': False,
|
|
'createStopLimitOrder': True,
|
|
'createStopMarketOrder': False,
|
|
'createStopOrder': True,
|
|
'fetchAllGreeks': False,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDepositAddress': False,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDepositAddressesByNetwork': False,
|
|
'fetchDepositsWithdrawals': True,
|
|
'fetchDepositWithdrawFees': False,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingInterval': False,
|
|
'fetchFundingIntervals': False,
|
|
'fetchFundingRate': False,
|
|
'fetchFundingRateHistory': False,
|
|
'fetchFundingRates': False,
|
|
'fetchGreeks': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchIsolatedPositions': False,
|
|
'fetchLeverage': False,
|
|
'fetchLeverages': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchLiquidations': False,
|
|
'fetchLongShortRatio': False,
|
|
'fetchLongShortRatioHistory': False,
|
|
'fetchMarginAdjustmentHistory': False,
|
|
'fetchMarginMode': False,
|
|
'fetchMarginModes': False,
|
|
'fetchMarketLeverageTiers': False,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMarkPrice': False,
|
|
'fetchMarkPrices': False,
|
|
'fetchMyLiquidations': False,
|
|
'fetchMySettlementHistory': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOpenInterest': False,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenInterests': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOption': False,
|
|
'fetchOptionChain': False,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': True,
|
|
'fetchPosition': False,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': False,
|
|
'fetchPositionsForSymbol': False,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchSettlementHistory': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': True,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': True,
|
|
'fetchTradingFees': False,
|
|
'fetchTransactions': 'emulated',
|
|
'fetchTransfer': False,
|
|
'fetchTransfers': True,
|
|
'fetchUnderlyingAssets': False,
|
|
'fetchVolatilityHistory': False,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'transfer': True,
|
|
},
|
|
'urls': {
|
|
'logo': 'https://user-images.githubusercontent.com/1294454/61511972-24c39f00-aa01-11e9-9f7c-471f1d6e5214.jpg',
|
|
'api': {
|
|
'rest': 'https://api.latoken.com',
|
|
},
|
|
'www': 'https://latoken.com',
|
|
'doc': [
|
|
'https://api.latoken.com',
|
|
],
|
|
'fees': 'https://latoken.com/fees',
|
|
'referral': 'https://latoken.com/invite?r=mvgp2djk',
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'book/{currency}/{quote}': 1,
|
|
'chart/week': 1,
|
|
'chart/week/{currency}/{quote}': 1,
|
|
'currency': 1,
|
|
'currency/available': 1,
|
|
'currency/quotes': 1,
|
|
'currency/{currency}': 1,
|
|
'pair': 1,
|
|
'pair/available': 1,
|
|
'ticker': 1,
|
|
'ticker/{base}/{quote}': 1,
|
|
'time': 1,
|
|
'trade/history/{currency}/{quote}': 1,
|
|
'trade/fee/{currency}/{quote}': 1,
|
|
'trade/feeLevels': 1,
|
|
'transaction/bindings': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'auth/account': 1,
|
|
'auth/account/currency/{currency}/{type}': 1,
|
|
'auth/order': 1,
|
|
'auth/order/getOrder/{id}': 1,
|
|
'auth/order/pair/{currency}/{quote}': 1,
|
|
'auth/order/pair/{currency}/{quote}/active': 1,
|
|
'auth/stopOrder': 1,
|
|
'auth/stopOrder/getOrder/{id}': 1,
|
|
'auth/stopOrder/pair/{currency}/{quote}': 1,
|
|
'auth/stopOrder/pair/{currency}/{quote}/active': 1,
|
|
'auth/trade': 1,
|
|
'auth/trade/pair/{currency}/{quote}': 1,
|
|
'auth/trade/fee/{currency}/{quote}': 1,
|
|
'auth/transaction': 1,
|
|
'auth/transaction/bindings': 1,
|
|
'auth/transaction/bindings/{currency}': 1,
|
|
'auth/transaction/{id}': 1,
|
|
'auth/transfer': 1,
|
|
},
|
|
'post': {
|
|
'auth/order/cancel': 1,
|
|
'auth/order/cancelAll': 1,
|
|
'auth/order/cancelAll/{currency}/{quote}': 1,
|
|
'auth/order/place': 1,
|
|
'auth/spot/deposit': 1,
|
|
'auth/spot/withdraw': 1,
|
|
'auth/stopOrder/cancel': 1,
|
|
'auth/stopOrder/cancelAll': 1,
|
|
'auth/stopOrder/cancelAll/{currency}/{quote}': 1,
|
|
'auth/stopOrder/place': 1,
|
|
'auth/transaction/depositAddress': 1,
|
|
'auth/transaction/withdraw': 1,
|
|
'auth/transaction/withdraw/cancel': 1,
|
|
'auth/transaction/withdraw/confirm': 1,
|
|
'auth/transaction/withdraw/resendCode': 1,
|
|
'auth/transfer/email': 1,
|
|
'auth/transfer/id': 1,
|
|
'auth/transfer/phone': 1,
|
|
},
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'fees': {
|
|
'trading': {
|
|
'feeSide': 'get',
|
|
'tierBased': False,
|
|
'percentage': True,
|
|
'maker': self.parse_number('0.0049'),
|
|
'taker': self.parse_number('0.0049'),
|
|
},
|
|
},
|
|
'commonCurrencies': {
|
|
'BUX': 'Buxcoin',
|
|
'CBT': 'Community Business Token',
|
|
'CTC': 'CyberTronchain',
|
|
'DMD': 'Diamond Coin',
|
|
'FREN': 'Frenchie',
|
|
'GDX': 'GoldenX',
|
|
'GEC': 'Geco One',
|
|
'GEM': 'NFTmall',
|
|
'GMT': 'GMT Token',
|
|
'IMC': 'IMCoin',
|
|
'MT': 'Monarch',
|
|
'TPAY': 'Tetra Pay',
|
|
'TRADE': 'Smart Trade Coin',
|
|
'TSL': 'Treasure SL',
|
|
'UNO': 'Unobtanium',
|
|
'WAR': 'Warrior Token',
|
|
},
|
|
'exceptions': {
|
|
'exact': {
|
|
'INTERNAL_ERROR': ExchangeError, # internal server error. You can contact our support to solve self problem. {"message":"Internal Server Error","error":"INTERNAL_ERROR","status":"FAILURE"}
|
|
'SERVICE_UNAVAILABLE': ExchangeNotAvailable, # requested information currently not available. You can contact our support to solve self problem or retry later.
|
|
'NOT_AUTHORIZED': AuthenticationError, # user's query not authorized. Check if you are logged in.
|
|
'FORBIDDEN': PermissionDenied, # you don't have enough access rights.
|
|
'BAD_REQUEST': BadRequest, # some bad request, for example bad fields values or something else. Read response message for more information.
|
|
'NOT_FOUND': ExchangeError, # entity not found. Read message for more information.
|
|
'ACCESS_DENIED': PermissionDenied, # access is denied. Probably you don't have enough access rights, you contact our support.
|
|
'REQUEST_REJECTED': ExchangeError, # user's request rejected for some reasons. Check error message.
|
|
'HTTP_MEDIA_TYPE_NOT_SUPPORTED': BadRequest, # http media type not supported.
|
|
'MEDIA_TYPE_NOT_ACCEPTABLE': BadRequest, # media type not acceptable
|
|
'METHOD_ARGUMENT_NOT_VALID': BadRequest, # one of method argument is invalid. Check argument types and error message for more information.
|
|
'VALIDATION_ERROR': BadRequest, # check errors field to get reasons.
|
|
'ACCOUNT_EXPIRED': AccountSuspended, # restore your account or create a new one.
|
|
'BAD_CREDENTIALS': AuthenticationError, # invalid username or password.
|
|
'COOKIE_THEFT': AuthenticationError, # cookie has been stolen. Let's try reset your cookies.
|
|
'CREDENTIALS_EXPIRED': AccountSuspended, # credentials expired.
|
|
'INSUFFICIENT_AUTHENTICATION': AuthenticationError, # for example, 2FA required.
|
|
'UNKNOWN_LOCATION': AuthenticationError, # user logged from unusual location, email confirmation required.
|
|
'TOO_MANY_REQUESTS': RateLimitExceeded, # too many requests at the time. A response header X-Rate-Limit-Remaining indicates the number of allowed request per a period.
|
|
'INSUFFICIENT_FUNDS': InsufficientFunds, # {"message":"not enough balance on the spot account for currency(USDT), need(20.000)","error":"INSUFFICIENT_FUNDS","status":"FAILURE"}
|
|
'ORDER_VALIDATION': InvalidOrder, # {"message":"Quantity(0) is not positive","error":"ORDER_VALIDATION","status":"FAILURE"}
|
|
'BAD_TICKS': InvalidOrder, # {"status":"FAILURE","message":"Quantity(1.4) does not match quantity tick(10)","error":"BAD_TICKS","errors":null,"result":false}
|
|
},
|
|
'broad': {
|
|
'invalid API key, signature or digest': AuthenticationError, # {"result":false,"message":"invalid API key, signature or digest","error":"BAD_REQUEST","status":"FAILURE"}
|
|
'The API key was revoked': AuthenticationError, # {"result":false,"message":"The API key was revoked","error":"BAD_REQUEST","status":"FAILURE"}
|
|
'request expired or bad': InvalidNonce, # {"result":false,"message":"request expired or bad <timeAlive>/<timestamp> format","error":"BAD_REQUEST","status":"FAILURE"}
|
|
'For input string': BadRequest, # {"result":false,"message":"Internal error","error":"For input string: \"NaN\"","status":"FAILURE"}
|
|
'Unable to resolve currency by tag': BadSymbol, # {"message":"Unable to resolve currency by tag(None)","error":"NOT_FOUND","status":"FAILURE"}
|
|
"Can't find currency with tag": BadSymbol, # {"status":"FAILURE","message":"Can't find currency with tag = None","error":"NOT_FOUND","errors":null,"result":false}
|
|
'Unable to place order because pair is in inactive state': BadSymbol, # {"message":"Unable to place order because pair is in inactive state(PAIR_STATUS_INACTIVE)","error":"ORDER_VALIDATION","status":"FAILURE"}
|
|
'API keys are not available for': AccountSuspended, # {"result":false,"message":"API keys are not available for FROZEN user","error":"BAD_REQUEST","status":"FAILURE"}
|
|
},
|
|
},
|
|
'options': {
|
|
'defaultType': 'spot',
|
|
'types': {
|
|
'wallet': 'ACCOUNT_TYPE_WALLET',
|
|
'funding': 'ACCOUNT_TYPE_WALLET',
|
|
'spot': 'ACCOUNT_TYPE_SPOT',
|
|
},
|
|
'accounts': {
|
|
'ACCOUNT_TYPE_WALLET': 'wallet',
|
|
'ACCOUNT_TYPE_SPOT': 'spot',
|
|
},
|
|
'fetchTradingFee': {
|
|
'method': 'fetchPrivateTradingFee', # or 'fetchPublicTradingFee'
|
|
},
|
|
'timeDifference': 0, # the difference between system clock and exchange clock
|
|
'adjustForTimeDifference': True, # controls the adjustment logic upon instantiation
|
|
},
|
|
'features': {
|
|
'spot': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': False,
|
|
'stopLossPrice': False, # todo
|
|
'takeProfitPrice': False, # todo
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True, # todo: for non-trigger orders
|
|
'FOK': True,
|
|
'PO': False,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'selfTradePrevention': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyByCost': True,
|
|
'marketBuyRequiresPrice': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000, # todo
|
|
'untilDays': None,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': True,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000, # todo
|
|
'daysBackCanceled': 1,
|
|
'untilDays': None,
|
|
'trigger': True,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOHLCV': None,
|
|
},
|
|
'swap': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
})
|
|
|
|
def nonce(self):
|
|
return self.milliseconds() - self.options['timeDifference']
|
|
|
|
async def fetch_time(self, params={}) -> Int:
|
|
"""
|
|
fetches the current integer timestamp in milliseconds from the exchange server
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Time/operation/currentTime
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int: the current integer timestamp in milliseconds from the exchange server
|
|
"""
|
|
response = await self.publicGetTime(params)
|
|
#
|
|
# {
|
|
# "serverTime": 1570615577321
|
|
# }
|
|
#
|
|
return self.safe_integer(response, 'serverTime')
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for latoken
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Pair/operation/getActivePairs
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = await self.publicGetPair(params)
|
|
#
|
|
# [
|
|
# {
|
|
# "id":"dba4289b-6b46-4d94-bf55-49eec9a163ad",
|
|
# "status":"PAIR_STATUS_ACTIVE", # CURRENCY_STATUS_INACTIVE
|
|
# "baseCurrency":"fb9b53d6-bbf6-472f-b6ba-73cc0d606c9b",
|
|
# "quoteCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "priceTick":"0.000000100000000000",
|
|
# "priceDecimals":7,
|
|
# "quantityTick":"0.010000000",
|
|
# "quantityDecimals":2,
|
|
# "costDisplayDecimals":7,
|
|
# "created":1572957210501,
|
|
# "minOrderQuantity":"0",
|
|
# "maxOrderCostUsd":"999999999999999999",
|
|
# "minOrderCostUsd":"0",
|
|
# "externalSymbol":""
|
|
# }
|
|
# ]
|
|
#
|
|
if self.safe_bool(self.options, 'adjustForTimeDifference', False):
|
|
await self.load_time_difference()
|
|
currencies = self.safe_dict(self.options, 'cachedCurrencies', {})
|
|
currenciesById = self.index_by(currencies, 'id')
|
|
result = []
|
|
for i in range(0, len(response)):
|
|
market = response[i]
|
|
id = self.safe_string(market, 'id')
|
|
# the exchange shows them inverted
|
|
baseId = self.safe_string(market, 'baseCurrency')
|
|
quoteId = self.safe_string(market, 'quoteCurrency')
|
|
baseCurrency = self.safe_dict(currenciesById, baseId)
|
|
quoteCurrency = self.safe_dict(currenciesById, quoteId)
|
|
baseCurrencyInfo = self.safe_dict(baseCurrency, 'info')
|
|
quoteCurrencyInfo = self.safe_dict(quoteCurrency, 'info')
|
|
if baseCurrencyInfo is not None and quoteCurrencyInfo is not None:
|
|
base = self.safe_currency_code(self.safe_string(baseCurrencyInfo, 'tag'))
|
|
quote = self.safe_currency_code(self.safe_string(quoteCurrencyInfo, 'tag'))
|
|
lowercaseQuote = quote.lower()
|
|
capitalizedQuote = self.capitalize(lowercaseQuote)
|
|
status = self.safe_string(market, 'status')
|
|
result.append({
|
|
'id': id,
|
|
'symbol': base + '/' + quote,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': None,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': None,
|
|
'type': 'spot',
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'active': (status == 'PAIR_STATUS_ACTIVE'), # assuming True
|
|
'contract': False,
|
|
'linear': None,
|
|
'inverse': None,
|
|
'contractSize': None,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': self.safe_number(market, 'quantityTick'),
|
|
'price': self.safe_number(market, 'priceTick'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': self.safe_number(market, 'minOrderQuantity'),
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': self.safe_number(market, 'minOrderCost' + capitalizedQuote),
|
|
'max': self.safe_number(market, 'maxOrderCost' + capitalizedQuote),
|
|
},
|
|
},
|
|
'created': self.safe_integer(market, 'created'),
|
|
'info': market,
|
|
})
|
|
return result
|
|
|
|
async 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 = await self.publicGetCurrency(params)
|
|
#
|
|
# [
|
|
# {
|
|
# "id":"1a075819-9e0b-48fc-8784-4dab1d186d6d",
|
|
# "status":"CURRENCY_STATUS_ACTIVE",
|
|
# "type":"CURRENCY_TYPE_ALTERNATIVE", # CURRENCY_TYPE_CRYPTO, CURRENCY_TYPE_IEO
|
|
# "name":"MyCryptoBank",
|
|
# "tag":"MCB",
|
|
# "description":"",
|
|
# "logo":"",
|
|
# "decimals":18,
|
|
# "created":1572912000000,
|
|
# "tier":1,
|
|
# "assetClass":"ASSET_CLASS_UNKNOWN",
|
|
# "minTransferAmount":0
|
|
# },
|
|
# {
|
|
# "id":"db02758e-2507-46a5-a805-7bc60355b3eb",
|
|
# "status":"CURRENCY_STATUS_ACTIVE",
|
|
# "type":"CURRENCY_TYPE_FUTURES_CONTRACT",
|
|
# "name":"BTC USDT Futures Contract",
|
|
# "tag":"BTCUSDT",
|
|
# "description":"",
|
|
# "logo":"",
|
|
# "decimals":8,
|
|
# "created":1589459984395,
|
|
# "tier":1,
|
|
# "assetClass":"ASSET_CLASS_UNKNOWN",
|
|
# "minTransferAmount":0
|
|
# },
|
|
# ]
|
|
#
|
|
result: dict = {}
|
|
for i in range(0, len(response)):
|
|
currency = response[i]
|
|
id = self.safe_string(currency, 'id')
|
|
tag = self.safe_string(currency, 'tag')
|
|
code = self.safe_currency_code(tag)
|
|
currencyType = self.safe_string(currency, 'type')
|
|
isCrypto = (currencyType == 'CURRENCY_TYPE_CRYPTO' or currencyType == 'CURRENCY_TYPE_IEO')
|
|
result[code] = self.safe_currency_structure({
|
|
'id': id,
|
|
'code': code,
|
|
'info': currency,
|
|
'name': self.safe_string(currency, 'name'),
|
|
'type': 'crypto' if isCrypto else 'other',
|
|
'active': self.safe_string(currency, 'status') == 'CURRENCY_STATUS_ACTIVE',
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'fee': self.safe_number(currency, 'fee'),
|
|
'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 'decimals'))),
|
|
'limits': {
|
|
'amount': {
|
|
'min': self.safe_number(currency, 'minTransferAmount'),
|
|
'max': None,
|
|
},
|
|
'withdraw': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'networks': {},
|
|
})
|
|
return result
|
|
|
|
async def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Account/operation/getBalancesByUser
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.privateGetAuthAccount(params)
|
|
#
|
|
# [
|
|
# {
|
|
# "id": "e5852e02-8711-431c-9749-a6f5503c6dbe",
|
|
# "status": "ACCOUNT_STATUS_ACTIVE",
|
|
# "type": "ACCOUNT_TYPE_WALLET",
|
|
# "timestamp": "1635920106506",
|
|
# "currency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "available": "100.000000",
|
|
# "blocked": "0.000000"
|
|
# },
|
|
# {
|
|
# "id": "369df204-acbc-467e-a25e-b16e3cc09cf6",
|
|
# "status": "ACCOUNT_STATUS_ACTIVE",
|
|
# "type": "ACCOUNT_TYPE_SPOT",
|
|
# "timestamp": "1635920106504",
|
|
# "currency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "available": "100.000000",
|
|
# "blocked": "0.000000"
|
|
# }
|
|
# ]
|
|
#
|
|
result: dict = {
|
|
'info': response,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
}
|
|
maxTimestamp = None
|
|
defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
|
|
type = self.safe_string(params, 'type', defaultType)
|
|
types = self.safe_value(self.options, 'types', {})
|
|
accountType = self.safe_string(types, type, type)
|
|
balancesByType = self.group_by(response, 'type')
|
|
balances = self.safe_value(balancesByType, accountType, [])
|
|
for i in range(0, len(balances)):
|
|
balance = balances[i]
|
|
currencyId = self.safe_string(balance, 'currency')
|
|
timestamp = self.safe_integer(balance, 'timestamp')
|
|
if timestamp is not None:
|
|
if maxTimestamp is None:
|
|
maxTimestamp = timestamp
|
|
else:
|
|
maxTimestamp = max(maxTimestamp, timestamp)
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(balance, 'available')
|
|
account['used'] = self.safe_string(balance, 'blocked')
|
|
result[code] = account
|
|
result['timestamp'] = maxTimestamp
|
|
result['datetime'] = self.iso8601(maxTimestamp)
|
|
return self.safe_balance(result)
|
|
|
|
async 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://api.latoken.com/doc/v2/#tag/Order-Book/operation/getOrderBook
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'currency': market['baseId'],
|
|
'quote': market['quoteId'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit # max 1000
|
|
response = await self.publicGetBookCurrencyQuote(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "ask":[
|
|
# {"price":"4428.76","quantity":"0.08136","cost":"360.3239136","accumulated":"360.3239136"},
|
|
# {"price":"4429.77","quantity":"1.11786","cost":"4951.8626922","accumulated":"5312.1866058"},
|
|
# {"price":"4430.94","quantity":"1.78418","cost":"7905.5945292","accumulated":"13217.781135"},
|
|
# ],
|
|
# "bid":[
|
|
# {"price":"4428.43","quantity":"0.13675","cost":"605.5878025","accumulated":"605.5878025"},
|
|
# {"price":"4428.19","quantity":"0.03619","cost":"160.2561961","accumulated":"765.8439986"},
|
|
# {"price":"4428.15","quantity":"0.02926","cost":"129.567669","accumulated":"895.4116676"},
|
|
# ],
|
|
# "totalAsk":"53.14814",
|
|
# "totalBid":"112216.9029791"
|
|
# }
|
|
#
|
|
return self.parse_order_book(response, symbol, None, 'bid', 'ask', 'price', 'quantity')
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "symbol": "92151d82-df98-4d88-9a4d-284fa9eca49f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "baseCurrency": "92151d82-df98-4d88-9a4d-284fa9eca49f",
|
|
# "quoteCurrency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "volume24h": "165723597.189022176000000000",
|
|
# "volume7d": "934505768.625109571000000000",
|
|
# "change24h": "0.0200",
|
|
# "change7d": "-6.4200",
|
|
# "amount24h": "6438.457663100000000000",
|
|
# "amount7d": "35657.785013800000000000",
|
|
# "lastPrice": "25779.16",
|
|
# "lastQuantity": "0.248403300000000000",
|
|
# "bestBid": "25778.74",
|
|
# "bestBidQuantity": "0.6520232",
|
|
# "bestAsk": "25779.17",
|
|
# "bestAskQuantity": "0.4956043",
|
|
# "updateTimestamp": "1693965231406"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(ticker, 'symbol')
|
|
last = self.safe_string(ticker, 'lastPrice')
|
|
timestamp = self.safe_integer_omit_zero(ticker, 'updateTimestamp') # sometimes latoken provided '0' ts from /ticker endpoint
|
|
return self.safe_ticker({
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'low': None,
|
|
'high': None,
|
|
'bid': self.safe_string(ticker, 'bestBid'),
|
|
'bidVolume': self.safe_string(ticker, 'bestBidQuantity'),
|
|
'ask': self.safe_string(ticker, 'bestAsk'),
|
|
'askVolume': self.safe_string(ticker, 'bestAskQuantity'),
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': self.safe_string(ticker, 'change24h'),
|
|
'average': None,
|
|
'baseVolume': self.safe_string(ticker, 'amount24h'),
|
|
'quoteVolume': self.safe_string(ticker, 'volume24h'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
async 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://api.latoken.com/doc/v2/#tag/Ticker/operation/getTicker
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'base': market['baseId'],
|
|
'quote': market['quoteId'],
|
|
}
|
|
response = await self.publicGetTickerBaseQuote(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "symbol": "92151d82-df98-4d88-9a4d-284fa9eca49f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "baseCurrency": "92151d82-df98-4d88-9a4d-284fa9eca49f",
|
|
# "quoteCurrency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "volume24h": "165723597.189022176000000000",
|
|
# "volume7d": "934505768.625109571000000000",
|
|
# "change24h": "0.0200",
|
|
# "change7d": "-6.4200",
|
|
# "amount24h": "6438.457663100000000000",
|
|
# "amount7d": "35657.785013800000000000",
|
|
# "lastPrice": "25779.16",
|
|
# "lastQuantity": "0.248403300000000000",
|
|
# "bestBid": "25778.74",
|
|
# "bestBidQuantity": "0.6520232",
|
|
# "bestAsk": "25779.17",
|
|
# "bestAskQuantity": "0.4956043",
|
|
# "updateTimestamp": "1693965231406"
|
|
# }
|
|
#
|
|
return self.parse_ticker(response, market)
|
|
|
|
async 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://api.latoken.com/doc/v2/#tag/Ticker/operation/getAllTickers
|
|
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.publicGetTicker(params)
|
|
#
|
|
# [
|
|
# {
|
|
# "symbol": "92151d82-df98-4d88-9a4d-284fa9eca49f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "baseCurrency": "92151d82-df98-4d88-9a4d-284fa9eca49f",
|
|
# "quoteCurrency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "volume24h": "165723597.189022176000000000",
|
|
# "volume7d": "934505768.625109571000000000",
|
|
# "change24h": "0.0200",
|
|
# "change7d": "-6.4200",
|
|
# "amount24h": "6438.457663100000000000",
|
|
# "amount7d": "35657.785013800000000000",
|
|
# "lastPrice": "25779.16",
|
|
# "lastQuantity": "0.248403300000000000",
|
|
# "bestBid": "25778.74",
|
|
# "bestBidQuantity": "0.6520232",
|
|
# "bestAsk": "25779.17",
|
|
# "bestAskQuantity": "0.4956043",
|
|
# "updateTimestamp": "1693965231406"
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_tickers(response, symbols)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades(public)
|
|
#
|
|
# {
|
|
# "id":"c152f814-8eeb-44f0-8f3f-e5c568f2ffcf",
|
|
# "isMakerBuyer":false,
|
|
# "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "price":"4435.56",
|
|
# "quantity":"0.32534",
|
|
# "cost":"1443.0650904",
|
|
# "timestamp":1635854642725,
|
|
# "makerBuyer":false
|
|
# }
|
|
#
|
|
# fetchMyTrades(private)
|
|
#
|
|
# {
|
|
# "id":"02e02533-b4bf-4ba9-9271-24e2108dfbf7",
|
|
# "isMakerBuyer":false,
|
|
# "direction":"TRADE_DIRECTION_BUY",
|
|
# "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "price":"4564.32",
|
|
# "quantity":"0.01000",
|
|
# "cost":"45.6432",
|
|
# "fee":"0.223651680000000000",
|
|
# "order":"c9cac6a0-484c-4892-88e7-ad51b39f2ce1",
|
|
# "timestamp":1635921580399,
|
|
# "makerBuyer":false
|
|
# }
|
|
#
|
|
type = None
|
|
timestamp = self.safe_integer(trade, 'timestamp')
|
|
priceString = self.safe_string(trade, 'price')
|
|
amountString = self.safe_string(trade, 'quantity')
|
|
costString = self.safe_string(trade, 'cost')
|
|
makerBuyer = self.safe_value(trade, 'makerBuyer')
|
|
side = self.safe_string(trade, 'direction')
|
|
if side is None:
|
|
side = 'sell' if makerBuyer else 'buy'
|
|
else:
|
|
if side == 'TRADE_DIRECTION_BUY':
|
|
side = 'buy'
|
|
elif side == 'TRADE_DIRECTION_SELL':
|
|
side = 'sell'
|
|
isBuy = (side == 'buy')
|
|
takerOrMaker = 'maker' if (makerBuyer and isBuy) else 'taker'
|
|
baseId = self.safe_string(trade, 'baseCurrency')
|
|
quoteId = self.safe_string(trade, 'quoteCurrency')
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
symbol = base + '/' + quote
|
|
if symbol in self.markets:
|
|
market = self.market(symbol)
|
|
id = self.safe_string(trade, 'id')
|
|
orderId = self.safe_string(trade, 'order')
|
|
feeCost = self.safe_string(trade, 'fee')
|
|
fee = None
|
|
if feeCost is not None:
|
|
fee = {
|
|
'cost': feeCost,
|
|
'currency': quote,
|
|
}
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'id': id,
|
|
'order': orderId,
|
|
'type': type,
|
|
'takerOrMaker': takerOrMaker,
|
|
'side': side,
|
|
'price': priceString,
|
|
'amount': amountString,
|
|
'cost': costString,
|
|
'fee': fee,
|
|
}, market)
|
|
|
|
async 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://api.latoken.com/doc/v2/#tag/Trade/operation/getTradesByPair
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'currency': market['baseId'],
|
|
'quote': market['quoteId'],
|
|
# 'from': str(since), # milliseconds
|
|
# 'limit': limit, # default 100, limit 100
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = min(limit, 100) # default 100, limit 100
|
|
response = await self.publicGetTradeHistoryCurrencyQuote(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {"id":"c152f814-8eeb-44f0-8f3f-e5c568f2ffcf","isMakerBuyer":false,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.56","quantity":"0.32534","cost":"1443.0650904","timestamp":1635854642725,"makerBuyer":false},
|
|
# {"id":"cfecbefb-3d11-43d7-b9d4-fa16211aad8a","isMakerBuyer":false,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.13","quantity":"0.26540","cost":"1177.083502","timestamp":1635854641114,"makerBuyer":false},
|
|
# {"id":"f43d3ec8-db94-49f3-b534-91dbc2779296","isMakerBuyer":true,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.00","quantity":"0.41738","cost":"1851.0803","timestamp":1635854640323,"makerBuyer":true},
|
|
# ]
|
|
#
|
|
return self.parse_trades(response, market, since, limit)
|
|
|
|
async def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
|
"""
|
|
fetch the trading fees for a market
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Trade/operation/getFeeByPair
|
|
https://api.latoken.com/doc/v2/#tag/Trade/operation/getAuthFeeByPair
|
|
|
|
: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>`
|
|
"""
|
|
options = self.safe_value(self.options, 'fetchTradingFee', {})
|
|
defaultMethod = self.safe_string(options, 'method', 'fetchPrivateTradingFee')
|
|
method = self.safe_string(params, 'method', defaultMethod)
|
|
params = self.omit(params, 'method')
|
|
if method == 'fetchPrivateTradingFee':
|
|
return await self.fetch_private_trading_fee(symbol, params)
|
|
elif method == 'fetchPublicTradingFee':
|
|
return await self.fetch_public_trading_fee(symbol, params)
|
|
else:
|
|
raise NotSupported(self.id + ' not support self method')
|
|
|
|
async def fetch_public_trading_fee(self, symbol: str, params={}):
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'currency': market['baseId'],
|
|
'quote': market['quoteId'],
|
|
}
|
|
response = await self.publicGetTradeFeeCurrencyQuote(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "makerFee": "0.004900000000000000",
|
|
# "takerFee": "0.004900000000000000",
|
|
# "type": "FEE_SCHEME_TYPE_PERCENT_QUOTE",
|
|
# "take": "FEE_SCHEME_TAKE_PROPORTION"
|
|
# }
|
|
#
|
|
return {
|
|
'info': response,
|
|
'symbol': market['symbol'],
|
|
'maker': self.safe_number(response, 'makerFee'),
|
|
'taker': self.safe_number(response, 'takerFee'),
|
|
'percentage': None,
|
|
'tierBased': None,
|
|
}
|
|
|
|
async def fetch_private_trading_fee(self, symbol: str, params={}):
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'currency': market['baseId'],
|
|
'quote': market['quoteId'],
|
|
}
|
|
response = await self.privateGetAuthTradeFeeCurrencyQuote(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "makerFee": "0.004900000000000000",
|
|
# "takerFee": "0.004900000000000000",
|
|
# "type": "FEE_SCHEME_TYPE_PERCENT_QUOTE",
|
|
# "take": "FEE_SCHEME_TAKE_PROPORTION"
|
|
# }
|
|
#
|
|
return {
|
|
'info': response,
|
|
'symbol': market['symbol'],
|
|
'maker': self.safe_number(response, 'makerFee'),
|
|
'taker': self.safe_number(response, 'takerFee'),
|
|
'percentage': None,
|
|
'tierBased': None,
|
|
}
|
|
|
|
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Trade/operation/getTradesByTrader
|
|
https://api.latoken.com/doc/v2/#tag/Trade/operation/getTradesByAssetAndTrader
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trades structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
# 'currency': market['baseId'],
|
|
# 'quote': market['quoteId'],
|
|
# 'from': self.milliseconds(),
|
|
# 'limit': limit, # default '100'
|
|
}
|
|
market = None
|
|
if limit is not None:
|
|
request['limit'] = limit # default 100
|
|
response = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['currency'] = market['baseId']
|
|
request['quote'] = market['quoteId']
|
|
response = await self.privateGetAuthTradePairCurrencyQuote(self.extend(request, params))
|
|
else:
|
|
response = await self.privateGetAuthTrade(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id":"02e02533-b4bf-4ba9-9271-24e2108dfbf7",
|
|
# "isMakerBuyer":false,
|
|
# "direction":"TRADE_DIRECTION_BUY",
|
|
# "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "price":"4564.32",
|
|
# "quantity":"0.01000",
|
|
# "cost":"45.6432",
|
|
# "fee":"0.223651680000000000",
|
|
# "order":"c9cac6a0-484c-4892-88e7-ad51b39f2ce1",
|
|
# "timestamp":1635921580399,
|
|
# "makerBuyer":false
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_trades(response, market, since, limit)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'ORDER_STATUS_PLACED': 'open',
|
|
'ORDER_STATUS_CLOSED': 'closed',
|
|
'ORDER_STATUS_CANCELLED': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order_type(self, status):
|
|
statuses: dict = {
|
|
'ORDER_TYPE_MARKET': 'market',
|
|
'ORDER_TYPE_LIMIT': 'limit',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_time_in_force(self, timeInForce: Str):
|
|
timeInForces: dict = {
|
|
'ORDER_CONDITION_GOOD_TILL_CANCELLED': 'GTC',
|
|
'ORDER_CONDITION_IMMEDIATE_OR_CANCEL': 'IOC',
|
|
'ORDER_CONDITION_FILL_OR_KILL': 'FOK',
|
|
}
|
|
return self.safe_string(timeInForces, timeInForce, timeInForce)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# createOrder
|
|
#
|
|
# {
|
|
# "baseCurrency": "f7dac554-8139-4ff6-841f-0e586a5984a0",
|
|
# "quoteCurrency": "a5a7a7a9-e2a3-43f9-8754-29a02f6b709b",
|
|
# "side": "BID",
|
|
# "clientOrderId": "my-wonderful-order-number-71566",
|
|
# "price": "10103.19",
|
|
# "stopPrice": "10103.19",
|
|
# "quantity": "3.21",
|
|
# "timestamp": 1568185507
|
|
# }
|
|
#
|
|
# fetchOrder, fetchOpenOrders, fetchOrders
|
|
#
|
|
# {
|
|
# "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
|
|
# "status":"ORDER_STATUS_PLACED",
|
|
# "side":"ORDER_SIDE_BUY",
|
|
# "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
|
|
# "type":"ORDER_TYPE_LIMIT",
|
|
# "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
|
|
# "price":"4000.00",
|
|
# "quantity":"0.01",
|
|
# "cost":"40.000000000000000000",
|
|
# "filled":"0",
|
|
# "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
|
|
# "creator":"ORDER_CREATOR_USER",
|
|
# "creatorId":"",
|
|
# "timestamp":1635920767648
|
|
# }
|
|
#
|
|
# cancelOrder
|
|
#
|
|
# {
|
|
# "message":"cancellation request successfully submitted",
|
|
# "status":"SUCCESS",
|
|
# "id":"a631426d-3543-45ba-941e-75f7825afb0f"
|
|
# }
|
|
#
|
|
id = self.safe_string(order, 'id')
|
|
timestamp = self.safe_integer(order, 'timestamp')
|
|
baseId = self.safe_string(order, 'baseCurrency')
|
|
quoteId = self.safe_string(order, 'quoteCurrency')
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
symbol = None
|
|
if (base is not None) and (quote is not None):
|
|
symbol = base + '/' + quote
|
|
if symbol in self.markets:
|
|
market = self.market(symbol)
|
|
orderSide = self.safe_string(order, 'side')
|
|
side = None
|
|
if orderSide is not None:
|
|
parts = orderSide.split('_')
|
|
partsLength = len(parts)
|
|
side = self.safe_string_lower(parts, partsLength - 1)
|
|
type = self.parse_order_type(self.safe_string(order, 'type'))
|
|
price = self.safe_string(order, 'price')
|
|
amount = self.safe_string(order, 'quantity')
|
|
filled = self.safe_string(order, 'filled')
|
|
cost = self.safe_string(order, 'cost')
|
|
status = self.parse_order_status(self.safe_string(order, 'status'))
|
|
message = self.safe_string(order, 'message')
|
|
if message is not None:
|
|
if message.find('cancel') >= 0:
|
|
status = 'canceled'
|
|
elif message.find('accept') >= 0:
|
|
status = 'open'
|
|
clientOrderId = self.safe_string(order, 'clientOrderId')
|
|
timeInForce = self.parse_time_in_force(self.safe_string(order, 'condition'))
|
|
return self.safe_order({
|
|
'id': id,
|
|
'clientOrderId': clientOrderId,
|
|
'info': order,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'status': status,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': timeInForce,
|
|
'postOnly': None,
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': self.safe_string(order, 'stopPrice'),
|
|
'cost': cost,
|
|
'amount': amount,
|
|
'filled': filled,
|
|
'average': None,
|
|
'remaining': None,
|
|
'fee': None,
|
|
'trades': None,
|
|
}, market)
|
|
|
|
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Order/operation/getMyActiveOrdersByPair
|
|
https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getMyActiveStopOrdersByPair # stop
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch open orders for
|
|
:param int [limit]: the maximum number of open orders structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.trigger]: True if fetching trigger orders
|
|
: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')
|
|
await self.load_markets()
|
|
response = None
|
|
isTrigger = self.safe_value_2(params, 'trigger', 'stop')
|
|
params = self.omit(params, 'stop')
|
|
# privateGetAuthOrderActive doesn't work even though its listed at https://api.latoken.com/doc/v2/#tag/Order/operation/getMyActiveOrders
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'currency': market['baseId'],
|
|
'quote': market['quoteId'],
|
|
}
|
|
if isTrigger:
|
|
response = await self.privateGetAuthStopOrderPairCurrencyQuoteActive(self.extend(request, params))
|
|
else:
|
|
response = await self.privateGetAuthOrderPairCurrencyQuoteActive(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
|
|
# "status":"ORDER_STATUS_PLACED",
|
|
# "side":"ORDER_SIDE_BUY",
|
|
# "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
|
|
# "type":"ORDER_TYPE_LIMIT",
|
|
# "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
|
|
# "price":"4000.00",
|
|
# "quantity":"0.01000",
|
|
# "cost":"40.00",
|
|
# "filled":"0.00000",
|
|
# "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
|
|
# "creator":"USER",
|
|
# "creatorId":"",
|
|
# "timestamp":1635920767648
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_orders(response, market, since, limit)
|
|
|
|
async 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://api.latoken.com/doc/v2/#tag/Order/operation/getMyOrders
|
|
https://api.latoken.com/doc/v2/#tag/Order/operation/getMyOrdersByPair
|
|
https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getMyStopOrders # stop
|
|
https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getMyStopOrdersByPair # stop
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.trigger]: True if fetching trigger orders
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
# 'currency': market['baseId'],
|
|
# 'quote': market['quoteId'],
|
|
# 'from': self.milliseconds(),
|
|
# 'limit': limit, # default '100'
|
|
}
|
|
market = None
|
|
isTrigger = self.safe_value_2(params, 'trigger', 'stop')
|
|
params = self.omit(params, ['stop', 'trigger'])
|
|
if limit is not None:
|
|
request['limit'] = limit # default 100
|
|
response = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['currency'] = market['baseId']
|
|
request['quote'] = market['quoteId']
|
|
if isTrigger:
|
|
response = await self.privateGetAuthStopOrderPairCurrencyQuote(self.extend(request, params))
|
|
else:
|
|
response = await self.privateGetAuthOrderPairCurrencyQuote(self.extend(request, params))
|
|
else:
|
|
if isTrigger:
|
|
response = await self.privateGetAuthStopOrder(self.extend(request, params))
|
|
else:
|
|
response = await self.privateGetAuthOrder(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
|
|
# "status":"ORDER_STATUS_PLACED",
|
|
# "side":"ORDER_SIDE_BUY",
|
|
# "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
|
|
# "type":"ORDER_TYPE_LIMIT",
|
|
# "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
|
|
# "price":"4000.00",
|
|
# "quantity":"0.01000",
|
|
# "cost":"40.00",
|
|
# "filled":"0.00000",
|
|
# "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
|
|
# "creator":"USER",
|
|
# "creatorId":"",
|
|
# "timestamp":1635920767648
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_orders(response, market, since, limit)
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Order/operation/getOrderById
|
|
https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getStopOrderById
|
|
|
|
:param str id: order id
|
|
:param str [symbol]: not used by latoken fetchOrder
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.trigger]: True if fetching a trigger order
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'id': id,
|
|
}
|
|
isTrigger = self.safe_value_2(params, 'trigger', 'stop')
|
|
params = self.omit(params, ['stop', 'trigger'])
|
|
response = None
|
|
if isTrigger:
|
|
response = await self.privateGetAuthStopOrderGetOrderId(self.extend(request, params))
|
|
else:
|
|
response = await self.privateGetAuthOrderGetOrderId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
|
|
# "status":"ORDER_STATUS_PLACED",
|
|
# "side":"ORDER_SIDE_BUY",
|
|
# "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
|
|
# "type":"ORDER_TYPE_LIMIT",
|
|
# "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
|
|
# "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
|
|
# "price":"4000.00",
|
|
# "quantity":"0.01",
|
|
# "cost":"40.000000000000000000",
|
|
# "filled":"0",
|
|
# "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
|
|
# "creator":"ORDER_CREATOR_USER",
|
|
# "creatorId":"",
|
|
# "timestamp":1635920767648
|
|
# }
|
|
#
|
|
return self.parse_order(response)
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Order/operation/placeOrder
|
|
https://api.latoken.com/doc/v2/#tag/StopOrder/operation/placeStopOrder # stop
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much of currency you want to trade in units of base currency
|
|
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.triggerPrice]: the price at which a trigger order is triggered at
|
|
|
|
EXCHANGE SPECIFIC PARAMETERS
|
|
:param str [params.condition]: "GTC", "IOC", or "FOK"
|
|
:param str [params.clientOrderId]: [0 .. 50] characters, client's custom order id(free field for your convenience)
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
uppercaseType = type.upper()
|
|
request: dict = {
|
|
'baseCurrency': market['baseId'],
|
|
'quoteCurrency': market['quoteId'],
|
|
'side': side.upper(), # "BUY", "BID", "SELL", "ASK"
|
|
'condition': 'GTC', # "GTC", "GOOD_TILL_CANCELLED", "IOC", "IMMEDIATE_OR_CANCEL", "FOK", "FILL_OR_KILL"
|
|
'type': uppercaseType, # "LIMIT", "MARKET"
|
|
'clientOrderId': self.uuid(), # 50 characters max
|
|
# 'price': self.price_to_precision(symbol, price),
|
|
# 'quantity': self.amount_to_precision(symbol, amount),
|
|
'quantity': self.amount_to_precision(symbol, amount),
|
|
'timestamp': self.seconds(),
|
|
}
|
|
if uppercaseType == 'LIMIT':
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
|
params = self.omit(params, ['triggerPrice', 'stopPrice'])
|
|
response = None
|
|
if triggerPrice is not None:
|
|
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
|
|
response = await self.privatePostAuthStopOrderPlace(self.extend(request, params))
|
|
else:
|
|
response = await self.privatePostAuthOrderPlace(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "baseCurrency": "f7dac554-8139-4ff6-841f-0e586a5984a0",
|
|
# "quoteCurrency": "a5a7a7a9-e2a3-43f9-8754-29a02f6b709b",
|
|
# "side": "BID",
|
|
# "clientOrderId": "my-wonderful-order-number-71566",
|
|
# "price": "10103.19",
|
|
# "stopPrice": "10103.19",
|
|
# "quantity": "3.21",
|
|
# "timestamp": 1568185507
|
|
# }
|
|
#
|
|
return self.parse_order(response, market)
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Order/operation/cancelOrder
|
|
https://api.latoken.com/doc/v2/#tag/StopOrder/operation/cancelStopOrder # stop
|
|
|
|
:param str id: order id
|
|
:param str symbol: not used by latoken cancelOrder()
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.trigger]: True if cancelling a trigger order
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'id': id,
|
|
}
|
|
isTrigger = self.safe_value_2(params, 'trigger', 'stop')
|
|
params = self.omit(params, ['stop', 'trigger'])
|
|
response = None
|
|
if isTrigger:
|
|
response = await self.privatePostAuthStopOrderCancel(self.extend(request, params))
|
|
else:
|
|
response = await self.privatePostAuthOrderCancel(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": "12345678-1234-1244-1244-123456789012",
|
|
# "message": "cancellation request successfully submitted",
|
|
# "status": "SUCCESS",
|
|
# "error": "",
|
|
# "errors": {}
|
|
# }
|
|
#
|
|
return self.parse_order(response)
|
|
|
|
async def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders in a market
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Order/operation/cancelAllOrders
|
|
https://api.latoken.com/doc/v2/#tag/Order/operation/cancelAllOrdersByPair
|
|
|
|
:param str symbol: unified market symbol of the market to cancel orders in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.trigger]: True if cancelling trigger orders
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
# 'currency': market['baseId'],
|
|
# 'quote': market['quoteId'],
|
|
}
|
|
market = None
|
|
isTrigger = self.safe_value_2(params, 'trigger', 'stop')
|
|
params = self.omit(params, ['stop', 'trigger'])
|
|
response = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['currency'] = market['baseId']
|
|
request['quote'] = market['quoteId']
|
|
if isTrigger:
|
|
response = await self.privatePostAuthStopOrderCancelAllCurrencyQuote(self.extend(request, params))
|
|
else:
|
|
response = await self.privatePostAuthOrderCancelAllCurrencyQuote(self.extend(request, params))
|
|
else:
|
|
if isTrigger:
|
|
response = await self.privatePostAuthStopOrderCancelAll(self.extend(request, params))
|
|
else:
|
|
response = await self.privatePostAuthOrderCancelAll(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "message":"cancellation request successfully submitted",
|
|
# "status":"SUCCESS"
|
|
# }
|
|
#
|
|
return [
|
|
self.safe_order({
|
|
'info': response,
|
|
}),
|
|
]
|
|
|
|
async def fetch_transactions(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
@deprecated
|
|
use fetchDepositsWithdrawals instead
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Transaction/operation/getUserTransactions
|
|
|
|
:param str code: unified currency code for the currency of the transactions, default is None
|
|
:param int [since]: timestamp in ms of the earliest transaction, default is None
|
|
:param int [limit]: max number of transactions to return, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
# 'page': '1',
|
|
# 'size': 100,
|
|
}
|
|
response = await self.privateGetAuthTransaction(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "hasNext":false,
|
|
# "content":[
|
|
# {
|
|
# "id":"fbf7d0d1-2629-4ad8-9def-7a1dba423362",
|
|
# "status":"TRANSACTION_STATUS_CONFIRMED",
|
|
# "type":"TRANSACTION_TYPE_DEPOSIT",
|
|
# "senderAddress":"",
|
|
# "recipientAddress":"0x3c46fa2e3f9023bc4897828ed173f8ecb3a554bc",
|
|
# "amount":"200.000000000000000000",
|
|
# "transactionFee":"0.000000000000000000",
|
|
# "timestamp":1635893208404,
|
|
# "transactionHash":"0x28bad3b74a042df13d64ddfbca855566a51bf7f190b8cd565c236a18d5cd493f#42",
|
|
# "blockHeight":13540262,
|
|
# "currency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "memo":null,
|
|
# "paymentProvider":"a8d6d1cb-f84a-4e9d-aa82-c6a08b356ee1",
|
|
# "requiresCode":false
|
|
# }
|
|
# ],
|
|
# "first":true,
|
|
# "hasContent":true,
|
|
# "pageSize":10
|
|
# }
|
|
#
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
content = self.safe_list(response, 'content', [])
|
|
return self.parse_transactions(content, currency, since, limit)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# {
|
|
# "id":"fbf7d0d1-2629-4ad8-9def-7a1dba423362",
|
|
# "status":"TRANSACTION_STATUS_CONFIRMED",
|
|
# "type":"TRANSACTION_TYPE_DEPOSIT",
|
|
# "senderAddress":"",
|
|
# "recipientAddress":"0x3c46fa2e3f9023bc4897828ed173f8ecb3a554bc",
|
|
# "amount":"200.000000000000000000",
|
|
# "transactionFee":"0.000000000000000000",
|
|
# "timestamp":1635893208404,
|
|
# "transactionHash":"0x28bad3b74a042df13d64ddfbca855566a51bf7f190b8cd565c236a18d5cd493f#42",
|
|
# "blockHeight":13540262,
|
|
# "currency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "memo":null,
|
|
# "paymentProvider":"a8d6d1cb-f84a-4e9d-aa82-c6a08b356ee1",
|
|
# "requiresCode":false
|
|
# }
|
|
#
|
|
id = self.safe_string(transaction, 'id')
|
|
timestamp = self.safe_integer(transaction, 'timestamp')
|
|
currencyId = self.safe_string(transaction, 'currency')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
|
|
amount = self.safe_number(transaction, 'amount')
|
|
addressFrom = self.safe_string(transaction, 'senderAddress')
|
|
addressTo = self.safe_string(transaction, 'recipientAddress')
|
|
txid = self.safe_string(transaction, 'transactionHash')
|
|
tagTo = self.safe_string(transaction, 'memo')
|
|
fee = {
|
|
'currency': None,
|
|
'cost': None,
|
|
'rate': None,
|
|
}
|
|
feeCost = self.safe_number(transaction, 'transactionFee')
|
|
if feeCost is not None:
|
|
fee['cost'] = feeCost
|
|
fee['currency'] = code
|
|
type = self.parse_transaction_type(self.safe_string(transaction, 'type'))
|
|
return {
|
|
'info': transaction,
|
|
'id': id,
|
|
'txid': txid,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'network': None,
|
|
'addressFrom': addressFrom,
|
|
'addressTo': addressTo,
|
|
'address': addressTo,
|
|
'tagFrom': None,
|
|
'tagTo': tagTo,
|
|
'tag': tagTo,
|
|
'type': type,
|
|
'amount': amount,
|
|
'currency': code,
|
|
'status': status,
|
|
'updated': None,
|
|
'comment': None,
|
|
'internal': None,
|
|
'fee': fee,
|
|
}
|
|
|
|
def parse_transaction_status(self, status: Str):
|
|
statuses: dict = {
|
|
'TRANSACTION_STATUS_CONFIRMED': 'ok',
|
|
'TRANSACTION_STATUS_EXECUTED': 'ok',
|
|
'TRANSACTION_STATUS_CHECKING': 'pending',
|
|
'TRANSACTION_STATUS_CANCELLED': 'canceled',
|
|
'TRANSACTION_STATUS_FAILED': 'failed',
|
|
'TRANSACTION_STATUS_REJECTED': 'rejected',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_transaction_type(self, type):
|
|
types: dict = {
|
|
'TRANSACTION_TYPE_DEPOSIT': 'deposit',
|
|
'TRANSACTION_TYPE_WITHDRAWAL': 'withdrawal',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
async def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[TransferEntry]:
|
|
"""
|
|
fetch a history of internal transfers made on an account
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Transfer/operation/getUsersTransfers
|
|
|
|
:param str code: unified currency code of the currency transferred
|
|
:param int [since]: the earliest time in ms to fetch transfers for
|
|
:param int [limit]: the maximum number of transfers structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
response = await self.privateGetAuthTransfer(params)
|
|
#
|
|
# {
|
|
# "hasNext": True,
|
|
# "content": [
|
|
# {
|
|
# "id": "ebd6312f-cb4f-45d1-9409-4b0b3027f21e",
|
|
# "status": "TRANSFER_STATUS_COMPLETED",
|
|
# "type": "TRANSFER_TYPE_WITHDRAW_SPOT",
|
|
# "fromAccount": "c429c551-adbb-4078-b74b-276bea308a36",
|
|
# "toAccount": "631c6203-bd62-4734-a04d-9b2a951f43b9",
|
|
# "transferringFunds": 1259.0321785,
|
|
# "usdValue": 1259.032179,
|
|
# "rejectReason": null,
|
|
# "timestamp": 1633515579530,
|
|
# "direction": "INTERNAL",
|
|
# "method": "TRANSFER_METHOD_UNKNOWN",
|
|
# "recipient": null,
|
|
# "sender": null,
|
|
# "currency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
|
|
# "codeRequired": False,
|
|
# "fromUser": "ce555f3f-585d-46fb-9ae6-487f66738073",
|
|
# "toUser": "ce555f3f-585d-46fb-9ae6-487f66738073",
|
|
# "fee": 0
|
|
# },
|
|
# ...
|
|
# ],
|
|
# "first": True,
|
|
# "pageSize": 20,
|
|
# "hasContent": True
|
|
# }
|
|
#
|
|
transfers = self.safe_list(response, 'content', [])
|
|
return self.parse_transfers(transfers, currency, since, limit)
|
|
|
|
async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
|
"""
|
|
transfer currency internally between wallets on the same account
|
|
|
|
https://api.latoken.com/doc/v2/#tag/Transfer/operation/transferByEmail
|
|
https://api.latoken.com/doc/v2/#tag/Transfer/operation/transferById
|
|
https://api.latoken.com/doc/v2/#tag/Transfer/operation/transferByPhone
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: amount to transfer
|
|
:param str fromAccount: account to transfer from
|
|
:param str toAccount: account to transfer to
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'currency': currency['id'],
|
|
'recipient': toAccount,
|
|
'value': self.currency_to_precision(code, amount),
|
|
}
|
|
response = None
|
|
if toAccount.find('@') >= 0:
|
|
response = await self.privatePostAuthTransferEmail(self.extend(request, params))
|
|
elif len(toAccount) == 36:
|
|
response = await self.privatePostAuthTransferId(self.extend(request, params))
|
|
else:
|
|
response = await self.privatePostAuthTransferPhone(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": "e6fc4ace-7750-44e4-b7e9-6af038ac7107",
|
|
# "status": "TRANSFER_STATUS_COMPLETED",
|
|
# "type": "TRANSFER_TYPE_DEPOSIT_SPOT",
|
|
# "fromAccount": "3bf61015-bf32-47a6-b237-c9f70df772ad",
|
|
# "toAccount": "355eb279-7c7e-4515-814a-575a49dc0325",
|
|
# "transferringFunds": "500000.000000000000000000",
|
|
# "usdValue": "0.000000000000000000",
|
|
# "rejectReason": "",
|
|
# "timestamp": 1576844438402,
|
|
# "direction": "INTERNAL",
|
|
# "method": "TRANSFER_METHOD_UNKNOWN",
|
|
# "recipient": "",
|
|
# "sender": "",
|
|
# "currency": "40af7879-a8cc-4576-a42d-7d2749821b58",
|
|
# "codeRequired": False,
|
|
# "fromUser": "cd555555-666d-46fb-9ae6-487f66738073",
|
|
# "toUser": "cd555555-666d-46fb-9ae6-487f66738073",
|
|
# "fee": 0
|
|
# }
|
|
#
|
|
return self.parse_transfer(response)
|
|
|
|
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
|
#
|
|
# {
|
|
# "id": "e6fc4ace-7750-44e4-b7e9-6af038ac7107",
|
|
# "status": "TRANSFER_STATUS_COMPLETED",
|
|
# "type": "TRANSFER_TYPE_DEPOSIT_SPOT",
|
|
# "fromAccount": "3bf61015-bf32-47a6-b237-c9f70df772ad",
|
|
# "toAccount": "355eb279-7c7e-4515-814a-575a49dc0325",
|
|
# "transferringFunds": "500000.000000000000000000",
|
|
# "usdValue": "0.000000000000000000",
|
|
# "rejectReason": "",
|
|
# "timestamp": 1576844438402,
|
|
# "direction": "INTERNAL",
|
|
# "method": "TRANSFER_METHOD_UNKNOWN",
|
|
# "recipient": "",
|
|
# "sender": "",
|
|
# "currency": "40af7879-a8cc-4576-a42d-7d2749821b58",
|
|
# "codeRequired": False,
|
|
# "fromUser": "cd555555-666d-46fb-9ae6-487f66738073",
|
|
# "toUser": "cd555555-666d-46fb-9ae6-487f66738073",
|
|
# "fee": 0
|
|
# }
|
|
#
|
|
timestamp = self.safe_timestamp(transfer, 'timestamp')
|
|
currencyId = self.safe_string(transfer, 'currency')
|
|
status = self.safe_string(transfer, 'status')
|
|
return {
|
|
'info': transfer,
|
|
'id': self.safe_string(transfer, 'id'),
|
|
'timestamp': self.safe_integer(transfer, 'timestamp'),
|
|
'datetime': self.iso8601(timestamp),
|
|
'currency': self.safe_currency_code(currencyId, currency),
|
|
'amount': self.safe_number(transfer, 'transferringFunds'),
|
|
'fromAccount': self.safe_string(transfer, 'fromAccount'),
|
|
'toAccount': self.safe_string(transfer, 'toAccount'),
|
|
'status': self.parse_transfer_status(status),
|
|
}
|
|
|
|
def parse_transfer_status(self, status: Str) -> Str:
|
|
statuses: dict = {
|
|
'TRANSFER_STATUS_COMPLETED': 'ok',
|
|
'TRANSFER_STATUS_PENDING': 'pending',
|
|
'TRANSFER_STATUS_REJECTED': 'failed',
|
|
'TRANSFER_STATUS_UNVERIFIED': 'pending',
|
|
'TRANSFER_STATUS_CANCELLED': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def sign(self, path, api='public', method='GET', params=None, headers=None, body=None):
|
|
request = '/' + self.version + '/' + self.implode_params(path, params)
|
|
requestString = request
|
|
query = self.omit(params, self.extract_params(path))
|
|
urlencodedQuery = self.urlencode(query)
|
|
if method == 'GET':
|
|
if query:
|
|
requestString += '?' + urlencodedQuery
|
|
if api == 'private':
|
|
self.check_required_credentials()
|
|
auth = method + request + urlencodedQuery
|
|
signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha512)
|
|
headers = {
|
|
'X-LA-APIKEY': self.apiKey,
|
|
'X-LA-SIGNATURE': signature,
|
|
'X-LA-DIGEST': 'HMAC-SHA512', # HMAC-SHA384, HMAC-SHA512, optional
|
|
}
|
|
if method == 'POST':
|
|
headers['Content-Type'] = 'application/json'
|
|
body = self.json(query)
|
|
url = self.urls['api']['rest'] + requestString
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
if not response:
|
|
return None
|
|
#
|
|
# {"result":false,"message":"invalid API key, signature or digest","error":"BAD_REQUEST","status":"FAILURE"}
|
|
# {"result":false,"message":"request expired or bad <timeAlive>/<timestamp> format","error":"BAD_REQUEST","status":"FAILURE"}
|
|
# {"message":"Internal Server Error","error":"INTERNAL_ERROR","status":"FAILURE"}
|
|
# {"result":false,"message":"Internal error","error":"For input string: \"NaN\"","status":"FAILURE"}
|
|
#
|
|
message = self.safe_string(response, 'message')
|
|
feedback = self.id + ' ' + body
|
|
if message is not None:
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
error = self.safe_value(response, 'error')
|
|
errorMessage = self.safe_string(error, 'message')
|
|
if (error is not None) or (errorMessage is not None):
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
|
|
raise ExchangeError(feedback) # unknown message
|
|
return None
|