From ea270d3396ad31302b73bfa34fbc913c1eeb5be1 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 27 Jun 2023 09:31:08 -0400 Subject: [PATCH] .data.ticktools: add reverse flag, better docs Since it may be handy to get the latest ticks first, add a `reverse: bool` to `iterticks()` and add some cleaner logic and a proper doc string to `frame_ticks()`. --- piker/data/ticktools.py | 134 +++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 58 deletions(-) diff --git a/piker/data/ticktools.py b/piker/data/ticktools.py index bc3543f8..1ce7fa45 100644 --- a/piker/data/ticktools.py +++ b/piker/data/ticktools.py @@ -1,5 +1,5 @@ # piker: trading gear for hackers -# Copyright (C) Tyler Goodlet (in stewardship for piker0) +# Copyright (C) Tyler Goodlet (in stewardship for pikers) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -15,7 +15,7 @@ # along with this program. If not, see . ''' -Stream format enforcement. +Tick event stream processing, filter-by-types, format-normalization. ''' from itertools import chain @@ -40,6 +40,69 @@ _tick_groups: dict[str, set[str]] = { _auction_ticks: set[str] = set.union(*_tick_groups.values()) +def frame_ticks( + quote: dict[str, Any], + + ticks_by_type: dict | None = None, + ticks_in_order: list[dict[str, Any]] | None = None + +) -> dict[ + str, + list[dict[str, Any]] +]: + ''' + XXX: build a tick-by-type table of lists + of tick messages. This allows for less + iteration on the receiver side by allowing for + a single "latest tick event" look up by + indexing the last entry in each sub-list. + + tbt = { + 'types': ['bid', 'asize', 'last', .. ''], + + 'bid': [tick0, tick1, tick2, .., tickn], + 'asize': [tick0, tick1, tick2, .., tickn], + 'last': [tick0, tick1, tick2, .., tickn], + ... + '': [tick0, tick1, tick2, .., tickn], + } + + If `ticks_in_order` is provided, append any retrieved ticks + since last iteration into this array/buffer/list. + + ''' + # 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. + + tbt = ticks_by_type if ticks_by_type is not None else {} + if not (ticks := quote.get('ticks')): + return tbt + + # append in reverse FIFO order for in-order iteration on + # receiver side. + tick: dict[str, Any] + for tick in ticks: + tbt.setdefault( + tick['type'], + [], + ).append(tick) + + # TODO: do we need this any more or can we just + # expect the receiver to unwind the below + # `ticks_by_type: dict`? + # => undwinding would potentially require a + # `dict[str, set | list]` instead with an + # included `'types' field which is an (ordered) + # set of tick type fields in the order which + # types arrived? + if ticks_in_order: + ticks_in_order.extend(ticks) + + return tbt + + def iterticks( quote: dict, types: tuple[str] = ( @@ -47,10 +110,16 @@ def iterticks( 'dark_trade', ), deduplicate_darks: bool = False, + reverse: bool = False, + + # TODO: should we offer delegating to `frame_ticks()` above + # with this? + frame_by_type: bool = False, ) -> AsyncIterator: ''' - Iterate through ticks delivered per quote cycle. + Iterate through ticks delivered per quote cycle, filter and + yield any declared in `types`. ''' if deduplicate_darks: @@ -93,63 +162,12 @@ def iterticks( # re-insert ticks ticks.extend(list(chain(trades.values(), darks.values()))) + # most-recent-first + if reverse: + ticks = reversed(ticks) + for tick in ticks: # print(f"{quote['symbol']}: {tick}") ttype = tick.get('type') if ttype in types: yield tick - - -def frame_ticks( - quote: dict[str, Any], - - ticks_by_type: dict[str, list[dict[str, Any]]] = {}, - ticks_in_order: list[dict[str, Any]] | None = None - -) -> dict: - - # 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. - - if ticks := quote.get('ticks'): - - # XXX: build a tick-by-type table of lists - # of tick messages. This allows for less - # iteration on the receiver side by allowing for - # a single "latest tick event" look up by - # indexing the last entry in each sub-list. - # tbt = { - # 'types': ['bid', 'asize', 'last', .. ''], - - # 'bid': [tick0, tick1, tick2, .., tickn], - # 'asize': [tick0, tick1, tick2, .., tickn], - # 'last': [tick0, tick1, tick2, .., tickn], - # ... - # '': [tick0, tick1, tick2, .., tickn], - # } - - # append in reverse FIFO order for in-order iteration on - # receiver side. - tick: dict[str, Any] - for tick in ticks: - ticks_by_type.setdefault( - tick['type'], - [], - ).append(tick) - - # TODO: do we need this any more or can we just - # expect the receiver to unwind the below - # `ticks_by_type: dict`? - # => undwinding would potentially require a - # `dict[str, set | list]` instead with an - # included `'types' field which is an (ordered) - # set of tick type fields in the order which - # types arrived? - if ticks_in_order: - ticks_in_order.extend(ticks) - - return ticks_by_type