diff --git a/piker/storage/cli.py b/piker/storage/cli.py index e97f4023..90d5baed 100644 --- a/piker/storage/cli.py +++ b/piker/storage/cli.py @@ -19,16 +19,10 @@ Storage middle-ware CLIs. """ from __future__ import annotations -# from datetime import datetime -# from contextlib import ( -# AsyncExitStack, -# ) from pathlib import Path -from math import copysign import time from types import ModuleType from typing import ( - Any, TYPE_CHECKING, ) @@ -47,7 +41,6 @@ from piker.data import ( ShmArray, ) from piker import tsp -from piker.data._formatters import BGM from . import log from . import ( __tsdbs__, @@ -242,138 +235,6 @@ def anal( trio.run(main) -# TODO, move to `.tsp._annotate` -async def markup_gaps( - fqme: str, - timeframe: float, - actl: AnnotCtl, - wdts: pl.DataFrame, - gaps: pl.DataFrame, - -) -> dict[int, dict]: - ''' - Remote annotate time-gaps in a dt-fielded ts (normally OHLC) - with rectangles. - - ''' - aids: dict[int] = {} - for i in range(gaps.height): - - row: pl.DataFrame = gaps[i] - - # the gap's RIGHT-most bar's OPEN value - # at that time (sample) step. - iend: int = row['index'][0] - # dt: datetime = row['dt'][0] - # dt_prev: datetime = row['dt_prev'][0] - # dt_end_t: float = dt.timestamp() - - - # TODO: can we eventually remove this - # once we figure out why the epoch cols - # don't match? - # TODO: FIX HOW/WHY these aren't matching - # and are instead off by 4hours (EST - # vs. UTC?!?!) - # end_t: float = row['time'] - # assert ( - # dt.timestamp() - # == - # end_t - # ) - - # the gap's LEFT-most bar's CLOSE value - # at that time (sample) step. - prev_r: pl.DataFrame = wdts.filter( - pl.col('index') == iend - 1 - ) - # XXX: probably a gap in the (newly sorted or de-duplicated) - # dt-df, so we might need to re-index first.. - dt: pl.Series = row['dt'] - dt_prev: pl.Series = row['dt_prev'] - if prev_r.is_empty(): - - # XXX, filter out any special ignore cases, - # - UNIX-epoch stamped datums - # - first row - if ( - dt_prev.dt.epoch()[0] == 0 - or - dt.dt.epoch()[0] == 0 - ): - log.warning('Skipping row with UNIX epoch timestamp ??') - continue - - if wdts[0]['index'][0] == iend: # first row - log.warning('Skipping first-row (has no previous obvi) !!') - continue - - # XXX, if the previous-row by shm-index is missing, - # meaning there is a missing sample (set), get the prior - # row by df index and attempt to use it? - i_wdts: pl.DataFrame = wdts.with_row_index(name='i') - i_row: int = i_wdts.filter(pl.col('index') == iend)['i'][0] - prev_row_by_i = wdts[i_row] - prev_r: pl.DataFrame = prev_row_by_i - - # debug any missing pre-row - if tractor._state.is_debug_mode(): - await tractor.pause() - - istart: int = prev_r['index'][0] - - # TODO: implement px-col width measure - # and ensure at least as many px-cols - # shown per rect as configured by user. - # gap_w: float = abs((iend - istart)) - # if gap_w < 6: - # margin: float = 6 - # iend += margin - # istart -= margin - - rect_gap: float = BGM*3/8 - opn: float = row['open'][0] - ro: tuple[float, float] = ( - # dt_end_t, - iend + rect_gap + 1, - opn, - ) - cls: float = prev_r['close'][0] - lc: tuple[float, float] = ( - # dt_start_t, - istart - rect_gap, # + 1 , - cls, - ) - - color: str = 'dad_blue' - diff: float = cls - opn - sgn: float = copysign(1, diff) - color: str = { - -1: 'buy_green', - 1: 'sell_red', - }[sgn] - - rect_kwargs: dict[str, Any] = dict( - fqme=fqme, - timeframe=timeframe, - start_pos=lc, - end_pos=ro, - color=color, - ) - - aid: int = await actl.add_rect(**rect_kwargs) - assert aid - aids[aid] = rect_kwargs - - # tell chart to redraw all its - # graphics view layers Bo - await actl.redraw( - fqme=fqme, - timeframe=timeframe, - ) - return aids - - @store.command() def ldshm( fqme: str, @@ -577,7 +438,7 @@ def ldshm( do_markup_gaps: bool = True if do_markup_gaps: new_df: pl.DataFrame = tsp.np2pl(new) - aids: dict = await markup_gaps( + aids: dict = await tsp._annotate.markup_gaps( fqme, period_s, actl, diff --git a/piker/tsp/__init__.py b/piker/tsp/__init__.py index 81274ed8..baa28c82 100644 --- a/piker/tsp/__init__.py +++ b/piker/tsp/__init__.py @@ -47,3 +47,6 @@ from ._history import ( iter_dfs_from_shms as iter_dfs_from_shms, manage_history as manage_history, ) +from ._annotate import ( + markup_gaps as markup_gaps, +) diff --git a/piker/tsp/_annotate.py b/piker/tsp/_annotate.py new file mode 100644 index 00000000..797c38cf --- /dev/null +++ b/piker/tsp/_annotate.py @@ -0,0 +1,166 @@ +# piker: trading gear for hackers +# Copyright (C) 2018-present Tyler Goodlet (in stewardship of 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Time-series (remote) annotation APIs. + +""" +from __future__ import annotations +from math import copysign +from typing import ( + Any, + TYPE_CHECKING, +) + +import polars as pl +import tractor + +from piker.data._formatters import BGM +from piker.storage import log + +if TYPE_CHECKING: + from piker.ui._remote_ctl import AnnotCtl + + +async def markup_gaps( + fqme: str, + timeframe: float, + actl: AnnotCtl, + wdts: pl.DataFrame, + gaps: pl.DataFrame, + +) -> dict[int, dict]: + ''' + Remote annotate time-gaps in a dt-fielded ts (normally OHLC) + with rectangles. + + ''' + aids: dict[int] = {} + for i in range(gaps.height): + + row: pl.DataFrame = gaps[i] + + # the gap's RIGHT-most bar's OPEN value + # at that time (sample) step. + iend: int = row['index'][0] + # dt: datetime = row['dt'][0] + # dt_prev: datetime = row['dt_prev'][0] + # dt_end_t: float = dt.timestamp() + + + # TODO: can we eventually remove this + # once we figure out why the epoch cols + # don't match? + # TODO: FIX HOW/WHY these aren't matching + # and are instead off by 4hours (EST + # vs. UTC?!?!) + # end_t: float = row['time'] + # assert ( + # dt.timestamp() + # == + # end_t + # ) + + # the gap's LEFT-most bar's CLOSE value + # at that time (sample) step. + prev_r: pl.DataFrame = wdts.filter( + pl.col('index') == iend - 1 + ) + # XXX: probably a gap in the (newly sorted or de-duplicated) + # dt-df, so we might need to re-index first.. + dt: pl.Series = row['dt'] + dt_prev: pl.Series = row['dt_prev'] + if prev_r.is_empty(): + + # XXX, filter out any special ignore cases, + # - UNIX-epoch stamped datums + # - first row + if ( + dt_prev.dt.epoch()[0] == 0 + or + dt.dt.epoch()[0] == 0 + ): + log.warning('Skipping row with UNIX epoch timestamp ??') + continue + + if wdts[0]['index'][0] == iend: # first row + log.warning('Skipping first-row (has no previous obvi) !!') + continue + + # XXX, if the previous-row by shm-index is missing, + # meaning there is a missing sample (set), get the prior + # row by df index and attempt to use it? + i_wdts: pl.DataFrame = wdts.with_row_index(name='i') + i_row: int = i_wdts.filter(pl.col('index') == iend)['i'][0] + prev_row_by_i = wdts[i_row] + prev_r: pl.DataFrame = prev_row_by_i + + # debug any missing pre-row + if tractor._state.is_debug_mode(): + await tractor.pause() + + istart: int = prev_r['index'][0] + + # TODO: implement px-col width measure + # and ensure at least as many px-cols + # shown per rect as configured by user. + # gap_w: float = abs((iend - istart)) + # if gap_w < 6: + # margin: float = 6 + # iend += margin + # istart -= margin + + rect_gap: float = BGM*3/8 + opn: float = row['open'][0] + ro: tuple[float, float] = ( + # dt_end_t, + iend + rect_gap + 1, + opn, + ) + cls: float = prev_r['close'][0] + lc: tuple[float, float] = ( + # dt_start_t, + istart - rect_gap, # + 1 , + cls, + ) + + color: str = 'dad_blue' + diff: float = cls - opn + sgn: float = copysign(1, diff) + color: str = { + -1: 'buy_green', + 1: 'sell_red', + }[sgn] + + rect_kwargs: dict[str, Any] = dict( + fqme=fqme, + timeframe=timeframe, + start_pos=lc, + end_pos=ro, + color=color, + ) + + aid: int = await actl.add_rect(**rect_kwargs) + assert aid + aids[aid] = rect_kwargs + + # tell chart to redraw all its + # graphics view layers Bo + await actl.redraw( + fqme=fqme, + timeframe=timeframe, + ) + return aids