Finally backfilling is working, still need to work on realtime updates!

binance_backend
Guillermo Rodriguez 2021-05-05 10:17:52 -03:00
parent d327584039
commit 7e493625f6
No known key found for this signature in database
GPG Key ID: 3F61096EC7DF75A8
1 changed files with 79 additions and 35 deletions

View File

@ -21,7 +21,7 @@ Binance backend
from contextlib import asynccontextmanager, AsyncExitStack from contextlib import asynccontextmanager, AsyncExitStack
from dataclasses import asdict, field from dataclasses import asdict, field
from types import ModuleType from types import ModuleType
from typing import List, Dict, Any, Tuple, Optional from typing import List, Dict, Any, Tuple, Union, Optional
import json import json
import time import time
@ -52,13 +52,14 @@ from ..data import ShmArray
log = get_logger(__name__) log = get_logger(__name__)
_url = 'https://api.binance.com/' _url = 'https://api.binance.com'
# Broker specific ohlc schema # Broker specific ohlc schema (websocket)
_ohlc_dtype = [ _websocket_ohlc_dtype = [
('kline_start_time', int), ('index', int),
('kline_close_time', int), ('time', int),
('close_time', int),
('symbol', str), ('symbol', str),
('interval', str), ('interval', str),
('first_trade_id', int), ('first_trade_id', int),
@ -76,30 +77,52 @@ _ohlc_dtype = [
('ignore', int) ('ignore', int)
] ]
# Broker specific ohlc schema (rest)
_ohlc_dtype = [
('index', int),
('time', int),
('open', float),
('high', float),
('low', float),
('close', float),
('volume', float),
('close_time', int),
('quote_vol', float),
('num_trades', int),
('buy_base_vol', float),
('buy_quote_vol', float),
('ignore', float)
]
# UI components allow this to be declared such that additional # UI components allow this to be declared such that additional
# (historical) fields can be exposed. # (historical) fields can be exposed.
ohlc_dtype = np.dtype(_ohlc_dtype) ohlc_dtype = np.dtype(_ohlc_dtype)
_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(BaseModel): class Pair(BaseModel):
symbol: str symbol: str
status: str status: str
base_asset: str baseAsset: str
base_precision: int baseAssetPrecision: int
quote_asset: str quoteAsset: str
quote_precision: int quotePrecision: int
quote_asset_precision: int quoteAssetPrecision: int
order_types: List[str] baseCommissionPrecision: int
quoteCommissionPrecision: int
iceberg_allowed: bool orderTypes: List[str]
oco_allowed: bool
is_spot_trading_allowed: bool
is_margin_trading_allowed: bool
filters: List[str] icebergAllowed: bool
ocoAllowed: bool
quoteOrderQtyMarketAllowed: bool
isSpotTradingAllowed: bool
isMarginTradingAllowed: bool
filters: List[Dict[str, Union[str, int, float]]]
permissions: List[str] permissions: List[str]
@ -117,15 +140,21 @@ class OHLC:
first_id: int first_id: int
last_id: int last_id: int
open: float open: float
close: float
high: float high: float
low: float low: float
close: float
base_vol: float base_vol: float
num_trades: int num_trades: int
closed: bool closed: bool
quote_vol: float quote_vol: float
buy_base_vol: float buy_base_vol: float
buy_quote_vol: float buy_quote_vol: float
ignore: int
# convert arrow timestamp to unixtime in miliseconds
def binance_timestamp(when):
return int((when.timestamp * 1000) + (when.microsecond / 1000))
class Client: class Client:
@ -139,7 +168,7 @@ class Client:
method: str, method: str,
data: dict, data: dict,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
resp = await self._sesh.post( resp = await self._sesh.get(
path=f'/api/v3/{method}', path=f'/api/v3/{method}',
params=data, params=data,
timeout=float('inf') timeout=float('inf')
@ -152,11 +181,11 @@ class Client:
): ):
resp = await self._api('exchangeInfo', {}) resp = await self._api('exchangeInfo', {})
if sym is not None: if sym is not None:
return [ for sym_info in resp['symbols']:
sym_info if sym_info['symbol'] == sym:
for sym_info in resp['symbols'] return sym_info
if sym_info['symbol'] == sym else:
] raise BrokerError(f'{sym} not found')
else: else:
return resp['symbols'] return resp['symbols']
@ -169,13 +198,16 @@ class Client:
as_np: bool = True, as_np: bool = True,
) -> dict: ) -> dict:
if start_time is None: if start_time is None:
start_time = int(arrow.utcnow().floor('minute').shift( start_time = binance_timestamp(
minutes=-limit).format('x')) arrow.utcnow()
.floor('minute')
.shift(minutes=-limit)
)
if end_time is None: if end_time is None:
end_time = int(arrow.utcnow().format('x')) end_time = binance_timestamp(arrow.utcnow())
json = await self._api( bars = await self._api(
'klines', 'klines',
{ {
'symbol': symbol, 'symbol': symbol,
@ -186,8 +218,14 @@ class Client:
} }
) )
bars = next(iter(json)) new_bars = [
array = np.array(bars, dtype=_ohlc_dtype) if as_np else bars (i,) + tuple(
ftype(bar[j])
for j, (name, ftype) in enumerate(_ohlc_dtype[1:])
) for i, bar in enumerate(bars)
]
array = np.array(new_bars, dtype=_ohlc_dtype) if as_np else bars
return array return array
@ -205,7 +243,9 @@ async def stream_messages(ws):
with trio.move_on_after(5) as cs: with trio.move_on_after(5) as cs:
msg = await ws.recv_msg() msg = await ws.recv_msg()
breakpoint() if msg.get('e') == 'kline':
yield 'ohlc', OHLC(*msg['k'].values())
def normalize( def normalize(
@ -215,9 +255,10 @@ def normalize(
quote['broker_ts'] = quote['start_time'] quote['broker_ts'] = quote['start_time']
quote['brokerd_ts'] = time.time() quote['brokerd_ts'] = time.time()
quote['last'] = quote['close'] quote['last'] = quote['close']
quote['time'] = quote['start_time']
# print(quote) # print(quote)
return topic, quote return ohlc.symbol, quote
def make_sub(pairs: List[str], sub_name: str, uid: int) -> Dict[str, str]: def make_sub(pairs: List[str], sub_name: str, uid: int) -> Dict[str, str]:
@ -228,7 +269,7 @@ def make_sub(pairs: List[str], sub_name: str, uid: int) -> Dict[str, str]:
return { return {
'method': 'SUBSCRIBE', 'method': 'SUBSCRIBE',
'params': [ 'params': [
f'{pair}@{sub_name}' f'{pair.lower()}@{sub_name}'
for pair in pairs for pair in pairs
], ],
'id': uid 'id': uid
@ -362,7 +403,8 @@ async def stream_quotes(
# keep client cached for real-time section # keep client cached for real-time section
for sym in symbols: for sym in symbols:
syminfo = Pair(*await client.symbol_info(sym)) # validation d = await client.symbol_info(sym)
syminfo = Pair(**d) # validation
sym_infos[sym] = syminfo.dict() sym_infos[sym] = syminfo.dict()
symbol = symbols[0] symbol = symbols[0]
@ -376,19 +418,21 @@ async def stream_quotes(
}, },
} }
async with open_autorecon_ws('wss://stream.binance.com:9443') as ws: async with open_autorecon_ws('wss://stream.binance.com/ws') as ws:
# XXX: setup subs # XXX: setup subs
ohlc_sub = make_sub(symbols, 'kline_1m', uid) ohlc_sub = make_sub(symbols, 'kline_1m', uid)
uid += 1 uid += 1
await ws.send_msg(ohlc_sub) await ws.send_msg(ohlc_sub)
res = await ws.recv_msg()
# trade data (aka L1) # trade data (aka L1)
l1_sub = make_sub(symbols, 'trade', uid) l1_sub = make_sub(symbols, 'trade', uid)
uid += 1 uid += 1
await ws.send_msg(l1_sub) await ws.send_msg(l1_sub)
res = await ws.recv_msg()
# pull a first quote and deliver # pull a first quote and deliver
msg_gen = stream_messages(ws) msg_gen = stream_messages(ws)