1419 lines
57 KiB
Python
1419 lines
57 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.yobit import ImplicitAPI
|
|
import asyncio
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, OrderBooks, Trade, TradingFees, Transaction
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.errors import DDoSProtection
|
|
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
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class yobit(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(yobit, self).describe(), {
|
|
'id': 'yobit',
|
|
'name': 'YoBit',
|
|
'countries': ['RU'],
|
|
'rateLimit': 2000, # responses are cached every 2 seconds
|
|
'version': '3',
|
|
'pro': False,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'borrowMargin': False,
|
|
'cancelOrder': True,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createDepositAddress': True,
|
|
'createMarketOrder': False,
|
|
'createOrder': True,
|
|
'createReduceOnlyOrder': False,
|
|
'createStopLimitOrder': False,
|
|
'createStopMarketOrder': False,
|
|
'createStopOrder': False,
|
|
'fetchAllGreeks': False,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': False,
|
|
'fetchDepositAddress': True,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDepositAddressesByNetwork': False,
|
|
'fetchDeposits': 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,
|
|
'fetchOrderBooks': True,
|
|
'fetchPosition': False,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': False,
|
|
'fetchPositionsForSymbol': False,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchSettlementHistory': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': True,
|
|
'fetchTransactions': False,
|
|
'fetchTransfer': False,
|
|
'fetchTransfers': False,
|
|
'fetchUnderlyingAssets': False,
|
|
'fetchVolatilityHistory': False,
|
|
'fetchWithdrawals': False,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'repayMargin': False,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': True,
|
|
'ws': False,
|
|
},
|
|
'urls': {
|
|
'logo': 'https://user-images.githubusercontent.com/1294454/27766910-cdcbfdae-5eea-11e7-9859-03fea873272d.jpg',
|
|
'api': {
|
|
'public': 'https://yobit.net/api',
|
|
'private': 'https://yobit.net/tapi',
|
|
},
|
|
'www': 'https://www.yobit.net',
|
|
'doc': 'https://www.yobit.net/en/api/',
|
|
'fees': 'https://www.yobit.net/en/fees/',
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'depth/{pair}': 1,
|
|
'info': 1,
|
|
'ticker/{pair}': 1,
|
|
'trades/{pair}': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'post': {
|
|
'ActiveOrders': 1,
|
|
'CancelOrder': 1,
|
|
'GetDepositAddress': 1,
|
|
'getInfo': 1,
|
|
'OrderInfo': 1,
|
|
'Trade': 1,
|
|
'TradeHistory': 1,
|
|
'WithdrawCoinsToAddress': 1,
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'maker': 0.002,
|
|
'taker': 0.002,
|
|
},
|
|
'funding': {
|
|
'withdraw': {},
|
|
},
|
|
},
|
|
'commonCurrencies': {
|
|
'AIR': 'AirCoin',
|
|
'ANI': 'ANICoin',
|
|
'ANT': 'AntsCoin', # what is self, a coin for ants?
|
|
'ATMCHA': 'ATM',
|
|
'ASN': 'Ascension',
|
|
'AST': 'Astral',
|
|
'ATM': 'Autumncoin',
|
|
'AUR': 'AuroraCoin',
|
|
'BAB': 'Babel',
|
|
'BAN': 'BANcoin',
|
|
'BCC': 'BCH',
|
|
'BCS': 'BitcoinStake',
|
|
'BITS': 'Bitstar',
|
|
'BLN': 'Bulleon',
|
|
'BNS': 'Benefit Bonus Coin',
|
|
'BOT': 'BOTcoin',
|
|
'BON': 'BONES',
|
|
'BPC': 'BitcoinPremium',
|
|
'BST': 'BitStone',
|
|
'BTS': 'Bitshares2',
|
|
'CAT': 'BitClave',
|
|
'CBC': 'CryptoBossCoin',
|
|
'CMT': 'CometCoin',
|
|
'COIN': 'Coin.com',
|
|
'COV': 'Coven Coin',
|
|
'COVX': 'COV',
|
|
'CPC': 'Capricoin',
|
|
'CREDIT': 'Creditbit',
|
|
'CS': 'CryptoSpots',
|
|
'DCT': 'Discount',
|
|
'DFT': 'DraftCoin',
|
|
'DGD': 'DarkGoldCoin',
|
|
'DIRT': 'DIRTY',
|
|
'DROP': 'FaucetCoin',
|
|
'DSH': 'DASH',
|
|
'EGC': 'EverGreenCoin',
|
|
'EGG': 'EggCoin',
|
|
'EKO': 'EkoCoin',
|
|
'ENTER': 'ENTRC',
|
|
'EPC': 'ExperienceCoin',
|
|
'ESC': 'EdwardSnowden',
|
|
'EUROPE': 'EUROP',
|
|
'EXT': 'LifeExtension',
|
|
'FUND': 'FUNDChains',
|
|
'FUNK': 'FUNKCoin',
|
|
'FX': 'FCoin',
|
|
'GCC': 'GlobalCryptocurrency',
|
|
'GEN': 'Genstake',
|
|
'GENE': 'Genesiscoin',
|
|
'GMR': 'Gimmer',
|
|
'GOLD': 'GoldMint',
|
|
'GOT': 'Giotto Coin',
|
|
'GSX': 'GlowShares',
|
|
'GT': 'GTcoin',
|
|
'HTML5': 'HTML',
|
|
'HYPERX': 'HYPER',
|
|
'ICN': 'iCoin',
|
|
'INSANE': 'INSN',
|
|
'JNT': 'JointCoin',
|
|
'JPC': 'JupiterCoin',
|
|
'JWL': 'Jewels',
|
|
'KNC': 'KingN Coin',
|
|
'LBTCX': 'LiteBitcoin',
|
|
'LIZI': 'LiZi',
|
|
'LOC': 'LocoCoin',
|
|
'LOCX': 'LOC',
|
|
'LUNYR': 'LUN',
|
|
'LUN': 'LunarCoin', # they just change the ticker if it is already taken
|
|
'LUNA': 'Luna Coin',
|
|
'MASK': 'Yobit MASK',
|
|
'MDT': 'Midnight',
|
|
'MEME': 'Memez Token', # conflict with Meme Inu / Degenerator Meme
|
|
'MIS': 'MIScoin',
|
|
'MM': 'MasterMint', # conflict with MilliMeter
|
|
'NAV': 'NavajoCoin',
|
|
'NBT': 'NiceBytes',
|
|
'OMG': 'OMGame',
|
|
'ONX': 'Onix',
|
|
'PAC': '$PAC',
|
|
'PLAY': 'PlayCoin',
|
|
'PIVX': 'Darknet',
|
|
'PURE': 'PurePOS',
|
|
'PUTIN': 'PutinCoin',
|
|
'SPACE': 'Spacecoin',
|
|
'STK': 'StakeCoin',
|
|
'SUB': 'Subscriptio',
|
|
'PAY': 'EPAY',
|
|
'PLC': 'Platin Coin',
|
|
'RAI': 'RaiderCoin',
|
|
'RCN': 'RCoin',
|
|
'REP': 'Republicoin',
|
|
'RUR': 'RUB',
|
|
'SBTC': 'Super Bitcoin',
|
|
'SMC': 'SmartCoin',
|
|
'SOLO': 'SoloCoin',
|
|
'SOUL': 'SoulCoin',
|
|
'STAR': 'StarCoin',
|
|
'SUPER': 'SuperCoin',
|
|
'TNS': 'Transcodium',
|
|
'TTC': 'TittieCoin',
|
|
'UNI': 'Universe',
|
|
'UST': 'Uservice',
|
|
'VOL': 'VolumeCoin',
|
|
'XIN': 'XINCoin',
|
|
'XMT': 'SummitCoin',
|
|
'XRA': 'Ratecoin',
|
|
'BCHN': 'BSV',
|
|
},
|
|
'options': {
|
|
'maxUrlLength': 2048,
|
|
'fetchOrdersRequiresSymbol': True,
|
|
'networks': {
|
|
'ETH': 'ERC20',
|
|
'TRX': 'TRC20',
|
|
'BSC': 'BEP20',
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'exceptions': {
|
|
'exact': {
|
|
'803': InvalidOrder, # "Count could not be less than 0.001."(selling below minAmount)
|
|
'804': InvalidOrder, # "Count could not be more than 10000."(buying above maxAmount)
|
|
'805': InvalidOrder, # "price could not be less than X."(minPrice violation on buy & sell)
|
|
'806': InvalidOrder, # "price could not be more than X."(maxPrice violation on buy & sell)
|
|
'807': InvalidOrder, # "cost could not be less than X."(minCost violation on buy & sell)
|
|
'831': InsufficientFunds, # "Not enougth X to create buy order."(buying with balance.quote < order.cost)
|
|
'832': InsufficientFunds, # "Not enougth X to create sell order."(selling with balance.base < order.amount)
|
|
'833': OrderNotFound, # "Order with id X was not found."(cancelling non-existent, closed and cancelled order)
|
|
},
|
|
'broad': {
|
|
'Invalid pair name': ExchangeError, # {"success":0,"error":"Invalid pair name: btc_eth"}
|
|
'invalid api key': AuthenticationError,
|
|
'invalid sign': AuthenticationError,
|
|
'api key dont have trade permission': AuthenticationError,
|
|
'invalid parameter': InvalidOrder,
|
|
'invalid order': InvalidOrder,
|
|
'The given order has already been cancelled': InvalidOrder,
|
|
'Requests too often': DDoSProtection,
|
|
'not available': ExchangeNotAvailable,
|
|
'data unavailable': ExchangeNotAvailable,
|
|
'external service unavailable': ExchangeNotAvailable,
|
|
'Total transaction amount': InvalidOrder, # {"success": 0, "error": "Total transaction amount is less than minimal total: 0.00010000"}
|
|
'The given order has already been closed and cannot be cancelled': InvalidOrder,
|
|
'Insufficient funds': InsufficientFunds,
|
|
'invalid key': AuthenticationError,
|
|
'invalid nonce': InvalidNonce, # {"success":0,"error":"invalid nonce(has already been used)"}'
|
|
'Total order amount is less than minimal amount': InvalidOrder,
|
|
'Rate Limited': RateLimitExceeded,
|
|
},
|
|
},
|
|
'features': {
|
|
'spot': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': False,
|
|
'triggerDirection': False,
|
|
'triggerPriceType': None,
|
|
'stopLossPrice': False,
|
|
'takeProfitPrice': False,
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': False,
|
|
'FOK': False,
|
|
'PO': False,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyByCost': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000, # todo
|
|
'untilDays': 100000, # todo
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': None,
|
|
'fetchOHLCV': None,
|
|
},
|
|
'swap': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
'orders': {}, # orders cache / emulation
|
|
})
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
balances = self.safe_dict(response, 'return', {})
|
|
timestamp = self.safe_integer(balances, 'server_time')
|
|
result: dict = {
|
|
'info': response,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
free = self.safe_dict(balances, 'funds', {})
|
|
total = self.safe_dict(balances, 'funds_incl_orders', {})
|
|
currencyIds = list(self.extend(free, total).keys())
|
|
for i in range(0, len(currencyIds)):
|
|
currencyId = currencyIds[i]
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(free, currencyId)
|
|
account['total'] = self.safe_string(total, currencyId)
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
async def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.privatePostGetInfo(params)
|
|
#
|
|
# {
|
|
# "success":1,
|
|
# "return":{
|
|
# "funds":{
|
|
# "ltc":22,
|
|
# "nvc":423.998,
|
|
# "ppc":10,
|
|
# },
|
|
# "funds_incl_orders":{
|
|
# "ltc":32,
|
|
# "nvc":523.998,
|
|
# "ppc":20,
|
|
# },
|
|
# "rights":{
|
|
# "info":1,
|
|
# "trade":0,
|
|
# "withdraw":0
|
|
# },
|
|
# "transaction_count":0,
|
|
# "open_orders":1,
|
|
# "server_time":1418654530
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_balance(response)
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
retrieves data on all markets for yobit
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = await self.publicGetInfo(params)
|
|
#
|
|
# {
|
|
# "server_time":1615856752,
|
|
# "pairs":{
|
|
# "ltc_btc":{
|
|
# "decimal_places":8,
|
|
# "min_price":0.00000001,
|
|
# "max_price":10000,
|
|
# "min_amount":0.0001,
|
|
# "min_total":0.0001,
|
|
# "hidden":0,
|
|
# "fee":0.2,
|
|
# "fee_buyer":0.2,
|
|
# "fee_seller":0.2
|
|
# },
|
|
# },
|
|
# }
|
|
#
|
|
markets = self.safe_dict(response, 'pairs', {})
|
|
keys = list(markets.keys())
|
|
result = []
|
|
for i in range(0, len(keys)):
|
|
id = keys[i]
|
|
market = markets[id]
|
|
baseId, quoteId = id.split('_')
|
|
base = baseId.upper()
|
|
quote = quoteId.upper()
|
|
base = self.safe_currency_code(base)
|
|
quote = self.safe_currency_code(quote)
|
|
hidden = self.safe_integer(market, 'hidden')
|
|
feeString = self.safe_string(market, 'fee')
|
|
feeString = Precise.string_div(feeString, '100')
|
|
# yobit maker = taker
|
|
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': (hidden == 0),
|
|
'contract': False,
|
|
'linear': None,
|
|
'inverse': None,
|
|
'taker': self.parse_number(feeString),
|
|
'maker': self.parse_number(feeString),
|
|
'contractSize': None,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'decimal_places'))),
|
|
'price': self.parse_number(self.parse_precision(self.safe_string(market, 'decimal_places'))),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': self.safe_number(market, 'min_amount'),
|
|
'max': self.safe_number(market, 'max_amount'),
|
|
},
|
|
'price': {
|
|
'min': self.safe_number(market, 'min_price'),
|
|
'max': self.safe_number(market, 'max_price'),
|
|
},
|
|
'cost': {
|
|
'min': self.safe_number(market, 'min_total'),
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None,
|
|
'info': market,
|
|
})
|
|
return result
|
|
|
|
async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit # default = 150, max = 2000
|
|
response = await self.publicGetDepthPair(self.extend(request, params))
|
|
market_id_in_reponse = (market['id'] in response)
|
|
if not market_id_in_reponse:
|
|
raise ExchangeError(self.id + ' ' + market['symbol'] + ' order book is empty or not available')
|
|
orderbook = response[market['id']]
|
|
return self.parse_order_book(orderbook, symbol)
|
|
|
|
async def fetch_order_books(self, symbols: Strings = None, limit: Int = None, params={}) -> OrderBooks:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data for multiple markets
|
|
:param str[]|None symbols: list of unified market symbols, all symbols fetched if None, default is None
|
|
:param int [limit]: max number of entries per orderbook to return, default is None
|
|
: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 symbol
|
|
"""
|
|
await self.load_markets()
|
|
ids = None
|
|
if symbols is None:
|
|
ids = '-'.join(self.ids)
|
|
# max URL length is 2083 symbols, including http schema, hostname, tld, etc...
|
|
if len(ids) > 2048:
|
|
numIds = len(self.ids)
|
|
raise ExchangeError(self.id + ' fetchOrderBooks() has ' + str(numIds) + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks')
|
|
else:
|
|
ids = self.market_ids(symbols)
|
|
ids = '-'.join(ids)
|
|
request: dict = {
|
|
'pair': ids,
|
|
# 'ignore_invalid': True,
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.publicGetDepthPair(self.extend(request, params))
|
|
result: dict = {}
|
|
ids = list(response.keys())
|
|
for i in range(0, len(ids)):
|
|
id = ids[i]
|
|
symbol = self.safe_symbol(id)
|
|
result[symbol] = self.parse_order_book(response[id], symbol)
|
|
return result
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "high": 0.03497582,
|
|
# "low": 0.03248474,
|
|
# "avg": 0.03373028,
|
|
# "vol": 120.11485715062999,
|
|
# "vol_cur": 3572.24914074,
|
|
# "last": 0.0337611,
|
|
# "buy": 0.0337442,
|
|
# "sell": 0.03377798,
|
|
# "updated": 1537522009
|
|
# }
|
|
#
|
|
timestamp = self.safe_timestamp(ticker, 'updated')
|
|
last = self.safe_string(ticker, 'last')
|
|
return self.safe_ticker({
|
|
'symbol': self.safe_symbol(None, market),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_string(ticker, 'high'),
|
|
'low': self.safe_string(ticker, 'low'),
|
|
'bid': self.safe_string(ticker, 'buy'),
|
|
'bidVolume': None,
|
|
'ask': self.safe_string(ticker, 'sell'),
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': self.safe_string(ticker, 'avg'),
|
|
'baseVolume': self.safe_string(ticker, 'vol_cur'),
|
|
'quoteVolume': self.safe_string(ticker, 'vol'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
async def fetch_tickers_helper(self, idsString: str, params={}) -> Tickers:
|
|
request: dict = {
|
|
'pair': idsString,
|
|
}
|
|
tickers = await self.publicGetTickerPair(self.extend(request, params))
|
|
result: dict = {}
|
|
keys = list(tickers.keys())
|
|
for k in range(0, len(keys)):
|
|
id = keys[k]
|
|
ticker = tickers[id]
|
|
market = self.safe_market(id)
|
|
symbol = market['symbol']
|
|
result[symbol] = self.parse_ticker(ticker, market)
|
|
return result
|
|
|
|
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param dict [params.all]: you can set to `true` for convenience to fetch all tickers from self exchange by sending multiple requests
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
allSymbols = None
|
|
allSymbols, params = self.handle_param_bool(params, 'all', False)
|
|
if symbols is None and not allSymbols:
|
|
raise ArgumentsRequired(self.id + ' fetchTickers() requires "symbols" argument or use `params["all"] = True` to send multiple requests for all markets')
|
|
await self.load_markets()
|
|
promises = []
|
|
maxLength = self.safe_integer(self.options, 'maxUrlLength', 2048)
|
|
# max URL length is 2048 symbols, including http schema, hostname, tld, etc...
|
|
lenghtOfBaseUrl = 40 # safe space for the url including api-base and endpoint dir is 30 chars
|
|
if allSymbols:
|
|
symbols = self.symbols
|
|
ids = ''
|
|
for i in range(0, len(self.ids)):
|
|
id = self.ids[i]
|
|
prefix = '' if (ids == '') else '-'
|
|
ids += prefix + id
|
|
if len(ids) > maxLength:
|
|
promises.append(self.fetch_tickers_helper(ids, params))
|
|
ids = ''
|
|
if ids != '':
|
|
promises.append(self.fetch_tickers_helper(ids, params))
|
|
else:
|
|
symbols = self.market_symbols(symbols)
|
|
ids = self.market_ids(symbols)
|
|
idsLength: number = len(ids)
|
|
idsString = '-'.join(ids)
|
|
actualLength = len(idsString) + lenghtOfBaseUrl
|
|
if actualLength > maxLength:
|
|
raise ArgumentsRequired(self.id + ' fetchTickers() is being requested for ' + str(idsLength) + ' markets(which has an URL length of ' + str(actualLength) + ' characters), but it exceedes max URL length(' + str(maxLength) + '), please pass limisted symbols array to fetchTickers to fit in one request')
|
|
promises.append(self.fetch_tickers_helper(idsString, params))
|
|
resultAll = await asyncio.gather(*promises)
|
|
finalResult = {}
|
|
for i in range(0, len(resultAll)):
|
|
result = self.filter_by_array_tickers(resultAll[i], 'symbol', symbols)
|
|
finalResult = self.extend(finalResult, result)
|
|
return finalResult
|
|
|
|
async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
tickers = await self.fetch_tickers([symbol], params)
|
|
return tickers[symbol]
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades(public)
|
|
#
|
|
# {
|
|
# "type":"bid",
|
|
# "price":0.14046179,
|
|
# "amount":0.001,
|
|
# "tid":200256901,
|
|
# "timestamp":1649861004
|
|
# }
|
|
#
|
|
# fetchMyTrades(private)
|
|
#
|
|
# {
|
|
# "pair":"doge_usdt",
|
|
# "type":"sell",
|
|
# "amount":139,
|
|
# "rate":0.139,
|
|
# "order_id":"2101103631773172",
|
|
# "is_your_order":1,
|
|
# "timestamp":"1649861561"
|
|
# }
|
|
#
|
|
timestamp = self.safe_timestamp(trade, 'timestamp')
|
|
side = self.safe_string(trade, 'type')
|
|
if side == 'ask':
|
|
side = 'sell'
|
|
elif side == 'bid':
|
|
side = 'buy'
|
|
priceString = self.safe_string_2(trade, 'rate', 'price')
|
|
id = self.safe_string_2(trade, 'trade_id', 'tid')
|
|
order = self.safe_string(trade, 'order_id')
|
|
marketId = self.safe_string(trade, 'pair')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
amountString = self.safe_string(trade, 'amount')
|
|
# arguments for calculateFee(need to be numbers)
|
|
price = self.parse_number(priceString)
|
|
amount = self.parse_number(amountString)
|
|
type = 'limit' # all trades are still limit trades
|
|
fee = None
|
|
feeCostString = self.safe_number(trade, 'commission')
|
|
if feeCostString is not None:
|
|
feeCurrencyId = self.safe_string(trade, 'commissionCurrency')
|
|
feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
|
|
fee = {
|
|
'cost': feeCostString,
|
|
'currency': feeCurrencyCode,
|
|
}
|
|
isYourOrder = self.safe_string(trade, 'is_your_order')
|
|
if isYourOrder is not None:
|
|
if fee is None:
|
|
feeInNumbers = self.calculate_fee(symbol, type, side, amount, price, 'taker')
|
|
fee = {
|
|
'currency': self.safe_string(feeInNumbers, 'currency'),
|
|
'cost': self.safe_string(feeInNumbers, 'cost'),
|
|
'rate': self.safe_string(feeInNumbers, 'rate'),
|
|
}
|
|
return self.safe_trade({
|
|
'id': id,
|
|
'order': order,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'side': side,
|
|
'takerOrMaker': None,
|
|
'price': priceString,
|
|
'amount': amountString,
|
|
'cost': None,
|
|
'fee': fee,
|
|
'info': trade,
|
|
}, market)
|
|
|
|
async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
get the list of most recent trades for a particular symbol
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.publicGetTradesPair(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "doge_usdt": [
|
|
# {
|
|
# "type":"ask",
|
|
# "price":0.13956743,
|
|
# "amount":0.0008,
|
|
# "tid":200256900,
|
|
# "timestamp":1649860521
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
if isinstance(response, list):
|
|
numElements = len(response)
|
|
if numElements == 0:
|
|
return []
|
|
result = self.safe_list(response, market['id'], [])
|
|
return self.parse_trades(result, market, since, limit)
|
|
|
|
async def fetch_trading_fees(self, params={}) -> TradingFees:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetch the trading fees for multiple markets
|
|
: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
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.publicGetInfo(params)
|
|
#
|
|
# {
|
|
# "server_time":1615856752,
|
|
# "pairs":{
|
|
# "ltc_btc":{
|
|
# "decimal_places":8,
|
|
# "min_price":0.00000001,
|
|
# "max_price":10000,
|
|
# "min_amount":0.0001,
|
|
# "min_total":0.0001,
|
|
# "hidden":0,
|
|
# "fee":0.2,
|
|
# "fee_buyer":0.2,
|
|
# "fee_seller":0.2
|
|
# },
|
|
# ...
|
|
# },
|
|
# }
|
|
#
|
|
pairs = self.safe_dict(response, 'pairs', {})
|
|
marketIds = list(pairs.keys())
|
|
result: dict = {}
|
|
for i in range(0, len(marketIds)):
|
|
marketId = marketIds[i]
|
|
pair = self.safe_dict(pairs, marketId, {})
|
|
symbol = self.safe_symbol(marketId, None, '_')
|
|
takerString = self.safe_string(pair, 'fee_buyer')
|
|
makerString = self.safe_string(pair, 'fee_seller')
|
|
taker = self.parse_number(Precise.string_div(takerString, '100'))
|
|
maker = self.parse_number(Precise.string_div(makerString, '100'))
|
|
result[symbol] = {
|
|
'info': pair,
|
|
'symbol': symbol,
|
|
'taker': taker,
|
|
'maker': maker,
|
|
'percentage': True,
|
|
'tierBased': False,
|
|
}
|
|
return result
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
create a trade order
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: must be '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>`
|
|
"""
|
|
if type == 'market':
|
|
raise ExchangeError(self.id + ' createOrder() allows limit orders only')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
'type': side,
|
|
'amount': self.amount_to_precision(symbol, amount),
|
|
'rate': self.price_to_precision(symbol, price),
|
|
}
|
|
response = await self.privatePostTrade(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success":1,
|
|
# "return": {
|
|
# "received":0,
|
|
# "remains":10,
|
|
# "order_id":1101103635125179,
|
|
# "funds": {
|
|
# "usdt":27.84756553,
|
|
# "usdttrc20":0,
|
|
# "doge":19.98327206
|
|
# },
|
|
# "funds_incl_orders": {
|
|
# "usdt":30.35256553,
|
|
# "usdttrc20":0,
|
|
# "doge":19.98327206
|
|
# },
|
|
# "server_time":1650114256
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'return')
|
|
return self.parse_order(result, market)
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
cancels an open order
|
|
:param str id: order id
|
|
:param str symbol: not used by yobit cancelOrder()
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'order_id': int(id),
|
|
}
|
|
response = await self.privatePostCancelOrder(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success":1,
|
|
# "return": {
|
|
# "order_id":1101103632552304,
|
|
# "funds": {
|
|
# "usdt":30.71055443,
|
|
# "usdttrc20":0,
|
|
# "doge":9.98327206
|
|
# },
|
|
# "funds_incl_orders": {
|
|
# "usdt":31.81275443,
|
|
# "usdttrc20":0,
|
|
# "doge":9.98327206
|
|
# },
|
|
# "server_time":1649918298
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'return', {})
|
|
return self.parse_order(result)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'0': 'open',
|
|
'1': 'closed',
|
|
'2': 'canceled',
|
|
'3': 'open', # or partially-filled and canceled? https://github.com/ccxt/ccxt/issues/1594
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# createOrder(private)
|
|
#
|
|
# {
|
|
# "received":0,
|
|
# "remains":10,
|
|
# "order_id":1101103635125179,
|
|
# "funds": {
|
|
# "usdt":27.84756553,
|
|
# "usdttrc20":0,
|
|
# "doge":19.98327206
|
|
# },
|
|
# "funds_incl_orders": {
|
|
# "usdt":30.35256553,
|
|
# "usdttrc20":0,
|
|
# "doge":19.98327206
|
|
# },
|
|
# "server_time":1650114256
|
|
# }
|
|
#
|
|
# fetchOrder(private)
|
|
#
|
|
# {
|
|
# "id: "1101103635103335", # id-field is manually added in fetchOrder() from exchange response id-order dictionary structure
|
|
# "pair":"doge_usdt",
|
|
# "type":"buy",
|
|
# "start_amount":10,
|
|
# "amount":10,
|
|
# "rate":0.05,
|
|
# "timestamp_created":"1650112553",
|
|
# "status":0
|
|
# }
|
|
#
|
|
# fetchOpenOrders(private)
|
|
#
|
|
# {
|
|
# "id":"1101103635103335", # id-field is manually added in fetchOpenOrders() from exchange response id-order dictionary structure
|
|
# "pair":"doge_usdt",
|
|
# "type":"buy",
|
|
# "amount":10,
|
|
# "rate":0.05,
|
|
# "timestamp_created":"1650112553",
|
|
# "status":0
|
|
# }
|
|
#
|
|
# cancelOrder(private)
|
|
#
|
|
# {
|
|
# "order_id":1101103634000197,
|
|
# "funds": {
|
|
# "usdt":31.81275443,
|
|
# "usdttrc20":0,
|
|
# "doge":9.98327206
|
|
# },
|
|
# "funds_incl_orders": {
|
|
# "usdt":31.81275443,
|
|
# "usdttrc20":0,
|
|
# "doge":9.98327206
|
|
# }
|
|
# }
|
|
#
|
|
id = self.safe_string_2(order, 'id', 'order_id')
|
|
status = self.parse_order_status(self.safe_string(order, 'status', 'open'))
|
|
if id == '0':
|
|
id = self.safe_string(order, 'init_order_id')
|
|
status = 'closed'
|
|
timestamp = self.safe_timestamp_2(order, 'timestamp_created', 'server_time')
|
|
marketId = self.safe_string(order, 'pair')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
amount = self.safe_string(order, 'start_amount')
|
|
remaining = self.safe_string_2(order, 'amount', 'remains')
|
|
filled = self.safe_string(order, 'received', '0.0')
|
|
price = self.safe_string(order, 'rate')
|
|
fee = None
|
|
type = 'limit'
|
|
side = self.safe_string(order, 'type')
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': id,
|
|
'clientOrderId': None,
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'type': type,
|
|
'timeInForce': None,
|
|
'postOnly': None,
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': None,
|
|
'cost': None,
|
|
'amount': amount,
|
|
'remaining': remaining,
|
|
'filled': filled,
|
|
'status': status,
|
|
'fee': fee,
|
|
'average': None,
|
|
'trades': None,
|
|
}, market)
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetches information on an order made by the user
|
|
:param str id: order id
|
|
:param str symbol: not used by yobit fetchOrder
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'order_id': int(id),
|
|
}
|
|
response = await self.privatePostOrderInfo(self.extend(request, params))
|
|
id = str(id)
|
|
orders = self.safe_dict(response, 'return', {})
|
|
#
|
|
# {
|
|
# "success":1,
|
|
# "return": {
|
|
# "1101103635103335": {
|
|
# "pair":"doge_usdt",
|
|
# "type":"buy",
|
|
# "start_amount":10,
|
|
# "amount":10,
|
|
# "rate":0.05,
|
|
# "timestamp_created":"1650112553",
|
|
# "status":0
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_order(self.extend({'id': id}, orders[id]))
|
|
|
|
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetch all unfilled currently open orders
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch open orders for
|
|
:param int [limit]: the maximum number of open order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
: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()
|
|
request: dict = {}
|
|
market = None
|
|
if symbol is not None:
|
|
marketInner = self.market(symbol)
|
|
request['pair'] = marketInner['id']
|
|
response = await self.privatePostActiveOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success":1,
|
|
# "return": {
|
|
# "1101103634006799": {
|
|
# "pair":"doge_usdt",
|
|
# "type":"buy",
|
|
# "amount":10,
|
|
# "rate":0.1,
|
|
# "timestamp_created":"1650034937",
|
|
# "status":0
|
|
# },
|
|
# "1101103634006738": {
|
|
# "pair":"doge_usdt",
|
|
# "type":"buy",
|
|
# "amount":10,
|
|
# "rate":0.1,
|
|
# "timestamp_created":"1650034932",
|
|
# "status":0
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'return', {})
|
|
return self.parse_orders(result, market, since, limit)
|
|
|
|
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
fetch all trades made by the user
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trades structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
# some derived classes use camelcase notation for request fields
|
|
request: dict = {
|
|
# 'from': 123456789, # trade ID, from which the display starts numerical 0(test result: liqui ignores self field)
|
|
# 'count': 1000, # the number of trades for display numerical, default = 1000
|
|
# 'from_id': trade ID, from which the display starts numerical 0
|
|
# 'end_id': trade ID on which the display ends numerical ∞
|
|
# 'order': 'ASC', # sorting, default = DESC(test result: liqui ignores self field, most recent trade always goes last)
|
|
# 'since': 1234567890, # UTC start time, default = 0(test result: liqui ignores self field)
|
|
# 'end': 1234567890, # UTC end time, default = ∞(test result: liqui ignores self field)
|
|
'pair': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['count'] = limit
|
|
if since is not None:
|
|
request['since'] = self.parse_to_int(since / 1000)
|
|
response = await self.privatePostTradeHistory(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success":1,
|
|
# "return": {
|
|
# "200257004": {
|
|
# "pair":"doge_usdt",
|
|
# "type":"sell",
|
|
# "amount":139,
|
|
# "rate":0.139,
|
|
# "order_id":"2101103631773172",
|
|
# "is_your_order":1,
|
|
# "timestamp":"1649861561"
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
trades = self.safe_dict(response, 'return', {})
|
|
ids = list(trades.keys())
|
|
result = []
|
|
for i in range(0, len(ids)):
|
|
id = self.safe_string(ids, i)
|
|
trade = self.parse_trade(self.extend(trades[id], {
|
|
'trade_id': id,
|
|
}), market)
|
|
result.append(trade)
|
|
return self.filter_by_symbol_since_limit(result, market['symbol'], since, limit)
|
|
|
|
async def create_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
create a currency deposit address
|
|
:param str code: unified currency code of the currency for the deposit address
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
request: dict = {
|
|
'need_new': 1,
|
|
}
|
|
response = await self.fetch_deposit_address(code, self.extend(request, params))
|
|
address = self.safe_string(response, 'address')
|
|
self.check_address(address)
|
|
return {
|
|
'currency': code,
|
|
'address': address,
|
|
'tag': None,
|
|
'network': None,
|
|
'info': response['info'],
|
|
}
|
|
|
|
async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://yobit.net/en/api
|
|
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
currencyId = currency['id']
|
|
networks = self.safe_dict(self.options, 'networks', {})
|
|
network = self.safe_string_upper(params, 'network') # self line allows the user to specify either ERC20 or ETH
|
|
network = self.safe_string(networks, network, network) # handle ERC20>ETH alias
|
|
if network is not None:
|
|
if network != 'ERC20':
|
|
currencyId = currencyId + network.lower()
|
|
params = self.omit(params, 'network')
|
|
request: dict = {
|
|
'coinName': currencyId,
|
|
'need_new': 0,
|
|
}
|
|
response = await self.privatePostGetDepositAddress(self.extend(request, params))
|
|
address = self.safe_string(response['return'], 'address')
|
|
self.check_address(address)
|
|
return {
|
|
'info': response,
|
|
'currency': code,
|
|
'network': None,
|
|
'address': address,
|
|
'tag': None,
|
|
}
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
|
|
https://yobit.net/en/api
|
|
|
|
make a withdrawal
|
|
:param str code: unified currency code
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str tag:
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
|
self.check_address(address)
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'coinName': currency['id'],
|
|
'amount': amount,
|
|
'address': address,
|
|
}
|
|
# no docs on the tag, yet...
|
|
if tag is not None:
|
|
raise ExchangeError(self.id + ' withdraw() does not support the tag argument yet due to a lack of docs on withdrawing with tag/memo on behalf of the exchange.')
|
|
response = await self.privatePostWithdrawCoinsToAddress(self.extend(request, params))
|
|
return {
|
|
'info': response,
|
|
'id': None,
|
|
'txid': None,
|
|
'type': None,
|
|
'currency': None,
|
|
'network': None,
|
|
'amount': None,
|
|
'status': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'address': None,
|
|
'addressFrom': None,
|
|
'addressTo': None,
|
|
'tag': None,
|
|
'tagFrom': None,
|
|
'tagTo': None,
|
|
'updated': None,
|
|
'comment': None,
|
|
'fee': {
|
|
'currency': None,
|
|
'cost': None,
|
|
'rate': None,
|
|
},
|
|
}
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
url = self.urls['api'][api]
|
|
query = self.omit(params, self.extract_params(path))
|
|
if api == 'private':
|
|
self.check_required_credentials()
|
|
nonce = self.nonce()
|
|
body = self.urlencode(self.extend({
|
|
'nonce': nonce,
|
|
'method': path,
|
|
}, query))
|
|
signature = self.hmac(self.encode(body), self.encode(self.secret), hashlib.sha512)
|
|
headers = {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'Key': self.apiKey,
|
|
'Sign': signature,
|
|
}
|
|
elif api == 'public':
|
|
url += '/' + self.version + '/' + self.implode_params(path, params)
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
else:
|
|
url += '/' + self.implode_params(path, params)
|
|
if method == 'GET':
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
else:
|
|
if query:
|
|
body = self.json(query)
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
if response is None:
|
|
return None # fallback to default error handler
|
|
if 'success' in response:
|
|
#
|
|
# 1 - Liqui only returns the integer 'success' key from their private API
|
|
#
|
|
# {"success": 1, ...} httpCode == 200
|
|
# {"success": 0, ...} httpCode == 200
|
|
#
|
|
# 2 - However, exchanges derived from Liqui, can return non-integers
|
|
#
|
|
# It can be a numeric string
|
|
# {"sucesss": "1", ...}
|
|
# {"sucesss": "0", ...}, httpCode >= 200(can be 403, 502, etc)
|
|
#
|
|
# Or just a string
|
|
# {"success": "true", ...}
|
|
# {"success": "false", ...}, httpCode >= 200
|
|
#
|
|
# Or a boolean
|
|
# {"success": True, ...}
|
|
# {"success": False, ...}, httpCode >= 200
|
|
#
|
|
# 3 - Oversimplified, Python PEP8 forbids comparison operator(==) of different types
|
|
#
|
|
# 4 - We do not want to copy-paste and duplicate the code of self handler to other exchanges derived from Liqui
|
|
#
|
|
# To cover points 1, 2, 3 and 4 combined self handler should work like self:
|
|
#
|
|
success = self.safe_value(response, 'success') # don't replace with safeBool here
|
|
if isinstance(success, str):
|
|
if (success == 'true') or (success == '1'):
|
|
success = True
|
|
else:
|
|
success = False
|
|
if not success:
|
|
code = self.safe_string(response, 'code')
|
|
message = self.safe_string(response, 'error')
|
|
feedback = self.id + ' ' + body
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
raise ExchangeError(feedback) # unknown message
|
|
return None
|