Source code for binpan.api.wallet_api

from decimal import Decimal as dd
from time import sleep
import pandas as pd

from ..core.logs import LogManager
from .auth import signed_get, signed_post
from ..core.time_helper import convert_milliseconds_to_str, convert_string_to_milliseconds
from .market import get_prices_dic, convert_coin

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

##########
# Helper #
##########

[docs] def convert_str_date_to_ms(date: str | int, time_zone: str): """ Converts dates strings formatted as "2022-05-11 06:45:42" to timestamp in milliseconds. If timestamp passed it returns the timestamp. :param str or int date: Date to check format. :param time_zone: A time zone like 'Europe/Madrid' :return int: Milliseconds of timestamp. """ if type(date) == str: date = convert_string_to_milliseconds(date, timezoned=time_zone) return date
########## # WALLET # ##########
[docs] def daily_account_snapshot(account_type: str, decimal_mode: bool, startTime: int | str = None, endTime: int | str = None, limit=30, time_zone=None) -> pd.DataFrame: """ The query time period must be inside the previous 30 days. Support query within the last month, one month only. If startTime and endTime not sent, return records of the last days by default. Weight(IP): 2400 :param str account_type: SPOT or MARGIN (cross) :param bool decimal_mode: Fixes Decimal return type and operative. :param int limit: Days limit. Default 30. :param int or str startTime: Can be integer timestamp in milliseconds or formatted string: 2022-05-11 06:45:42 :param int or str endTime: Can be integer timestamp in milliseconds or formatted string: 2022-05-11 06:45:42 :param str time_zone: A time zone to parse index like 'Europe/Madrid' :return pd.DataFrame: Pandas DataFrame """ startTime = convert_str_date_to_ms(date=startTime, time_zone=time_zone) endTime = convert_str_date_to_ms(date=endTime, time_zone=time_zone) available = ['SPOT', 'MARGIN'] if account_type not in available: exc = f"BinPan error: {account_type} not in {available}" wallet_logger.error(exc) raise Exception(exc) elif limit > 30 or limit < 7: exc = f"BinPan error: {limit} is over the maximum 30 or under the minimum 7." wallet_logger.error(exc) raise Exception(exc) ret = signed_get(endpoint='/sapi/v1/accountSnapshot', params={'type': account_type, 'startTime': startTime, 'endTime': endTime, 'limit': limit}, decimal_mode=decimal_mode) rows = [] for update in ret['snapshotVos']: updateTime = update['updateTime'] data = update['data'] totalAssetOfBtc = float(data['totalAssetOfBtc']) if account_type == 'MARGIN': assets_field = 'userAssets' else: assets_field = 'balances' for balance in data[assets_field]: free = float(balance['free']) locked = float(balance['locked']) if free > 0 or locked > 0: row = {'updateTime': updateTime, 'totalAssetOfBtc': totalAssetOfBtc, 'asset': balance['asset'], 'free': free, 'locked': locked} if time_zone: row['datetime'] = convert_milliseconds_to_str(ms=updateTime, timezoned=time_zone) rows.append(row) if time_zone: ret = pd.DataFrame(rows) if not ret.empty: ret = ret.set_index('datetime').sort_index() ret.index.name = f"{account_type} {time_zone}" else: ret = pd.DataFrame(rows) if not ret.empty: ret = ret.set_index('updateTime').sort_index() ret.index.name = f"{account_type} timestamp" return ret
[docs] def assets_convertible_dust(decimal_mode: bool, confirm: bool = False) -> dict | None: """ Assets dust that can be converted to BNB. Weight(IP): 1 :param bool decimal_mode: It flags to work in decimal mode. :param bool confirm: Must be True to execute. Prevents accidental wallet modifications. :return dict: A dictionary. .. code-block:: { "details": [ { "asset": "ADA", "assetFullName": "ADA", "amountFree": "6.21", //Convertible amount "toBTC": "0.00016848", //BTC amount "toBNB": "0.01777302", //BNB amount(Not deducted commission fee) "toBNBOffExchange": "0.01741756", //BNB amount(Deducted commission fee) "exchange": "0.00035546" //Commission fee } ], "totalTransferBtc": "0.00016848", "totalTransferBNB": "0.01777302", "dribbletPercentage": "0.02" //Commission fee } """ if not confirm: wallet_logger.warning("This command may change or convert assets from your wallet. Pass confirm=True to execute.") return None return signed_post(endpoint='/sapi/v1/asset/dust-btc', decimal_mode=decimal_mode)
########## # trades # ##########
[docs] def get_fills_price(original_order_dict: dict, isBuyer: bool, margin: bool, test_mode: bool, operation_time: int, decimal_mode: bool) -> float: """ Obtain averaged price from order API response or claims last trade. :param dict original_order_dict: API order dict response. :param bool isBuyer: Sets if was a buyer order to filter for wanted order. :param bool margin: Sets margin order if it is to claim. :param bool test_mode: Set if test mode is on. :param int operation_time: Time limit of operation to analyze. :param bool decimal_mode: Fixes Decimal return type and operative. :return float: Price averaged. Example of not totally done order: .. code-block:: {'symbol': 'ACMBUSD', 'orderId': 53255492, 'orderListId': -1, 'clientOrderId': 'DUHlcCqIX2OO298XoAbvEB', 'transactTime': 1660332903950, 'price': 4.158, 'origQty': 21.1, 'executedQty': 0.0, 'cummulativeQuoteQty': 0.0, 'status': 'NEW', 'timeInForce': 'GTC', 'type': 'LIMIT', 'side': 'BUY', 'fills': []} Example of full SPOT response: .. code-block:: { "symbol": "BTCUSDT", "orderId": 28, "orderListId": -1, //Unless OCO, value will be -1 "clientOrderId": "6gCrw2kRUFF9CvJDWP16IP", "transactTime": 1507725176595, "price": "0.00000000", "origQty": "10.00000000", "executedQty": "10.00000000", "cummulativeQuoteQty": "10.00000000", "status": "FILLED", "timeInForce": "GTC", "type": "MARKET", "side": "SELL", "strategyId": 1, // This is only visible if the field was populated on order placement. "strategyType": 1000000 // This is only visible if the field was populated on order placement. "fills": [ { "price": "4000.00000000", "qty": "1.00000000", "commission": "4.00000000", "commissionAsset": "USDT", "tradeId": 56 }, { "price": "3999.00000000", "qty": "5.00000000", "commission": "19.99500000", "commissionAsset": "USDT", "tradeId": 57 }, { "price": "3998.00000000", "qty": "2.00000000", "commission": "7.99600000", "commissionAsset": "USDT", "tradeId": 58 }, { "price": "3997.00000000", "qty": "1.00000000", "commission": "3.99700000", "commissionAsset": "USDT", "tradeId": 59 }, { "price": "3995.00000000", "qty": "1.00000000", "commission": "3.99500000", "commissionAsset": "USDT", "tradeId": 60 } ] } Example of FULL MARGIN RESPONSE: .. code-block:: { "symbol": "BTCUSDT", "orderId": 28, "clientOrderId": "6gCrw2kRUFF9CvJDWP16IP", "transactTime": 1507725176595, "price": "1.00000000", "origQty": "10.00000000", "executedQty": "10.00000000", "cummulativeQuoteQty": "10.00000000", "status": "FILLED", "timeInForce": "GTC", "type": "MARKET", "side": "SELL", "marginBuyBorrowAmount": 5, // will not return if no margin trade happens "marginBuyBorrowAsset": "BTC", // will not return if no margin trade happens "isIsolated": true, // if isolated margin "fills": [ { "price": "4000.00000000", "qty": "1.00000000", "commission": "4.00000000", "commissionAsset": "USDT" }, { "price": "3999.00000000", "qty": "5.00000000", "commission": "19.99500000", "commissionAsset": "USDT" }, { "price": "3998.00000000", "qty": "2.00000000", "commission": "7.99600000", "commissionAsset": "USDT" }, { "price": "3997.00000000", "qty": "1.00000000", "commission": "3.99700000", "commissionAsset": "USDT" }, { "price": "3995.00000000", "qty": "1.00000000", "commission": "3.99500000", "commissionAsset": "USDT" } ] } STOP LIMIT EXAMPLE requested order response: .. code-block:: {'symbol': 'CHZBUSD', 'orderId': 212320642, 'orderListId': 72689217, 'clientOrderId': 'FLc7AHuuZCpChiH99eHtZh', 'price': 0.2139, 'origQty': 224.0, 'executedQty': 224.0, 'cummulativeQuoteQty': 47.9584, 'status': 'FILLED', 'timeInForce': 'GTC', 'type': 'STOP_LOSS_LIMIT', 'side': 'SELL', 'stopPrice': 0.2141, 'icebergQty': '0.00000000', 'time': 1661203865507, 'updateTime': 1661203966780, 'isWorking': True, 'origQuoteOrderQty': 0.0}, """ ordered = original_order_dict.copy() if decimal_mode: my_type = dd else: my_type = float if test_mode and 'STOP_LOSS' in ordered['type']: return my_type(ordered['stopPrice']) if test_mode and 'price' in ordered.keys() and ordered['price']: # MUST RETURN FOR TEST ORDERS return my_type(ordered['price']) if 'fills' in ordered.keys(): if type(ordered['fills']) == dict: if ordered['fills']['price']: return my_type(ordered['fills']['price']) elif type(ordered['fills']) == list: n = 0 m = 0 for fill in ordered['fills']: price = my_type(fill['price']) qty = my_type(fill['qty']) n += price * qty m += qty if m: return n / m else: raise Exception(f"BinPan Error: Error en parseo de orden ejecutada.\n{ordered}") # any other case wait 3 times for order to appear fills_price = None tour = 0 symbol = ordered['symbol'] while not fills_price and tour < 5: wallet_logger.info(f"Get fills from API trades!... wait 5 seconds for trade to appear. Round:{tour}") if not margin: last_trades = get_spot_trades_list(symbol=symbol, decimal_mode=decimal_mode) else: last_trades = get_margin_trades_list(symbol=symbol, decimal_mode=decimal_mode) try: # trades return time field with a timestamp fills_price = [i for i in last_trades if i['isBuyer'] == isBuyer and int(i['time']) >= operation_time][-1]['price'] tour += 1 except IndexError: sleep(5) return my_type(fills_price)
[docs] def get_fills_qty(original_order_dict: dict, margin: bool, isBuyer: bool, operation_time: int, decimal_mode: bool) -> float | dd: """ Extracts order quantity from an order response or checks a trade from API. :param dict original_order_dict: A putting order response from API dict. :param bool margin: Checks if margin or not. :param bool isBuyer: Sets if buy or sell side. :param int operation_time: A limit old timestamp to verify existence of order. :param bool decimal_mode: Sets Decimal operating mode. :return float or decimal.Decimal: Returns the needed value. """ ordered = original_order_dict.copy() wallet_logger.debug(f"fills_qty: {ordered}") ret = None if decimal_mode: my_type = dd else: my_type = float if 'fills' in ordered.keys(): if type(ordered['fills']) == dict: return my_type(ordered['fills']['qty']) elif type(ordered['fills']) == list: m = 0 for fill in ordered['fills']: qty = my_type(fill['qty']) m += qty return m else: raise Exception(f"BinPan Error: Error parsing order.\n{ordered}") else: # si fue una orden de compra a precio de mercado no tiene precio, price = 0.0, cantidad ????? if 'test_quantity' in ordered.keys(): ret = ordered['test_quantity'] elif 'executedQty' in ordered.keys(): qty_ret = my_type(ordered['executedQty']) if qty_ret != 0: return qty_ret else: fills_qty = None tour = 0 symbol = ordered['symbol'] while not fills_qty and tour < 5: wallet_logger.info(f"Get fills qty from API trades!... wait 5 seconds for trade to appear. Round:{tour}") if not margin: last_trades = get_spot_trades_list(symbol=symbol, decimal_mode=decimal_mode) else: last_trades = get_margin_trades_list(symbol=symbol, decimal_mode=decimal_mode) try: # trades return time field with a timestamp fills_qty = [i for i in last_trades if i['isBuyer'] == isBuyer and int(i['time']) >= operation_time][-1]['qty'] tour += 1 except IndexError: sleep(5) ret = fills_qty return my_type(ret)
[docs] def get_spot_trades_list(symbol: str, decimal_mode: bool, limit: int = 1000, orderId: int = None, startTime: int = None, endTime: int = None, fromId: int = None, recvWindow: int = 10000) -> list: """ Get trades for a specific account and symbol. Weight(IP): 10 :param str symbol: Symbol's trades. :param bool decimal_mode: Fixes Decimal return type and operative. :param int limit: Default 500; max 1000. :param fromId: TradeId to fetch from. Default gets most recent trades. If fromId is set, it will get id >= that fromId. Otherwise, most recent trades are returned. :param int endTime: Optional. :param int startTime: Optional. :param int orderId: This can only be used in combination with symbol. :param int recvWindow: The value cannot be greater than 60000 :return list: A list. Example: .. code-block:: [ { "symbol": "BNBBTC", "id": 28457, "orderId": 100234, "orderListId": -1, //Unless OCO, the value will always be -1 "price": "4.00000100", "qty": "12.00000000", "quoteQty": "48.000012", "commission": "10.10000000", "commissionAsset": "BNB", "time": 1499865549590, "isBuyer": true, "isMaker": false, "isBestMatch": true } ] """ endpoint = '/api/v3/myTrades' return signed_get(endpoint=endpoint, params={'limit': limit, 'symbol': symbol, 'orderId': orderId, 'startTime': startTime, 'endTime': endTime, 'fromId': fromId, 'recvWindow': recvWindow}, decimal_mode=decimal_mode)
[docs] def get_margin_trades_list(symbol: str, decimal_mode: bool, isIsolated: bool = False, limit: int = 1000, orderId: int = None, startTime: int = None, endTime: int = None, fromId: int = None, recvWindow: int = 10000) -> list: """ Get margin trades for a specific account and symbol. If fromId is set, it will get trades >= that fromId. Otherwise most recent trades are returned. Weight(IP): 10 :param str symbol: Symbol's trades. :param bool decimal_mode: Fixes Decimal return type and operative. :param bool isIsolated: Sets for getting isolated or not isolated trades. Default is false. :param int limit: Default 500; max 1000. :param fromId: TradeId to fetch from. Default gets most recent trades. If fromId is set, it will get id >= that fromId. Otherwise, most recent trades are returned. :param int endTime: Optional. :param int startTime: Optional. :param int orderId: This can only be used in combination with symbol. :param int recvWindow: The value cannot be greater than 60000 :return list: A list. Example: .. code-block:: [ { "commission": "0.00006000", "commissionAsset": "BTC", "id": 34, "isBestMatch": true, "isBuyer": false, "isMaker": false, "orderId": 39324, "price": "0.02000000", "qty": "3.00000000", "symbol": "BNBBTC", "isIsolated": false, "time": 1561973357171 } ] """ endpoint = '/sapi/v1/margin/myTrades' return signed_get(endpoint=endpoint, params={'limit': limit, 'isIsolated': isIsolated, 'symbol': symbol, 'orderId': orderId, 'startTime': startTime, 'endTime': endTime, 'fromId': fromId, 'recvWindow': recvWindow}, decimal_mode=decimal_mode)
############ # balances # ############
[docs] def get_spot_account_info(decimal_mode: bool, recvWindow: int = 10000) -> dict: """ Get current account information. Weight(IP): 10 :param bool decimal_mode: Fixes Decimal return type and operative. :param int recvWindow: The value cannot be greater than 60000 :return dict: A dictionary with data. Example: .. code-block:: { "makerCommission": 15, "takerCommission": 15, "buyerCommission": 0, "sellerCommission": 0, "canTrade": true, "canWithdraw": true, "canDeposit": true, "updateTime": 123456789, "accountType": "SPOT", "balances": [ {"asset": "BTC", "free": "4723846.89208129","locked": "0.00000000"}, {"asset": "LTC","free": "4763368.68006011","locked": "0.00000000"} ], "permissions": ["SPOT"] } """ endpoint = '/api/v3/account' return signed_get(endpoint=endpoint, params={'recvWindow': recvWindow}, decimal_mode=decimal_mode)
[docs] def get_spot_free_balances(decimal_mode: bool, data_dic: dict = None ) -> dict: """ Parses available balances from account info. :param dict data_dic: If available, account info can be passed as data_dic parameter to avoid API calling. :param bool decimal_mode: Fixes Decimal return type and operative. :return dict: Free balances. """ ret = {} if not data_dic: data_dic = get_spot_account_info(decimal_mode=decimal_mode) wallet_logger.debug(f"spot_free_balances_parsed len(get_spot_account_info()):{len(data_dic)}") if decimal_mode: for asset in data_dic['balances']: qty = dd(asset['free']) if qty: ret.update({asset['asset']: qty}) else: for asset in data_dic['balances']: qty = float(asset['free']) if qty: ret.update({asset['asset']: qty}) return ret
[docs] def get_spot_locked_balances(decimal_mode: bool, data_dic: dict = None) -> dict: """ Parses locked in order balances from account info. :param bool decimal_mode: Fixes Decimal return type and operative. :param dict data_dic: If available, account info can be passed as data_dic parameter to avoid API calling. :return dict: Locked balances. """ ret = {} if not data_dic: data_dic = get_spot_account_info(decimal_mode=decimal_mode) wallet_logger.debug(f"spot_locked_balances_parsed get_spot_account_info: {len(data_dic)}") for asset in data_dic['balances']: qty = float(asset['locked']) if qty: ret.update({asset['asset']: qty}) return ret
[docs] def get_coins_with_balance(decimal_mode: bool) -> list: """ Get the non-zero balances of an account, free or locked ones. Useful getting wallet value. :param bool decimal_mode: Fixes Decimal return type and operative. :return list: """ data_dic = get_spot_account_info(decimal_mode=decimal_mode) free = get_spot_free_balances(data_dic=data_dic, decimal_mode=decimal_mode) locked = get_spot_locked_balances(data_dic=data_dic, decimal_mode=decimal_mode) free = [symbol for symbol, balance in free.items() if float(balance) > 0] locked = [symbol for symbol, balance in locked.items() if float(balance) > 0] symbols = free + locked return list(set(symbols))
[docs] def get_spot_balances_df(decimal_mode: bool, filter_empty: bool = True) -> pd.DataFrame: """ Create a dataframe with the free or blocked amounts of the spot wallet. The index is the assets list. Example: .. code-block:: python from binpan.api.wallet_api import get_spot_balances_df df = get_spot_balances_df(decimal_mode=False) print(df) free locked asset BNB 0.128359 0.0 BUSD 0.000001 0.0 :param bool decimal_mode: Fixes Decimal return type and operative. :param bool filter_empty: Discards empty quantities. :return pd.DataFrame: A dataframe with assets locked or free. """ balances = get_spot_account_info(decimal_mode=decimal_mode)['balances'] df_ = pd.DataFrame(balances) df_['free'] = df_['free'].apply(pd.to_numeric) df_['locked'] = df_['locked'].apply(pd.to_numeric) df_ = df_.set_index('asset', drop=True) if filter_empty: return df_[(df_['free'] != 0) | (df_['locked'] != 0)] else: return df_
[docs] def get_spot_balances_total_value(decimal_mode: bool, balances_df: pd.DataFrame = None, convert_to: str = 'BUSD') -> float: """ Returns total value expressed in a quote coin. Counts free and locked assets. :param bool decimal_mode: Fixes Decimal return type and operative. :param pd.DataFrame balances_df: A BinPan balances dataframe. :param str convert_to: A Binance coin. :return float: Total quantity expressed in quote. """ if decimal_mode: my_type = dd else: my_type = float if type(balances_df) != pd.DataFrame: balances_df = get_spot_balances_df(decimal_mode=decimal_mode) prices = get_prices_dic(decimal_mode=decimal_mode) symbols = list(balances_df.index) free_values = balances_df['free'].tolist() locked_values = balances_df['locked'].tolist() total = 0 for i in range(len(symbols)): coin = symbols[i] free = my_type(free_values[i]) locked = my_type(locked_values[i]) if free: free = convert_coin(coin=coin, prices=prices, convert_to=convert_to, coin_qty=free, decimal_mode=decimal_mode) if locked: locked = convert_coin(coin=coin, prices=prices, convert_to=convert_to, coin_qty=locked, decimal_mode=decimal_mode) total += my_type(free) total += my_type(locked) return total
################# # MARGIN WALLET # #################
[docs] def get_margin_account_details(decimal_mode: bool) -> dict: """ Query Cross Margin Account Details (USER_DATA) GET /sapi/v1/margin/account (HMAC SHA256) Weight(IP): 10 :param bool decimal_mode: Fixes Decimal return type and operative. Response: .. code-block:: { "borrowEnabled": true, "marginLevel": "11.64405625", "totalAssetOfBtc": "6.82728457", "totalLiabilityOfBtc": "0.58633215", "totalNetAssetOfBtc": "6.24095242", "tradeEnabled": true, "transferEnabled": true, "userAssets": [ { "asset": "BTC", "borrowed": "0.00000000", "free": "0.00499500", "interest": "0.00000000", "locked": "0.00000000", "netAsset": "0.00499500" }, { "asset": "BNB", "borrowed": "201.66666672", "free": "2346.50000000", "interest": "0.00000000", "locked": "0.00000000", "netAsset": "2144.83333328" }, { "asset": "ETH", "borrowed": "0.00000000", "free": "0.00000000", "interest": "0.00000000", "locked": "0.00000000", "netAsset": "0.00000000" }, { "asset": "USDT", "borrowed": "0.00000000", "free": "0.00000000", "interest": "0.00000000", "locked": "0.00000000", "netAsset": "0.00000000" } ] } """ margin_endpoint = '/sapi/v1/margin/account' ret = signed_get(endpoint=margin_endpoint, params={}, decimal_mode=decimal_mode) return ret
################### # margin balances # ###################
[docs] def get_margin_balances(decimal_mode: bool) -> dict: """ Collects balances in the margin account that are not null. :param bool decimal_mode: Fixes Decimal return type and operative. :return dict: A dictionary with coins with balances. Example: .. code-block:: {'BNB': {'free': 0.06, 'locked': 0.0, 'borrowed': 0.0, 'interest': 0.0, 'netAsset': 0.06}, 'BUSD': {'free': 50.0, 'locked': 0.0, 'borrowed': 0.0, 'interest': 0.0, 'netAsset': 50.0}} """ margin_status = get_margin_account_details(decimal_mode=decimal_mode) ret = {} for asset in margin_status['userAssets']: entry = {} for k, v in asset.items(): if k != 'asset': if decimal_mode: entry[k] = dd(v) else: entry[k] = float(v) if sum(entry.values()) != 0: ret[asset['asset']] = entry return ret
[docs] def get_margin_free_balances(decimal_mode: bool, balances: dict = None ) -> dict: """ Just returns free existing balances. It is optional to avoid an API call. :param dict balances: Returns dict with assets as keys and a float value for not null quantities. :param bool decimal_mode: Fixes Decimal return type. :return dict: A dict with float values. """ if not balances: balances = get_margin_balances(decimal_mode=decimal_mode) ret = {} for k, v in balances.items(): if 'free' in v.keys(): if v['free']: ret[k] = v['free'] return ret
[docs] def get_margin_locked_balances(decimal_mode: bool, balances: dict = None) -> dict: """ Just returns locked existing balances. It is optional to avoid an API call. :param bool decimal_mode: Fixes Decimal return type. :param dict balances: Returns dict with assets as keys and a float value for not null quantities. :return dict: A dict with float values. """ if not balances: balances = get_margin_balances(decimal_mode=decimal_mode) ret = {} for k, v in balances.items(): if 'locked' in v.keys(): if v['locked']: ret[k] = v['locked'] return ret
[docs] def get_margin_borrowed_balances(decimal_mode: bool, balances: dict = None) -> dict: """ Just returns borrowed existing balances. It is optional to avoid an API call. :param bool decimal_mode: Fixes Decimal return type. :param dict balances: Returns dict with assets as keys and a float value for not null quantities. :return dict: A dict with float values. """ if not balances: balances = get_margin_balances(decimal_mode=decimal_mode) ret = {} for k, v in balances.items(): if 'borrowed' in v.keys(): if v['borrowed']: ret[k] = v['borrowed'] return ret
[docs] def get_margin_interest_balances(decimal_mode: bool, balances: dict = None) -> dict: """ Just returns interest existing balances. It is optional to avoid an API call. :param bool decimal_mode: Fixes Decimal return type. :param dict balances: Returns dict with assets as keys and a float value for not null quantities. :return dict: A dict with float values. """ if not balances: balances = get_margin_balances(decimal_mode=decimal_mode) if not balances: balances = get_margin_balances(decimal_mode=decimal_mode) ret = {} for k, v in balances.items(): if 'interest' in v.keys(): if v['interest']: ret[k] = v['interest'] return ret
[docs] def get_margin_netAsset_balances(decimal_mode: bool, balances: dict = None): """ Just returns netAsset existing balances. It is optional to avoid an API call. :param bool decimal_mode: Fixes Decimal return type. :param dict balances: Returns dict with assets as keys and a float value for not null quantities. :return dict: A dict with float values. """ if not balances: balances = get_margin_balances(decimal_mode=decimal_mode) return {k: v['netAsset'] for k, v in balances.items() if v['netAsset']}
[docs] def get_margin_balances_total_value(decimal_mode: bool, balances: dict = None, convert_to: str = 'BUSD' ) -> float | dd: """ Returns total value expressed in a quote coin. Counts free, locked, borrowed and interest assets. :param dict balances: A BinPan balances dict. It is optional to avoid an API call. :param str convert_to: A Binance coin. :param bool decimal_mode: Fixes Decimal return type. :return float: Total quantity expressed in quote. """ if decimal_mode: my_type = dd else: my_type = float prices = get_prices_dic(decimal_mode=decimal_mode) if not balances: balances = get_margin_balances(decimal_mode=decimal_mode) assets = list(balances.keys()) free_values = get_margin_free_balances(balances=balances, decimal_mode=decimal_mode) locked_values = get_margin_locked_balances(balances=balances, decimal_mode=decimal_mode) borrowed_values = get_margin_borrowed_balances(balances=balances, decimal_mode=decimal_mode) # TODO: saber si el interest tiene signo negativo interest_values = get_margin_interest_balances(balances=balances, decimal_mode=decimal_mode) total = 0 for i in range(len(assets)): coin = assets[i] if coin in free_values.keys(): free = free_values[coin] else: free = 0 if coin in locked_values.keys(): locked = locked_values[coin] else: locked = 0 if coin in borrowed_values.keys(): borrowed = borrowed_values[coin] else: borrowed = 0 if coin in interest_values.keys(): interest = interest_values[coin] else: interest = 0 # TODO: saber si el interest tiene signo negativo total_coins = free + locked + borrowed - interest converted_value = convert_coin(coin=coin, prices=prices, convert_to=convert_to, coin_qty=total_coins, decimal_mode=decimal_mode) total += my_type(converted_value) return my_type(total)