Commit Graph

3285 Commits (896e640e8af447780004ac24032cf6b50cd2322b)

Author SHA1 Message Date
Tyler Goodlet 896e640e8a Better handle dynamic registry sampler broadcasts
In situations where clients are (dynamically) subscribing *while*
broadcasts are starting to taking place we need to handle the
`set`-modified-during-iteration case. This scenario seems to be more
common during races on concurrent startup of multiple symbols. The
solution here is to use another set to take note of subscribers which
are successfully sent-to and then skipping them on re-try.

This also contains an attempt to exception-handle throttled stream
overruns caused by higher frequency feeds (like binance) pushing more
quotes then can be handled during (UI) client startup.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 98a8979474 Drop old loop and wait on fsp engine tasks startups 2023-01-30 11:49:37 -05:00
Tyler Goodlet e42b48732c Comment out all median usage, turns out it's uneeded.. 2023-01-30 11:49:37 -05:00
Tyler Goodlet 84bfc9b73a Lul, actually scaled main chart from linked set
This was a subtle logic error when building the `plots: dict` we weren't
adding the "main (ohlc or other source) chart" from the `LinkedSplits`
set when interacting with some sub-chart from `.subplots`..

Further this tries out bypassing `numpy.median()` altogether by just
using `median = (ymx - ymn) / 2` which should be nearly the same?
2023-01-30 11:49:37 -05:00
Tyler Goodlet 756bb70fc0 Return fast on bad range in `.default_view()` 2023-01-30 11:49:37 -05:00
Tyler Goodlet fc13743e9c Use `._pathops.slice_from_time()` for overlay intersects
It's way faster since it uses a uniform time arithmetic to narrow the
`numpy.searchsorted()` range before actually doing the index search B)
2023-01-30 11:49:37 -05:00
Tyler Goodlet 66c455a2e8 Fix return type annot for `slice_from_time()` 2023-01-30 11:49:37 -05:00
Tyler Goodlet 1c9b9d4f2b Don't scale overlays on linked from display loop
In the (incrementally updated) display loop we have range logic that is
incrementally updated in real-time by streams, as such we don't really
need to update all linked chart's (for any given, currently updated
chart) y-ranges on calls of each separate (sub-)chart's
`ChartView.interact_graphics_cycle()`. In practise there are plenty of
cases where resizing in one chart (say the vlm fsps sub-plot) requires
a y-range re-calc but not in the OHLC price chart. Therefore
we always avoid doing more resizing then necessary despite it resulting
in potentially more method call overhead (which will later be justified
by better leveraging incrementally updated `Viz.maxmin()` and
`media_from_range()` calcs).
2023-01-30 11:49:37 -05:00
Tyler Goodlet 07de93c11c Don't skip overlay scaling in disp-loop for now 2023-01-30 11:49:37 -05:00
Tyler Goodlet 1cfb2b083f Add linked charts guard-flag for use in display loop 2023-01-30 11:49:37 -05:00
Tyler Goodlet cb4ccb5cfe Fix `do_px_step` output for epoch step sizing 2023-01-30 11:49:37 -05:00
Tyler Goodlet c41bc5dfd4 Use new cached median method in overlay scaling
Massively speeds up scaling transform cycles (duh).

Also includes a draft for an "overlay transform" type/api; obviously
still a WIP 🏄..
2023-01-30 11:49:37 -05:00
Tyler Goodlet 0480b5e08a Add `Viz.median_from_range()`
A super snappy `numpy.median()` calculator (per input range) which we
slap an `lru_cache` on thanks to handy dunder method hacks for such
things on mutable types XD
2023-01-30 11:49:37 -05:00
Tyler Goodlet fb9156ebc5 Speed up ranging in display loop
use the new `do_overlay_scaling: bool` since we know each feed will have
its own updates (cuz multiplexed by feed..) and we can avoid
ranging/scaling overlays that will make their own calls.

Also, pass in the last datum "brighter" color for ohlc curves as it was
originally (and now that we can pass that styling bit through).
2023-01-30 11:49:37 -05:00
Tyler Goodlet 753d666347 Support chart draw-api-kwargs-passthrough in lined plot meths 2023-01-30 11:49:37 -05:00
Tyler Goodlet e84e5e3c39 Use normal pen when last-datum color not provided 2023-01-30 11:49:37 -05:00
Tyler Goodlet 99d0e4aab2 Add full profiling to `.interact_graphics_cycle()` 2023-01-30 11:49:37 -05:00
Tyler Goodlet cdc876a8dd Make profiler work when nested and not? 2023-01-30 11:49:37 -05:00
Tyler Goodlet d9f27ade7e Add back `.prepareGeometryChange()`, seems faster? 2023-01-30 11:49:37 -05:00
Tyler Goodlet 2f3d6af4a6 Factor color and cache mode settings into `FlowGraphics`
Curve-path colouring and cache mode settings are used (and can thus be
factored out of) all child types; this moves them into the parent type's
`.__init__()` and adjusts all sub-types match:

- the bulk was moved out of the `Curve.__init__()` including all
  previous commentary around cache settings.
- adjust `BarItems` to use a `NoCache` mode and instead use the
  `last_step_pen: pg.Pen` and `._pen` inside it's `.pain()` instead of
  defining functionally duplicate vars.
- adjust all (transitive) calls to `BarItems` to use the new kwargs
  names.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 6d418d35cb Fix intersect detection using time indexing
Facepalm, obviously absolute array indexes are not going to necessarily
align vs. time over multiple feeds/history. Instead use
`np.searchsorted()` on whatever curve has the smallest support and find
the appropriate index of intersection in time so that alignment always
starts at a sensible reference.

Also adds a `debug_print: bool` input arg which can enable all the
prints when working on this.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 215bfa21a7 Factor curve-dispersion sorting into primary loop
We can determine the major curve (in view) in the first pass of all
`Viz`s so drop the 2nd loop and thus the `mxmn_groups: dict`. Also
simplifies logic for the case of only one (the major) curve in view.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 96847d71c7 When only one curve is in view, skip group ranging 2023-01-30 11:49:37 -05:00
Tyler Goodlet 94cb66daf9 Return `in_view: bool` from `Viz.update_graphics()`
Allows callers to know if they should care about a particular viz
rendering call by immediately knowing if the graphics are in view. This
turns out super useful particularly when doing dynamic y-ranging overlay
calcs.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 59d4535bc7 Drop `update_graphics_from_flow()` 2023-01-30 11:49:37 -05:00
Tyler Goodlet 285fd92181 Just warn log on bad intersect indexing errors (for now) 2023-01-30 11:49:37 -05:00
Tyler Goodlet 05aab6d749 Only set the major curve's range once (per render cycle)
Turns out this is a limitation of the `ViewBox.setYRange()` api: you
can't call it more then once and expect anything but the first call to
be applied without letting a render cycle run. As such, we wait until
the end of the log-linear scaling loop to finally apply the major curves
y-mx/mn after all minor curves have been evaluated.

This also drops all the debug prints (for now) to get a feel for latency
in production mode.
2023-01-30 11:49:37 -05:00
Tyler Goodlet a17fae2ef8 Move axis hiding into `.overlay_plotitem()`
Since we pretty much always want the 'bottom' and any side that is not
declared by the caller move the axis hides into this method. Lets us
drop the same calls in `.ui._fsp` and `._display`.

This also disables the auto-ranging back-linking for now since it
doesn't seem to be working quite yet?
2023-01-30 11:49:37 -05:00
Tyler Goodlet 82f988fa02 Only remove axis from scene when in one 2023-01-30 11:49:37 -05:00
Tyler Goodlet 97520b60fe Drop `.group_maxmin()`
We ended up doing groups maxmin sorting at the interaction layer (new
the view box) and thus this method is no longer needed, though it was
the reference for the code now in `ChartView.interact_graphics_cycle()`.

Further this adds a `remove_axes: bool` arg to `.insert_plotitem()`
which can be used to drop axis entries from the inserted pi (though it
doesn't seem like we really ever need that?) and does the removal in
a separate loop to avoid removing axes before they are registered in
`ComposedGridLayout._pi2axes`.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 7d190ed893 Clean up cross-curve intersect point indexing
When there are `N`-curves we need to consider the smallest
x-data-support subset when figuring out for each major-minor pair such
that the "shorter" series is always returns aligned to the longer one.

This makes the var naming more explicit with `major/minor_i_start` as
well as clarifies more stringently a bunch of other variables and
explicitly uses the `minor_y_intersect` y value in the scaling transform
calcs. Also fixes some debug prints.
2023-01-30 11:49:37 -05:00
Tyler Goodlet c579a27931 3rdz the charm: log-linearize minor y-ranges to a major
In very close manner to the original (gut instinct) attempt, this
properly (y-axis-vertically) aligns and scales overlaid curves according
to what we are calling a "log-linearized y-range multi-plot" B)

The basic idea is that a simple returns measure (eg. `R = (p1 - p0)
/ p0`) applied to all curves gives a constant output `R` no matter the
price co-domain in use and thus gives a constant returns over all assets
in view styled scaling; a intuitive visual of returns correlation. The
reference point is for now the left-most point in view (or highest
common index available to all curves), though we can make this
a parameter based on user needs.

A slew of debug `print()`s are left in for now until we iron out the
remaining edge cases to do with re-scaling a major (dispersion) curve
based on a minor now requiring a larger log-linear y-range from that
previous major' range.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 2bc0f7b423 2nd try: dispersion normalize y-ranges around median
In the dispersion swing calcs, use the series median from the in-view
data to determine swing proportions to apply on each "minor curve"
(series with lesser dispersion the one with the greatest). Track the
major `Viz` as before by max dispersion. Apply the dispersion swing
proportions to each minor curve-series in a third loop/pass of all
overlay groups: this ensures all overlays are dispersion normalized in
their ranges but, minor curves are currently (vertically) centered (vs.
the major) via their medians.

There is a ton of commented code from attempts to try and vertically
align minor curves to the major via the "first datum" in-view/available.
This still needs work and we may want to offer it as optional.

Also adds logic to allow skipping margin adjustments in `._set_yrange()`
if you pass `range_margin=None`.
2023-01-30 11:49:37 -05:00
Tyler Goodlet dc8823570a First draft, group y-minmax transform algo
On overlaid ohlc vizs we compute the largest max/min spread and
apply that maxmimum "up and down swing" proportion to each `Viz`'s
viewbox in the group.

We obviously still need to clip to the shortest x-range so that
it doesn't look exactly the same as before XD
2023-01-30 11:49:37 -05:00
Tyler Goodlet c20982f6e1 Fix profiler f-strings 2023-01-30 11:49:37 -05:00
Tyler Goodlet 5bfd850e4f Rename `.maybe_downsample_graphics()` -> `.interact_graphics_cycle()` 2023-01-30 11:49:37 -05:00
Tyler Goodlet c505e9ac42 Disable coordinate caching on OHLC ds curves to avoid smearing 2023-01-30 11:49:37 -05:00
Tyler Goodlet a19ae2015f Fix `Viz.draw_last()` to divide by `.flat_index_ratio` for uppx index lookback 2023-01-30 11:49:37 -05:00
Tyler Goodlet 3be9259441 Drop masked `._maxmin()` override code from fsp stuff 2023-01-30 11:49:37 -05:00
Tyler Goodlet 5bb4c24ae0 Right, handle y-ranging multiple paths per plot
We were hacking this before using the whole `ChartView._maxmin()`
setting stuff since in some cases you might want similarly ranged paths
on the same view, but of course you need to max/min them together..

This adds that group sorting by using a table of `dict[PlotItem,
tuple[float, float]` and taking the abs highest/lowest value for each
plot in the viz interaction update loop.

Also removes the now commented signal registry calls and thus
`._yranger`, drops the `set_range: bool` from `._set_yrange` and adds
and extra `.maybe_downsample_graphics()` to the mouse wheel handler to
avoid a weird slow debounce where ds-ing is delayed until a further
interaction.
2023-01-30 11:49:37 -05:00
Tyler Goodlet a77c42edf3 Document `Viz.incr_info()` outputs 2023-01-30 11:49:37 -05:00
Tyler Goodlet 6e3518a860 Rework display loop maxmin-ing with `Viz` pipelining
First, we rename what was `chart_maxmin()` -> `multi_maxmin()` and don't
`partial` it in to the `DisplayState`, just call it with correct `Viz`
ref inputs.

Second, as we've done with `ChartView.maybe_downsample_graphics()` use
the output from the main `Viz.update_graphics()` and feed it to the
`.maxmin()` calls for the ohlc and vlm chart but still deliver the same
output signature as prior. Also accept and use an optional profiler
input, drop `DisplayState.maxmin()` and add `.vlm_viz`.

Further perf related tweak to do with more efficient incremental
updates:
- only call `multi_maxmin()` if the main fast chart viz does a pixel
  column step.
- mask out hist viz and vlm viz and all linked fsp `._set_yrange()`
  calls for now until we figure out how to best optimize these updates
  when considering the new group-scaled-by-% style for multicharts.
- drop `.enable_auto_yrange()` calls during startup.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 909ecd80a1 Drop Qt interaction signal usage
It's kind of hard to understand with the C++ fan-out to multiple views
(imo a cluster-f#$*&) and seems honestly just plain faster to loop (in
python) through all the linked view handlers XD

Core adjustments:
- make the panning and wheel-scroll handlers just call
  `.maybe_downsample_graphics()` directly; drop all signal emissions.
- make `.maybe_downsample_graphics()` loop through all vizs per subchart
  and use the new pipeline-style call sequence of:
  - `Viz.update_graphics() -> <read_slc>: tuple`
  - `Viz.maxmin(i_read_range=<read_slc>) -> yrange: tuple`
  - `Viz.plot.vb._set_yrange(yrange=yrange)`
  which inlines all the necessary calls in the most efficient way whilst
  leveraging `.maxmin()` caching and ymxmn-from-m4-during-render to
  boot.
- drop registering `._set_yrange()` for handling `.sigRangeChangedManually`.
2023-01-30 11:49:37 -05:00
Tyler Goodlet 6d119d56d2 Adjust vlm fsp code to new `Viz.update_graphics()` output sig 2023-01-30 11:49:36 -05:00
Tyler Goodlet 55356ae7e8 Support read-slice input to `Viz.maxmin()`
Acts as short cut when pipe-lining from `Viz.update_graphics()` (which
now returns the needed in-view array-relative-read-slice as output) such
that `Viz.read()` and `.datums_range()` doesn't need to be called
internally multiple times. In this case where `i_read_range` is provided
we of course skip doing time index translations and consequently lookup
the appropriate (epoch-time) index indices for caching.
2023-01-30 11:49:36 -05:00
Tyler Goodlet 998f60b961 Backlink subchart views to "main chart" in `.add_plot()` 2023-01-30 11:49:36 -05:00
Tyler Goodlet ed3de9eb5f Drop `ChartView._maxmin()` usage in `.ui._fsp`
Removes the multi-maxmin usage as well as ensures appropriate `Viz` refs
are passed into the view methods now requiring it. Also drops the "back
linking" of the vlm chart view to the source OHLC chart since we're
going to add this as a default to the charting API.
2023-01-30 11:49:36 -05:00
Tyler Goodlet 9da93478df Drop `ChartView._maxmin()` idea, use `Viz.maxmin()`
The max min for a given data range is defined on the lowest level
through the `Viz` api intermingling it with the view is a layering
issue. Instead make `._set_yrange()` call the appropriate view's viz
(since they should be one-to-one) directly and thus avoid any callback
monkey patching nonsense.

Requires that we now make `._set_yrange()` require either one of an
explicit `yrange: tuple[float, float]` min/max pair or the `Viz` ref (so
that maxmin can be called) as input. Adjust
`enable/disable_auto_yrange()` to bind in a new `._yranger()` partial
that's (solely) needed for signal reg/unreg which binds in the now
required input `Viz` to these methods.

Comment the `autoscale_overlays` block in `.maybe_downsample_graphics()`
for now until we figure out the most sane way to auto-range all linked
overlays and subplots (with their own overlays).
2023-01-30 11:49:36 -05:00
Tyler Goodlet a66be2592a More thoroughly profile the display loop 2023-01-30 11:49:36 -05:00
Tyler Goodlet 5dda0ca287 Use `Viz.draw_last()` inside `.update_graphics()`
In an effort to ensure uniform and uppx-optimized last datum graphics
updates call this method directly instead of the equivalent graphics
object thus ensuring we only update the last pixel column according with
the appropriate max/min computed from the last uppx's worth of data.

Fixes / improvements to enable `.draw_last()` usage include,
- change `Viz._render_table` -> `._alt_r: tuple[Renderer, pg.GraphicsItem] | None`
  which holds an alternative (usually downsampled) render and graphics
  obj.
- extend the `.draw_last()` signature to include:
  - `last_read` to allow passing in the already read data from
    `.update_graphics()`, if it isn't passed then a manual read is done
    internally.
  - `reset_cache: bool` which is passed through to the graphics obj.
- use the new `Formatter.flat_index_ratio: float` when indexing into xy
  1d data to compute the max/min for that px column.

Other,
- drop `bars_range` input from `maxmin()` since it's unused.
2023-01-30 11:49:36 -05:00