Port chart code to new subsys apis

- make `GodWidget.load_symbol()` async
- track loaded feeds with a private `._feeds` dict
- add methods to pause/resume all feeds when chart is (un)focussed
- add some commented test code for 2nd feed consumer task and rsi2 fsp
- load async signal handler for view clicking
fsp_feeds
Tyler Goodlet 2021-08-16 07:52:15 -04:00
parent 1cb311602c
commit a1d4e61fc2
1 changed files with 73 additions and 47 deletions

View File

@ -63,17 +63,16 @@ from ._style import (
)
from . import _search
from . import _event
from ..data import maybe_open_shm_array
from ..data.feed import open_feed, Feed, install_brokerd_search
from ..data._source import Symbol
from ..data._sharedmem import ShmArray
from ..data import maybe_open_shm_array
from .. import brokers
from .. import data
from ..log import get_logger
from ._exec import run_qtractor
from ._interaction import ChartView
from .order_mode import run_order_mode
from .. import fsp
from ..data import feed
from ._forms import (
FieldsForm,
mk_form,
@ -169,13 +168,13 @@ class GodWidget(QWidget):
# self.strategy_box = StrategyBoxWidget(self)
# self.toolbar_layout.addWidget(self.strategy_box)
def load_symbol(
async def load_symbol(
self,
providername: str,
symbol_key: str,
loglevel: str,
ohlc: bool = True,
reset: bool = False,
) -> trio.Event:
@ -226,12 +225,16 @@ class GodWidget(QWidget):
# symbol is already loaded and ems ready
order_mode_started.set()
# TODO: we'll probably want per-instrument/provider state here?
# change the order config form over to the new chart
# TODO:
# - we'll probably want per-instrument/provider state here?
# change the order config form over to the new chart
# XXX: since the pp config is a singleton widget we have to
# also switch it over to the new chart's interal-layout
self.linkedsplits.chart.qframe.hbox.removeWidget(self.pp_pane)
linkedsplits.chart.qframe.hbox.addWidget(
chart = linkedsplits.chart
await chart.resume_all_feeds()
chart.qframe.hbox.addWidget(
self.pp_pane,
alignment=Qt.AlignTop
)
@ -627,6 +630,8 @@ class ChartPlotWidget(pg.PlotWidget):
self._graphics = {} # registry of underlying graphics
self._overlays = set() # registry of overlay curve names
self._feeds: dict[Symbol, Feed] = {}
self._labels = {} # registry of underlying graphics
self._ysticks = {} # registry of underlying graphics
@ -654,6 +659,14 @@ class ChartPlotWidget(pg.PlotWidget):
# for when the splitter(s) are resized
self._vb.sigResized.connect(self._set_yrange)
async def resume_all_feeds(self):
for feed in self._feeds.values():
await feed.resume()
async def pause_all_feeds(self):
for feed in self._feeds.values():
await feed.pause()
@property
def view(self) -> ChartView:
return self._vb
@ -1067,7 +1080,7 @@ class ChartPlotWidget(pg.PlotWidget):
self.scene().leaveEvent(ev)
_clear_throttle_rate: int = 50 # Hz
_clear_throttle_rate: int = 60 # Hz
_book_throttle_rate: int = 16 # Hz
@ -1154,6 +1167,7 @@ async def chart_from_quotes(
# chart isn't actively shown so just skip render cycle
if chart.linked.isHidden():
await chart.pause_all_feeds()
continue
for sym, quote in quotes.items():
@ -1377,6 +1391,7 @@ async def run_fsp(
group_key=group_status_key,
)
# make sidepane config widget
class FspConfig(BaseModel):
class Config:
@ -1423,7 +1438,10 @@ async def run_fsp(
) as stream,
# TODO:
# open_form_input_handling(sidepane),
open_form_input_handling(
sidepane,
focus_next=linkedsplits.godwidget
),
):
@ -1577,7 +1595,6 @@ async def check_for_new_bars(feed, ohlcv, linkedsplits):
async with feed.index_stream() as stream:
async for index in stream:
# update chart historical bars graphics by incrementing
# a time step and drawing the history and new bar
@ -1654,7 +1671,7 @@ async def display_symbol_data(
# )
async with(
data.feed.open_feed(
open_feed(
provider,
[sym],
loglevel=loglevel,
@ -1665,19 +1682,19 @@ async def display_symbol_data(
) as feed,
trio.open_nursery() as n,
):
async def print_quotes():
async with feed.stream.subscribe() as bstream:
last_tick = time.time()
async for quotes in bstream:
now = time.time()
period = now - last_tick
for sym, quote in quotes.items():
ticks = quote.get('ticks', ())
if ticks:
# print(f'{1/period} Hz')
last_tick = time.time()
# async def print_quotes():
# async with feed.stream.subscribe() as bstream:
# last_tick = time.time()
# async for quotes in bstream:
# now = time.time()
# period = now - last_tick
# for sym, quote in quotes.items():
# ticks = quote.get('ticks', ())
# if ticks:
# # print(f'{1/period} Hz')
# last_tick = time.time()
n.start_soon(print_quotes)
# n.start_soon(print_quotes)
ohlcv: ShmArray = feed.shm
bars = ohlcv.array
@ -1693,6 +1710,7 @@ async def display_symbol_data(
linkedsplits._symbol = symbol
chart = linkedsplits.plot_ohlc_main(symbol, bars)
chart._feeds[symbol.key] = feed
chart.setFocus()
# plot historical vwap if available
@ -1723,13 +1741,13 @@ async def display_symbol_data(
'static_yrange': (0, 100),
},
},
'rsi2': {
'fsp_func_name': 'rsi',
'period': 14,
'chart_kwargs': {
'static_yrange': (0, 100),
},
},
# 'rsi2': {
# 'fsp_func_name': 'rsi',
# 'period': 14,
# 'chart_kwargs': {
# 'static_yrange': (0, 100),
# },
# },
}
@ -1811,7 +1829,7 @@ async def load_provider_search(
loglevel=loglevel
) as portal,
feed.install_brokerd_search(
install_brokerd_search(
portal,
get_brokermod(broker),
),
@ -1872,23 +1890,21 @@ async def _async_main(
godwidget._root_n = root_n
# setup search widget and focus main chart view at startup
# search widget is a singleton alongside the godwidget
search = _search.SearchWidget(godwidget=godwidget)
search.bar.unfocus()
# add search singleton to global chart-space widget
godwidget.hbox.addWidget(
search,
# alights to top and uses minmial space based on
# search bar size hint (i think?)
alignment=Qt.AlignTop
)
godwidget.hbox.addWidget(search)
godwidget.search = search
symbol, _, provider = sym.rpartition('.')
# this internally starts a ``display_symbol_data()`` task above
order_mode_ready = godwidget.load_symbol(provider, symbol, loglevel)
order_mode_ready = await godwidget.load_symbol(
provider,
symbol,
loglevel
)
# spin up a search engine for the local cached symbol set
async with _search.register_symbol_search(
@ -1912,17 +1928,27 @@ async def _async_main(
# start handling peripherals input for top level widgets
async with (
# search bar kb inputs
# search bar kb input handling
_event.open_handlers(
[search.bar],
event_types={QEvent.KeyPress},
event_types={
QEvent.KeyPress,
},
async_handler=_search.handle_keyboard_input,
# let key repeats pass through for search
filter_auto_repeats=False,
filter_auto_repeats=False, # let repeats passthrough
),
# completer view mouse click signal handling
_event.open_signal_handler(
search.view.pressed,
search.view.on_pressed,
),
# pp pane kb inputs
open_form_input_handling(pp_pane),
open_form_input_handling(
pp_pane,
focus_next=godwidget,
)
):
# remove startup status text
starting_done()