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.
account_tests
Tyler Goodlet 2023-07-10 11:26:24 -04:00
parent 19be8348e5
commit a2c6749112
2 changed files with 77 additions and 36 deletions

View File

@ -400,7 +400,7 @@ async def open_trade_dialog(
# and comparison with binance's own position calcs. # and comparison with binance's own position calcs.
# - load pps and accounts using accounting apis, write # - load pps and accounts using accounting apis, write
# the ledger and account files # the ledger and account files
# - table: PpTable # - table: Account
# - ledger: TransactionLedger # - ledger: TransactionLedger
async with ( async with (

View File

@ -24,8 +24,11 @@ from contextlib import (
aclosing, aclosing,
) )
from datetime import datetime from datetime import datetime
from functools import partial from functools import (
partial,
)
import itertools import itertools
from pprint import pformat
from typing import ( from typing import (
Any, Any,
AsyncGenerator, AsyncGenerator,
@ -54,7 +57,6 @@ from piker.accounting import (
DerivTypes, DerivTypes,
MktPair, MktPair,
unpack_fqme, unpack_fqme,
digits_to_dec,
) )
from piker.data.types import Struct from piker.data.types import Struct
from piker.data.validate import FeedInit from piker.data.validate import FeedInit
@ -277,69 +279,107 @@ async def open_history_client(
async def get_mkt_info( async def get_mkt_info(
fqme: str, fqme: str,
) -> tuple[MktPair, Pair]: ) -> tuple[MktPair, Pair] | None:
# uppercase since kraken bs_mktid is always upper # uppercase since kraken bs_mktid is always upper
if 'binance' not in fqme: if 'binance' not in fqme.lower():
fqme += '.binance' fqme += '.binance'
bs_fqme, _, broker = fqme.rpartition('.') mkt_mode: str = ''
broker, mkt_ep, venue, expiry = unpack_fqme(fqme) broker, mkt_ep, venue, expiry = unpack_fqme(fqme)
venue: str = venue.lower()
# NOTE: see the `FutesPair.bs_fqme: str` implementation # XXX TODO: we should change the usdtm_futes name to just
# to understand the reverse market info lookup below. # usdm_futes (dropping the tether part) since it turns out that
mkt_mode = venue = venue.lower() or 'spot' # there are indeed USD-tokens OTHER THEN tether being used as
_atype: str = '' # 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 ( if (
venue venue
and 'spot' not in venue.lower() and 'spot' not in venue
# XXX: catch all in case user doesn't know which # XXX: catch all in case user doesn't know which
# venue they want (usdtm vs. coinm) and we can choose # venue they want (usdtm vs. coinm) and we can choose
# a default (via config?) once we support coin-m APIs. # 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 not mkt_mode:
if 'perp' in expiry: mkt_mode: str = f'{venue}_futes'
_atype = 'perpetual_future'
else: sectype: str = 'future'
_atype = 'future' if 'perp' in expiry:
sectype = 'perpetual_future'
async with open_cached_client( async with open_cached_client(
'binance', 'binance',
) as client: ) 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 client.mkt_mode = mkt_mode
pair_str: str = mkt_ep.upper() pair: Pair = await client.exch_info(
pair: Pair = await client.exch_info(pair_str) pair_str,
venue=mkt_mode, # explicit
expiry=expiry,
)
if 'futes' in mkt_mode: if 'futes' in mkt_mode:
assert isinstance(pair, FutesPair) 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( mkt = MktPair(
dst=Asset( dst=dst,
name=pair.baseAsset, src=assets[pair.bs_src_asset],
atype='crypto',
tx_tick=digits_to_dec(pair.baseAssetPrecision),
),
src=Asset(
name=pair.quoteAsset,
atype='crypto',
tx_tick=digits_to_dec(pair.quoteAssetPrecision),
),
price_tick=pair.price_tick, price_tick=pair.price_tick,
size_tick=pair.size_tick, size_tick=pair.size_tick,
bs_mktid=pair.symbol, bs_mktid=pair.symbol,
expiry=expiry, expiry=expiry,
venue=venue, venue=venue,
broker='binance', broker='binance',
_atype=_atype, _atype=sectype,
) )
both = mkt, pair return mkt, pair
return both
@acm @acm
@ -472,10 +512,11 @@ async def open_symbol_search(
ctx: tractor.Context, ctx: tractor.Context,
) -> Client: ) -> 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: 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 # TODO: maybe we should deliver the cache
# so that client's can always do a local-lookup-first # so that client's can always do a local-lookup-first
# style try and then update async as (new) match results # style try and then update async as (new) match results
@ -488,7 +529,7 @@ async def open_symbol_search(
async for pattern in stream: async for pattern in stream:
matches = fuzzy.extractBests( matches = fuzzy.extractBests(
pattern, pattern,
fqpairs_cache, client._pairs,
score_cutoff=50, score_cutoff=50,
) )