# piker: trading gear for hackers # Copyright (C) Tyler Goodlet (in stewardship for pikers) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . ''' Symbology defs and search. ''' from decimal import Decimal import tractor from piker._cacheables import ( async_lifo_cache, ) from piker.accounting._mktinfo import ( digits_to_dec, ) from piker.brokers import ( open_cached_client, SymbolNotFound, ) from piker.types import Struct from piker.accounting._mktinfo import ( Asset, MktPair, unpack_fqme, ) class Pair(Struct): ''' A tradable asset pair as schema-defined by, https://docs.kraken.com/api/docs/rest-api/get-tradable-asset-pairs ''' xname: str # idiotic bs_mktid equiv i guess? altname: str # alternate pair name wsname: str # WebSocket pair name (if available) aclass_base: str # asset class of base component base: str # asset id of base component aclass_quote: str # asset class of quote component quote: str # asset id of quote component lot: str # volume lot size cost_decimals: int pair_decimals: int # scaling decimal places for pair lot_decimals: int # scaling decimal places for volume # amount to multiply lot volume by to get currency volume lot_multiplier: float # array of leverage amounts available when buying leverage_buy: list[int] # array of leverage amounts available when selling leverage_sell: list[int] # fee schedule array in [volume, percent fee] tuples fees: list[tuple[int, float]] # maker fee schedule array in [volume, percent fee] tuples (if on # maker/taker) fees_maker: list[tuple[int, float]] fee_volume_currency: str # volume discount currency margin_call: str # margin call level margin_stop: str # stop-out/liquidation margin level ordermin: float # minimum order volume for pair tick_size: float # min price step size status: str costmin: str|None = None # XXX, only some mktpairs? short_position_limit: float = 0 long_position_limit: float = float('inf') # TODO: should we make this a literal NamespacePath ref? ns_path: str = 'piker.brokers.kraken:Pair' @property def bs_mktid(self) -> str: ''' Kraken seems to index it's market symbol sets in transaction ledgers using the key returned from rest queries.. so use that since apparently they can't make up their minds on a better key set XD ''' return self.xname @property def price_tick(self) -> Decimal: return digits_to_dec(self.pair_decimals) @property def size_tick(self) -> Decimal: return digits_to_dec(self.lot_decimals) @property def bs_dst_asset(self) -> str: dst, _ = self.wsname.split('/') return dst @property def bs_src_asset(self) -> str: _, src = self.wsname.split('/') return src @property def bs_fqme(self) -> str: ''' Basically the `.altname` but with special '.' handling and `.SPOT` suffix appending (for future multi-venue support). ''' dst, src = self.wsname.split('/') # XXX: omg for stupid shite like ETH2.S/ETH.. dst = dst.replace('.', '-') return f'{dst}{src}.SPOT' @tractor.context async def open_symbol_search(ctx: tractor.Context) -> None: async with open_cached_client('kraken') as client: # load all symbols locally for fast search cache = await client.get_mkt_pairs() await ctx.started(cache) async with ctx.open_stream() as stream: async for pattern in stream: await stream.send( await client.search_symbols(pattern) ) @async_lifo_cache() async def get_mkt_info( fqme: str, ) -> tuple[MktPair, Pair]: ''' Query for and return a `MktPair` and backend-native `Pair` (or wtv else) info. If more then one fqme is provided return a ``dict`` of native key-strs to `MktPair`s. ''' venue: str = 'spot' expiry: str = '' if '.kraken' not in fqme: fqme += '.kraken' broker, pair, venue, expiry = unpack_fqme(fqme) venue: str = venue or 'spot' if venue.lower() != 'spot': raise SymbolNotFound( 'kraken only supports spot markets right now!\n' f'{fqme}\n' ) async with open_cached_client('kraken') as client: # uppercase since kraken bs_mktid is always upper # bs_fqme, _, broker = fqme.partition('.') # pair_str: str = bs_fqme.upper() pair_str: str = f'{pair}.{venue}' pair: Pair | None = client._pairs.get(pair_str.upper()) if not pair: bs_fqme: str = client.to_bs_fqme(pair_str) pair: Pair = client._pairs[bs_fqme] if not (assets := client._assets): assets: dict[str, Asset] = await client.get_assets() dst_asset: Asset = assets[pair.bs_dst_asset] src_asset: Asset = assets[pair.bs_src_asset] mkt = MktPair( dst=dst_asset, src=src_asset, price_tick=pair.price_tick, size_tick=pair.size_tick, bs_mktid=pair.bs_mktid, expiry=expiry, venue=venue or 'spot', # TODO: futes # _atype=_atype, broker='kraken', ) return mkt, pair