Add L1 data feed and correct history issue
parent
79956abc5e
commit
480b8c591a
|
@ -1,4 +1,3 @@
|
||||||
# piker: trading gear for hackers
|
|
||||||
# Copyright (C) Jared Goldman (in stewardship for pikers)
|
# Copyright (C) Jared Goldman (in stewardship for pikers)
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -127,19 +126,29 @@ class KucoinTrade(Struct, frozen=True):
|
||||||
time: float
|
time: float
|
||||||
|
|
||||||
|
|
||||||
|
class KucoinL2(Struct, frozen=True):
|
||||||
|
'''
|
||||||
|
Real-time L2 order book format
|
||||||
|
|
||||||
|
'''
|
||||||
|
asks: list[list[float]]
|
||||||
|
bids: list[list[float]]
|
||||||
|
timestamp: float
|
||||||
|
|
||||||
|
|
||||||
|
class KucoinMsg(Struct, frozen=True):
|
||||||
|
type: str
|
||||||
|
topic: str
|
||||||
|
subject: str
|
||||||
|
data: list[KucoinTrade | KucoinL2]
|
||||||
|
|
||||||
|
|
||||||
class BrokerConfig(Struct, frozen=True):
|
class BrokerConfig(Struct, frozen=True):
|
||||||
key_id: str
|
key_id: str
|
||||||
key_secret: str
|
key_secret: str
|
||||||
key_passphrase: str
|
key_passphrase: str
|
||||||
|
|
||||||
|
|
||||||
class KucoinTradeMsg(Struct, frozen=True):
|
|
||||||
type: str
|
|
||||||
topic: str
|
|
||||||
subject: str
|
|
||||||
data: list[KucoinTrade]
|
|
||||||
|
|
||||||
|
|
||||||
def get_config() -> BrokerConfig | None:
|
def get_config() -> BrokerConfig | None:
|
||||||
conf, path = config.load()
|
conf, path = config.load()
|
||||||
|
|
||||||
|
@ -182,6 +191,7 @@ class Client:
|
||||||
https://docs.kucoin.com/#authentication
|
https://docs.kucoin.com/#authentication
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
breakpoint()
|
||||||
now = int(time.time() * 1000)
|
now = int(time.time() * 1000)
|
||||||
path = f"/api/{api_v}{endpoint}"
|
path = f"/api/{api_v}{endpoint}"
|
||||||
str_to_sign = str(now) + action + path
|
str_to_sign = str(now) + action + path
|
||||||
|
@ -327,22 +337,22 @@ class Client:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# Generate generic end and start time if values not passed
|
# Generate generic end and start time if values not passed
|
||||||
|
# Currently gives us 12hrs of data
|
||||||
if end_dt is None:
|
if end_dt is None:
|
||||||
end_dt = pendulum.now("UTC").add(minutes=1)
|
end_dt = pendulum.now("UTC").add(minutes=1)
|
||||||
|
|
||||||
if start_dt is None:
|
if start_dt is None:
|
||||||
start_dt = end_dt.start_of("minute").subtract(minutes=limit)
|
start_dt = end_dt.start_of("minute").subtract(minutes=limit)
|
||||||
|
|
||||||
# Format datetime to unix timestamp
|
start_dt = int(start_dt.timestamp())
|
||||||
start_dt = math.trunc(time.mktime(start_dt.timetuple()))
|
end_dt = int(end_dt.timestamp())
|
||||||
end_dt = math.trunc(time.mktime(end_dt.timetuple()))
|
|
||||||
kucoin_sym = fqsn_to_kucoin_sym(fqsn, self._pairs)
|
kucoin_sym = fqsn_to_kucoin_sym(fqsn, self._pairs)
|
||||||
|
|
||||||
url = f"/market/candles?type={type}&symbol={kucoin_sym}&startAt={start_dt}&endAt={end_dt}"
|
url = f"/market/candles?type={type}&symbol={kucoin_sym}&startAt={start_dt}&endAt={end_dt}"
|
||||||
bars = []
|
bars = []
|
||||||
|
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
|
|
||||||
data = await self._request(
|
data = await self._request(
|
||||||
"GET",
|
"GET",
|
||||||
url,
|
url,
|
||||||
|
@ -375,7 +385,7 @@ class Client:
|
||||||
}
|
}
|
||||||
|
|
||||||
row = []
|
row = []
|
||||||
for j, (field_name, field_type) in enumerate(_ohlc_dtype):
|
for _, (field_name, field_type) in enumerate(_ohlc_dtype):
|
||||||
|
|
||||||
value = data[field_name]
|
value = data[field_name]
|
||||||
|
|
||||||
|
@ -383,16 +393,21 @@ class Client:
|
||||||
case "index":
|
case "index":
|
||||||
row.append(int(value))
|
row.append(int(value))
|
||||||
case "time":
|
case "time":
|
||||||
|
# row.append(int(value) + (3600 * 4))
|
||||||
row.append(value)
|
row.append(value)
|
||||||
case _:
|
case _:
|
||||||
row.append(float(value))
|
row.append(float(value))
|
||||||
|
|
||||||
new_bars.append(tuple(row))
|
new_bars.append(tuple(row))
|
||||||
|
|
||||||
self._bars = array = np.array(new_bars, dtype=_ohlc_dtype) if as_np else bars
|
array = np.array(new_bars, dtype=_ohlc_dtype) if as_np else bars
|
||||||
return array
|
return array
|
||||||
|
|
||||||
|
|
||||||
|
def kucoin_timestamp(dt: datetime):
|
||||||
|
return math.trunc(time.mktime(dt.timetuple()))
|
||||||
|
|
||||||
|
|
||||||
def fqsn_to_kucoin_sym(
|
def fqsn_to_kucoin_sym(
|
||||||
fqsn: str,
|
fqsn: str,
|
||||||
pairs: dict[str, KucoinMktPair]
|
pairs: dict[str, KucoinMktPair]
|
||||||
|
@ -474,8 +489,8 @@ async def stream_quotes(
|
||||||
@acm
|
@acm
|
||||||
async def open_ping_task(ws: wsproto.WSConnection):
|
async def open_ping_task(ws: wsproto.WSConnection):
|
||||||
'''
|
'''
|
||||||
Spawn a non-blocking task that pings the ws
|
Spawn a non-blocking task that pings the ws
|
||||||
server every ping_interval so Kucoin doesn't drop
|
server every ping_interval so Kucoin doesn't drop
|
||||||
our connection
|
our connection
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
@ -496,7 +511,9 @@ async def stream_quotes(
|
||||||
async with open_ping_task(ws) as ws:
|
async with open_ping_task(ws) as ws:
|
||||||
# subscribe to market feedz here
|
# subscribe to market feedz here
|
||||||
log.info(f'Subscribing to {kucoin_sym} feed')
|
log.info(f'Subscribing to {kucoin_sym} feed')
|
||||||
l1_sub = make_sub(kucoin_sym, connect_id)
|
trade_sub = make_sub(kucoin_sym, connect_id, level='l3')
|
||||||
|
l1_sub = make_sub(kucoin_sym, connect_id, level='l1')
|
||||||
|
await ws.send_msg(trade_sub)
|
||||||
await ws.send_msg(l1_sub)
|
await ws.send_msg(l1_sub)
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
@ -520,7 +537,7 @@ async def stream_quotes(
|
||||||
) as ws:
|
) as ws:
|
||||||
msg_gen = stream_messages(ws, sym)
|
msg_gen = stream_messages(ws, sym)
|
||||||
typ, quote = await msg_gen.__anext__()
|
typ, quote = await msg_gen.__anext__()
|
||||||
#
|
|
||||||
while typ != "trade":
|
while typ != "trade":
|
||||||
# TODO: use ``anext()`` when it lands in 3.10!
|
# TODO: use ``anext()`` when it lands in 3.10!
|
||||||
typ, quote = await msg_gen.__anext__()
|
typ, quote = await msg_gen.__anext__()
|
||||||
|
@ -532,14 +549,26 @@ async def stream_quotes(
|
||||||
await send_chan.send({sym: msg})
|
await send_chan.send({sym: msg})
|
||||||
|
|
||||||
|
|
||||||
def make_sub(sym, connect_id) -> dict[str, str | bool]:
|
def make_sub(sym, connect_id, level='l1') -> dict[str, str | bool]:
|
||||||
return {
|
match level:
|
||||||
"id": connect_id,
|
case 'l1':
|
||||||
"type": "subscribe",
|
return {
|
||||||
"topic": f"/market/ticker:{sym}",
|
"id": connect_id,
|
||||||
"privateChannel": False,
|
"type": "subscribe",
|
||||||
"response": True,
|
"topic": f"/spotMarket/level2Depth5:{sym}",
|
||||||
}
|
"privateChannel": False,
|
||||||
|
"response": True,
|
||||||
|
}
|
||||||
|
case 'l3':
|
||||||
|
return {
|
||||||
|
"id": connect_id,
|
||||||
|
"type": "subscribe",
|
||||||
|
"topic": f"/market/ticker:{sym}",
|
||||||
|
"privateChannel": False,
|
||||||
|
"response": True,
|
||||||
|
}
|
||||||
|
case _:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
async def stream_messages(
|
async def stream_messages(
|
||||||
|
@ -560,28 +589,43 @@ async def stream_messages(
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "subject" in msg and msg["subject"] == "trade.ticker":
|
if msg.get("subject") != None:
|
||||||
|
|
||||||
trade_msg = KucoinTradeMsg(**msg)
|
msg = KucoinMsg(**msg)
|
||||||
trade_data = KucoinTrade(**trade_msg.data)
|
|
||||||
|
|
||||||
yield "trade", {
|
match msg.subject:
|
||||||
"symbol": sym,
|
case "trade.ticker":
|
||||||
"last": trade_data.price,
|
|
||||||
"brokerd_ts": trade_data.time,
|
trade_data = KucoinTrade(**msg.data)
|
||||||
"ticks": [
|
yield "trade", {
|
||||||
{
|
"symbol": sym,
|
||||||
"type": "trade",
|
"last": trade_data.price,
|
||||||
"price": float(trade_data.price),
|
"brokerd_ts": trade_data.time,
|
||||||
"size": float(trade_data.size),
|
"ticks": [
|
||||||
"broker_ts": trade_data.time,
|
{
|
||||||
|
"type": "trade",
|
||||||
|
"price": float(trade_data.price),
|
||||||
|
"size": float(trade_data.size),
|
||||||
|
"broker_ts": trade_data.time,
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
case "level2":
|
||||||
continue
|
|
||||||
|
|
||||||
|
l2_data = KucoinL2(**msg.data)
|
||||||
|
|
||||||
|
ticks = []
|
||||||
|
for trade in l2_data.bids:
|
||||||
|
tick = {'type': 'bid', 'price': float(trade[0]), 'size': float(trade[1])}
|
||||||
|
ticks.append(tick)
|
||||||
|
for trade in l2_data.asks:
|
||||||
|
tick = {'type': 'ask', 'price': float(trade[0]), 'size': float(trade[1])}
|
||||||
|
ticks.append(tick)
|
||||||
|
yield 'l1', {
|
||||||
|
'symbol': sym,
|
||||||
|
'ticks': ticks,
|
||||||
|
}
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def open_history_client(
|
async def open_history_client(
|
||||||
|
@ -609,7 +653,6 @@ async def open_history_client(
|
||||||
)
|
)
|
||||||
|
|
||||||
times = array["time"]
|
times = array["time"]
|
||||||
|
|
||||||
if end_dt is None:
|
if end_dt is None:
|
||||||
|
|
||||||
inow = round(time.time())
|
inow = round(time.time())
|
||||||
|
@ -620,10 +663,13 @@ async def open_history_client(
|
||||||
|
|
||||||
if (inow - times[-1]) > 60:
|
if (inow - times[-1]) > 60:
|
||||||
await tractor.breakpoint()
|
await tractor.breakpoint()
|
||||||
|
|
||||||
start_dt = pendulum.from_timestamp(times[0])
|
start_dt = pendulum.from_timestamp(times[0])
|
||||||
end_dt = pendulum.from_timestamp(times[-1])
|
end_dt = pendulum.from_timestamp(times[-1])
|
||||||
log.info('History succesfully fetched baby')
|
log.info('History succesfully fetched baby')
|
||||||
|
# breakpoint()
|
||||||
|
# print(f'OUTPUTTED END TIME: {time.ctime(kucoin_timestamp(end_dt))}')
|
||||||
|
# print(f'OUTPUTTED START TIME: {time.ctime(kucoin_timestamp(start_dt))}')
|
||||||
|
# print(f'DIFFERENCE IN MINUTES {(end_dt - start_dt).in_minutes()}')
|
||||||
return array, start_dt, end_dt
|
return array, start_dt, end_dt
|
||||||
|
|
||||||
yield get_ohlc_history, {"erlangs": 3, "rate": 3}
|
yield get_ohlc_history, {}
|
||||||
|
|
Loading…
Reference in New Issue