binance.api: add venue qualified symcache support
Meaning we add the `Client.get_assets()` and `.get_mkt_pairs()` methods. Also implement `.exch_info()` to take in a `expiry: str` to detect whether to look up a derivative venue instead of spot. In support of all this we now explicitly key all assets (via `._cache_pairs() during the populate of `._venue2assets` sub-tables) with their `.bs_dst_asset: str` value to ensure, for ex., a spot `BTCUSDT` has a distinct value from any futures contracts with the same `Pair.symbol: str` value! Also, ensure we always create a `brokers.toml` (from template) if DNE and binance is the user's first used backend XDaccount_tests
parent
3c84ac326a
commit
19be8348e5
|
@ -78,7 +78,7 @@ def get_config() -> dict:
|
||||||
|
|
||||||
conf: dict
|
conf: dict
|
||||||
path: Path
|
path: Path
|
||||||
conf, path = config.load()
|
conf, path = config.load(touch_if_dne=True)
|
||||||
|
|
||||||
section = conf.get('binance')
|
section = conf.get('binance')
|
||||||
|
|
||||||
|
@ -396,7 +396,6 @@ class Client:
|
||||||
) -> None:
|
) -> None:
|
||||||
# lookup internal mkt-specific pair table to update
|
# lookup internal mkt-specific pair table to update
|
||||||
pair_table: dict[str, Pair] = self._venue2pairs[venue]
|
pair_table: dict[str, Pair] = self._venue2pairs[venue]
|
||||||
asset_table: dict[str, Asset] = self._venue2assets[venue]
|
|
||||||
|
|
||||||
# make API request(s)
|
# make API request(s)
|
||||||
resp = await self._api(
|
resp = await self._api(
|
||||||
|
@ -408,6 +407,7 @@ class Client:
|
||||||
venue=venue,
|
venue=venue,
|
||||||
allow_testnet=False, # XXX: never use testnet for symbol lookups
|
allow_testnet=False, # XXX: never use testnet for symbol lookups
|
||||||
)
|
)
|
||||||
|
|
||||||
mkt_pairs = resp['symbols']
|
mkt_pairs = resp['symbols']
|
||||||
if not mkt_pairs:
|
if not mkt_pairs:
|
||||||
raise SymbolNotFound(f'No market pairs found!?:\n{resp}')
|
raise SymbolNotFound(f'No market pairs found!?:\n{resp}')
|
||||||
|
@ -432,21 +432,45 @@ class Client:
|
||||||
# `._pairs: ChainMap` for search B0
|
# `._pairs: ChainMap` for search B0
|
||||||
pairs_view_subtable[pair.bs_fqme] = pair
|
pairs_view_subtable[pair.bs_fqme] = pair
|
||||||
|
|
||||||
|
# XXX WOW: TURNS OUT THIS ISN'T TRUE !?
|
||||||
|
# > (populate `Asset` table for spot mkts only since it
|
||||||
|
# > should be a superset of any other venues such as
|
||||||
|
# > futes or margin)
|
||||||
if venue == 'spot':
|
if venue == 'spot':
|
||||||
if (name := pair.quoteAsset) not in asset_table:
|
dst_sectype: str = 'crypto_currency'
|
||||||
asset_table[name] = Asset(
|
|
||||||
|
elif venue in {'usdtm_futes'}:
|
||||||
|
dst_sectype: str = 'future'
|
||||||
|
if pair.contractType == 'PERPETUAL':
|
||||||
|
dst_sectype: str = 'perpetual_future'
|
||||||
|
|
||||||
|
spot_asset_table: dict[str, Asset] = self._venue2assets['spot']
|
||||||
|
ven_asset_table: dict[str, Asset] = self._venue2assets[venue]
|
||||||
|
|
||||||
|
if (
|
||||||
|
(name := pair.quoteAsset) not in spot_asset_table
|
||||||
|
):
|
||||||
|
spot_asset_table[pair.bs_src_asset] = Asset(
|
||||||
name=name,
|
name=name,
|
||||||
atype='crypto_currency',
|
atype='crypto_currency',
|
||||||
tx_tick=digits_to_dec(pair.quoteAssetPrecision),
|
tx_tick=digits_to_dec(pair.quoteAssetPrecision),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (name := pair.baseAsset) not in asset_table:
|
if (
|
||||||
asset_table[name] = Asset(
|
(name := pair.baseAsset) not in ven_asset_table
|
||||||
|
):
|
||||||
|
if venue != 'spot':
|
||||||
|
assert dst_sectype != 'crypto_currency'
|
||||||
|
|
||||||
|
ven_asset_table[pair.bs_dst_asset] = Asset(
|
||||||
name=name,
|
name=name,
|
||||||
atype='crypto_currency',
|
atype=dst_sectype,
|
||||||
tx_tick=digits_to_dec(pair.baseAssetPrecision),
|
tx_tick=digits_to_dec(pair.baseAssetPrecision),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# log.warning(
|
||||||
|
# f'Assets not YET found in spot set: `{pformat(dne)}`!?'
|
||||||
|
# )
|
||||||
# NOTE: make merged view of all market-type pairs but
|
# NOTE: make merged view of all market-type pairs but
|
||||||
# use market specific `Pair.bs_fqme` for keys!
|
# use market specific `Pair.bs_fqme` for keys!
|
||||||
# this allows searching for market pairs with different
|
# this allows searching for market pairs with different
|
||||||
|
@ -458,16 +482,29 @@ class Client:
|
||||||
if venue == 'spot':
|
if venue == 'spot':
|
||||||
return
|
return
|
||||||
|
|
||||||
assets: list[dict] = resp.get('assets', ())
|
# TODO: maybe use this assets response for non-spot venues?
|
||||||
for entry in assets:
|
# -> issue is we do the exch_info queries conc, so we can't
|
||||||
name: str = entry['asset']
|
# guarantee order for inter-table lookups..
|
||||||
asset_table[name] = self._venue2assets['spot'].get(name)
|
# if venue ep delivers an explicit set of assets copy just
|
||||||
|
# ensure they are also already listed in the spot equivs.
|
||||||
|
# assets: list[dict] = resp.get('assets', ())
|
||||||
|
# for entry in assets:
|
||||||
|
# name: str = entry['asset']
|
||||||
|
# spot_asset_table: dict[str, Asset] = self._venue2assets['spot']
|
||||||
|
# if name not in spot_asset_table:
|
||||||
|
# log.warning(
|
||||||
|
# f'COULDNT FIND ASSET {name}\n{entry}\n'
|
||||||
|
# f'ADDING AS FUTES ONLY!?'
|
||||||
|
# )
|
||||||
|
# asset_table: dict[str, Asset] = self._venue2assets[venue]
|
||||||
|
# asset_table[name] = spot_asset_table.get(name)
|
||||||
|
|
||||||
async def exch_info(
|
async def exch_info(
|
||||||
self,
|
self,
|
||||||
sym: str | None = None,
|
sym: str | None = None,
|
||||||
|
|
||||||
venue: MarketType | None = None,
|
venue: MarketType | None = None,
|
||||||
|
expiry: str | None = None,
|
||||||
|
|
||||||
) -> dict[str, Pair] | Pair:
|
) -> dict[str, Pair] | Pair:
|
||||||
'''
|
'''
|
||||||
|
@ -485,7 +522,16 @@ class Client:
|
||||||
pair_table: dict[str, Pair] = self._venue2pairs[
|
pair_table: dict[str, Pair] = self._venue2pairs[
|
||||||
venue or self.mkt_mode
|
venue or self.mkt_mode
|
||||||
]
|
]
|
||||||
if cached_pair := pair_table.get(sym):
|
if (
|
||||||
|
expiry
|
||||||
|
and 'perp' not in expiry.lower()
|
||||||
|
):
|
||||||
|
sym: str = f'{sym}_{expiry}'
|
||||||
|
|
||||||
|
if (
|
||||||
|
sym
|
||||||
|
and (cached_pair := pair_table.get(sym))
|
||||||
|
):
|
||||||
return cached_pair
|
return cached_pair
|
||||||
|
|
||||||
venues: list[str] = ['spot', 'usdtm_futes']
|
venues: list[str] = ['spot', 'usdtm_futes']
|
||||||
|
@ -500,7 +546,45 @@ class Client:
|
||||||
ven,
|
ven,
|
||||||
)
|
)
|
||||||
|
|
||||||
return pair_table[sym] if sym else self._pairs
|
if sym:
|
||||||
|
return pair_table[sym]
|
||||||
|
else:
|
||||||
|
self._pairs
|
||||||
|
|
||||||
|
async def get_assets(
|
||||||
|
self,
|
||||||
|
venue: str | None = None,
|
||||||
|
|
||||||
|
) -> dict[str, Asset]:
|
||||||
|
if (
|
||||||
|
venue
|
||||||
|
and venue != 'spot'
|
||||||
|
):
|
||||||
|
venues = [venue]
|
||||||
|
else:
|
||||||
|
venues = ['usdtm_futes']
|
||||||
|
|
||||||
|
ass_table: dict[str, Asset] = self._venue2assets['spot']
|
||||||
|
|
||||||
|
# merge in futes contracts with a sectype suffix
|
||||||
|
for venue in venues:
|
||||||
|
ass_table |= self._venue2assets[venue]
|
||||||
|
|
||||||
|
return ass_table
|
||||||
|
|
||||||
|
|
||||||
|
async def get_mkt_pairs(self) -> dict[str, Pair]:
|
||||||
|
'''
|
||||||
|
Flatten the multi-venue (chain) map of market pairs
|
||||||
|
to a fqme indexed table for data layer caching.
|
||||||
|
|
||||||
|
'''
|
||||||
|
flat: dict[str, Pair] = {}
|
||||||
|
for venmap in self._pairs.maps:
|
||||||
|
for bs_fqme, pair in venmap.items():
|
||||||
|
flat[pair.bs_fqme] = pair
|
||||||
|
|
||||||
|
return flat
|
||||||
|
|
||||||
# TODO: unused except by `brokers.core.search_symbols()`?
|
# TODO: unused except by `brokers.core.search_symbols()`?
|
||||||
async def search_symbols(
|
async def search_symbols(
|
||||||
|
|
Loading…
Reference in New Issue