Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log linearized curve overlays #455

Merged
merged 138 commits into from
Mar 13, 2023
Merged

Conversation

goodboy
Copy link
Contributor

@goodboy goodboy commented Feb 3, 2023

The cross-symbol y-alignment you always wanted..

screenshot-2023-02-03_08-17-43

Yah it's for #420 🏄🏼 now swapped with #461.


wUt dIs?!

  • overlays multiple time series using a "log-linearized returns transform", meaning all % returns (moves) on any symbol's chart are the same (from the reference point) as any other overlapped with it.
    • keeps support for multiple Curve per PlotItem y-range sorting
    • there are edge cases we handle:
      • any shorter series is aligned according to its intersect with the longer series it overlays with (in the case of > 2 this series is normally the one measured to have the most dispersion).
      • gaps are handled as though the pre-gap value is the value used as the ref-y-value
  • reimplements all auto-y-ranging as part of a new ChartView.interact_graphics_cycle() which does the more efficient pipelining of Viz related graphics update calls per mouse event.
    • use this inside the _display loop as well.
  • completely drops all the Qt.Signal stuff for interaction which results in more reliable, easier to understand, and generally faster interaction handling code 😂

Solved ToDo bugs:

  • we need to add back y-ranging on the vlm chart to make sure the RHS label is always in view (done in 0d45495)

  • stupid gap on LHS of vlm fsp chart which still seems to be due to some kinda PlotItem.layout related setting?
    LUL, fixed in d4262c9
    screenshot-2023-02-03_10-48-22

  • get the immediate y-range incremental update on L1 working again?

    • not sure how this broke yet again, pretty sure it was just fine a a while back in
    • fixed and improved as of fc0a793
  • rewrite commit 325fe3c which is duplicate of 325fe3c from Overlays interaction latency tuning #453 after cherry picking it there.


Final refinements

  • for a single fqsn using array-indexing the x-datetime labels for the cursor still aren't working.. (fixed by 269f659)
  • bleh, still an issue with 3+ feeds on slow chart and overlays, eg.
    screenshot-2023-02-10_14-40-34
    • HeH, figured it out -> we need to rescale any already scaled minors whenever the major (dispersion) needs to be rescaled (since any previously scaled minors are scaled to the previous major's range 🤦🏼 ) (fixed in 1be5ae4)
  • finish formalizing the OverlayT type and API and use lru caching on range (inverse) transform outputs
  • support the following 4 viewlist "modes" (finalized in 5e729dd)
    • only one feed
    • ymxmn for each feed
    • pin to dispersion major (1be5ae4)
    • pin from first datum in view (rework in 16eda1e)

FINAL FINAL bug fixes and tweaks:

  • fix upsampling (re-)triggering when using the 'r' to reset chart hotkey in 1d649e5

  • there's still an issue with de-synced 1m last bars?
    screenshot-2023-03-06_10-47-57

  • lol, the alignment to "sigma major" default can definitely still cause a terrible config as per this example of bitcoin overlayed with a deribit call:
    screenshot-2023-03-03_17-44-41

    • the issue is that the call option is clearly causing the bitcoin move to be mega squashed in the y-range 😂
    • it might make sense in these cases to instead use a "leverage and delta adjusted" scalar for the opt contract so that you can see a "some what linear" translation of the deriv price vs. the underlying..
    • yet another issue seems to be that the "pin to dispersion major" curve idea doesn't work well when detecting intersects and the sigma major is the shorter curve?
align_to_majordisp_still_borked_Peek.2023-03-06.10-53.mp4

@goodboy
Copy link
Contributor Author

goodboy commented Feb 9, 2023

Weird, CI breaking on Struct not having a default factor someting somethin?

Not sure when that started?

(fixed with #457)

@goodboy goodboy force-pushed the overlays_interaction_latency_tuning branch from e280e48 to 07d941d Compare February 9, 2023 21:47
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from 94e212d to 3c4031e Compare February 9, 2023 21:49
@goodboy goodboy force-pushed the overlays_interaction_latency_tuning branch from 07d941d to ea3ea8d Compare February 12, 2023 19:09
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from d4262c9 to 4f204dc Compare February 12, 2023 20:34
@goodboy goodboy force-pushed the overlays_interaction_latency_tuning branch from ea3ea8d to 02b92e8 Compare February 12, 2023 20:39
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from 4f204dc to 38bb493 Compare February 12, 2023 20:39
@goodboy goodboy force-pushed the overlays_interaction_latency_tuning branch from 02b92e8 to fefb0de Compare February 13, 2023 17:28
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from 38bb493 to f697354 Compare February 13, 2023 17:28
@goodboy goodboy mentioned this pull request Feb 13, 2023
43 tasks
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from f697354 to 1d6270f Compare February 13, 2023 20:11
@@ -340,6 +360,49 @@ async def handle_viewmode_mouse(
view.order_mode.submit_order()


class OverlayT(Struct):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note this code isn't used at all yet, it's just a draft for a more encapsulated API for the main logic inside .interact_graphics_cycle() below.

Base automatically changed from overlays_interaction_latency_tuning to master February 14, 2023 18:48
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from 1d6270f to 3d091f2 Compare February 14, 2023 18:49
goodboy added a commit that referenced this pull request Feb 16, 2023
As part of solving a final bullet-issue in #455, which is specifically
a case:
- with N > 2 curves, one of which is the "major" dispersion curve" and
  the others are "minors",
- we can run into a scenario where some minor curve which gets pinned to
  the major (due to the original "pinning technique" -> "align to
  major") at some `P(t)` which is *not* the major's minimum / maximum
  due to the minor having a smaller/shorter support and thus,
- requires that in order to show then max/min on the minor curve we have
  to expand the range of the major curve as well but,
- that also means any previously scaled (to the major) minor curves need
  to be adjusted as well or they'll not be pinned to the major the same
  way!

I originally was trying to avoid doing the recursive iteration back
through all previously scaled minor curves and instead decided to try
implementing the "per side" curve dispersion detection (as was
originally attempted when first starting this work). The idea is to
decide which curve's up or down "swing in % returns" would determine the
global y-range *on that side*. Turns out I stumbled on the "align to
first" technique in the process: "for each overlay curve we align its
earliest sample (in time) to the same level of the earliest such sample
for whatever is deemed the major (directionally disperse) curve in
view".

I decided (with help) that this "pin to first" approach/style is equally
as useful and maybe often more so when wanting to view support-disjoint
time series:

- instead of compressing the y-range on "longer series which have lesser
  sigma" to make whatever "shorter but larger-sigma series" pin to it at
  an intersect time step, this instead will expand the price ranges
  based on the earliest time step in each series.
- the output global-returns-overlay-range for any N-set of series is equal to
  the same in the previous "pin to intersect time" technique.
- the only time this technique seems less useful is for overlaying
  market feeds which have the same destination asset but different
  source assets (eg. btceur and btcusd on the same chart since if one
  of the series is shorter it will always be aligned to the earliest
  datum on the longer instead of more naturally to the intersect sample
  level as was in the previous approach).

As such I'm going to keep this technique as discovered and will later
add back optional support for the "align to intersect" approach from
previous (which will again require detecting the highest dispersion
curve direction-agnostic) and pin all minors to the price level at which
they start on the major.

Further details of the implementation rework in
`.interact_graphics_cycle()` include:

- add `intersect_from_longer()` to detect and deliver a common datum
  from 2 series which are different in length: the first time-index
  sample in the longer.
- Rewrite the drafted `OverlayT` to only compute (inversed log-returns)
  transforms for a single direction and use 2 instances, one for each
  direction inside the `Viz`-overlay iteration loop.
- do all dispersion-per-side major curve detection in the first pass of
  all `Viz`s on a plot, instead updating the `OverlayT` instances for
  each side and compensating for any length mismatch and
  rescale-to-minor cases in each loop cycle.
goodboy added a commit that referenced this pull request Feb 21, 2023
As part of solving a final bullet-issue in #455, which is specifically
a case:
- with N > 2 curves, one of which is the "major" dispersion curve" and
  the others are "minors",
- we can run into a scenario where some minor curve which gets pinned to
  the major (due to the original "pinning technique" -> "align to
  major") at some `P(t)` which is *not* the major's minimum / maximum
  due to the minor having a smaller/shorter support and thus,
- requires that in order to show then max/min on the minor curve we have
  to expand the range of the major curve as well but,
- that also means any previously scaled (to the major) minor curves need
  to be adjusted as well or they'll not be pinned to the major the same
  way!

I originally was trying to avoid doing the recursive iteration back
through all previously scaled minor curves and instead decided to try
implementing the "per side" curve dispersion detection (as was
originally attempted when first starting this work). The idea is to
decide which curve's up or down "swing in % returns" would determine the
global y-range *on that side*. Turns out I stumbled on the "align to
first" technique in the process: "for each overlay curve we align its
earliest sample (in time) to the same level of the earliest such sample
for whatever is deemed the major (directionally disperse) curve in
view".

I decided (with help) that this "pin to first" approach/style is equally
as useful and maybe often more so when wanting to view support-disjoint
time series:

- instead of compressing the y-range on "longer series which have lesser
  sigma" to make whatever "shorter but larger-sigma series" pin to it at
  an intersect time step, this instead will expand the price ranges
  based on the earliest time step in each series.
- the output global-returns-overlay-range for any N-set of series is equal to
  the same in the previous "pin to intersect time" technique.
- the only time this technique seems less useful is for overlaying
  market feeds which have the same destination asset but different
  source assets (eg. btceur and btcusd on the same chart since if one
  of the series is shorter it will always be aligned to the earliest
  datum on the longer instead of more naturally to the intersect sample
  level as was in the previous approach).

As such I'm going to keep this technique as discovered and will later
add back optional support for the "align to intersect" approach from
previous (which will again require detecting the highest dispersion
curve direction-agnostic) and pin all minors to the price level at which
they start on the major.

Further details of the implementation rework in
`.interact_graphics_cycle()` include:

- add `intersect_from_longer()` to detect and deliver a common datum
  from 2 series which are different in length: the first time-index
  sample in the longer.
- Rewrite the drafted `OverlayT` to only compute (inversed log-returns)
  transforms for a single direction and use 2 instances, one for each
  direction inside the `Viz`-overlay iteration loop.
- do all dispersion-per-side major curve detection in the first pass of
  all `Viz`s on a plot, instead updating the `OverlayT` instances for
  each side and compensating for any length mismatch and
  rescale-to-minor cases in each loop cycle.
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from 5223eb1 to 020e1ae Compare February 21, 2023 18:20
@goodboy goodboy added UI viz graphics (charting related) geometry chops perf efficiency and latency optimization labels Feb 24, 2023
goodboy added a commit that referenced this pull request Feb 28, 2023
As part of solving a final bullet-issue in #455, which is specifically
a case:
- with N > 2 curves, one of which is the "major" dispersion curve" and
  the others are "minors",
- we can run into a scenario where some minor curve which gets pinned to
  the major (due to the original "pinning technique" -> "align to
  major") at some `P(t)` which is *not* the major's minimum / maximum
  due to the minor having a smaller/shorter support and thus,
- requires that in order to show then max/min on the minor curve we have
  to expand the range of the major curve as well but,
- that also means any previously scaled (to the major) minor curves need
  to be adjusted as well or they'll not be pinned to the major the same
  way!

I originally was trying to avoid doing the recursive iteration back
through all previously scaled minor curves and instead decided to try
implementing the "per side" curve dispersion detection (as was
originally attempted when first starting this work). The idea is to
decide which curve's up or down "swing in % returns" would determine the
global y-range *on that side*. Turns out I stumbled on the "align to
first" technique in the process: "for each overlay curve we align its
earliest sample (in time) to the same level of the earliest such sample
for whatever is deemed the major (directionally disperse) curve in
view".

I decided (with help) that this "pin to first" approach/style is equally
as useful and maybe often more so when wanting to view support-disjoint
time series:

- instead of compressing the y-range on "longer series which have lesser
  sigma" to make whatever "shorter but larger-sigma series" pin to it at
  an intersect time step, this instead will expand the price ranges
  based on the earliest time step in each series.
- the output global-returns-overlay-range for any N-set of series is equal to
  the same in the previous "pin to intersect time" technique.
- the only time this technique seems less useful is for overlaying
  market feeds which have the same destination asset but different
  source assets (eg. btceur and btcusd on the same chart since if one
  of the series is shorter it will always be aligned to the earliest
  datum on the longer instead of more naturally to the intersect sample
  level as was in the previous approach).

As such I'm going to keep this technique as discovered and will later
add back optional support for the "align to intersect" approach from
previous (which will again require detecting the highest dispersion
curve direction-agnostic) and pin all minors to the price level at which
they start on the major.

Further details of the implementation rework in
`.interact_graphics_cycle()` include:

- add `intersect_from_longer()` to detect and deliver a common datum
  from 2 series which are different in length: the first time-index
  sample in the longer.
- Rewrite the drafted `OverlayT` to only compute (inversed log-returns)
  transforms for a single direction and use 2 instances, one for each
  direction inside the `Viz`-overlay iteration loop.
- do all dispersion-per-side major curve detection in the first pass of
  all `Viz`s on a plot, instead updating the `OverlayT` instances for
  each side and compensating for any length mismatch and
  rescale-to-minor cases in each loop cycle.
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from 1b83f4b to de2f77b Compare February 28, 2023 16:50
@goodboy goodboy changed the base branch from master to deribit_updates February 28, 2023 16:50
@goodboy
Copy link
Contributor Author

goodboy commented Feb 28, 2023

tweaked base branch to that from #471 for now just until we either drop those commits from this history or land that branch (which is slightly more critical 😂 )

goodboy added a commit that referenced this pull request Feb 28, 2023
As part of solving a final bullet-issue in #455, which is specifically
a case:
- with N > 2 curves, one of which is the "major" dispersion curve" and
  the others are "minors",
- we can run into a scenario where some minor curve which gets pinned to
  the major (due to the original "pinning technique" -> "align to
  major") at some `P(t)` which is *not* the major's minimum / maximum
  due to the minor having a smaller/shorter support and thus,
- requires that in order to show then max/min on the minor curve we have
  to expand the range of the major curve as well but,
- that also means any previously scaled (to the major) minor curves need
  to be adjusted as well or they'll not be pinned to the major the same
  way!

I originally was trying to avoid doing the recursive iteration back
through all previously scaled minor curves and instead decided to try
implementing the "per side" curve dispersion detection (as was
originally attempted when first starting this work). The idea is to
decide which curve's up or down "swing in % returns" would determine the
global y-range *on that side*. Turns out I stumbled on the "align to
first" technique in the process: "for each overlay curve we align its
earliest sample (in time) to the same level of the earliest such sample
for whatever is deemed the major (directionally disperse) curve in
view".

I decided (with help) that this "pin to first" approach/style is equally
as useful and maybe often more so when wanting to view support-disjoint
time series:

- instead of compressing the y-range on "longer series which have lesser
  sigma" to make whatever "shorter but larger-sigma series" pin to it at
  an intersect time step, this instead will expand the price ranges
  based on the earliest time step in each series.
- the output global-returns-overlay-range for any N-set of series is equal to
  the same in the previous "pin to intersect time" technique.
- the only time this technique seems less useful is for overlaying
  market feeds which have the same destination asset but different
  source assets (eg. btceur and btcusd on the same chart since if one
  of the series is shorter it will always be aligned to the earliest
  datum on the longer instead of more naturally to the intersect sample
  level as was in the previous approach).

As such I'm going to keep this technique as discovered and will later
add back optional support for the "align to intersect" approach from
previous (which will again require detecting the highest dispersion
curve direction-agnostic) and pin all minors to the price level at which
they start on the major.

Further details of the implementation rework in
`.interact_graphics_cycle()` include:

- add `intersect_from_longer()` to detect and deliver a common datum
  from 2 series which are different in length: the first time-index
  sample in the longer.
- Rewrite the drafted `OverlayT` to only compute (inversed log-returns)
  transforms for a single direction and use 2 instances, one for each
  direction inside the `Viz`-overlay iteration loop.
- do all dispersion-per-side major curve detection in the first pass of
  all `Viz`s on a plot, instead updating the `OverlayT` instances for
  each side and compensating for any length mismatch and
  rescale-to-minor cases in each loop cycle.
goodboy added 20 commits March 10, 2023 18:20
Instead delegate directly to `Viz.default_view()` throughout charting
startup and interaction handlers.

Also add a `ChartPlotWidget.reset_graphics_caches()` context mngr which
resets all managed graphics object's cacheing modes on enter and
restores them on exit for simplified use in interaction handling code.
Previously when very zoomed out and using the `'r'` hotkey the
interaction handler loop wouldn't trigger a re-(up)sampling to get
a more detailed curve graphic and instead the previous downsampled
(under-detailed) graphic would show. Fix that by ensuring we yield back
to the Qt event loop and do at least a couple render cycles with paired
`.interact_graphics_cycle()` calls.

Further this flips the `.start/signal_ic()` methods to use the new
`.reset_graphics_caches()` ctr-mngr method.
When the target pinning curve (by default, the dispersion major) is
shorter then the pinned curve, we need to make sure we find still find
the x-intersect for computing returns scalars! Use `Viz.i_from_t()` to
accomplish this as well and, augment that method with a `return_y: bool`
to allow the caller to also retrieve the equivalent y-value at the
requested input time `t: float` for convenience.

Also tweak a few more internals around the 'loglin_ref_to_curve'
method:
- only solve / adjust for the above case when the major's xref is
  detected as being "earlier" in time the current minor's.
- pop the major viz entry from the overlay table ahead of time to avoid
  a needless iteration and simplify the transform calc phase loop to
  avoid handling that needless cycle B)
- add much better "organized" debug printing with more clear headers
  around which "phase"/loop the message pertains and well as more
  explicit details in terms of x and y-range values on each cycle of
  each loop.
There's been way too many issues when trying to calculate this
dynamically from the input array, so just expect the caller to know what
it's doing and don't bother with ever hitting the error case of
calculating and incorrect value internally.
As per the change to `slice_from_time()` this ensures this `Viz` always
passes its self-calculated time indexing step size to the time slicing
routine(s).

Further this contains a slight impl tweak to `.scalars_from_index()` to
slice the actual view range from `xref` to `Viz.ViewState.xrange[1]` and
then reading the corresponding `yref` from the first entry in that
array; this should be no slower in theory and makes way for further
caching of x-read-range to `ViewState` opportunities later.
Again, as per the signature change, never expect implicit time step
calcs from overlay processing/machinery code. Also, extend the debug
printing (yet again) to include better details around
"rescale-due-to-minor-range-out-of-view" cases and a detailed msg for
the transform/scaling calculation (inputs/outputs), particularly for the
cases when one of the curves has a lesser support.
For the purposes of eventually trying to resolve last-step indexing
synchronization (an intermittent but still existing) issue(s) that can
happen due to races during history frame query and shm writing during
startup. In fact, here we drop all `hist_viz` info queries from the main
display loop for now anticipating that this code will either be removed
or improved later.
Not sure how i missed this (and left in handling of `list.remove()` and
it ever worked for that?) after the `samplerd` impl in 5ec1a72 but, this
adjusts the remove-broken-subscriber loop to catch the correct
`set.remove()` exception type on a missing (likely already removed)
subscription entry.
This is particularly more "good looking" when we boot with a pair that
doesn't have historical 1s OHLC and thus the fast chart is empty from
outset. In this case it's a lot nicer to be already zoomed to
a comfortable preset number of "datums in view" even when the history
isn't yet filled in.

Adjusts the chart display `Viz.default_view()` startup to explicitly
ensure this happens via the `do_min_bars=True` flag B)
Solve this by always scaling the y-range for the major/target curve
*before* the final overlay scaling loop; this implicitly always solve
the case where the major series is the only one in view.

Tidy up debug print formatting and add some loop-end demarcation comment
lines.
@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from ca71e32 to 1aab9f1 Compare March 10, 2023 23:20
@goodboy
Copy link
Contributor Author

goodboy commented Mar 13, 2023

Outstanding bullets added to #461.

Think this is ready to land 🏄🏼

@goodboy goodboy force-pushed the log_linearized_curve_overlays branch from 5a07542 to 889e920 Compare March 13, 2023 19:05
@goodboy goodboy merged commit 3948791 into deribit_updates Mar 13, 2023
@goodboy goodboy deleted the log_linearized_curve_overlays branch March 13, 2023 19:08
@goodboy goodboy restored the log_linearized_curve_overlays branch March 13, 2023 19:09
@goodboy goodboy deleted the log_linearized_curve_overlays branch March 13, 2023 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
graphics (charting related) geometry chops perf efficiency and latency optimization UI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants