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 from contextlib import asynccontextmanager
import time import time
from typing import Any
from types import ModuleType from types import ModuleType
import numpy as np import numpy as np
from pydantic import BaseModel from pydantic import create_model
import tractor import tractor
import trio import trio
@ -345,45 +344,51 @@ async def fan_out_spawn_fsp_daemons(
# blocks here until all fsp actors complete # blocks here until all fsp actors complete
class FspConfig(BaseModel):
class Config:
validate_assignment = True
name: str
period: int
@asynccontextmanager @asynccontextmanager
async def open_sidepane( async def open_sidepane(
linked: LinkedSplits, 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( sidepane: FieldsForm = mk_form(
parent=linked.godwidget, parent=linked.godwidget,
fields_schema={ fields_schema=schema,
'name': { )
'label': '**fsp**:',
'type': 'select',
'default_value': [
f'{display_name}'
],
},
# TODO: generate this from input map # https://pydantic-docs.helpmanual.io/usage/models/#dynamic-model-creation
'period': { FspConfig = create_model(
'label': '**period**:', 'FspConfig',
'type': 'edit',
'default_value': 14,
},
},
)
sidepane.model = FspConfig(
name=display_name, name=display_name,
period=14, **defaults,
) )
sidepane.model = FspConfig()
# just a logger for now until we get fsp configs up and running. # just a logger for now until we get fsp configs up and running.
async def settings_change(key: str, value: str) -> bool: async def settings_change(key: str, value: str) -> bool:
@ -410,7 +415,7 @@ async def run_fsp(
src_shm: ShmArray, src_shm: ShmArray,
fsp_func_name: str, fsp_func_name: str,
display_name: str, display_name: str,
conf: dict[str, Any], conf: dict[str, dict],
group_status_key: str, group_status_key: str,
loglevel: str, loglevel: str,
@ -444,7 +449,7 @@ async def run_fsp(
ctx.open_stream() as stream, ctx.open_stream() as stream,
open_sidepane( open_sidepane(
linkedsplits, linkedsplits,
display_name, {display_name: conf},
) as sidepane, ) as sidepane,
): ):
@ -453,9 +458,10 @@ async def run_fsp(
if conf.get('overlay'): if conf.get('overlay'):
chart = linkedsplits.chart chart = linkedsplits.chart
chart.draw_curve( chart.draw_curve(
name='vwap', name=display_name,
data=shm.array, data=shm.array,
overlay=True, overlay=True,
color='default_light',
) )
last_val_sticky = None last_val_sticky = None
@ -658,22 +664,23 @@ async def maybe_open_vlm_display(
) -> ChartPlotWidget: ) -> 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): if not has_vlm(ohlcv):
log.warning(f"{linked.symbol.key} does not seem to have volume info") log.warning(f"{linked.symbol.key} does not seem to have volume info")
else: else:
async with open_sidepane(linked, 'volume') as sidepane: async with open_sidepane(
linked, {
'volume': {
'params': {
'price_func': 'ohl3'
}
}
},
) as sidepane:
# built-in $vlm # built-in $vlm
shm = ohlcv shm = ohlcv
chart = linked.add_plot( chart = linked.add_plot(
name='vlm', name='volume',
array=shm.array, array=shm.array,
array_key='volume', array_key='volume',
@ -681,10 +688,10 @@ async def maybe_open_vlm_display(
# curve by default # curve by default
ohlc=False, ohlc=False,
style='step',
# vertical bars # vertical bars, we do this internally ourselves
# stepMode=True, # stepMode=True,
# static_yrange=(0, 100),
) )
# XXX: ONLY for sub-chart fsps, overlays have their # 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) last_val_sticky.update_from_data(-1, value)
chart.update_curve_from_array(
'volume',
shm.array,
)
# size view to data once at outset # size view to data once at outset
chart._set_yrange() 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 yield chart
@ -805,20 +826,11 @@ async def display_symbol_data(
fsp_conf = { fsp_conf = {
'rsi': { 'rsi': {
'fsp_func_name': 'rsi', 'fsp_func_name': 'rsi',
'period': 14, 'params': {'period': 14},
'chart_kwargs': { 'chart_kwargs': {
'static_yrange': (0, 100), '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): 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, trio.open_nursery() as ln,
): ):
# load initial fsp chain (otherwise known as "indicators") # load initial fsp chain (otherwise known as "indicators")
@ -864,10 +882,7 @@ async def display_symbol_data(
) )
async with ( async with (
# XXX: this slipped in during a commits refacotr, maybe_open_vlm_display(linkedsplits, ohlcv),
# it's actually landing proper in #231
# maybe_open_vlm_display(linkedsplits, ohlcv),
open_order_mode( open_order_mode(
feed, feed,
chart, chart,