Finally get a chart going! lots of fixes to streaming machinery and custom cryptofeed fork with fixes
parent
e558e5837e
commit
28e025d02e
|
@ -19,6 +19,7 @@ Deribit backend
|
|||
|
||||
"""
|
||||
import asyncio
|
||||
from async_generator import aclosing
|
||||
from contextlib import asynccontextmanager as acm
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
|
@ -74,7 +75,7 @@ def get_config() -> dict[str, Any]:
|
|||
return {}
|
||||
|
||||
conf['log'] = {}
|
||||
conf['log']['filename'] = 'feedhandler.log'
|
||||
conf['log']['filename'] = '/tmp/feedhandler.log'
|
||||
conf['log']['level'] = 'WARNING'
|
||||
|
||||
return conf
|
||||
|
@ -95,9 +96,19 @@ _ohlc_dtype = [
|
|||
('low', float),
|
||||
('close', float),
|
||||
('volume', float),
|
||||
('bar_wap', float), # will be zeroed by sampler if not filled
|
||||
# ('bar_wap', float), # will be zeroed by sampler if not filled
|
||||
]
|
||||
|
||||
|
||||
class JSONRPCResult(BaseModel):
|
||||
jsonrpc: str = '2.0'
|
||||
result: dict
|
||||
usIn: int
|
||||
usOut: int
|
||||
usDiff: int
|
||||
testnet: bool
|
||||
|
||||
|
||||
class KLinesResult(BaseModel):
|
||||
close: List[float]
|
||||
cost: List[float]
|
||||
|
@ -108,13 +119,30 @@ class KLinesResult(BaseModel):
|
|||
ticks: List[int]
|
||||
volume: List[float]
|
||||
|
||||
class KLines(BaseModel):
|
||||
jsonrpc: str = '2.0'
|
||||
|
||||
class KLines(JSONRPCResult):
|
||||
result: KLinesResult
|
||||
usIn: int
|
||||
usOut: int
|
||||
usDiff: int
|
||||
testnet: bool
|
||||
|
||||
|
||||
class Trade(BaseModel):
|
||||
trade_seq: int
|
||||
trade_id: str
|
||||
timestamp: int
|
||||
tick_direction: int
|
||||
price: float
|
||||
mark_price: float
|
||||
iv: float
|
||||
instrument_name: str
|
||||
index_price: float
|
||||
direction: str
|
||||
amount: float
|
||||
|
||||
class LastTradesResult(BaseModel):
|
||||
trades: List[Trade]
|
||||
has_more: bool
|
||||
|
||||
class LastTrades(JSONRPCResult):
|
||||
result: LastTradesResult
|
||||
|
||||
|
||||
# convert datetime obj timestamp to unixtime in milliseconds
|
||||
|
@ -122,6 +150,68 @@ def deribit_timestamp(when):
|
|||
return int((when.timestamp() * 1000) + (when.microsecond / 1000))
|
||||
|
||||
|
||||
def str_to_cb_sym(name: str) -> Symbol:
|
||||
base, strike_price, expiry_date, option_type = name.split('-')
|
||||
|
||||
quote = base
|
||||
|
||||
if option_type == 'put':
|
||||
option_type = PUT
|
||||
elif option_type == 'call':
|
||||
option_type = CALL
|
||||
else:
|
||||
raise BaseException("Couldn\'t parse option type")
|
||||
|
||||
return Symbol(
|
||||
base, quote,
|
||||
type=OPTION,
|
||||
strike_price=strike_price,
|
||||
option_type=option_type,
|
||||
expiry_date=expiry_date,
|
||||
expiry_normalize=False)
|
||||
|
||||
|
||||
|
||||
def piker_sym_to_cb_sym(name: str) -> Symbol:
|
||||
base, expiry_date, strike_price, option_type = tuple(
|
||||
name.upper().split('-'))
|
||||
|
||||
quote = base
|
||||
|
||||
if option_type == 'P':
|
||||
option_type = PUT
|
||||
elif option_type == 'C':
|
||||
option_type = CALL
|
||||
else:
|
||||
raise BaseException("Couldn\'t parse option type")
|
||||
|
||||
return Symbol(
|
||||
base, quote,
|
||||
type=OPTION,
|
||||
strike_price=strike_price,
|
||||
option_type=option_type,
|
||||
expiry_date=expiry_date.upper())
|
||||
|
||||
|
||||
def cb_sym_to_deribit_inst(sym: Symbol):
|
||||
# cryptofeed normalized
|
||||
cb_norm = ['F', 'G', 'H', 'J', 'K', 'M', 'N', 'Q', 'U', 'V', 'X', 'Z']
|
||||
|
||||
# deribit specific
|
||||
months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
|
||||
|
||||
exp = sym.expiry_date
|
||||
|
||||
# YYMDD
|
||||
# 01234
|
||||
year, month, day = (
|
||||
exp[:2], months[cb_norm.index(exp[2:3])], exp[3:])
|
||||
|
||||
otype = 'C' if sym.option_type == CALL else 'P'
|
||||
|
||||
return f'{sym.base}-{day}{month}{year}-{sym.strike_price}-{otype}'
|
||||
|
||||
|
||||
class Client:
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
@ -255,10 +345,24 @@ class Client:
|
|||
|
||||
new_bars.append((i,) + tuple(row))
|
||||
|
||||
array = np.array(
|
||||
[i, ], dtype=_ohlc_dtype) if as_np else klines
|
||||
array = np.array(new_bars, dtype=_ohlc_dtype) if as_np else klines
|
||||
return array
|
||||
|
||||
async def last_trades(
|
||||
self,
|
||||
instrument: str,
|
||||
count: int = 10
|
||||
):
|
||||
response = await self._api(
|
||||
'get_last_trades_by_instrument',
|
||||
params={
|
||||
'instrument_name': instrument,
|
||||
'count': count
|
||||
}
|
||||
)
|
||||
|
||||
return LastTrades(**response)
|
||||
|
||||
|
||||
@acm
|
||||
async def get_client() -> Client:
|
||||
|
@ -274,42 +378,22 @@ async def open_aio_cryptofeed_relay(
|
|||
instruments: List[str] = []
|
||||
) -> None:
|
||||
|
||||
conf = get_config()
|
||||
|
||||
def format_sym(name: str) -> str:
|
||||
base, expiry_date, strike_price, option_type = tuple(
|
||||
name.upper().split('-'))
|
||||
|
||||
quote = base
|
||||
|
||||
if option_type == 'P':
|
||||
option_type = PUT
|
||||
elif option_type == 'C':
|
||||
option_type = CALL
|
||||
else:
|
||||
raise BaseException("Instrument name must end in 'c' for calls or 'p' for puts")
|
||||
|
||||
return Symbol(
|
||||
base, quote,
|
||||
type=OPTION,
|
||||
strike_price=strike_price,
|
||||
option_type=option_type,
|
||||
expiry_date=expiry_date.upper()).normalized
|
||||
|
||||
instruments = [format_sym(i) for i in instruments]
|
||||
instruments = [piker_sym_to_cb_sym(i) for i in instruments]
|
||||
|
||||
async def trade_cb(data: dict, receipt_timestamp):
|
||||
breakpoint()
|
||||
# to_trio.send_nowait(('trade', {
|
||||
# 'symbol': data.symbol.lower(),
|
||||
# 'last': data.
|
||||
# 'broker_ts': time.time(),
|
||||
# 'data': data.to_dict(),
|
||||
# 'receipt': receipt_timestamp}))
|
||||
to_trio.send_nowait(('trade', {
|
||||
'symbol': cb_sym_to_deribit_inst(
|
||||
str_to_cb_sym(data.symbol)).lower(),
|
||||
'last': data,
|
||||
'broker_ts': time.time(),
|
||||
'data': data.to_dict(),
|
||||
'receipt': receipt_timestamp
|
||||
}))
|
||||
|
||||
async def l1_book_cb(data: dict, receipt_timestamp):
|
||||
to_trio.send_nowait(('l1', {
|
||||
'symbol': data.symbol.lower(),
|
||||
'symbol': cb_sym_to_deribit_inst(
|
||||
str_to_cb_sym(data.symbol)).lower(),
|
||||
'ticks': [
|
||||
{'type': 'bid',
|
||||
'price': float(data.bid_price), 'size': float(data.bid_size)},
|
||||
|
@ -319,45 +403,42 @@ async def open_aio_cryptofeed_relay(
|
|||
'price': float(data.ask_price), 'size': float(data.ask_size)},
|
||||
{'type': 'asize',
|
||||
'price': float(data.ask_price), 'size': float(data.ask_size)}
|
||||
]}))
|
||||
]
|
||||
}))
|
||||
|
||||
fh = FeedHandler(config=conf)
|
||||
fh = FeedHandler(config=get_config())
|
||||
fh.run(start_loop=False)
|
||||
|
||||
fh.add_feed(
|
||||
DERIBIT,
|
||||
channels=[L1_BOOK, TRADES],
|
||||
channels=[L1_BOOK],
|
||||
symbols=instruments,
|
||||
callbacks={
|
||||
L1_BOOK: L1BookCallback(l1_book_cb),
|
||||
TRADES: TradeCallback(trade_cb)
|
||||
})
|
||||
callbacks={L1_BOOK: l1_book_cb})
|
||||
|
||||
fh.add_feed(
|
||||
DERIBIT,
|
||||
channels=[TRADES],
|
||||
symbols=instruments,
|
||||
callbacks={TRADES: trade_cb})
|
||||
|
||||
# sync with trio
|
||||
to_trio.send_nowait(None)
|
||||
|
||||
await from_trio.get()
|
||||
await asyncio.sleep(float('inf'))
|
||||
|
||||
|
||||
@acm
|
||||
async def open_cryptofeeds(
|
||||
instruments: List[str],
|
||||
to_chart: trio.abc.SendChannel,
|
||||
|
||||
# startup sync
|
||||
task_status: TaskStatus[tuple[dict, dict]] = trio.TASK_STATUS_IGNORED,
|
||||
):
|
||||
instruments: List[str]
|
||||
|
||||
) -> trio.abc.ReceiveStream:
|
||||
|
||||
async with to_asyncio.open_channel_from(
|
||||
open_aio_cryptofeed_relay,
|
||||
instruments=instruments,
|
||||
) as (first, chan):
|
||||
assert first is None
|
||||
|
||||
await chan.send(None)
|
||||
|
||||
async with chan.subscribe() as msg_stream:
|
||||
task_status.started()
|
||||
async for msg in msg_stream:
|
||||
await to_chart.send(msg)
|
||||
yield chan
|
||||
|
||||
|
||||
@acm
|
||||
|
@ -420,40 +501,53 @@ async def stream_quotes(
|
|||
get_console_log(loglevel or tractor.current_actor().loglevel)
|
||||
|
||||
sym = symbols[0]
|
||||
to_chart, from_feed = trio.open_memory_channel(1)
|
||||
|
||||
async with (
|
||||
open_cached_client('deribit') as client,
|
||||
send_chan as send_chan,
|
||||
trio.open_nursery() as n
|
||||
trio.open_nursery() as n,
|
||||
open_cryptofeeds(symbols) as stream
|
||||
):
|
||||
await n.start(
|
||||
open_cryptofeeds, symbols, to_chart)
|
||||
|
||||
init_msgs = {
|
||||
# pass back token, and bool, signalling if we're the writer
|
||||
# and that history has been written
|
||||
sym: {
|
||||
'symbol_info': {},
|
||||
'symbol_info': {
|
||||
'asset_type': 'option'
|
||||
},
|
||||
'shm_write_opts': {'sum_tick_vml': False},
|
||||
'fqsn': sym,
|
||||
},
|
||||
}
|
||||
|
||||
nsym = piker_sym_to_cb_sym(sym)
|
||||
|
||||
# keep client cached for real-time section
|
||||
cache = await client.cache_symbols()
|
||||
|
||||
async with from_feed:
|
||||
typ, quote = await anext(from_feed)
|
||||
last_trade = (await client.last_trades(
|
||||
cb_sym_to_deribit_inst(nsym), count=1)).result.trades[0]
|
||||
|
||||
while typ != 'trade':
|
||||
typ, quote = await anext(from_feed)
|
||||
first_quote = {
|
||||
'symbol': sym,
|
||||
'last': last_trade.price,
|
||||
'brokerd_ts': last_trade.timestamp,
|
||||
'ticks': [{
|
||||
'type': 'trade',
|
||||
'price': last_trade.price,
|
||||
'size': last_trade.amount,
|
||||
'broker_ts': last_trade.timestamp
|
||||
}]
|
||||
}
|
||||
task_status.started((init_msgs, first_quote))
|
||||
|
||||
task_status.started((init_msgs, quote))
|
||||
async with aclosing(stream):
|
||||
feed_is_live.set()
|
||||
|
||||
async for typ, msg in from_feed:
|
||||
topic = msg['symbol']
|
||||
await send_chan.send({topic: msg})
|
||||
async for typ, quote in stream:
|
||||
topic = quote['symbol']
|
||||
await send_chan.send({topic: quote})
|
||||
|
||||
|
||||
@tractor.context
|
||||
|
|
|
@ -18,3 +18,6 @@
|
|||
|
||||
# ``asyncvnc`` for sending interactions to ib-gw inside docker
|
||||
-e git+https://github.com/pikers/asyncvnc.git@main#egg=asyncvnc
|
||||
|
||||
# ``cryptofeed`` for connecting to various crypto exchanges + custom fixes
|
||||
-e git+https://github.com/guilledk/cryptofeed.git@date_parsing#egg=cryptofeed
|
||||
|
|
Loading…
Reference in New Issue