Compare commits

...

15 Commits

4 changed files with 241 additions and 53 deletions

View File

@ -25,6 +25,7 @@ from .api import (
get_client, get_client,
) )
from .feed import ( from .feed import (
get_mkt_info,
open_history_client, open_history_client,
open_symbol_search, open_symbol_search,
stream_quotes, stream_quotes,
@ -34,15 +35,20 @@ from .feed import (
# open_trade_dialog, # open_trade_dialog,
# norm_trade_records, # norm_trade_records,
# ) # )
from .venues import (
OptionPair,
)
log = get_logger(__name__) log = get_logger(__name__)
__all__ = [ __all__ = [
'get_client', 'get_client',
# 'trades_dialogue', # 'trades_dialogue',
'get_mkt_info',
'open_history_client', 'open_history_client',
'open_symbol_search', 'open_symbol_search',
'stream_quotes', 'stream_quotes',
'OptionPair',
# 'norm_trade_records', # 'norm_trade_records',
] ]

View File

@ -19,10 +19,14 @@ Deribit backend.
''' '''
import asyncio import asyncio
from collections import ChainMap
from contextlib import ( from contextlib import (
asynccontextmanager as acm, asynccontextmanager as acm,
) )
from datetime import datetime from datetime import datetime
from decimal import (
Decimal,
)
from functools import partial from functools import partial
import time import time
from typing import ( from typing import (
@ -53,7 +57,16 @@ from cryptofeed.defines import (
from cryptofeed.symbols import Symbol from cryptofeed.symbols import Symbol
# types for managing the cb callbacks. # types for managing the cb callbacks.
# from cryptofeed.types import L1Book # from cryptofeed.types import L1Book
from piker.accounting import MktPair from .venues import (
MarketType,
PAIRTYPES,
Pair,
OptionPair,
)
from piker.accounting import (
Asset,
MktPair,
)
from piker.data import ( from piker.data import (
def_iohlcv_fields, def_iohlcv_fields,
match_from_pairs, match_from_pairs,
@ -257,7 +270,7 @@ class Client:
json_rpc: Callable json_rpc: Callable
) -> None: ) -> None:
self._pairs: dict[str, Any] = None self._pairs: ChainMap[str, Pair] = ChainMap()
config = get_config().get('option', {}) config = get_config().get('option', {})
@ -289,20 +302,39 @@ class Client:
return balances return balances
async def get_assets(self) -> dict[str, float]: async def get_assets(
self,
venue: str | None = None,
) -> dict[str, Asset]:
"""Return the set of asset balances for this account """Return the set of asset balances for this account
by symbol. by symbol.
""" """
balances = {} assets = {}
for currency in self.currencies:
resp = await self.json_rpc( resp = await self.json_rpc(
'private/get_account_summary', params={ 'private/get_account_summaries',
'currency': currency.upper()}) params={
'extended' : True
}
)
summaries = resp.result['summaries']
for summary in summaries:
currency = summary['currency']
tx_tick = Decimal('1e-08')
atype='crypto_currency'
assets[currency] = Asset(
name=currency,
atype=atype,
tx_tick=tx_tick)
return assets
balances[currency] = resp.result['balance'] async def get_mkt_pairs(self) -> dict[str, Pair]:
flat: dict[str, Pair] = {}
for key in self._pairs:
item = self._pairs.get(key)
flat[item.bs_fqme] = item
return balances return flat
async def submit_limit( async def submit_limit(
self, self,
@ -331,6 +363,28 @@ class Client:
'private/cancel', {'order_id': oid}) 'private/cancel', {'order_id': oid})
return resp.result return resp.result
async def exch_info(
self,
sym: str | None = None,
venue: MarketType | None = None,
expiry: str | None = None,
) -> dict[str, Pair] | Pair:
pair_table: dict[str, Pair] = self._pairs
if (
sym
and (cached_pair := pair_table.get(sym))
):
return cached_pair
if sym:
return pair_table[sym.lower()]
else:
return self._pairs
async def symbol_info( async def symbol_info(
self, self,
instrument: Optional[str] = None, instrument: Optional[str] = None,
@ -338,7 +392,7 @@ class Client:
kind: str = 'option', kind: str = 'option',
expired: bool = False expired: bool = False
) -> dict[str, dict]: ) -> dict[str, Pair] | Pair:
''' '''
Get symbol infos. Get symbol infos.
@ -358,14 +412,29 @@ class Client:
params, params,
) )
# convert to symbol-keyed table # convert to symbol-keyed table
pair_type: Type = PAIRTYPES[kind]
results: list[dict] | None = resp.result results: list[dict] | None = resp.result
instruments: dict[str, dict] = {
item['instrument_name'].lower(): item instruments: dict[str, Pair] = {}
for item in results for item in results:
} symbol=item['instrument_name'].lower()
try:
pair: Pair = pair_type(
symbol=symbol,
**item
)
except Exception as e:
e.add_note(
"\nDon't panic, prolly stupid deribit changed their symbology schema again..\n"
'Check out their API docs here:\n\n'
'https://docs.deribit.com/?python#deribit-api-v2-1-1'
)
raise
instruments[symbol] = pair
if instrument is not None: if instrument is not None:
return instruments[instrument] return instruments[instrument.lower()]
else: else:
return instruments return instruments
@ -382,12 +451,12 @@ class Client:
self, self,
pattern: str, pattern: str,
limit: int = 30, limit: int = 30,
) -> dict[str, Any]: ) -> dict[str, Pair]:
''' '''
Fuzzy search symbology set for pairs matching `pattern`. Fuzzy search symbology set for pairs matching `pattern`.
''' '''
pairs: dict[str, Any] = await self.symbol_info() pairs: dict[str, Pair] = await self.symbol_info()
matches: dict[str, Pair] = match_from_pairs( matches: dict[str, Pair] = match_from_pairs(
pairs=pairs, pairs=pairs,
query=pattern.upper(), query=pattern.upper(),
@ -588,14 +657,26 @@ async def aio_price_feed_relay(
'symbol': cb_sym_to_deribit_inst( 'symbol': cb_sym_to_deribit_inst(
str_to_cb_sym(data.symbol)).lower(), str_to_cb_sym(data.symbol)).lower(),
'ticks': [ 'ticks': [
{'type': 'bid', {
'price': float(data.bid_price), 'size': float(data.bid_size)}, 'type': 'bid',
{'type': 'bsize', 'price': float(data.bid_price),
'price': float(data.bid_price), 'size': float(data.bid_size)}, 'size': float(data.bid_size)
{'type': 'ask', },
'price': float(data.ask_price), 'size': float(data.ask_size)}, {
{'type': 'asize', 'type': 'bsize',
'price': float(data.ask_price), 'size': float(data.ask_size)} 'price': float(data.bid_price),
'size': float(data.bid_size)
},
{
'type': 'ask',
'price': float(data.ask_price),
'size': float(data.ask_size)
},
{
'type': 'asize',
'price': float(data.ask_price),
'size': float(data.ask_size)
}
] ]
})) }))
sym: Symbol = piker_sym_to_cb_sym(instrument) sym: Symbol = piker_sym_to_cb_sym(instrument)

View File

@ -21,6 +21,7 @@ Deribit backend.
from contextlib import asynccontextmanager as acm from contextlib import asynccontextmanager as acm
from datetime import datetime from datetime import datetime
from typing import Any, Optional, Callable from typing import Any, Optional, Callable
from pprint import pformat
import time import time
import trio import trio
@ -33,13 +34,20 @@ from rapidfuzz import process as fuzzy
import numpy as np import numpy as np
import tractor import tractor
from piker.accounting import MktPair from piker.accounting import (
MktPair,
unpack_fqme,
)
from piker.brokers import ( from piker.brokers import (
open_cached_client, open_cached_client,
NoData, NoData,
) )
from piker._cacheables import (
async_lifo_cache,
)
from piker.log import get_logger, get_console_log from piker.log import get_logger, get_console_log
from piker.data import ShmArray from piker.data import ShmArray
from piker.data.validate import FeedInit
from piker.brokers._util import ( from piker.brokers._util import (
BrokerError, BrokerError,
DataUnavailable, DataUnavailable,
@ -57,6 +65,10 @@ from .api import (
piker_sym_to_cb_sym, cb_sym_to_deribit_inst, piker_sym_to_cb_sym, cb_sym_to_deribit_inst,
maybe_open_price_feed maybe_open_price_feed
) )
from .venues import (
Pair,
OptionPair,
)
_spawn_kwargs = { _spawn_kwargs = {
'infect_asyncio': True, 'infect_asyncio': True,
@ -117,6 +129,73 @@ async def open_history_client(
yield get_ohlc, {'erlangs': 3, 'rate': 3} yield get_ohlc, {'erlangs': 3, 'rate': 3}
@async_lifo_cache()
async def get_mkt_info(
fqme: str,
) -> tuple[MktPair, Pair] | None:
# uppercase since kraken bs_mktid is always upper
if 'deribit' not in fqme.lower():
fqme += '.deribit'
mkt_mode: str = ''
broker, mkt_ep, venue, expiry = unpack_fqme(fqme)
# NOTE: we always upper case all tokens to be consistent with
# binance's symbology style for pairs, like `BTCUSDT`, but in
# theory we could also just keep things lower case; as long as
# we're consistent and the symcache matches whatever this func
# returns, always!
expiry: str = expiry.upper()
venue: str = venue.upper()
venue_lower: str = venue.lower()
mkt_mode: str = 'option'
async with open_cached_client(
'deribit',
) as client:
assets: dict[str, Asset] = await client.get_assets()
pair_str: str = mkt_ep.lower()
# 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
pair: Pair = await client.exch_info(
sym=pair_str,
)
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.base_currency} 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(
dst=dst,
src=assets.get(pair.bs_src_asset),
price_tick=pair.price_tick,
size_tick=pair.size_tick,
bs_mktid=pair.symbol,
expiry=expiry,
venue=venue,
broker='deribit',
)
return mkt, pair
async def stream_quotes( async def stream_quotes(
send_chan: trio.abc.SendChannel, send_chan: trio.abc.SendChannel,
@ -133,29 +212,19 @@ async def stream_quotes(
sym = symbols[0] sym = symbols[0]
#init_msgs: list[FeedInit] = [] init_msgs: list[FeedInit] = []
async with ( async with (
open_cached_client('deribit') as client, open_cached_client('deribit') as client,
send_chan as send_chan send_chan as send_chan
): ):
init_msgs = { mkt, pair = await get_mkt_info(sym)
# pass back token, and bool, signalling if we're the writer
# and that history has been written
sym: {
'symbol_info': {
'asset_type': 'option',
'price_tick_size': 0.0005
},
'shm_write_opts': {
'sum_tick_vml': True,
'has_vlm': True
},
'fqsn': sym,
},
}
# build out init msgs according to latest spec
init_msgs.append(
FeedInit(mkt_info=mkt)
)
nsym = piker_sym_to_cb_sym(sym.split('.')[0]) nsym = piker_sym_to_cb_sym(sym.split('.')[0])
async with maybe_open_price_feed(sym) as stream: async with maybe_open_price_feed(sym) as stream:
@ -207,7 +276,16 @@ async def open_symbol_search(
async with ctx.open_stream() as stream: async with ctx.open_stream() as stream:
pattern: str
async for pattern in stream: async for pattern in stream:
# repack in dict form # NOTE: pattern fuzzy-matching is done within
await stream.send( # the methd impl.
await client.search_symbols(pattern)) pairs: dict[str, Pair] = await client.search_symbols(
pattern,
)
# repack in fqme-keyed table
byfqme: dict[str, Pair] = {}
for pair in pairs.values():
byfqme[pair.bs_fqme] = pair
await stream.send(byfqme)

View File

@ -56,25 +56,45 @@ def get_api_eps(venue: MarketType) -> tuple[str, str]:
}[venue] }[venue]
class OptionPair(Struct, frozen=True, kw_only=True): class Pair(Struct, frozen=True, kw_only=True):
symbol: str symbol: str
venue: str
# src # src
quote_currency: str quote_currency: str # 'BTC'
# dst # dst
base_currency: str base_currency: str # "BTC",
tick_size: float # 0.0001 tick_size: float # 0.0001
tick_size_steps: list[dict] # [{'above_price': 0.005, 'tick_size': 0.0005}] tick_size_steps: list[dict[str, str | int | float]] # [{'above_price': 0.005, 'tick_size': 0.0005}]
@property
def price_tick(self) -> Decimal:
step_size: float = self.tick_size_steps[0].get('above_price')
return Decimal(step_size)
@property
def size_tick(self) -> Decimal:
step_size: float = self.tick_size_steps[0].get('tick_size')
return Decimal(step_size)
@property
def bs_fqme(self) -> str:
return self.symbol
@property
def bs_mktid(self) -> str:
return f'{self.symbol}.{self.venue}'
class OptionPair(Pair, frozen=True, kw_only=True):
taker_commission: float # 0.0003 taker_commission: float # 0.0003
strike: float # 5000.0 strike: float # 5000.0
settlement_period: str # 'day' settlement_period: str # 'day'
settlement_currency: str # "BTC", settlement_currency: str # "BTC",
rfq: bool # false rfq: bool # false
quote_currency: str # 'BTC'
price_index: str # 'btc_usd' price_index: str # 'btc_usd'
option_type: str # 'call' option_type: str # 'call'
min_trade_amount: float # 0.1 min_trade_amount: float # 0.1
@ -117,3 +137,6 @@ class OptionPair(Struct, frozen=True, kw_only=True):
return f'{self.symbol}.{self.venue}' return f'{self.symbol}.{self.venue}'
PAIRTYPES: dict[MarketType, Pair] = {
'option': OptionPair,
}