Source code for binpan.api.exchange_info

"""
BinPan Classes Main Module
"""

import pandas as pd
from datetime import datetime, timezone
from decimal import Decimal as dd
import numpy as np

from .market import get_prices_dic, _get_panzer
from .auth import signed_get
from ..core.logs import LogManager

stablecoins = ['PAX', 'TUSD', 'USDC', 'USDS', 'USDT', 'BUSD', 'DAI', 'UST', 'USDP', 'TRIBE', 'USSD', 'FDUSD', 'FRAX', 'USDJ']

exchange_logger = LogManager(filename='./logs/exchange_logger.log', name='exchange_logger', info_level='INFO')

#########################
# exchange general data #
#########################

[docs] def get_exchange_info() -> dict: """ Returns general exchange info from /api/v3/exchangeInfo endpoint. :return dict: dict_keys(['timezone', 'serverTime', 'rateLimits', 'exchangeFilters', 'symbols']) Example: .. code-block:: { "timezone": "UTC", "serverTime": 1565246363776, "rateLimits": [ { //These are defined in the `ENUM definitions` section under `Rate Limiters (rateLimitType)`. //All limits are optional } ], "exchangeFilters": [ //These are the defined filters in the `Filters` section. //All filters are optional. ], "symbols": [ { "symbol": "ETHBTC", "status": "TRADING", "baseAsset": "ETH", "baseAssetPrecision": 8, "quoteAsset": "BTC", "quotePrecision": 8, "quoteAssetPrecision": 8, "orderTypes": [ "LIMIT", "LIMIT_MAKER", "MARKET", "STOP_LOSS", "STOP_LOSS_LIMIT", "TAKE_PROFIT", "TAKE_PROFIT_LIMIT" ], "icebergAllowed": true, "ocoAllowed": true, "quoteOrderQtyMarketAllowed": true, "allowTrailingStop": false, "cancelReplaceAllowed": false, "isSpotTradingAllowed": true, "isMarginTradingAllowed": true, "filters": [ //These are defined in the Filters section. //All filters are optional ], "permissions": [ "SPOT", "MARGIN" ] } ] } """ client = _get_panzer() return client.exchange_info()
[docs] def get_info_dic() -> dict: """ Get the dictionary of each symbol with its information from the exchange. :return dict: Returns info for each symbol as keys in the dict. Example: .. code-block:: from binpan.api import exchange_info info_dic = exchange_info.get_info_dic() info_dic['ETHUSDT'] {'symbol': 'ETHUSDT', 'status': 'TRADING', 'baseAsset': 'ETH', 'baseAssetPrecision': 8, 'quoteAsset': 'USDT', 'quotePrecision': 8, 'quoteAssetPrecision': 8, 'baseCommissionPrecision': 8, 'quoteCommissionPrecision': 8, 'orderTypes': ['LIMIT', 'LIMIT_MAKER', 'MARKET', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT_LIMIT'], 'icebergAllowed': True, 'ocoAllowed': True, 'quoteOrderQtyMarketAllowed': True, 'allowTrailingStop': True, 'cancelReplaceAllowed': True, 'isSpotTradingAllowed': True, 'isMarginTradingAllowed': True, 'filters': [{'filterType': 'PRICE_FILTER', 'minPrice': '0.01000000', 'maxPrice': '1000000.00000000', 'tickSize': '0.01000000'}, {'filterType': 'PERCENT_PRICE', 'multiplierUp': '5', 'multiplierDown': '0.2', 'avgPriceMins': 5}, {'filterType': 'LOT_SIZE', 'minQty': '0.00010000', 'maxQty': '9000.00000000', 'stepSize': '0.00010000'}, {'filterType': 'MIN_NOTIONAL', 'minNotional': '10.00000000', 'applyToMarket': True, 'avgPriceMins': 5}, {'filterType': 'ICEBERG_PARTS', 'limit': 10}, {'filterType': 'MARKET_LOT_SIZE', 'minQty': '0.00000000', 'maxQty': '6175.01628506', 'stepSize': '0.00000000'}, {'filterType': 'TRAILING_DELTA', 'minTrailingAboveDelta': 10, 'maxTrailingAboveDelta': 2000, 'minTrailingBelowDelta': 10, 'maxTrailingBelowDelta': 2000}, {'filterType': 'MAX_NUM_ORDERS', 'maxNumOrders': 200}, {'filterType': 'MAX_NUM_ALGO_ORDERS', 'maxNumAlgoOrders': 5}], 'permissions': ['SPOT', 'MARGIN', 'TRD_GRP_004']} """ return {k['symbol']: k for k in get_exchange_info()['symbols']}
########### # account # ###########
[docs] def get_account_status(decimal_mode: bool) -> dict: """ Fetch account status detail. For Machine Learning limits, restrictions will be applied to account. If a user has been restricted by the ML system, they may check the reason and the duration by using the [/sapi/v1/account/status] endpoint :return dict: Example: .. code-block:: { "data": "Normal" } """ endpoint = '/sapi/v1/account/status' return signed_get(endpoint=endpoint, decimal_mode=decimal_mode)
[docs] def get_margin_bnb_interest_status(decimal_mode: bool) -> dict: """ Get BNB Burn Status (USER_DATA) GET /sapi/v1/bnbBurn (HMAC SHA256) Weight(IP): 1 :return dict: Example .. code-block:: { "spotBNBBurn":true, "interestBNBBurn": false } """ endpoint = '/sapi/v1/bnbBurn' return signed_get(endpoint=endpoint, decimal_mode=decimal_mode)
################# # weight limits # #################
[docs] def get_exchange_limits(info_dict: dict = None) -> dict: """ Binance manage several limits: RAW_REQUESTS, REQUEST_WEIGHT, and ORDERS rate limits. The headers for those limits, I assume that are: - RAW_REQUESTS: x-mbx-used-weight. Is cross all the api calls. - REQUEST_WEIGHT: Example: x-mbx-order-count-10s. Time interval limited requests. - ORDERS: Example: x-mbx-order-count-10s. Rate limit for orders. - X-SAPI-USED-IP-WEIGHT-1M: For sapi endpoint requests. Example response: .. code-block:: {'x-mbx-used-weight-1m': 1200, 'X-SAPI-USED-IP-WEIGHT-1M': 1200, 'X-SAPI-USED-UID-WEIGHT-1M': 1200, 'x-mbx-order-count-10s': 50, 'x-mbx-order-count-1d': 160000, 'x-mbx-used-weight': 6100} :return dict: """ if not info_dict: info_dict = get_exchange_info() limits = info_dict['rateLimits'] limits_dict = {} for i in limits: k3 = None if i['rateLimitType'].upper() == 'ORDERS': k1 = f"x-mbx-order-count-{i['intervalNum']}{i['interval'][0].lower()}" k2 = f"x-mbx-order-count-{i['intervalNum']}{i['interval'][0].lower()}" elif i['rateLimitType'].upper() == 'REQUEST_WEIGHT': k1 = f"x-mbx-used-weight-{i['intervalNum']}{i['interval'][0].lower()}" k2 = f"X-SAPI-USED-IP-WEIGHT-{i['intervalNum']}{i['interval'][0].upper()}" k3 = f"X-SAPI-USED-UID-WEIGHT-{i['intervalNum']}{i['interval'][0].upper()}" elif i['rateLimitType'].upper() == 'RAW_REQUESTS': k1 = "x-mbx-used-weight" k2 = "x-mbx-used-weight" else: raise Exception("BinPan Rate Limit not parsed") v = i['limit'] limits_dict[k1] = v limits_dict[k2] = v if k3: limits_dict[k3] = v return limits_dict
################## # symbol filters # ##################
[docs] def flatten_filter(filters: list) -> dict: """ It flattens a dict to one level dictionary. :param list filters: A dict with API filter data. :return dict: A one level flattened dict with keys expanded with original sub-dicts. """ ret = {} for f in filters: head = f['filterType'] for k, v in f.items(): if k != 'filterType': new_key = f"{head}_{k}" ret.update({new_key: v}) return ret
[docs] def get_symbols_filters(info_dic: dict = None) -> dict: """ Example: .. code-block:: Python exchange.get_filters('TLMBUSD', info_dic=info_dic) {'TLMBUSD': {'PRICE_FILTER_minPrice': '0.00001000', 'PRICE_FILTER_maxPrice': '1000.00000000', 'PRICE_FILTER_tickSize': '0.00001000', 'PERCENT_PRICE_multiplierUp': '5', 'PERCENT_PRICE_multiplierDown': '0.2', 'PERCENT_PRICE_avgPriceMins': 5, 'LOT_SIZE_minQty': '1.00000000', 'LOT_SIZE_maxQty': '900000.00000000', 'LOT_SIZE_stepSize': '1.00000000', 'MIN_NOTIONAL_minNotional': '10.00000000', 'MIN_NOTIONAL_applyToMarket': True, 'MIN_NOTIONAL_avgPriceMins': 5, 'ICEBERG_PARTS_limit': 10, 'MARKET_LOT_SIZE_minQty': '0.00000000', 'MARKET_LOT_SIZE_maxQty': '1181959.38584316', 'MARKET_LOT_SIZE_stepSize': '0.00000000', 'TRAILING_DELTA_minTrailingAboveDelta': 10, 'TRAILING_DELTA_maxTrailingAboveDelta': 2000, 'TRAILING_DELTA_minTrailingBelowDelta': 10, 'TRAILING_DELTA_maxTrailingBelowDelta': 2000, 'MAX_NUM_ORDERS_maxNumOrders': 200, 'MAX_NUM_ALGO_ORDERS_maxNumAlgoOrders': 5}} :param dict info_dic: A BinPan exchange info dictionary :return dict: A dict with all flatten values. """ if not info_dic: info_dic = get_info_dic() return {k: flatten_filter(v['filters']) for k, v in info_dic.items()}
[docs] def get_filters(symbol: str, info_dic: dict = None) -> dict: """ For a symbol, get exchange filter conditions. :param str symbol: A Binance symbol. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. It's optional to avoid an API call. :return a dict: A dictionary with exclusively data for a symbol. Example: .. code-block:: from binpan.api import exchange_info filters = exchange_info.get_filters('ETHBTC') filters {'ETHBTC': {'PRICE_FILTER_minPrice': '0.00000100', 'PRICE_FILTER_maxPrice': '922327.00000000', 'PRICE_FILTER_tickSize': '0.00000100', 'PERCENT_PRICE_multiplierUp': '5', 'PERCENT_PRICE_multiplierDown': '0.2', 'PERCENT_PRICE_avgPriceMins': 5, 'LOT_SIZE_minQty': '0.00010000', 'LOT_SIZE_maxQty': '100000.00000000', 'LOT_SIZE_stepSize': '0.00010000', 'MIN_NOTIONAL_minNotional': '0.00010000', 'MIN_NOTIONAL_applyToMarket': True, 'MIN_NOTIONAL_avgPriceMins': 5, 'ICEBERG_PARTS_limit': 10, 'MARKET_LOT_SIZE_minQty': '0.00000000', 'MARKET_LOT_SIZE_maxQty': '1135.26522780', 'MARKET_LOT_SIZE_stepSize': '0.00000000', 'TRAILING_DELTA_minTrailingAboveDelta': 10, 'TRAILING_DELTA_maxTrailingAboveDelta': 2000, 'TRAILING_DELTA_minTrailingBelowDelta': 10, 'TRAILING_DELTA_maxTrailingBelowDelta': 2000, 'MAX_NUM_ORDERS_maxNumOrders': 200, 'MAX_NUM_ALGO_ORDERS_maxNumAlgoOrders': 5}} """ if not info_dic: info_dic = get_info_dic() return {k: flatten_filter(v['filters']) for k, v in info_dic.items() if k == symbol}
#################################### # symbol characteristics filtering # ####################################
[docs] def filter_tradeable(info_dic: dict) -> dict: """ Returns, from BinPan exchange info dictionary currently trading symbols. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dict: BinPan exchange info dictionary, but, just with currently trading symbols. """ return {k: v for k, v in info_dic.items() if v['status'].upper() == 'TRADING'}
def _get_permissions(symbol_info: dict) -> set: """ Extracts permissions from symbol info, supporting both legacy 'permissions' and new 'permissionSets' format. :param dict symbol_info: Single symbol info dictionary from exchange API. :return set: Set of permission strings (e.g. {'SPOT', 'MARGIN'}). """ perms = set(symbol_info.get('permissions', [])) for pset in symbol_info.get('permissionSets', []): perms.update(pset) return perms
[docs] def filter_spot(info_dic: dict) -> dict: """ Returns, from BinPan exchange info dictionary currently SPOT symbols. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dict: BinPan exchange info dictionary, but, just with currently SPOT symbols. """ return {k: v for k, v in info_dic.items() if 'SPOT' in _get_permissions(v)}
[docs] def filter_margin(info_dic: dict) -> dict: """ Returns, from BinPan exchange info dictionary currently MARGIN symbols. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dict: BinPan exchange info dictionary, but, just with currently MARGIN symbols. """ return {k: v for k, v in info_dic.items() if 'MARGIN' in _get_permissions(v)}
[docs] def filter_not_margin(symbols: list = None, info_dic: dict = None) -> list: """ Returns, from BinPan exchange info dictionary currently NOT MARGIN symbols. :param list symbols: A list of symbols to apply filter. Optional. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return list: BinPan exchange info dictionary, but, just with currently NOT MARGIN symbols. """ if not info_dic: info_dic = get_info_dic() permissions_dic = {k: _get_permissions(v) for k, v in info_dic.items()} if symbols: return [s for s, p in permissions_dic.items() if 'MARGIN' in p and s in symbols] else: return [s for s, p in permissions_dic.items() if 'MARGIN' in p]
[docs] def filter_leveraged_tokens(info_dic: dict) -> dict: """ Returns, from BinPan exchange info dictionary currently NOT LEVERAGED symbols. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dict: BinPan exchange info dictionary, but, just with currently NOT LEVERAGED symbols. """ return {k: v for k, v in info_dic.items() if all(lev not in k for lev in ['UP', 'DOWN', 'BULL', 'BEAR'])}
################# # Exchange Data # #################
[docs] def get_precision(info_dic: dict = None) -> dict: """ Gets a dictionary with decimal positions each symbol. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dict: dictionary with decimal positions each symbol. """ if not info_dic: info_dic = get_info_dic() return {k: {'baseAssetPrecision': v['baseAssetPrecision'], 'quoteAssetPrecision': v['quoteAssetPrecision'], 'baseCommissionPrecision': v['baseCommissionPrecision'], 'quoteCommissionPrecision': v['quoteCommissionPrecision']} for k, v in info_dic.items()}
[docs] def get_orderTypes_and_permissions(info_dic: dict = None) -> dict: """ Gets a dictionary with a list of order types suppoerted each symbol. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dict: dictionary with a list of order types suppoerted each symbol. """ if not info_dic: info_dic = get_info_dic() return {k: {'orderTypes': v['orderTypes'], 'permissions': list(_get_permissions(v))} for k, v in info_dic.items()}
[docs] def get_fees_dict(decimal_mode: bool, symbol: str = None ) -> dict: """ Returns fees for a symbol or for every symbol if not passed a symbol. :param str symbol: Optional to request just one symbol instead of all. :param bool decimal_mode: Fixes Decimal return type. :return dict: A dict with maker and taker fees. """ endpoint = '/sapi/v1/asset/tradeFee' if symbol: symbol = symbol.upper() ret = signed_get(endpoint, params={'symbol': symbol}, decimal_mode=decimal_mode) if decimal_mode: return {i['symbol']: {'makerCommission': dd(i['makerCommission']), 'takerCommission': dd(i['takerCommission'])} for i in ret} else: return {i['symbol']: {'makerCommission': float(i['makerCommission']), 'takerCommission': float(i['takerCommission'])} for i in ret}
[docs] def get_fees(decimal_mode: bool, symbol: str = None) -> pd.DataFrame: """ Returns fees for a symbol or for every symbol if not passed. :param bool decimal_mode: Fixes Decimal return type and operative. :param str symbol: Optional to request just one symbol instead of all. :return pd.DataFrame: A pandas dataframe with all the fees applied each symbol. """ ret = get_fees_dict(symbol=symbol, decimal_mode=decimal_mode) return pd.DataFrame(ret).transpose()
[docs] def get_system_status(): """ Fetch system status. Weight(IP): 1 :return dict: As shown in example. Example: .. code-block:: { "status": 0, // 0: normal,1:system maintenance "msg": "normal" // "normal", "system_maintenance" } """ client = _get_panzer() return client.get('/sapi/v1/system/status', weight=1)['msg']
[docs] def get_coins_and_networks_info(decimal_mode: bool) -> tuple: """ Get information of coins (available for deposit and withdraw) for user. GET /sapi/v1/capital/config/getall (HMAC SHA256) Weight(IP): 10 :return pd.DataFrame, pd.DataFrame: """ ret = signed_get(endpoint='/sapi/v1/capital/config/getall', decimal_mode=decimal_mode) networks = [] coins = [] for i in ret: network_list = i.pop('networkList', None) if network_list: networks += network_list coins.append(i) # networks = list(set(networks)) coins_df = pd.DataFrame(coins).drop_duplicates().set_index('coin') for col in coins_df.columns: try: coins_df[col] = pd.to_numeric(arg=coins_df[col]) except (ValueError, TypeError): pass networks_df = pd.DataFrame(networks).drop_duplicates().set_index('coin') for col in networks_df.columns: try: networks_df[col] = pd.to_numeric(arg=networks_df[col]) except (ValueError, TypeError): pass return coins_df.sort_index(), networks_df.sort_index()
[docs] def get_coins_info_list(decimal_mode: bool, coin: str = None) -> list: """ Bring all coins exchange info in a list if no one is specified. Returns a list of dictionaries, one for each currency. :param bool decimal_mode: Fixes Decimal return type and operative. :param str coin: Limit response to a coin. :return list: A list of dictionaries each coin. """ endpoint = '/sapi/v1/capital/config/getall' ret = signed_get(endpoint=endpoint, decimal_mode=decimal_mode) if not coin: return ret else: return [c for c in ret if c['coin'].upper() == coin.upper()]
[docs] def get_coins_info_dic(decimal_mode: bool, coin: str = None) -> dict: """ Useful managing coins info in a big dictionary with coins as keys. :param bool decimal_mode: Fixes Decimal return type and operative. :param str coin: Limit response to a coin. :return list: A dictionary with each coin data as value. """ coins_data_list = get_coins_info_list(coin=coin, decimal_mode=decimal_mode) return {c['coin']: c for c in coins_data_list}
[docs] def get_leveraged_coins(decimal_mode: bool, coins_dic: dict = None) -> list: """ Search for Binance leveraged coins by searching UP or DOWN before an existing coin, examples: .. code-block:: python ['1INCHDOWN', '1INCHUP', 'AAVEDOWN', 'AAVEUP', 'ADADOWN', ... ] :param bool decimal_mode: Fixes Decimal return type and operative. :param dict coins_dic: Avoid fetching the API by passing a dict with coins data. :return list: A list with leveraged coins names. """ if not coins_dic: coins_dic = get_coins_info_dic(decimal_mode=decimal_mode) leveraged = [] coins_up = [i + 'UP' for i in coins_dic.keys()] coins_down = [i + 'DOWN' for i in coins_dic.keys()] for coin, _ in coins_dic.items(): if coin in coins_up: leveraged.append(coin) elif coin in coins_down: leveraged.append(coin) return leveraged
[docs] def get_leveraged_symbols(decimal_mode: bool, info_dic: dict = None, leveraged_coins: list = None) -> list: """ Search for Binance symbols based on leveraged coins by searching UP or DOWN before an existing coin in symbol, leveraged coins examples are: .. code-block:: python # leveraged coins ['1INCHDOWN', '1INCHUP', 'AAVEDOWN', 'AAVEUP', 'ADADOWN', ... ] # leveraged symbols :param bool decimal_mode: Fixes Decimal return type and operative. :param dict info_dic: Avoid fetching the API by passing a dict with symbols data. :param list leveraged_coins: Avoid fetching the API for getting coins by passing a list with coins data. :return list: A list with leveraged coins names. """ if not info_dic: info_dic = get_info_dic() if not leveraged_coins: leveraged_coins = get_leveraged_coins(decimal_mode=decimal_mode) bases = get_bases_dic(info_dic=info_dic) quotes = get_quotes_dic(info_dic=info_dic) leveraged_symbols = [] for symbol in info_dic.keys(): b = bases[symbol] q = quotes[symbol] if b in leveraged_coins or q in leveraged_coins: leveraged_symbols.append(symbol) return leveraged_symbols
####################### # Exchange Statistics # #######################
[docs] def get_quotes_dic(info_dic: dict = None) -> dict: """ Get quote coin each symbol. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dictionary: It gets symbols as keys and quote coin as values. """ if not info_dic: info_dic = get_info_dic() return {k: v['quoteAsset'] for k, v in info_dic.items()}
[docs] def get_bases_dic(info_dic: dict = None) -> dict: """ Get base coin each symbol. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :return dictionary: It gets symbols as keys and base coin as values. :param info_dic: :return: """ if not info_dic: info_dic = get_info_dic() return {k: v['baseAsset'] for k, v in info_dic.items()}
[docs] def exchange_status(decimal_mode: bool, tradeable=True, spot_required=True, margin_required=True, drop_legal=True, filter_leveraged=True, info_dic: dict = None, symbol_filters: dict = None) -> tuple: """ It returns a lot of results: bases_dic, quotes_dic, legal_coins, not_legal_pairs, symbol_filters, filtered_pairs :param bool decimal_mode: Fixes Decimal return type and operative. :param bool tradeable: Require or not just currently trading symbols. :param bool spot_required: Requires just SPOT currently trading symbols. :param bool margin_required: Requires just MARGIN currently trading symbols. :param bool drop_legal: Drops symbols with legal coins. :param bool filter_leveraged: Drops symbols with leveraged coins. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :param dict symbol_filters: A BinPan symbols filters dict. :return tuple: bases_dic, quotes_dic, legal_coins, not_legal_pairs, symbol_filters, filtered_pairs """ # get symbols dict from server with some operational_filters if not info_dic: info_dic = get_info_dic() bases_dic = {k: v['baseAsset'] for k, v in info_dic.items()} quotes_dic = {k: v['quoteAsset'] for k, v in info_dic.items()} filtered_info = info_dic if filter_leveraged: filtered_info = filter_leveraged_tokens(info_dic) if tradeable: # elimina los no tradeables filtered_info = filter_tradeable(filtered_info) if spot_required: filtered_info = filter_spot(filtered_info) if margin_required: filtered_info = filter_margin(filtered_info) legal_coins = get_legal_coins(decimal_mode=decimal_mode) not_legal_pairs = filter_legal(legal_coins=legal_coins, info_dic=info_dic) if drop_legal: filtered_info = filter_legal(legal_coins=legal_coins, info_dic=filtered_info) if not symbol_filters: symbol_filters = get_symbols_filters(info_dic=info_dic) filtered_pairs = filtered_info.keys() return bases_dic, quotes_dic, legal_coins, not_legal_pairs, symbol_filters, filtered_pairs
[docs] def get_24h_statistics(symbol: str = None) -> dict: # 24h rolling window """ GET /api/v3/ticker/24hr 24 hour rolling window price change statistics. Careful when accessing this with no symbol. Weight(IP): Symbols requested weight: 1-20: 1 21-100: 20 101 or more: 40 If symbols parameter is omitted 40 :param str symbol: Optional symbol. :return dict: As example shown. Example: .. code-block:: { "symbol": "BNBBTC", "priceChange": "-94.99999800", "priceChangePercent": "-95.960", "weightedAvgPrice": "0.29628482", "prevClosePrice": "0.10002000", "lastPrice": "4.00000200", "lastQty": "200.00000000", "bidPrice": "4.00000000", "bidQty": "100.00000000", "askPrice": "4.00000200", "askQty": "100.00000000", "openPrice": "99.00000000", "highPrice": "100.00000000", "lowPrice": "0.10000000", "volume": "8913.30000000", "quoteVolume": "15.30000000", "openTime": 1499783499040, "closeTime": 1499869899040, "firstId": 28385, // First tradeId "lastId": 28460, // Last tradeId "count": 76 // Trade count } """ client = _get_panzer() params = {'symbol': symbol} if symbol else {} weight = 1 if symbol else 40 return client.get('/api/v3/ticker/24hr', params=params, weight=weight)
[docs] def try_coin_conversion_to_stablecoin_by_intermediate_symbol(coin: str, coin_qty: float = 1, prices: dict = None, coin_to_check_with_stablecoin: str = 'BTC', stablecoin: str = 'USDT', decimal_mode: bool = None ) -> float | None: """ Converts any coin quantity value to a reference coin. I tries to build a symbol name from coin and coin_to_check_with_usdt and then checks if it's in prices dict. If not, it tries to build a symbol name from coin_to_check_with_usdt and coin (reversed) and then checks if it's in prices dict. Else it returns None. When it finds a symbol intermediate, it uses it to convert to stablecoin. :param str coin: A coin to convert to other coin value. :param float coin_qty: A quantity of the coin selected to use in calculation. Default is 1. :param dict prices: Current prices of all symbols. Default is None. If None, it will be fetched. :param str coin_to_check_with_stablecoin: Intermediate coin to check with a stable coin. Default is USDT. :param str stablecoin: A Binance existing coin to convert to. Default is USDT. :param bool decimal_mode: Fixes Decimal return type and operative. Default is False. :return float: Converted value result by multiplying coin_qty by the intermediate coin price and then by the USDT price. If not found, returns None. """ if not prices: prices = get_prices_dic(decimal_mode=decimal_mode) try_symbol = f"{coin_to_check_with_stablecoin}{stablecoin.upper()}" # try_symbol_b = f"USDT{coin_to_check_with_usdt}" if coin + coin_to_check_with_stablecoin in prices.keys() and try_symbol in prices.keys(): price = prices[coin + coin_to_check_with_stablecoin] return price * coin_qty * prices[try_symbol] elif coin_to_check_with_stablecoin + coin in prices.keys() and try_symbol in prices.keys(): divisor = prices[coin_to_check_with_stablecoin + coin] if not divisor: return None price = 1 / divisor return price * coin_qty * prices[try_symbol] else: return None
[docs] def convert_to_other_coin(coin: str, decimal_mode: bool = False, convert_to: str = 'USDT', coin_qty: float = 1, prices: dict = None) -> float: """ Convert value of a quantity of coins to value in other coin.It tries to convert to a reference coin but if that symbol is not found, it tries to convert to BTC, BUSD, BNB, ETH, TUSD, USDC. :param bool decimal_mode: Fixes Decimal return type and operative. :param str coin: Your coin. :param str convert_to: A Binance existing coin to convert to. Default is USDT. :param float coin_qty: A quantity. Default is 1. :param dict prices: A dict with all current prices. Default is None. Where None, it will be fetched. :return float: Value of the quantity expressed in the converted coin. """ coin = coin.upper() convert_to = convert_to.upper() if coin == convert_to: return coin_qty if not prices: prices = get_prices_dic(decimal_mode=decimal_mode) symbol_a = coin + convert_to symbol_b = convert_to + coin if symbol_a in prices.keys(): # print(f"{coin} Selected {symbol_a} for ", coin) return coin_qty * prices[symbol_a] elif symbol_b in prices.keys(): # print(f"{coin} Selected {symbol_b} for ", coin) return coin_qty * (1 / prices[symbol_b]) else: # try using btc # try: ret1 = try_coin_conversion_to_stablecoin_by_intermediate_symbol(coin=coin, prices=prices, coin_to_check_with_stablecoin='BTC', coin_qty=coin_qty, decimal_mode=decimal_mode) if ret1: return ret1 ret2 = try_coin_conversion_to_stablecoin_by_intermediate_symbol(coin=coin, prices=prices, coin_to_check_with_stablecoin='BUSD', coin_qty=coin_qty, decimal_mode=decimal_mode) if ret2: return ret2 ret3 = try_coin_conversion_to_stablecoin_by_intermediate_symbol(coin=coin, prices=prices, coin_to_check_with_stablecoin='BNB', coin_qty=coin_qty, decimal_mode=decimal_mode) if ret3: return ret3 ret4 = try_coin_conversion_to_stablecoin_by_intermediate_symbol(coin=coin, prices=prices, coin_to_check_with_stablecoin='ETH', coin_qty=coin_qty, decimal_mode=decimal_mode) if ret4: return ret4 ret5 = try_coin_conversion_to_stablecoin_by_intermediate_symbol(coin=coin, prices=prices, coin_to_check_with_stablecoin='TUSD', coin_qty=coin_qty, decimal_mode=decimal_mode) if ret5: return ret5 ret6 = try_coin_conversion_to_stablecoin_by_intermediate_symbol(coin=coin, prices=prices, coin_to_check_with_stablecoin='USDC', coin_qty=coin_qty, decimal_mode=decimal_mode) if ret6: return ret6 else: return np.nan
[docs] def convert_symbol_base_to_other_coin(symbol_to_convert_base: str, base_qty: float = 1, convert_to: str = 'USDT', prices: dict = None, info_dic: dict = None, decimal_mode: bool = False) -> float | dd: """ Convert value of a quantity of coins to value in other coin. It tries to convert to a reference coin but if that symbol is not found, it tries to convert to BTC, BUSD, BNB, ETH, TUSD, USDC. :param bool decimal_mode: Fixes Decimal return type and operative. :param str symbol_to_convert_base: A symbol to get it's base to convert to other coin value. :param float base_qty: A quantity. Default is 1. :param str convert_to: A Binance existing coin. Default is USDT. :param dict prices: A dict with all current prices. Default is None. Where None, it will be fetched. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. Default is None. :return float: Value expressed in the converted coin. """ if decimal_mode: my_type = dd else: my_type = float if not info_dic: info_dic = get_info_dic() # bases_dic = {k: v['baseAsset'] for k, v in info_dic.items()} bases_dic = get_bases_dic(info_dic=info_dic) try: base = bases_dic[symbol_to_convert_base] except KeyError: return 0 if not prices: prices = get_prices_dic(decimal_mode=decimal_mode) ret = convert_to_other_coin(coin=base, convert_to=convert_to, coin_qty=base_qty, prices=prices, decimal_mode=decimal_mode) if type(ret) == int or type(ret) == float or type(ret) == dd: return my_type(ret) else: raise Exception('BinPan Error: convert_symbol_base_to_other_coin breakpoint')
[docs] def convert_symbol_base_price_series_to_stablecoin(symbol_close_series: pd.Series, stable_coin_symbol_series: pd.Series, symbol_to_convert_base: str = None, stable_symbol: str = None, quotes: dict = None, bases: dict = None) -> pd.Series: """ Converts a series of symbols to a series of stable coin converted prices. It asumes that the index of the symbol_close_series is included in the index of the stable_coin_series. The stable_coin_series is supposed to use stablecoin as quote. :param pd.Series symbol_close_series: A series with the close prices of a symbol. :param pd.Series stable_coin_symbol_series: A series with the close prices of an intermediate coin and stable coin as quote. :param str symbol_to_convert_base: A symbol to convert to stable coin value. Default is None. Example: 'ETHBTC'. :param str stable_symbol: A symbol to convert to stable coin value. Default is None. Example: 'ETHUSDT'. :param dict quotes: A dictionary with quotes. Default is None. Optional to avoid an API call. Or :param dict bases: A dictionary with bases. Default is None. Optional to avoid an API call. :return pd.Series: A series with the converted prices from symbol as base and stable coin as quote. """ # verifica que el index de la symbol_close_series esté incluido completamente en el index de la stable_coin_series if not symbol_close_series.index.isin(stable_coin_symbol_series.index).all(): raise ValueError('Index of symbol_close_series is not included in the index of stable_coin_series') if not symbol_to_convert_base: symbol_to_convert_base = symbol_close_series.name if not stable_symbol: stable_symbol = stable_coin_symbol_series.name if not quotes or not bases: info_dic = get_info_dic() quotes = get_quotes_dic(info_dic=info_dic) bases = get_bases_dic(info_dic=info_dic) quote = quotes[symbol_to_convert_base] stable_base = bases[stable_symbol] assert quote == stable_base, f'Quote {quote} is not equal to stable_base {stable_base}' return symbol_close_series * stable_coin_symbol_series
[docs] def convert_utc_milliseconds(ms: int) -> str: """ Converts milliseconds timestamp to formatted string. :param int ms: Milliseconds linux timestamp from epoch. :return str: Formatted date. """ seconds = int(ms) / 1000 return str(datetime.fromtimestamp(seconds, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S.%f'))
[docs] def statistics_24h(decimal_mode: bool, tradeable=True, spot_required=True, margin_required=False, drop_legal=True, filter_leveraged=True, info_dic=None, stablecoin_value='BUSD', sort_by: str = 'priceChangePercent') -> pd.DataFrame: """ Generates a dataframe with the filters to apply with the statistics of the last 24 hours. Optionally, you can generate the column to convert the volume to USDT. :param bool decimal_mode: Fixes Decimal return type and operative. :param bool tradeable: Require or not just currently trading symbols. :param bool spot_required: Requires just SPOT currently trading symbols. :param bool margin_required: Requires just MARGIN currently trading symbols. :param bool drop_legal: Drops symbols with legal coins. :param bool filter_leveraged: Drops symbols with leveraged coins. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :param stablecoin_value: StableCoin reference for value. :param str sort_by: A column to sort by. Default is 'priceChangePercent'. :return pd.DataFrame: Picture result. .. image:: images/exchange_statistics_24h.png :width: 1000 :alt: exchange_statistics_24h.png """ if not info_dic: info_dic = get_info_dic() bases_dic, quotes_dic, _, not_legal_pairs, _, filtered_pairs = exchange_status(tradeable=tradeable, spot_required=spot_required, margin_required=margin_required, drop_legal=drop_legal, filter_leveraged=filter_leveraged, info_dic=info_dic, decimal_mode=decimal_mode) stats = get_24h_statistics() df = pd.DataFrame(stats) # filter just coins not legal and with spot and tradeable df = df[df['symbol'].isin(filtered_pairs)] for col in df.columns: try: df[col] = pd.to_numeric(df[col]) except (ValueError, TypeError): pass df['openTime'] = df['openTime'].apply(lambda x: convert_utc_milliseconds(x)) df['closeTime'] = df['closeTime'].apply(lambda x: convert_utc_milliseconds(x)) df = df.set_index('symbol', drop=False) # df['filter_passed'] = df['symbol'].apply(lambda x: True if x in filtered_pairs else False) df['is_legal'] = df['symbol'].apply(lambda x: True if x not in not_legal_pairs else False) df['base'] = df['symbol'].apply(lambda x: bases_dic[x]) df['quote'] = df['symbol'].apply(lambda x: quotes_dic[x]) if stablecoin_value: prices = get_prices_dic(decimal_mode=decimal_mode) # info_dic = {k['symbol']: k for k in market.get_exchange_info()['symbols']} # bases_dic = market.get_bases_dic(info_dic=info_dic) # quotes_dic = market.get_quotes_dic(info_dic=info_dic) stable_coin_value_name = f"{stablecoin_value}_value" for pair in prices.keys(): usdt_val = convert_symbol_base_to_other_coin(symbol_to_convert_base=pair, base_qty=1, convert_to=stablecoin_value, prices=prices, info_dic=info_dic, decimal_mode=decimal_mode) if pair in df.index and usdt_val is not None: df.loc[pair, stable_coin_value_name] = float(usdt_val) stable_coin_volumen_name = f"{stablecoin_value}_volume" df[stable_coin_volumen_name] = df[stable_coin_value_name] * df['volume'] return df.sort_values(stable_coin_volumen_name, ascending=False) return df.sort_values(sort_by, ascending=False)
[docs] def get_top_gainers(decimal_mode: bool, info_dic: dict = None, tradeable=True, spot_required=True, margin_required=False, drop_legal=True, filter_leveraged=True, top_gainers_qty: int = None, my_quote: str = 'BUSD', drop_stable_pairs: bool = True, sort_by_column: str = 'priceChangePercent', full_return: bool = False ) -> pd.DataFrame: """ Generates a dataframe for symbols against a quote with the filters to apply with the statistics of the last 24 hours. Optionally, you can generate the column to convert the volume to USDT. :param bool decimal_mode: Fixes Decimal return type and operative. :param bool tradeable: Require or not just currently trading symbols. :param bool spot_required: Requires just SPOT currently trading symbols. :param bool margin_required: Requires just MARGIN currently trading symbols. :param bool drop_legal: Drops symbols with legal coins. :param bool filter_leveraged: Drops symbols with leveraged coins. :param dict info_dic: BinPan exchange info dictionary. It's optional to avoid an API call. :param top_gainers_qty: Limit result top lines. :param str my_quote: A quote coin to reference values. :param bool drop_stable_pairs: It drop stablecoin to stablecoin symbols. :param str sort_by_column: A column to sort by. Default is 'priceChangePercent'. :param full_return: Activate return full columns data from exchange, else, returns just a few basic columns. :return pd.DataFrame: A dataframe as in the example . Example: .. code-block:: from binpan.api.exchange_info import get_top_gainers get_top_gainers(decimal_mode=False) priceChangePercent volume BUSD_value BUSD_volume symbol PHABTC 198.477 9.291796e+07 0.33650 3.126689e+07 PHAUSDT 195.961 6.729636e+08 0.33650 2.264522e+08 PHABUSD 195.530 1.082033e+09 0.33650 3.641039e+08 LTOBTC 61.732 5.854178e+07 0.11070 6.480575e+06 LTOUSDT 60.637 2.813419e+08 0.11070 3.114455e+07 ... ... ... ... ... MDXBTC -21.195 4.096356e+07 0.15640 6.406700e+06 MDXBUSD -22.112 3.444397e+08 0.15640 5.387037e+07 MDXUSDT -22.123 2.896132e+08 0.15640 4.529550e+07 AGIXBTC -23.007 1.086561e+08 0.06489 7.050693e+06 AGIXBUSD -23.407 2.188445e+08 0.06489 1.420082e+07 1194 rows × 4 columns """ if not info_dic: info_dic = get_info_dic() top_gainers = statistics_24h(decimal_mode=decimal_mode, tradeable=tradeable, spot_required=spot_required, margin_required=margin_required, drop_legal=drop_legal, info_dic=info_dic, filter_leveraged=filter_leveraged, stablecoin_value=my_quote) # # filter quote # top_gainers = top_gainers.loc[top_gainers['quote'] == my_quote] if drop_stable_pairs: stable_pairs = [f"{s}{my_quote}" for s in stablecoins] stable_pairs += [f"{my_quote}{s}" for s in stablecoins] top_gainers = top_gainers.loc[~top_gainers['symbol'].isin(stable_pairs)] if not full_return: top_gainers = top_gainers[['priceChangePercent', 'volume', f'{my_quote}_value', f'{my_quote}_volume']] if top_gainers_qty: return top_gainers.sort_values(sort_by_column, ascending=False).head(top_gainers_qty) else: return top_gainers.sort_values(sort_by_column, ascending=False)
################# # numeric tools # #################
[docs] def get_decimal_positions(num: float | dd) -> int: """ Count decimal positions for a value, correctly handling floats and Decimal numbers, including those in scientific notation. :param num: Input number, can be a float or a Decimal. :return: Count of decimal positions. """ num = dd(str(num)).normalize() num_tuple = num.as_tuple() # El exponente negativo indica la cantidad de posiciones decimales # Un exponente positivo o cero indica que no hay posiciones decimales o que el número es entero decimal_positions = -num_tuple.exponent if num_tuple.exponent < 0 else 0 # Para números con exponente positivo, debemos verificar si hay posiciones decimales no representadas por el exponente if num_tuple.exponent >= 0: # Contamos la cantidad de dígitos en la mantisa mantissa_length = len(num_tuple.digits) # Las posiciones decimales son la longitud de la mantisa menos el exponente, ajustado para números enteros decimal_positions = max(0, mantissa_length - num_tuple.exponent - 1) return decimal_positions