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