Incrementally yield to Qt loop to resize sidepanes

Since our startup is very concurrent there is often races where widgets
have not fully spawned before python (re-)sizing code has a chance to
run sizing logic and thus incorrect dimensions are read. Instead ensure
the Qt render loop gets to run in between such checks.

Also add a `open_sidepane()` mngr for creating a minimal form widget for
FSP subchart sidepanes which can be configured from an input `dict`.
basic_vlm_display
Tyler Goodlet 2022-01-22 14:40:41 -05:00
parent 6ec0fdcabf
commit 2a59ccf1bb
1 changed files with 75 additions and 60 deletions

View File

@ -20,11 +20,10 @@ Real-time display tasks for charting / graphics.
'''
from contextlib import asynccontextmanager
import time
from typing import Any
from types import ModuleType
import numpy as np
from pydantic import BaseModel
from pydantic import create_model
import tractor
import trio
@ -345,45 +344,51 @@ async def fan_out_spawn_fsp_daemons(
# blocks here until all fsp actors complete
class FspConfig(BaseModel):
class Config:
validate_assignment = True
name: str
period: int
@asynccontextmanager
async def open_sidepane(
linked: LinkedSplits,
display_name: str,
conf: dict[str, dict[str, str]],
) -> FspConfig:
) -> FieldsForm:
schema = {}
assert len(conf) == 1 # for now
# add (single) selection widget
for display_name, config in conf.items():
schema[display_name] = {
'label': '**fsp**:',
'type': 'select',
'default_value': [display_name],
}
# add parameters for selection "options"
defaults = config.get('params', {})
for name, default in defaults.items():
# add to ORM schema
schema.update({
name: {
'label': f'**{name}**:',
'type': 'edit',
'default_value': default,
},
})
sidepane: FieldsForm = mk_form(
parent=linked.godwidget,
fields_schema={
'name': {
'label': '**fsp**:',
'type': 'select',
'default_value': [
f'{display_name}'
],
},
fields_schema=schema,
)
# TODO: generate this from input map
'period': {
'label': '**period**:',
'type': 'edit',
'default_value': 14,
},
},
)
sidepane.model = FspConfig(
# https://pydantic-docs.helpmanual.io/usage/models/#dynamic-model-creation
FspConfig = create_model(
'FspConfig',
name=display_name,
period=14,
**defaults,
)
sidepane.model = FspConfig()
# just a logger for now until we get fsp configs up and running.
async def settings_change(key: str, value: str) -> bool:
@ -410,7 +415,7 @@ async def run_fsp(
src_shm: ShmArray,
fsp_func_name: str,
display_name: str,
conf: dict[str, Any],
conf: dict[str, dict],
group_status_key: str,
loglevel: str,
@ -444,7 +449,7 @@ async def run_fsp(
ctx.open_stream() as stream,
open_sidepane(
linkedsplits,
display_name,
{display_name: conf},
) as sidepane,
):
@ -453,9 +458,10 @@ async def run_fsp(
if conf.get('overlay'):
chart = linkedsplits.chart
chart.draw_curve(
name='vwap',
name=display_name,
data=shm.array,
overlay=True,
color='default_light',
)
last_val_sticky = None
@ -658,22 +664,23 @@ async def maybe_open_vlm_display(
) -> ChartPlotWidget:
# make sure that the instrument supports volume history
# (sometimes this is not the case for some commodities and
# derivatives)
# volm = ohlcv.array['volume']
# if (
# np.all(np.isin(volm, -1)) or
# np.all(np.isnan(volm))
# ):
if not has_vlm(ohlcv):
log.warning(f"{linked.symbol.key} does not seem to have volume info")
else:
async with open_sidepane(linked, 'volume') as sidepane:
async with open_sidepane(
linked, {
'volume': {
'params': {
'price_func': 'ohl3'
}
}
},
) as sidepane:
# built-in $vlm
shm = ohlcv
chart = linked.add_plot(
name='vlm',
name='volume',
array=shm.array,
array_key='volume',
@ -681,10 +688,10 @@ async def maybe_open_vlm_display(
# curve by default
ohlc=False,
style='step',
# vertical bars
# vertical bars, we do this internally ourselves
# stepMode=True,
# static_yrange=(0, 100),
)
# XXX: ONLY for sub-chart fsps, overlays have their
@ -703,9 +710,23 @@ async def maybe_open_vlm_display(
last_val_sticky.update_from_data(-1, value)
chart.update_curve_from_array(
'volume',
shm.array,
)
# size view to data once at outset
chart._set_yrange()
# size pain to parent chart
# TODO: this appears to nearly fix a bug where the vlm sidepane
# could be sized correctly nearly immediately (since the
# order pane is already sized), right now it doesn't seem to
# fully align until the VWAP fsp-actor comes up...
await trio.sleep(0)
chart.linked.resize_sidepanes()
await trio.sleep(0)
yield chart
@ -805,20 +826,11 @@ async def display_symbol_data(
fsp_conf = {
'rsi': {
'fsp_func_name': 'rsi',
'period': 14,
'params': {'period': 14},
'chart_kwargs': {
'static_yrange': (0, 100),
},
},
# # test for duplicate fsps on same chart
# 'rsi2': {
# 'fsp_func_name': 'rsi',
# 'period': 14,
# 'chart_kwargs': {
# 'static_yrange': (0, 100),
# },
# },
}
if has_vlm(ohlcv):
@ -831,8 +843,14 @@ async def display_symbol_data(
},
})
async with (
# NOTE: we must immediately tell Qt to show the OHLC chart
# to avoid a race where the subplots get added/shown to
# the linked set *before* the main price chart!
linkedsplits.show()
linkedsplits.focus()
await trio.sleep(0)
async with (
trio.open_nursery() as ln,
):
# load initial fsp chain (otherwise known as "indicators")
@ -864,10 +882,7 @@ async def display_symbol_data(
)
async with (
# XXX: this slipped in during a commits refacotr,
# it's actually landing proper in #231
# maybe_open_vlm_display(linkedsplits, ohlcv),
maybe_open_vlm_display(linkedsplits, ohlcv),
open_order_mode(
feed,
chart,