`kraken`: parse and load info `Transaction.sym: Symbol`
Also includes a retyping of `Client._pair: dict[str, Pair]` to look up pair structs and map all alt-key-name-sets to each for easy precision info lookup to set the `.sym` field for each transaction including for on-chain transfers which kraken provides as an "asset decimals" field, presumably pulled from the particular block-token's limitation info.backward_compat_trans_with_symbolinfo
parent
69b85aa7e5
commit
b4a1cc8f22
|
@ -40,6 +40,8 @@ import base64
|
||||||
import trio
|
import trio
|
||||||
|
|
||||||
from piker import config
|
from piker import config
|
||||||
|
from piker.data.types import Struct
|
||||||
|
from piker.data._source import Symbol
|
||||||
from piker.brokers._util import (
|
from piker.brokers._util import (
|
||||||
resproc,
|
resproc,
|
||||||
SymbolNotFound,
|
SymbolNotFound,
|
||||||
|
@ -113,11 +115,53 @@ class InvalidKey(ValueError):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
# https://www.kraken.com/features/api#get-tradable-pairs
|
||||||
|
class Pair(Struct):
|
||||||
|
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
|
||||||
|
costmin: float
|
||||||
|
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
|
||||||
|
|
||||||
|
short_position_limit: float = 0
|
||||||
|
long_position_limit: float = float('inf')
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
|
|
||||||
# global symbol normalization table
|
# global symbol normalization table
|
||||||
_ntable: dict[str, str] = {}
|
_ntable: dict[str, str] = {}
|
||||||
_atable: bidict[str, str] = bidict()
|
_atable: bidict[str, str] = bidict()
|
||||||
|
_pairs: dict[str, Pair] = {}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -133,13 +177,12 @@ class Client:
|
||||||
'krakenex/2.1.0 (+https://github.com/veox/python3-krakenex)'
|
'krakenex/2.1.0 (+https://github.com/veox/python3-krakenex)'
|
||||||
})
|
})
|
||||||
self.conf: dict[str, str] = config
|
self.conf: dict[str, str] = config
|
||||||
self._pairs: list[str] = []
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._api_key = api_key
|
self._api_key = api_key
|
||||||
self._secret = secret
|
self._secret = secret
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pairs(self) -> dict[str, Any]:
|
def pairs(self) -> dict[str, Pair]:
|
||||||
if self._pairs is None:
|
if self._pairs is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Make sure to run `cache_symbols()` on startup!"
|
"Make sure to run `cache_symbols()` on startup!"
|
||||||
|
@ -295,15 +338,28 @@ class Client:
|
||||||
|
|
||||||
trans: dict[str, Transaction] = {}
|
trans: dict[str, Transaction] = {}
|
||||||
for entry in xfers:
|
for entry in xfers:
|
||||||
# look up the normalized name
|
|
||||||
asset = self._atable[entry['asset']].lower()
|
# look up the normalized name and asset info
|
||||||
|
asset_key = entry['asset']
|
||||||
|
asset_info = self.assets[asset_key]
|
||||||
|
asset = self._atable[asset_key].lower()
|
||||||
|
|
||||||
# XXX: this is in the asset units (likely) so it isn't
|
# XXX: this is in the asset units (likely) so it isn't
|
||||||
# quite the same as a commisions cost necessarily..)
|
# quite the same as a commisions cost necessarily..)
|
||||||
cost = float(entry['fee'])
|
cost = float(entry['fee'])
|
||||||
|
|
||||||
|
fqsn = asset + '.kraken'
|
||||||
|
pairinfo = Symbol.from_fqsn(
|
||||||
|
fqsn,
|
||||||
|
info={
|
||||||
|
'asset_type': 'crypto',
|
||||||
|
'lot_tick_size': asset_info['decimals'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
tran = Transaction(
|
tran = Transaction(
|
||||||
fqsn=asset + '.kraken',
|
fqsn=fqsn,
|
||||||
|
sym=pairinfo,
|
||||||
tid=entry['txid'],
|
tid=entry['txid'],
|
||||||
dt=pendulum.from_timestamp(entry['time']),
|
dt=pendulum.from_timestamp(entry['time']),
|
||||||
bsuid=f'{asset}{src_asset}',
|
bsuid=f'{asset}{src_asset}',
|
||||||
|
@ -317,7 +373,7 @@ class Client:
|
||||||
price='NaN',
|
price='NaN',
|
||||||
|
|
||||||
# XXX: see note above
|
# XXX: see note above
|
||||||
cost=0,
|
cost=cost,
|
||||||
)
|
)
|
||||||
trans[tran.tid] = tran
|
trans[tran.tid] = tran
|
||||||
|
|
||||||
|
@ -372,7 +428,7 @@ class Client:
|
||||||
self,
|
self,
|
||||||
pair: Optional[str] = None,
|
pair: Optional[str] = None,
|
||||||
|
|
||||||
) -> dict[str, dict[str, str]]:
|
) -> dict[str, Pair] | Pair:
|
||||||
|
|
||||||
if pair is not None:
|
if pair is not None:
|
||||||
pairs = {'pair': pair}
|
pairs = {'pair': pair}
|
||||||
|
@ -389,19 +445,36 @@ class Client:
|
||||||
|
|
||||||
if pair is not None:
|
if pair is not None:
|
||||||
_, data = next(iter(pairs.items()))
|
_, data = next(iter(pairs.items()))
|
||||||
return data
|
return Pair(**data)
|
||||||
else:
|
else:
|
||||||
return pairs
|
return {key: Pair(**data) for key, data in pairs.items()}
|
||||||
|
|
||||||
async def cache_symbols(
|
async def cache_symbols(self) -> dict:
|
||||||
self,
|
'''
|
||||||
) -> dict:
|
Load all market pair info build and cache it for downstream use.
|
||||||
|
|
||||||
|
A ``._ntable: dict[str, str]`` is available for mapping the
|
||||||
|
websocket pair name-keys and their http endpoint API (smh)
|
||||||
|
equivalents to the "alternative name" which is generally the one
|
||||||
|
we actually want to use XD
|
||||||
|
|
||||||
|
'''
|
||||||
if not self._pairs:
|
if not self._pairs:
|
||||||
self._pairs = await self.symbol_info()
|
self._pairs.update(await self.symbol_info())
|
||||||
|
|
||||||
ntable = {}
|
# table of all ws and rest keys to their alt-name values.
|
||||||
for restapikey, info in self._pairs.items():
|
ntable: dict[str, str] = {}
|
||||||
ntable[restapikey] = ntable[info['wsname']] = info['altname']
|
|
||||||
|
for rest_key in list(self._pairs.keys()):
|
||||||
|
|
||||||
|
pair: Pair = self._pairs[rest_key]
|
||||||
|
altname = pair.altname
|
||||||
|
wsname = pair.wsname
|
||||||
|
ntable[rest_key] = ntable[wsname] = altname
|
||||||
|
|
||||||
|
# register the pair under all monikers, a giant flat
|
||||||
|
# surjection of all possible names to each info obj.
|
||||||
|
self._pairs[altname] = self._pairs[wsname] = pair
|
||||||
|
|
||||||
self._ntable.update(ntable)
|
self._ntable.update(ntable)
|
||||||
|
|
||||||
|
@ -411,26 +484,34 @@ class Client:
|
||||||
self,
|
self,
|
||||||
pattern: str,
|
pattern: str,
|
||||||
limit: int = None,
|
limit: int = None,
|
||||||
|
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
if self._pairs is not None:
|
'''
|
||||||
data = self._pairs
|
Search for a symbol by "alt name"..
|
||||||
else:
|
|
||||||
data = await self.symbol_info()
|
It is expected that the ``Client._pairs`` table
|
||||||
|
gets populated before conducting the underlying fuzzy-search
|
||||||
|
over the pair-key set.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if not len(self._pairs):
|
||||||
|
await self.cache_symbols()
|
||||||
|
assert self._pairs, '`Client.cache_symbols()` was never called!?'
|
||||||
|
|
||||||
matches = fuzzy.extractBests(
|
matches = fuzzy.extractBests(
|
||||||
pattern,
|
pattern,
|
||||||
data,
|
self._pairs,
|
||||||
score_cutoff=50,
|
score_cutoff=50,
|
||||||
)
|
)
|
||||||
# repack in dict form
|
# repack in dict form
|
||||||
return {item[0]['altname']: item[0] for item in matches}
|
return {item[0].altname: item[0] for item in matches}
|
||||||
|
|
||||||
async def bars(
|
async def bars(
|
||||||
self,
|
self,
|
||||||
symbol: str = 'XBTUSD',
|
symbol: str = 'XBTUSD',
|
||||||
|
|
||||||
# UTC 2017-07-02 12:53:20
|
# UTC 2017-07-02 12:53:20
|
||||||
since: Optional[Union[int, datetime]] = None,
|
since: Union[int, datetime] | None = None,
|
||||||
count: int = 720, # <- max allowed per query
|
count: int = 720, # <- max allowed per query
|
||||||
as_np: bool = True,
|
as_np: bool = True,
|
||||||
|
|
||||||
|
@ -506,7 +587,7 @@ class Client:
|
||||||
def normalize_symbol(
|
def normalize_symbol(
|
||||||
cls,
|
cls,
|
||||||
ticker: str
|
ticker: str
|
||||||
) -> str:
|
) -> tuple[str, Pair]:
|
||||||
'''
|
'''
|
||||||
Normalize symbol names to to a 3x3 pair from the global
|
Normalize symbol names to to a 3x3 pair from the global
|
||||||
definition map which we build out from the data retreived from
|
definition map which we build out from the data retreived from
|
||||||
|
@ -514,7 +595,7 @@ class Client:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
ticker = cls._ntable[ticker]
|
ticker = cls._ntable[ticker]
|
||||||
return ticker.lower()
|
return ticker.lower(), cls._pairs[ticker]
|
||||||
|
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
|
|
|
@ -48,6 +48,7 @@ from piker.pp import (
|
||||||
open_trade_ledger,
|
open_trade_ledger,
|
||||||
open_pps,
|
open_pps,
|
||||||
)
|
)
|
||||||
|
from piker.data._source import Symbol
|
||||||
from piker.clearing._messages import (
|
from piker.clearing._messages import (
|
||||||
Order,
|
Order,
|
||||||
Status,
|
Status,
|
||||||
|
@ -1196,10 +1197,21 @@ def norm_trade_records(
|
||||||
}[record['type']]
|
}[record['type']]
|
||||||
|
|
||||||
# we normalize to kraken's `altname` always..
|
# we normalize to kraken's `altname` always..
|
||||||
bsuid = norm_sym = Client.normalize_symbol(record['pair'])
|
bsuid, pair_info = Client.normalize_symbol(record['pair'])
|
||||||
|
fqsn = f'{bsuid}.kraken'
|
||||||
|
|
||||||
|
mktpair = Symbol.from_fqsn(
|
||||||
|
fqsn,
|
||||||
|
info={
|
||||||
|
'lot_size_digits': pair_info.lot_decimals,
|
||||||
|
'tick_size_digits': pair_info.pair_decimals,
|
||||||
|
'asset_type': 'crypto',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
records[tid] = Transaction(
|
records[tid] = Transaction(
|
||||||
fqsn=f'{norm_sym}.kraken',
|
fqsn=fqsn,
|
||||||
|
sym=mktpair,
|
||||||
tid=tid,
|
tid=tid,
|
||||||
size=size,
|
size=size,
|
||||||
price=float(record['price']),
|
price=float(record['price']),
|
||||||
|
|
|
@ -42,56 +42,15 @@ from piker.brokers._util import (
|
||||||
DataUnavailable,
|
DataUnavailable,
|
||||||
)
|
)
|
||||||
from piker.log import get_console_log
|
from piker.log import get_console_log
|
||||||
from piker.data import ShmArray
|
|
||||||
from piker.data.types import Struct
|
from piker.data.types import Struct
|
||||||
from piker.data._web_bs import open_autorecon_ws, NoBsWs
|
from piker.data._web_bs import open_autorecon_ws, NoBsWs
|
||||||
from . import log
|
from . import log
|
||||||
from .api import (
|
from .api import (
|
||||||
Client,
|
Client,
|
||||||
|
Pair,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# https://www.kraken.com/features/api#get-tradable-pairs
|
|
||||||
class Pair(Struct):
|
|
||||||
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
|
|
||||||
costmin: float
|
|
||||||
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
|
|
||||||
|
|
||||||
short_position_limit: float
|
|
||||||
long_position_limit: float
|
|
||||||
|
|
||||||
|
|
||||||
class OHLC(Struct):
|
class OHLC(Struct):
|
||||||
'''
|
'''
|
||||||
Description of the flattened OHLC quote format.
|
Description of the flattened OHLC quote format.
|
||||||
|
@ -336,14 +295,14 @@ async def stream_quotes(
|
||||||
|
|
||||||
# transform to upper since piker style is always lower
|
# transform to upper since piker style is always lower
|
||||||
sym = sym.upper()
|
sym = sym.upper()
|
||||||
sym_info = await client.symbol_info(sym)
|
si: Pair = await client.symbol_info(sym)
|
||||||
try:
|
# try:
|
||||||
si = Pair(**sym_info) # validation
|
# si = Pair(**sym_info) # validation
|
||||||
except TypeError:
|
# except TypeError:
|
||||||
fields_diff = set(sym_info) - set(Pair.__struct_fields__)
|
# fields_diff = set(sym_info) - set(Pair.__struct_fields__)
|
||||||
raise TypeError(
|
# raise TypeError(
|
||||||
f'Missing msg fields {fields_diff}'
|
# f'Missing msg fields {fields_diff}'
|
||||||
)
|
# )
|
||||||
syminfo = si.to_dict()
|
syminfo = si.to_dict()
|
||||||
syminfo['price_tick_size'] = 1. / 10**si.pair_decimals
|
syminfo['price_tick_size'] = 1. / 10**si.pair_decimals
|
||||||
syminfo['lot_tick_size'] = 1. / 10**si.lot_decimals
|
syminfo['lot_tick_size'] = 1. / 10**si.lot_decimals
|
||||||
|
|
Loading…
Reference in New Issue