Port fsp daemon to tractor's context api
parent
31f4dbef7d
commit
33d1f56440
|
@ -36,7 +36,7 @@ from ..data._sharedmem import ShmArray
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_fsps = {
|
_fsp_builtins = {
|
||||||
'rsi': _rsi,
|
'rsi': _rsi,
|
||||||
'wma': _wma,
|
'wma': _wma,
|
||||||
'vwap': _tina_vwap,
|
'vwap': _tina_vwap,
|
||||||
|
@ -65,16 +65,39 @@ async def latency(
|
||||||
yield value
|
yield value
|
||||||
|
|
||||||
|
|
||||||
|
async def filter_quotes_by_sym(
|
||||||
|
|
||||||
|
sym: str,
|
||||||
|
quote_stream,
|
||||||
|
|
||||||
|
) -> AsyncIterator[dict]:
|
||||||
|
'''Filter quote stream by target symbol.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# TODO: make this the actualy first quote from feed
|
||||||
|
# XXX: this allows for a single iteration to run for history
|
||||||
|
# processing without waiting on the real-time feed for a new quote
|
||||||
|
yield {}
|
||||||
|
|
||||||
|
# task cancellation won't kill the channel
|
||||||
|
# since we shielded at the `open_feed()` call
|
||||||
|
async for quotes in quote_stream:
|
||||||
|
for symbol, quote in quotes.items():
|
||||||
|
if symbol == sym:
|
||||||
|
yield quote
|
||||||
|
|
||||||
|
|
||||||
async def fsp_compute(
|
async def fsp_compute(
|
||||||
ctx: tractor.Context,
|
|
||||||
|
stream: tractor.MsgStream,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
feed: Feed,
|
feed: Feed,
|
||||||
stream: trio.abc.ReceiveChannel,
|
quote_stream: trio.abc.ReceiveChannel,
|
||||||
|
|
||||||
src: ShmArray,
|
src: ShmArray,
|
||||||
dst: ShmArray,
|
dst: ShmArray,
|
||||||
|
|
||||||
fsp_func_name: str,
|
func_name: str,
|
||||||
func: Callable,
|
func: Callable,
|
||||||
|
|
||||||
task_status: TaskStatus[None] = trio.TASK_STATUS_IGNORED,
|
task_status: TaskStatus[None] = trio.TASK_STATUS_IGNORED,
|
||||||
|
@ -83,25 +106,13 @@ async def fsp_compute(
|
||||||
|
|
||||||
# TODO: load appropriate fsp with input args
|
# TODO: load appropriate fsp with input args
|
||||||
|
|
||||||
async def filter_by_sym(
|
|
||||||
sym: str,
|
|
||||||
stream,
|
|
||||||
):
|
|
||||||
|
|
||||||
# TODO: make this the actualy first quote from feed
|
|
||||||
# XXX: this allows for a single iteration to run for history
|
|
||||||
# processing without waiting on the real-time feed for a new quote
|
|
||||||
yield {}
|
|
||||||
|
|
||||||
# task cancellation won't kill the channel
|
|
||||||
# since we shielded at the `open_feed()` call
|
|
||||||
async for quotes in stream:
|
|
||||||
for symbol, quotes in quotes.items():
|
|
||||||
if symbol == sym:
|
|
||||||
yield quotes
|
|
||||||
|
|
||||||
out_stream = func(
|
out_stream = func(
|
||||||
filter_by_sym(symbol, stream),
|
|
||||||
|
# TODO: do we even need this if we do the feed api right?
|
||||||
|
# shouldn't a local stream do this before we get a handle
|
||||||
|
# to the async iterable? it's that or we do some kinda
|
||||||
|
# async itertools style?
|
||||||
|
filter_quotes_by_sym(symbol, quote_stream),
|
||||||
feed.shm,
|
feed.shm,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -129,11 +140,11 @@ async def fsp_compute(
|
||||||
np.arange(len(history_output)),
|
np.arange(len(history_output)),
|
||||||
dtype=dst.array.dtype
|
dtype=dst.array.dtype
|
||||||
)
|
)
|
||||||
history[fsp_func_name] = history_output
|
history[func_name] = history_output
|
||||||
|
|
||||||
# check for data length mis-allignment and fill missing values
|
# check for data length mis-allignment and fill missing values
|
||||||
diff = len(src.array) - len(history)
|
diff = len(src.array) - len(history)
|
||||||
if diff >= 0:
|
if diff > 0:
|
||||||
log.warning(f"WTF DIFF SIGNAL to HISTORY {diff}")
|
log.warning(f"WTF DIFF SIGNAL to HISTORY {diff}")
|
||||||
for _ in range(diff):
|
for _ in range(diff):
|
||||||
dst.push(history[:1])
|
dst.push(history[:1])
|
||||||
|
@ -141,36 +152,44 @@ async def fsp_compute(
|
||||||
# compare with source signal and time align
|
# compare with source signal and time align
|
||||||
index = dst.push(history)
|
index = dst.push(history)
|
||||||
|
|
||||||
await ctx.send_yield(index)
|
|
||||||
|
|
||||||
# setup a respawn handle
|
# setup a respawn handle
|
||||||
with trio.CancelScope() as cs:
|
with trio.CancelScope() as cs:
|
||||||
task_status.started(cs)
|
task_status.started((cs, index))
|
||||||
|
|
||||||
|
import time
|
||||||
|
last = time.time()
|
||||||
|
|
||||||
# rt stream
|
# rt stream
|
||||||
async for processed in out_stream:
|
async for processed in out_stream:
|
||||||
|
|
||||||
# period = time.time() - last
|
period = time.time() - last
|
||||||
# hz = 1/period if period else float('nan')
|
hz = 1/period if period else float('nan')
|
||||||
# if hz > 60:
|
if hz > 60:
|
||||||
# log.info(f'FSP quote too fast: {hz}')
|
log.info(f'FSP quote too fast: {hz}')
|
||||||
|
|
||||||
log.debug(f"{fsp_func_name}: {processed}")
|
log.debug(f"{func_name}: {processed}")
|
||||||
index = src.index
|
index = src.index
|
||||||
dst.array[-1][fsp_func_name] = processed
|
dst.array[-1][func_name] = processed
|
||||||
|
|
||||||
# stream latest shm array index entry
|
# stream latest array index entry which basically just acts
|
||||||
await ctx.send_yield(index)
|
# as trigger msg to tell the consumer to read from shm
|
||||||
|
await stream.send(index)
|
||||||
|
|
||||||
|
last = time.time()
|
||||||
|
|
||||||
|
|
||||||
@tractor.stream
|
@tractor.context
|
||||||
async def cascade(
|
async def cascade(
|
||||||
|
|
||||||
ctx: tractor.Context,
|
ctx: tractor.Context,
|
||||||
brokername: str,
|
brokername: str,
|
||||||
|
|
||||||
src_shm_token: dict,
|
src_shm_token: dict,
|
||||||
dst_shm_token: Tuple[str, np.dtype],
|
dst_shm_token: Tuple[str, np.dtype],
|
||||||
|
|
||||||
symbol: str,
|
symbol: str,
|
||||||
fsp_func_name: str,
|
func_name: str,
|
||||||
|
|
||||||
loglevel: Optional[str] = None,
|
loglevel: Optional[str] = None,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -184,39 +203,50 @@ async def cascade(
|
||||||
src = attach_shm_array(token=src_shm_token)
|
src = attach_shm_array(token=src_shm_token)
|
||||||
dst = attach_shm_array(readonly=False, token=dst_shm_token)
|
dst = attach_shm_array(readonly=False, token=dst_shm_token)
|
||||||
|
|
||||||
func: Callable = _fsps[fsp_func_name]
|
func: Callable = _fsp_builtins.get(func_name)
|
||||||
|
if not func:
|
||||||
|
# TODO: assume it's a func target path
|
||||||
|
raise ValueError('Unknown fsp target: {func_name}')
|
||||||
|
|
||||||
# open a data feed stream with requested broker
|
# open a data feed stream with requested broker
|
||||||
async with data.feed.maybe_open_feed(
|
async with data.feed.maybe_open_feed(
|
||||||
brokername,
|
brokername,
|
||||||
[symbol],
|
[symbol],
|
||||||
|
|
||||||
# TODO:
|
# TODO throttle tick outputs from *this* daemon since
|
||||||
|
# it'll emit tons of ticks due to the throttle only
|
||||||
|
# limits quote arrival periods, so the consumer of *this*
|
||||||
|
# needs to get throttled the ticks we generate.
|
||||||
# tick_throttle=60,
|
# tick_throttle=60,
|
||||||
|
|
||||||
) as (feed, stream):
|
) as (feed, quote_stream):
|
||||||
|
|
||||||
assert src.token == feed.shm.token
|
assert src.token == feed.shm.token
|
||||||
|
|
||||||
last_len = new_len = len(src.array)
|
last_len = new_len = len(src.array)
|
||||||
|
|
||||||
|
async with (
|
||||||
|
ctx.open_stream() as stream,
|
||||||
|
trio.open_nursery() as n,
|
||||||
|
):
|
||||||
|
|
||||||
fsp_target = partial(
|
fsp_target = partial(
|
||||||
|
|
||||||
fsp_compute,
|
fsp_compute,
|
||||||
ctx=ctx,
|
stream=stream,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
feed=feed,
|
feed=feed,
|
||||||
stream=stream,
|
quote_stream=quote_stream,
|
||||||
|
|
||||||
|
# shm
|
||||||
src=src,
|
src=src,
|
||||||
dst=dst,
|
dst=dst,
|
||||||
|
|
||||||
fsp_func_name=fsp_func_name,
|
func_name=func_name,
|
||||||
func=func
|
func=func
|
||||||
)
|
)
|
||||||
|
|
||||||
async with trio.open_nursery() as n:
|
cs, index = await n.start(fsp_target)
|
||||||
|
await ctx.started(index)
|
||||||
cs = await n.start(fsp_target)
|
|
||||||
|
|
||||||
# Increment the underlying shared memory buffer on every
|
# Increment the underlying shared memory buffer on every
|
||||||
# "increment" msg received from the underlying data feed.
|
# "increment" msg received from the underlying data feed.
|
||||||
|
@ -229,17 +259,15 @@ async def cascade(
|
||||||
if new_len > last_len + 1:
|
if new_len > last_len + 1:
|
||||||
# respawn the signal compute task if the source
|
# respawn the signal compute task if the source
|
||||||
# signal has been updated
|
# signal has been updated
|
||||||
|
log.warning(f'Re-spawning fsp {func_name}')
|
||||||
cs.cancel()
|
cs.cancel()
|
||||||
cs = await n.start(fsp_target)
|
cs, index = await n.start(fsp_target)
|
||||||
|
|
||||||
# TODO: adopt an incremental update engine/approach
|
# TODO: adopt an incremental update engine/approach
|
||||||
# where possible here eventually!
|
# where possible here eventually!
|
||||||
|
|
||||||
# read out last shm row
|
# read out last shm row, copy and write new row
|
||||||
array = dst.array
|
array = dst.array
|
||||||
last = array[-1:].copy()
|
last = array[-1:].copy()
|
||||||
|
|
||||||
# write new row to the shm buffer
|
|
||||||
dst.push(last)
|
dst.push(last)
|
||||||
|
|
||||||
last_len = new_len
|
last_len = new_len
|
||||||
|
|
Loading…
Reference in New Issue