First attempt data feed side quote throttling
Adding binance's "hft" ws feeds has resulted in a lot of context switching in our Qt charts, so much so it's chewin CPU and definitely worth it to throttle to the detected display rate as per discussion in issue #192. This is a first very very naive attempt at throttling L1 tick feeds on the `brokerd` end (producer side) using a constant and uniform delivery rate by way of a `trio` task + mem chan. The new func is `data._sampling.uniform_rate_send()`. Basically if a client request a feed and provides a throttle rate we just spawn a task and queue up ticks until approximately the next display rate's worth period of time has passed before forwarding. It's definitely nothing fancy but does provide fodder and a start point for an up and coming queueing eng to start digging into both #107 and #109 ;)naive_feed_throttling
parent
57a35a3c6c
commit
ccf81520cb
|
@ -17,6 +17,7 @@
|
||||||
"""
|
"""
|
||||||
Data buffers for fast shared humpy.
|
Data buffers for fast shared humpy.
|
||||||
"""
|
"""
|
||||||
|
import time
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
import tractor
|
import tractor
|
||||||
|
@ -152,10 +153,12 @@ async def iter_ohlc_periods(
|
||||||
|
|
||||||
|
|
||||||
async def sample_and_broadcast(
|
async def sample_and_broadcast(
|
||||||
|
|
||||||
bus: '_FeedBus', # noqa
|
bus: '_FeedBus', # noqa
|
||||||
shm: ShmArray,
|
shm: ShmArray,
|
||||||
quote_stream: trio.abc.ReceiveChannel,
|
quote_stream: trio.abc.ReceiveChannel,
|
||||||
sum_tick_vlm: bool = True,
|
sum_tick_vlm: bool = True,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
log.info("Started shared mem bar writer")
|
log.info("Started shared mem bar writer")
|
||||||
|
@ -177,11 +180,10 @@ async def sample_and_broadcast(
|
||||||
# trade data
|
# trade data
|
||||||
for tick in quote['ticks']:
|
for tick in quote['ticks']:
|
||||||
|
|
||||||
# if tick['type'] in ('utrade',):
|
ticktype = tick['type']
|
||||||
# print(tick)
|
|
||||||
|
|
||||||
# write trade events to shm last OHLC sample
|
# write trade events to shm last OHLC sample
|
||||||
if tick['type'] in ('trade', 'utrade'):
|
if ticktype in ('trade', 'utrade'):
|
||||||
|
|
||||||
last = tick['price']
|
last = tick['price']
|
||||||
|
|
||||||
|
@ -229,16 +231,71 @@ async def sample_and_broadcast(
|
||||||
# thus other consumers still attached.
|
# thus other consumers still attached.
|
||||||
subs = bus._subscribers[sym.lower()]
|
subs = bus._subscribers[sym.lower()]
|
||||||
|
|
||||||
for stream in subs:
|
for (stream, tick_throttle) in subs:
|
||||||
# print(f'sub is {ctx.chan.uid}')
|
|
||||||
try:
|
if tick_throttle:
|
||||||
await stream.send({sym: quote})
|
await stream.send(quote)
|
||||||
except (
|
|
||||||
trio.BrokenResourceError,
|
else:
|
||||||
trio.ClosedResourceError
|
try:
|
||||||
):
|
await stream.send({sym: quote})
|
||||||
# XXX: do we need to deregister here
|
except (
|
||||||
# if it's done in the fee bus code?
|
trio.BrokenResourceError,
|
||||||
# so far seems like no since this should all
|
trio.ClosedResourceError
|
||||||
# be single-threaded.
|
):
|
||||||
log.error(f'{stream._ctx.chan.uid} dropped connection')
|
# XXX: do we need to deregister here
|
||||||
|
# if it's done in the fee bus code?
|
||||||
|
# so far seems like no since this should all
|
||||||
|
# be single-threaded.
|
||||||
|
log.error(f'{stream._ctx.chan.uid} dropped connection')
|
||||||
|
|
||||||
|
|
||||||
|
async def uniform_rate_send(
|
||||||
|
rate: float,
|
||||||
|
quote_stream: trio.abc.ReceiveChannel,
|
||||||
|
stream: tractor.MsgStream,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
sleep_period = 1/rate - 0.000616
|
||||||
|
last_send = time.time()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
first_quote = await quote_stream.receive()
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
# append quotes since last iteration into the last quote's
|
||||||
|
# tick array/buffer.
|
||||||
|
|
||||||
|
# TODO: once we decide to get fancy really we should have
|
||||||
|
# a shared mem tick buffer that is just continually filled and
|
||||||
|
# the UI just ready from it at it's display rate.
|
||||||
|
# we'll likely head toward this once we get this issue going:
|
||||||
|
#
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
next_quote = quote_stream.receive_nowait()
|
||||||
|
ticks = next_quote.get('ticks')
|
||||||
|
|
||||||
|
if ticks:
|
||||||
|
first_quote['ticks'].extend(ticks)
|
||||||
|
|
||||||
|
except trio.WouldBlock:
|
||||||
|
now = time.time()
|
||||||
|
rate = 1 / (now - last_send)
|
||||||
|
last_send = now
|
||||||
|
|
||||||
|
# print(f'{rate} Hz sending quotes\n{first_quote}')
|
||||||
|
|
||||||
|
# TODO: now if only we could sync this to the display
|
||||||
|
# rate timing exactly lul
|
||||||
|
await stream.send({first_quote['symbol']: first_quote})
|
||||||
|
break
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
diff = end - start
|
||||||
|
|
||||||
|
# throttle to provided transmit rate
|
||||||
|
period = max(sleep_period - diff, 0)
|
||||||
|
if period > 0:
|
||||||
|
await trio.sleep(period)
|
||||||
|
|
Loading…
Reference in New Issue