`binance`: use `MktPair` in live feed setup
Turns out `binance` is pretty great with their schema since they have more or less the same data schema for their exchange info ep which we wrap in a `Pair` struct: https://binance-docs.github.io/apidocs/spot/en/#exchange-information That makes it super easy to provide the most general case for filling out a `MktPair` with both `.src/dst: Asset` to maintain maximum meta-data B) Deatz: - adjust `Pair` to have `.size/price_tick: Decimal` by parsing out the values from the filters field; TODO: we should probably just rewrite the input `.filter` at init time so we can keep the frozen style. - rename `Client.mkt_info()` (was `.symbol_info` to `.exch_info()` better matching the ep name and have it build, cache, and return a `dict[str, Pair]`; allows dropping `.cache_symbols()` - only pass the `mkt_info: MktPair` field in the init msg!rekt_pps
parent
8f79c37b99
commit
b718b5634e
|
@ -37,14 +37,19 @@ import numpy as np
|
||||||
import tractor
|
import tractor
|
||||||
import wsproto
|
import wsproto
|
||||||
|
|
||||||
|
from ..accounting._mktinfo import (
|
||||||
|
Asset,
|
||||||
|
MktPair,
|
||||||
|
digits_to_dec,
|
||||||
|
)
|
||||||
from .._cacheables import open_cached_client
|
from .._cacheables import open_cached_client
|
||||||
from ._util import (
|
from ._util import (
|
||||||
resproc,
|
resproc,
|
||||||
SymbolNotFound,
|
SymbolNotFound,
|
||||||
DataUnavailable,
|
DataUnavailable,
|
||||||
)
|
)
|
||||||
from ..log import (
|
from ._util import (
|
||||||
get_logger,
|
log,
|
||||||
get_console_log,
|
get_console_log,
|
||||||
)
|
)
|
||||||
from ..data.types import Struct
|
from ..data.types import Struct
|
||||||
|
@ -53,8 +58,6 @@ from ..data._web_bs import (
|
||||||
NoBsWs,
|
NoBsWs,
|
||||||
)
|
)
|
||||||
|
|
||||||
log = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
_url = 'https://api.binance.com'
|
_url = 'https://api.binance.com'
|
||||||
|
|
||||||
|
@ -89,7 +92,10 @@ _show_wap_in_history = False
|
||||||
|
|
||||||
|
|
||||||
# https://binance-docs.github.io/apidocs/spot/en/#exchange-information
|
# https://binance-docs.github.io/apidocs/spot/en/#exchange-information
|
||||||
class Pair(Struct, frozen=True):
|
|
||||||
|
# TODO: make this frozen again by pre-processing the
|
||||||
|
# filters list to a dict at init time?
|
||||||
|
class Pair(Struct): # , frozen=True):
|
||||||
symbol: str
|
symbol: str
|
||||||
status: str
|
status: str
|
||||||
|
|
||||||
|
@ -115,9 +121,41 @@ class Pair(Struct, frozen=True):
|
||||||
defaultSelfTradePreventionMode: str
|
defaultSelfTradePreventionMode: str
|
||||||
allowedSelfTradePreventionModes: list[str]
|
allowedSelfTradePreventionModes: list[str]
|
||||||
|
|
||||||
filters: list[dict[str, Union[str, int, float]]]
|
filters: list[
|
||||||
|
dict[
|
||||||
|
str,
|
||||||
|
Union[str, int, float]
|
||||||
|
]
|
||||||
|
]
|
||||||
permissions: list[str]
|
permissions: list[str]
|
||||||
|
|
||||||
|
_filtersbykey: dict | None = None
|
||||||
|
|
||||||
|
def get_filter(self) -> dict[str, dict]:
|
||||||
|
filters = self._filtersbykey
|
||||||
|
|
||||||
|
if self._filtersbykey:
|
||||||
|
return filters
|
||||||
|
|
||||||
|
filters = self._filtersbykey = {}
|
||||||
|
for entry in self.filters:
|
||||||
|
ftype = entry['filterType']
|
||||||
|
filters[ftype] = entry
|
||||||
|
|
||||||
|
return filters
|
||||||
|
|
||||||
|
def size_tick(self) -> Decimal:
|
||||||
|
# XXX: lul, after manually inspecting the response format we
|
||||||
|
# just directly pick out the info we need
|
||||||
|
return Decimal(
|
||||||
|
self.get_filter()['PRICE_FILTER']['tickSize'].rstrip('0')
|
||||||
|
)
|
||||||
|
|
||||||
|
def price_tick(self) -> Decimal:
|
||||||
|
return Decimal(
|
||||||
|
self.get_filter()['LOT_SIZE']['stepSize'].rstrip('0')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OHLC(Struct):
|
class OHLC(Struct):
|
||||||
'''
|
'''
|
||||||
|
@ -160,7 +198,7 @@ class Client:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._sesh = asks.Session(connections=4)
|
self._sesh = asks.Session(connections=4)
|
||||||
self._sesh.base_location = _url
|
self._sesh.base_location = _url
|
||||||
self._pairs: dict[str, Any] = {}
|
self._pairs: dict[str, Pair] = {}
|
||||||
|
|
||||||
async def _api(
|
async def _api(
|
||||||
self,
|
self,
|
||||||
|
@ -174,50 +212,43 @@ class Client:
|
||||||
)
|
)
|
||||||
return resproc(resp, log)
|
return resproc(resp, log)
|
||||||
|
|
||||||
async def mkt_info(
|
async def exch_info(
|
||||||
|
|
||||||
self,
|
self,
|
||||||
sym: str | None = None,
|
sym: str | None = None,
|
||||||
|
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Pair] | Pair:
|
||||||
'''Get symbol info for the exchange.
|
'''
|
||||||
|
Fresh exchange-pairs info query for symbol ``sym: str``:
|
||||||
|
https://binance-docs.github.io/apidocs/spot/en/#exchange-information
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# TODO: we can load from our self._pairs cache
|
cached_pair = self._pairs.get(sym)
|
||||||
# on repeat calls...
|
if cached_pair:
|
||||||
|
return cached_pair
|
||||||
|
|
||||||
# will retrieve all symbols by default
|
# retrieve all symbols by default
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
if sym is not None:
|
if sym is not None:
|
||||||
sym = sym.lower()
|
sym = sym.lower()
|
||||||
params = {'symbol': sym}
|
params = {'symbol': sym}
|
||||||
|
|
||||||
resp = await self._api(
|
resp = await self._api('exchangeInfo', params=params)
|
||||||
'exchangeInfo',
|
|
||||||
params=params,
|
|
||||||
)
|
|
||||||
|
|
||||||
entries = resp['symbols']
|
entries = resp['symbols']
|
||||||
if not entries:
|
if not entries:
|
||||||
raise SymbolNotFound(f'{sym} not found')
|
raise SymbolNotFound(f'{sym} not found:\n{resp}')
|
||||||
|
|
||||||
syms = {item['symbol']: item for item in entries}
|
pairs = {
|
||||||
|
item['symbol']: Pair(**item) for item in entries
|
||||||
|
}
|
||||||
|
self._pairs.update(pairs)
|
||||||
|
|
||||||
if sym is not None:
|
if sym is not None:
|
||||||
return syms[sym]
|
return pairs[sym]
|
||||||
else:
|
else:
|
||||||
return syms
|
return self._pairs
|
||||||
|
|
||||||
symbol_info = mkt_info
|
symbol_info = exch_info
|
||||||
|
|
||||||
async def cache_symbols(
|
|
||||||
self,
|
|
||||||
) -> dict:
|
|
||||||
if not self._pairs:
|
|
||||||
self._pairs = await self.mkt_info()
|
|
||||||
|
|
||||||
return self._pairs
|
|
||||||
|
|
||||||
async def search_symbols(
|
async def search_symbols(
|
||||||
self,
|
self,
|
||||||
|
@ -227,7 +258,7 @@ class Client:
|
||||||
if self._pairs is not None:
|
if self._pairs is not None:
|
||||||
data = self._pairs
|
data = self._pairs
|
||||||
else:
|
else:
|
||||||
data = await self.mkt_info()
|
data = await self.exch_info()
|
||||||
|
|
||||||
matches = fuzzy.extractBests(
|
matches = fuzzy.extractBests(
|
||||||
pattern,
|
pattern,
|
||||||
|
@ -302,7 +333,7 @@ class Client:
|
||||||
@acm
|
@acm
|
||||||
async def get_client() -> Client:
|
async def get_client() -> Client:
|
||||||
client = Client()
|
client = Client()
|
||||||
await client.cache_symbols()
|
await client.exch_info()
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@ -465,27 +496,38 @@ async def stream_quotes(
|
||||||
):
|
):
|
||||||
|
|
||||||
# keep client cached for real-time section
|
# keep client cached for real-time section
|
||||||
cache = await client.cache_symbols()
|
pairs = await client.exch_info()
|
||||||
|
sym_infos: dict[str, dict] = {}
|
||||||
|
mkt_infos: dict[str, MktPair] = {}
|
||||||
|
|
||||||
for sym in symbols:
|
for sym in symbols:
|
||||||
d = cache[sym.upper()]
|
|
||||||
syminfo = Pair(**d) # validation
|
|
||||||
|
|
||||||
si = sym_infos[sym] = syminfo.to_dict()
|
pair: Pair = pairs[sym.upper()]
|
||||||
filters = {}
|
price_tick = pair.price_tick()
|
||||||
for entry in syminfo.filters:
|
size_tick = pair.size_tick()
|
||||||
ftype = entry['filterType']
|
|
||||||
filters[ftype] = entry
|
|
||||||
|
|
||||||
# XXX: after manually inspecting the response format we
|
mkt_infos[sym] = MktPair(
|
||||||
# just directly pick out the info we need
|
dst=Asset(
|
||||||
si['price_tick_size'] = Decimal(
|
name=pair.baseAsset,
|
||||||
filters['PRICE_FILTER']['tickSize'].rstrip('0')
|
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=price_tick,
|
||||||
|
size_tick=size_tick,
|
||||||
|
bs_mktid=pair.symbol,
|
||||||
|
broker='binance',
|
||||||
)
|
)
|
||||||
si['lot_tick_size'] = Decimal(
|
|
||||||
filters['LOT_SIZE']['stepSize'].rstrip('0')
|
sym_infos[sym] = {
|
||||||
)
|
'price_tick_size': price_tick,
|
||||||
si['asset_type'] = 'crypto'
|
'lot_tick_size': size_tick,
|
||||||
|
'asset_type': 'crypto',
|
||||||
|
}
|
||||||
|
|
||||||
symbol = symbols[0]
|
symbol = symbols[0]
|
||||||
|
|
||||||
|
@ -493,9 +535,11 @@ async def stream_quotes(
|
||||||
# pass back token, and bool, signalling if we're the writer
|
# pass back token, and bool, signalling if we're the writer
|
||||||
# and that history has been written
|
# and that history has been written
|
||||||
symbol: {
|
symbol: {
|
||||||
'symbol_info': sym_infos[sym],
|
|
||||||
'shm_write_opts': {'sum_tick_vml': False},
|
|
||||||
'fqsn': sym,
|
'fqsn': sym,
|
||||||
|
|
||||||
|
# 'symbol_info': sym_infos[sym],
|
||||||
|
'mkt_info': mkt_infos[sym],
|
||||||
|
'shm_write_opts': {'sum_tick_vml': False},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,13 +626,13 @@ async def open_symbol_search(
|
||||||
async with open_cached_client('binance') as client:
|
async with open_cached_client('binance') as client:
|
||||||
|
|
||||||
# load all symbols locally for fast search
|
# load all symbols locally for fast search
|
||||||
cache = await client.cache_symbols()
|
cache = await client.exch_info()
|
||||||
await ctx.started()
|
await ctx.started()
|
||||||
|
|
||||||
async with ctx.open_stream() as stream:
|
async with ctx.open_stream() as stream:
|
||||||
|
|
||||||
async for pattern in stream:
|
async for pattern in stream:
|
||||||
# results = await client.mkt_info(sym=pattern.upper())
|
# results = await client.exch_info(sym=pattern.upper())
|
||||||
|
|
||||||
matches = fuzzy.extractBests(
|
matches = fuzzy.extractBests(
|
||||||
pattern,
|
pattern,
|
||||||
|
|
Loading…
Reference in New Issue