Add cached dispersion methods to `Viz`
In an effort to make overlay calcs cleaner and leverage caching of view range -> dispersion measures, this adds the following new methods: - `._dispersion()` an lru cached returns scalar calculator given input y-range and y-ref values. - `.disp_from_range()` which calls the above method and returns variable output depending on requested calc `method: str`. - `.i_from_t()` a currently unused cached method for slicing the in-view's array index from time stamp (though not working yet due to needing to parameterize the cache by the input `.vs.xrange`). Further refinements/adjustments: - rename `.view_state: ViewState` -> `.vs`. - drop the `.bars_range()` method as it's no longer used anywhere else in the code base. - always set the `ViewState.in_view: np.ndarray` inside `.read()`. - return the start array index (from slice) and `yref` value @ `xref` from `.scalars_from_index()` to aid with "pin to curve" rescaling caused by out-of-range pinned-minor curves.log_linearized_curve_overlays
parent
29418e9655
commit
4d11c5c89c
|
@ -279,7 +279,7 @@ class Viz(Struct): # , frozen=True):
|
||||||
flume: Flume
|
flume: Flume
|
||||||
graphics: Curve | BarItems
|
graphics: Curve | BarItems
|
||||||
|
|
||||||
view_state: ViewState = field(default_factory=ViewState)
|
vs: ViewState = field(default_factory=ViewState)
|
||||||
|
|
||||||
# last calculated y-mn/mx from m4 downsample code, this
|
# last calculated y-mn/mx from m4 downsample code, this
|
||||||
# is updated in the body of `Renderer.render()`.
|
# is updated in the body of `Renderer.render()`.
|
||||||
|
@ -520,6 +520,7 @@ class Viz(Struct): # , frozen=True):
|
||||||
# cache result for input range
|
# cache result for input range
|
||||||
assert mxmn
|
assert mxmn
|
||||||
self._mxmns[ixrng] = (read_slc, mxmn)
|
self._mxmns[ixrng] = (read_slc, mxmn)
|
||||||
|
self.vs.yrange = mxmn
|
||||||
profiler(f'yrange mxmn cacheing: {x_range} -> {mxmn}')
|
profiler(f'yrange mxmn cacheing: {x_range} -> {mxmn}')
|
||||||
return (
|
return (
|
||||||
ixrng,
|
ixrng,
|
||||||
|
@ -538,15 +539,6 @@ class Viz(Struct): # , frozen=True):
|
||||||
vr.right(),
|
vr.right(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def bars_range(self) -> tuple[int, int, int, int]:
|
|
||||||
'''
|
|
||||||
Return a range tuple for the left-view, left-datum, right-datum
|
|
||||||
and right-view x-indices.
|
|
||||||
|
|
||||||
'''
|
|
||||||
l, start, datum_start, datum_stop, stop, r = self.datums_range()
|
|
||||||
return l, datum_start, datum_stop, r
|
|
||||||
|
|
||||||
def datums_range(
|
def datums_range(
|
||||||
self,
|
self,
|
||||||
view_range: None | tuple[float, float] = None,
|
view_range: None | tuple[float, float] = None,
|
||||||
|
@ -574,11 +566,6 @@ class Viz(Struct): # , frozen=True):
|
||||||
first: int = floor(index[0])
|
first: int = floor(index[0])
|
||||||
last: int = ceil(index[-1])
|
last: int = ceil(index[-1])
|
||||||
|
|
||||||
# first and last datums in view determined by
|
|
||||||
# l -> r view range.
|
|
||||||
leftmost: int = floor(l)
|
|
||||||
rightmost: int = ceil(r)
|
|
||||||
|
|
||||||
# invalid view state
|
# invalid view state
|
||||||
if (
|
if (
|
||||||
r < l
|
r < l
|
||||||
|
@ -593,13 +580,15 @@ class Viz(Struct): # , frozen=True):
|
||||||
rightmost: int = last
|
rightmost: int = last
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
# determine first and last datums in view determined by
|
||||||
|
# l -> r view range.
|
||||||
rightmost = max(
|
rightmost = max(
|
||||||
min(last, rightmost),
|
min(last, ceil(r)),
|
||||||
first,
|
first,
|
||||||
)
|
)
|
||||||
|
|
||||||
leftmost = min(
|
leftmost = min(
|
||||||
max(first, leftmost),
|
max(first, floor(l)),
|
||||||
last,
|
last,
|
||||||
rightmost - 1,
|
rightmost - 1,
|
||||||
)
|
)
|
||||||
|
@ -607,7 +596,7 @@ class Viz(Struct): # , frozen=True):
|
||||||
# sanity
|
# sanity
|
||||||
# assert leftmost < rightmost
|
# assert leftmost < rightmost
|
||||||
|
|
||||||
self.view_state.xrange = leftmost, rightmost
|
self.vs.xrange = leftmost, rightmost
|
||||||
|
|
||||||
return (
|
return (
|
||||||
l, # left x-in-view
|
l, # left x-in-view
|
||||||
|
@ -671,14 +660,14 @@ class Viz(Struct): # , frozen=True):
|
||||||
# above?
|
# above?
|
||||||
in_view = array[read_slc]
|
in_view = array[read_slc]
|
||||||
if in_view.size:
|
if in_view.size:
|
||||||
self.view_state.in_view = in_view
|
self.vs.in_view = in_view
|
||||||
abs_indx = in_view['index']
|
abs_indx = in_view['index']
|
||||||
abs_slc = slice(
|
abs_slc = slice(
|
||||||
int(abs_indx[0]),
|
int(abs_indx[0]),
|
||||||
int(abs_indx[-1]),
|
int(abs_indx[-1]),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.view_state.in_view = None
|
self.vs.in_view = None
|
||||||
|
|
||||||
if profiler:
|
if profiler:
|
||||||
profiler(
|
profiler(
|
||||||
|
@ -699,7 +688,7 @@ class Viz(Struct): # , frozen=True):
|
||||||
# BUT the ``in_view`` slice DOES..
|
# BUT the ``in_view`` slice DOES..
|
||||||
read_slc = slice(lbar_i, rbar_i)
|
read_slc = slice(lbar_i, rbar_i)
|
||||||
in_view = array[lbar_i: rbar_i + 1]
|
in_view = array[lbar_i: rbar_i + 1]
|
||||||
self.view_state.in_view = in_view
|
self.vs.in_view = in_view
|
||||||
# in_view = array[lbar_i-1: rbar_i+1]
|
# in_view = array[lbar_i-1: rbar_i+1]
|
||||||
# XXX: same as ^
|
# XXX: same as ^
|
||||||
# to_draw = array[lbar - ifirst:(rbar - ifirst) + 1]
|
# to_draw = array[lbar - ifirst:(rbar - ifirst) + 1]
|
||||||
|
@ -1323,27 +1312,101 @@ class Viz(Struct): # , frozen=True):
|
||||||
return np.median(in_view[self.name])
|
return np.median(in_view[self.name])
|
||||||
|
|
||||||
@lru_cache(maxsize=6116)
|
@lru_cache(maxsize=6116)
|
||||||
def dispersion(
|
def _dispersion(
|
||||||
start: int,
|
self,
|
||||||
stop: int,
|
# xrange: tuple[float, float],
|
||||||
|
ymn: float,
|
||||||
|
ymx: float,
|
||||||
|
yref: float,
|
||||||
|
|
||||||
) -> float:
|
) -> tuple[float, float]:
|
||||||
pass
|
return (
|
||||||
|
(ymx - yref) / yref,
|
||||||
|
(ymn - yref) / yref,
|
||||||
|
)
|
||||||
|
|
||||||
|
def disp_from_range(
|
||||||
|
self,
|
||||||
|
xrange: tuple[float, float] | None = None,
|
||||||
|
yref: float | None = None,
|
||||||
|
method: Literal[
|
||||||
|
'up',
|
||||||
|
'down',
|
||||||
|
'full', # both sides
|
||||||
|
'both', # both up and down as separate scalars
|
||||||
|
|
||||||
|
] = 'full',
|
||||||
|
|
||||||
|
) -> float | tuple[float, float] | None:
|
||||||
|
'''
|
||||||
|
Return a dispersion metric referenced from an optionally
|
||||||
|
provided ``yref`` or the left-most datum level by default.
|
||||||
|
|
||||||
|
'''
|
||||||
|
vs = self.vs
|
||||||
|
yrange = vs.yrange
|
||||||
|
if yrange is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ymn, ymx = yrange
|
||||||
|
key = 'open' if self.is_ohlc else self.name
|
||||||
|
yref = yref or vs.in_view[0][key]
|
||||||
|
# xrange = xrange or vs.xrange
|
||||||
|
|
||||||
|
# call into the lru_cache-d sigma calculator method
|
||||||
|
r_up, r_down = self._dispersion(ymn, ymx, yref)
|
||||||
|
match method:
|
||||||
|
case 'full':
|
||||||
|
return r_up - r_down
|
||||||
|
case 'up':
|
||||||
|
return r_up
|
||||||
|
case 'down':
|
||||||
|
return r_up
|
||||||
|
case 'both':
|
||||||
|
return r_up, r_down
|
||||||
|
|
||||||
|
@lru_cache(maxsize=6116)
|
||||||
|
def i_from_t(
|
||||||
|
self,
|
||||||
|
t: float,
|
||||||
|
) -> int:
|
||||||
|
return slice_from_time(
|
||||||
|
self.vs.in_view,
|
||||||
|
start_t=t,
|
||||||
|
stop_t=t,
|
||||||
|
step=self.index_step(),
|
||||||
|
).start
|
||||||
|
|
||||||
def scalars_from_index(
|
def scalars_from_index(
|
||||||
self,
|
self,
|
||||||
xref: float,
|
xref: float | None = None,
|
||||||
|
|
||||||
|
) -> tuple[int, float, float, float]:
|
||||||
|
|
||||||
|
vs = self.vs
|
||||||
|
arr = vs.in_view
|
||||||
|
|
||||||
|
# TODO: make this work by parametrizing over input
|
||||||
|
# .vs.xrange input for caching?
|
||||||
|
# read_slc_start = self.i_from_t(xref)
|
||||||
|
|
||||||
) -> tuple[float, float]:
|
|
||||||
arr = self.view_state.in_view
|
|
||||||
slc = slice_from_time(
|
slc = slice_from_time(
|
||||||
arr=self.view_state.in_view,
|
arr=self.vs.in_view,
|
||||||
start_t=xref,
|
start_t=xref,
|
||||||
stop_t=xref,
|
stop_t=xref,
|
||||||
)
|
)
|
||||||
yref = arr[slc.start]
|
read_slc_start = slc.start
|
||||||
ymn, ymx = self.view_state.yrange
|
|
||||||
|
key = 'open' if self.is_ohlc else self.name
|
||||||
|
yref = arr[read_slc_start][key]
|
||||||
|
ymn, ymx = self.vs.yrange
|
||||||
|
# print(
|
||||||
|
# f'INTERSECT xref: {read_slc_start}\n'
|
||||||
|
# f'ymn, ymx: {(ymn, ymx)}\n'
|
||||||
|
# )
|
||||||
return (
|
return (
|
||||||
(ymn - yref) / yref,
|
read_slc_start,
|
||||||
|
yref,
|
||||||
(ymx - yref) / yref,
|
(ymx - yref) / yref,
|
||||||
|
(ymn - yref) / yref,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue