From a2c6749112bfea16b6dd07e9a83fc89382594c16 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 10 Jul 2023 11:26:24 -0400 Subject: [PATCH] binance.feed: use `Client.get_assets()` for mkt pairs Instead of constructing them (previously manually) in `.get_mkt_info()` ep, just call `.get_assets()` and do key lookups for assets to hand directly to the `.src/dst` of `MktPair`. Refine fqme input parsing to match: - adjust parsing logic to only use `unpack_fqme()` on the input fqme token. - set `.mkt_mode: str` to the derivs venue when an expiry token is detected in the fqme. - pass the parsed `expiry: str` to `Client.exch_info()` to ensure a deriv venue (table) is used for pair lookup. - skip any "DEFI" venue or other unknown asset type cases (since binance doesn't seem to define some assets anywhere?). Also, just use the `Client._pairs` unified table for search input since the first call to `.exch_info()` won't necessarily contain the most up-to-date state whereas `._pairs` always will. --- piker/brokers/binance/broker.py | 2 +- piker/brokers/binance/feed.py | 111 ++++++++++++++++++++++---------- 2 files changed, 77 insertions(+), 36 deletions(-) diff --git a/piker/brokers/binance/broker.py b/piker/brokers/binance/broker.py index f063bee1..04042f44 100644 --- a/piker/brokers/binance/broker.py +++ b/piker/brokers/binance/broker.py @@ -400,7 +400,7 @@ async def open_trade_dialog( # and comparison with binance's own position calcs. # - load pps and accounts using accounting apis, write # the ledger and account files - # - table: PpTable + # - table: Account # - ledger: TransactionLedger async with ( diff --git a/piker/brokers/binance/feed.py b/piker/brokers/binance/feed.py index 66a0bff0..8c5965f9 100644 --- a/piker/brokers/binance/feed.py +++ b/piker/brokers/binance/feed.py @@ -24,8 +24,11 @@ from contextlib import ( aclosing, ) from datetime import datetime -from functools import partial +from functools import ( + partial, +) import itertools +from pprint import pformat from typing import ( Any, AsyncGenerator, @@ -54,7 +57,6 @@ from piker.accounting import ( DerivTypes, MktPair, unpack_fqme, - digits_to_dec, ) from piker.data.types import Struct from piker.data.validate import FeedInit @@ -277,69 +279,107 @@ async def open_history_client( async def get_mkt_info( fqme: str, -) -> tuple[MktPair, Pair]: +) -> tuple[MktPair, Pair] | None: # uppercase since kraken bs_mktid is always upper - if 'binance' not in fqme: + if 'binance' not in fqme.lower(): fqme += '.binance' - bs_fqme, _, broker = fqme.rpartition('.') + mkt_mode: str = '' broker, mkt_ep, venue, expiry = unpack_fqme(fqme) + venue: str = venue.lower() - # NOTE: see the `FutesPair.bs_fqme: str` implementation - # to understand the reverse market info lookup below. - mkt_mode = venue = venue.lower() or 'spot' - _atype: str = '' + # XXX TODO: we should change the usdtm_futes name to just + # usdm_futes (dropping the tether part) since it turns out that + # there are indeed USD-tokens OTHER THEN tether being used as + # the margin assets.. it's going to require a wholesale + # (variable/key) rename as well as file name adjustments to any + # existing tsdb set.. + if 'usd' in venue: + mkt_mode: str = 'usdtm_futes' + + # NO IDEA what these contracts (some kinda DEX-ish futes?) are + # but we're masking them for now.. + elif ( + 'defi' in venue + + # TODO: handle coinm futes which have a margin asset that + # is some crypto token! + # https://binance-docs.github.io/apidocs/delivery/en/#exchange-information + or 'btc' in venue + ): + return None + + else: + # NOTE: see the `FutesPair.bs_fqme: str` implementation + # to understand the reverse market info lookup below. + mkt_mode = venue or 'spot' + + sectype: str = '' if ( venue - and 'spot' not in venue.lower() + and 'spot' not in venue # XXX: catch all in case user doesn't know which # venue they want (usdtm vs. coinm) and we can choose # a default (via config?) once we support coin-m APIs. - or 'perp' in bs_fqme.lower() + or 'perp' in venue ): - mkt_mode: str = f'{venue.lower()}_futes' - if 'perp' in expiry: - _atype = 'perpetual_future' + if not mkt_mode: + mkt_mode: str = f'{venue}_futes' - else: - _atype = 'future' + sectype: str = 'future' + if 'perp' in expiry: + sectype = 'perpetual_future' async with open_cached_client( 'binance', ) as client: - # switch mode depending on input pattern parsing + assets: dict[str, Asset] = await client.get_assets() + pair_str: str = mkt_ep.upper() + + # switch venue-mode depending on input pattern parsing + # since we want to use a particular endpoint (set) for + # pair info lookup! client.mkt_mode = mkt_mode - pair_str: str = mkt_ep.upper() - pair: Pair = await client.exch_info(pair_str) + pair: Pair = await client.exch_info( + pair_str, + venue=mkt_mode, # explicit + expiry=expiry, + ) if 'futes' in mkt_mode: assert isinstance(pair, FutesPair) + dst: Asset | None = assets.get(pair.bs_dst_asset) + if ( + not dst + # TODO: a known asset DNE list? + # and pair.baseAsset == 'DEFI' + ): + log.warning( + f'UNKNOWN {venue} asset {pair.baseAsset} from,\n' + f'{pformat(pair.to_dict())}' + ) + + # XXX UNKNOWN missing "asset", though no idea why? + # maybe it's only avail in the margin venue(s): /dapi/ ? + return None + mkt = MktPair( - dst=Asset( - name=pair.baseAsset, - atype='crypto', - tx_tick=digits_to_dec(pair.baseAssetPrecision), - ), - src=Asset( - name=pair.quoteAsset, - atype='crypto', - tx_tick=digits_to_dec(pair.quoteAssetPrecision), - ), + dst=dst, + src=assets[pair.bs_src_asset], price_tick=pair.price_tick, size_tick=pair.size_tick, bs_mktid=pair.symbol, expiry=expiry, venue=venue, broker='binance', - _atype=_atype, + _atype=sectype, ) - both = mkt, pair - return both + return mkt, pair @acm @@ -472,10 +512,11 @@ async def open_symbol_search( ctx: tractor.Context, ) -> Client: + # NOTE: symbology tables are loaded as part of client + # startup in ``.api.get_client()`` and in this case + # are stored as `Client._pairs`. async with open_cached_client('binance') as client: - # load all symbols locally for fast search - fqpairs_cache = await client.exch_info() # TODO: maybe we should deliver the cache # so that client's can always do a local-lookup-first # style try and then update async as (new) match results @@ -488,7 +529,7 @@ async def open_symbol_search( async for pattern in stream: matches = fuzzy.extractBests( pattern, - fqpairs_cache, + client._pairs, score_cutoff=50, )